Saturday, September 14, 2024

自己動手寫作業系統 第二章bootloader範例

這是拜讀對岸高手Solrex所著自己動手寫作業系統之後的心得。
下面是第二章的sample code。作為在閱讀這本書時的筆記。
這段code做的事情為,在boot sector內寫一個程式,之後把另外一個loader名為
"loader.bin"從fat12 file system裡頭找出來並且load到記憶體位置9000:0100的
地方之後jmp過去開始run。如果你無聊,loader.bin裡頭可以秀一段hello world字串。

完成了這段code以後,用它書裡頭的方法,先把這個image在linux底下mount起來
。之後因為裡面有fat12 file system,所以可以把loader.bin copy到mount的資料夾內,
這樣linux就會自動去把loader.bin加到我們的file system。umount以後,我再用windows
底下的vmware新增一個floppy的Device,把掛載的映像檔,設為我們的image,再用vmware開啟。

同時實驗後發現了用usb開機也是可行的,這樣就可以用usb開啟自己寫的bootloader了。
首先,把image從usb的MBR開始位置用winhex寫入。因為在eeepc底下,
usb開機被視為floppy disk,所以可以用int 13 , dl = 0去讀取sector。
winhex的寫入法有兩種,第一種把我們要copy的磁區先反白起來,接著ctrl+c,然後
切換到要貼上的磁碟磁區,把滑鼠游標移到第一個Byte,ctrl + b。第二種是用winhex
中的tools -> disk tools -> clone disk。就可以選擇來源檔案為我們的image,目的選
usb實體磁碟機(不能選logical disk不然會寫到partition的boot sector去)。或是你要
在linux底下用dd也是可以。

一開始所需要的必備知識就是GAS組語的語法,和FAT的格式
GAS的語法,網路上很多可以搜尋一下,或者這個bootsector多看幾次就會了解。
至於file system可以參考我的另外一篇文章FAT系列檔案系統




/* chapter2/2/boot.S

Author: Wenbo Yang

This file is part of the source code of book "Write Operating System with
Free Software". The latest version, see .

This file is licensed under the GNU General Public License; either
version 3 of the License, or (at your option) any later version. */

.code16 //因為一開機是Real mode所以是以16 bits為定址模式

.set BaseOfStack, 0x7c00 /* Stack base address, inner */
.set BaseOfLoader, 0x9000 /* Section loading address of LOADER.BIN */
.set OffsetOfLoader, 0x0100 /* Loading offset of LOADER.BIN */
.set RootDirSectors, 14 /* Root directory sector count */
.set SecNoOfRootDir, 19 /* 1st sector of root directory */
.set SecNoOfFAT1, 1 /* 1st sector of FAT1 */
.set DeltaSecNo, 17 /* BPB_(RsvdSecCnt+NumFATs*FATSz) -2 */
/* Start sector of file space =*/
.text //這邊是程式開始
//
前面的這些欄位是BPB(Bios Parameter Block)儲存後面程式用的到的變數
/* Floppy header of FAT12 */
jmp LABEL_START /* Start to boot. */
nop /* nop required 因為前面是三個bytes所以還需要一個nop來湊*/
BS_OEMName: .ascii "WB. YANG" /* OEM String, 8 bytes required */
BPB_BytsPerSec: .2byte 512 /* Bytes per sector */
BPB_SecPerCluse: .byte 1 /* Sector per cluse */
BPB_ResvdSecCnt: .2byte 1 /* Reserved sector count */
BPB_NumFATs: .byte 2 /* Number of FATs */
BPB_RootEntCnt: .2byte 224 /* Root entries count */
BPB_TotSec16: .2byte 2880 /* Total sector number */
BPB_Media: .byte 0xf0 /* Media descriptor */
BPB_FATSz16: .2byte 9 /* FAT size(sectors) */
BPB_SecPerTrk: .2byte 18 /* Sector per track */
BPB_NumHeads: .2byte 2 /* Number of magnetic heads */
BPB_HiddSec: .4byte 0 /* Number of hidden sectors */
BPB_TotSec32: .4byte 0 /* If TotSec16 equal 0, this works */
BS_DrvNum: .byte 0 /* Driver number of interrupt 13 */
BS_Reserved1: .byte 0 /* Reserved */
BS_BootSig: .byte 0x29 /* Boot signal */
BS_VolID: .4byte 0 /* Volume ID */
BS_VolLab: .ascii "Solrex 0.01" /* Volume label, 11 bytes required */
BS_FileSysType: .ascii "FAT12 " /* File system type, 8 bytes required */

/* Initial registers. */
//因為在一開機時只有cs有被填值,其他的segment register要靠自己填
LABEL_START:
mov %cs,%ax
mov %ax,%ds
mov %ax,%es
mov %ax,%ss
mov $BaseOfStack, %sp //BaseOfStack = 0x7c00,很明顯stack就是從0x7c00往下減

/* Clear screen */
mov $0x0600,%ax /* %ah=6, %al=0 */
mov $0x0700,%bx /* Black white */
mov $0,%cx /* Top left: (0,0) */
mov $0x184f,%dx /* Bottom right: (80,50) */
int $0x10 /* BIOS int 10h, ah=6: Initialize screen */

/* Display "Booting**" */
//它下面有三個字串,每個分別都是九個bytes那麼大。而第零個就是booting**
mov $0,%dh
call DispStr /* Display string(index 0)*/

/* Reset floppy */
//這邊我也不知道為什麼要重設floppy
xor %ah,%ah
xor %dl,%dl /* %dl=0: floppy driver 0 */
int $0x13 /* BIOS int 13h, ah=0: Reset driver 0 */

/* Find LOADER.BIN in root directory of driver 0 */
//在下面可以看到SecNoOfRootDir = 19,而實際上是第20個sectors,因為從零開始。
//因為wSectorNo是變數所以用括號。而SecNoOfRootDir是被define的所以用"$"的符號。
movw $SecNoOfRootDir, (wSectorNo)

/* Read root dir sector to memory */
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
//wRootDirSizeForLoop=14因為整個Root Directory只有14個sectors這麼大
cmpw $0,(wRootDirSizeForLoop) /* If searching in root dir */
jz LABEL_NO_LOADERBIN /* can find LOADER.BIN ? */
decw (wRootDirSizeForLoop)
mov $BaseOfLoader,%ax
mov %ax,%es /* %es <- BaseOfLoader*/
mov $OffsetOfLoader,%bx /* %bx <- OffsetOfLoader */
mov (wSectorNo),%ax /* %ax <- sector number in root */
mov $1,%cl //cl是儲存一次要讀幾個Sectors到es:bx去
call ReadSector //呼叫完了以後要讀取的Sector就會被copy到es:bx
mov $LoaderFileName,%si /* %ds:%si -> LOADER BIN */
mov $OffsetOfLoader,%di /* BaseOfLoader<<4+100*/> %al*/
cld //這邊清除了direction以後,後面的lodsb才會以遞增的方式去load
mov $0x10,%dx //2^9 / 2^5 = 16 因為一個sector 512 bytes,一個root entry 32 bytes

/* Search for "LOADER BIN", FAT12 save file name in 12 bytes, 8 bytes for
file name, 3 bytes for suffix, last 1 bytes for '\20'. If file name is
less than 8 bytes, filled with '\20'. So "LOADER.BIN" is saved as:
"LOADER BIN"(4f4c 4441 5245 2020 4942 204e).
*/
LABEL_SEARCH_FOR_LOADERBIN:
cmp $0,%dx /* Read control */
//一個Sector如果16個root entry都讀完了當然就讀下個sector
jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR
dec %dx
mov $11,%cx //每個entry的最前面11個bytes就是檔名

LABEL_CMP_FILENAME:
cmp $0,%cx //如果全部比完了,當然就是找到
jz LABEL_FILENAME_FOUND /* If 11 chars are all identical? */
dec %cx
//這個的lodsb就會把ds:si的值放到al去,並且由於"cld"的原因所以遞增一
lodsb /* %ds:(%si) -> %al*/
cmp %es:(%di),%al //es:di就是我們所要比對的root entry
jz LABEL_GO_ON
jmp LABEL_DIFFERENT /* Different */

LABEL_GO_ON:
inc %di
jmp LABEL_CMP_FILENAME /* Go on loop */

LABEL_DIFFERENT:
//因為只有後面的五個bits會用到2^5=32所以把di後面五個bits清掉
//之後跳到下一個檔名繼續比對
and $0xffe0,%di /* Go to head of this entry */
add $0x20,%di
mov $LoaderFileName,%si /* Next entry */
jmp LABEL_SEARCH_FOR_LOADERBIN

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
addw $1,(wSectorNo) //這邊就是讀下一個Sector,所以把Sector number加一
jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN

/* Not found LOADER.BIN in root dir. */
LABEL_NO_LOADERBIN:
//第三個字串
"No LOADER"
mov $2,%dh
call DispStr /* Display string(index 2) */
jmp . /* Infinite loop */

/* Found. */
LABEL_FILENAME_FOUND:
//RootDirSectors是14,DeltaSecNo是17,FAT表的前兩個欄位是系統欄位,不代表
//檔案的資訊,所以第三個欄位也就是2,開始是代表第33號sector(實際上第34個)。
//也就是Data的開始。

mov $RootDirSectors,%ax
and $0xffe0,%di /* Start of current entry, 32 bytes per entry */
add $0x1a,%di /* First sector of this file */
mov %es:(%di),%cx
push %cx /* Save index of this sector in FAT */
add %ax,%cx
add $DeltaSecNo,%cx /* LOADER.BIN's start sector saved in %cl */
//到這邊為止,要存取的Data放在哪個sector已經算好且放在cx裡面
//接下來就是把ES:BX兩個register的值填好,再把cx放到ax裡,接下
//來的code就可以呼叫ReadSector把Sector載入。
mov $BaseOfLoader,%ax
mov %ax,%es /* %es <- BaseOfLoader */
mov $OffsetOfLoader,%bx /* %bx <- OffsetOfLoader */
mov %cx,%ax /* %ax <- Sector number */
/* Load LOADER.BIN's sector's to memory. */
LABEL_GOON_LOADING_FILE
:
//抓幾個sectors就秀出幾個點
push %ax
push %bx
mov $0x0e,%ah
mov $'.',%al
/* Char to print */
mov $0x0f,%bl /* Front color: white */
int $0x10 /* BIOS int 10h, ah=0xe: Print char */
pop %bx
pop %ax
//到這邊為止,前面這幾行印出一個點,都不重要
//下面這行開始就要把剛剛填好的ax號sector抓到ES:BX內。
mov $1,%cl //只抓一個sector
call ReadSector
pop %ax //這邊pop出來的是剛剛存入的cx,代表現在在第幾個FAT欄位

/* Got index of this sector in FAT */
call GetFATEntry
cmp $0x0fff,%ax //如果這個Fat欄位顯示結束就jmp
jz LABEL_FILE_LOADED
push %ax /* Save index of this sector in FAT */
mov $RootDirSectors,%dx
add %dx,%ax
add $DeltaSecNo,%ax
add (BPB_BytsPerSec),%bx //load完一個sector要把offset加上512bytes
jmp LABEL_GOON_LOADING_FILE
LABEL_FILE_LOADED:
mov $1,%dh
call DispStr /* Display string(index 1) */

/* Jump to LOADER.BIN's start address in memory. */
//當然把loader.bin load到9000:0100也就是90100的位置就要jmp過去了
jmp $BaseOfLoader,$OffsetOfLoader //(這邊是整個boot sector跳到loader.bin的地方)

/*================================= Variable table */

wRootDirSizeForLoop: .2byte RootDirSectors
wSectorNo: .2byte 0 /* Sector number to read */
bOdd: .byte 0 /* odd or even? */

/* String table */

LoaderFileName:
.asciz "LOADER BIN" /* File name */
.set MessageLength,9
BootMessage: .ascii "Booting**" /* index 0 */
Message1: .ascii "Loaded in" /* index 1 */
Message2: .ascii "No LOADER" /* index 2 */

/* Routine: DispStr Action: Display a string, string index stored in %dh */

DispStr:
mov $MessageLength, %ax
mul %dh
add $BootMessage,%ax
mov %ax,%bp /* String address */
mov %ds,%ax
mov %ax,%es
mov $MessageLength,%cx /* String length */
mov $0x1301,%ax /* ah = 0x13, al = 0x01(W) */
mov $0x07,%bx /* PageNum 0(bh = 0), bw(bl= 0x07)*/
mov $0,%dl /* Start row and column */
int $0x10 /* BIOS INT 10h, display string */
ret

/* Routine: ReadSector Action: Read %cl Sectors from %ax sector(floppy) to %es:%bx(memory) Assume sector number is 'x', then: x/(BPB_SecPerTrk) = y, x%(BPB_SecPerTrk) = z. The remainder 'z' PLUS 1 is the start sector number; The quotient 'y' devide by BPB_NumHeads(RIGHT SHIFT 1 bit)is cylinder number; AND 'y' by 1 can got magnetic header. */
//剛開始在ax裡頭,我門只會有sector number,接著我門會計算總共
//占有幾個tracks,所以會先把sector number除以18,這個值代
//表總共占有幾個tracks。餘數加一以後就是sector number,因為
//sector number是從
0開始。接著因為floppy讀寫的順序是cylinder
//從裡到外
head 0->1讀完就到下一個cylinder。所以還要把它除以二
//就代表
有幾個cylinder。接著也可以從tracks是偶數還是奇數判斷
//我門的head是多少,這個時候就用and $1的方式。

ReadSector:
push %ebp
mov %esp,%ebp
sub $2,%esp /* Reserve space for saving %cl */
mov %cl,-2(%ebp)
push %bx /* Save bx */
mov (BPB_SecPerTrk), %bl /* %bl: the devider */
div %bl /* 'y' in %al, 'z' in %ah */
inc %ah /* z++, got start sector */
mov %ah,%cl /* %cl <- start sector number */
mov %al,%dh /* %dh <- 'y' */ //這邊就是除以二,shift right 1 bit
shr $1,%al /* 'y'/BPB_NumHeads */
mov %al,%ch /* %ch <- Cylinder number(y>>1) */
//檢查tracks是奇數還是偶數
and $1,%dh /* %dh <- Magnetic header(y&1) */
pop %bx /* Restore %bx */
/* Now, we got cylinder number in %ch, start sector number in %cl, magnetic header in %dh. */ mov (BS_DrvNum), %dl //設dl = 0,代表是對floppy做讀取
GoOnReading:

mov $2,%ah
mov -2(%ebp),%al /* Read %al sectors */
int $0x13
jc GoOnReading /* If CF set 1, mean read error, reread. */
add $2,%esp
pop %ebp
ret


/* Routine: GetFATEntry Action: Find %ax sector's index in FAT, save result in %ax */
//其實這邊每call一次GetFATEntry就要copy兩個Sector到記憶體0x8F000還蠻貴的,
//而且其實常常會重複,因為這個例子來說,只會用到FAT的第一個Sector。
GetFATEntry:
push %es
push %bx
push %ax
mov $BaseOfLoader,%ax
sub $0x0100,%ax
mov %ax,%es /* Left 4K bytes for FAT */
pop %ax //這邊pop出來的ax就是第幾個FAT entry
movb $0,(bOdd)
mov $3,%bx
mul %bx /* %dx:%ax = %ax*3 */
mov $2,%bx
div %bx /* %dx:%ax/2 */
cmp $0,%dx /* remainder %dx = 0 ? */
jz LABEL_EVEN
movb $1,(bOdd)
LABEL_EVEN:
xor %dx,%dx /* Now %ax is the offset of FATEntry in FAT */
mov (BPB_BytsPerSec),%bx
div %bx /* %dx:%ax/BPB_BytsPerSec */
push %dx
mov $0,%bx
add $SecNoOfFAT1,%ax /* %ax <- FATEntry's sector */
mov $2,%cl /* Read 2 sectors in 1 time, because FATEntry */
call ReadSector /* may be in 2 sectors. */ //有可能fat的entry剛好在兩個sector的交界
pop %dx
add %dx,%bx
mov %es:(%bx),%ax
cmpb $1,(bOdd) //如果是奇數的話就不用shift
jnz LABEL_EVEN_2 //如果是偶數的話就要shift right 4 bits
shr $4,%ax
LABEL_EVEN_2:

and $0x0fff,%ax

LABEL_GET_FAT_ENTRY_OK:
pop %bx
pop %es
ret
.org 510 /* Skip to address 0x510. */
.2byte 0xaa55 /* Write boot flag to 1st sector(512 bytes) end */

Multiboot USB Bootloader

因為之前工作上的需要,所以我有一顆usb專門裝上DOS,有一個usb裝winpe,另一個usb是裝上Gparted linux。因為usb實在是太多顆了,突發奇想,看看能不能把所有的OS放在一個USB上面。又因為winpe是目的端Windows時,才需要load起來,Linux是目的端Linux,才需要load起來。後來就參考了solrex的文章自己寫了一個bootloader判斷目的端的作業系統,然後自動載入usb上面對應的作業系統。至於如何製作一個multiboot partition請參考我的另一篇文章

其實這篇文章主要的概念就在下面的圖示。
我會在USB partition做好了以後,把我的bootloader用Winhex放到USB MBR裡面。(ctrl+c & ctrl +b千萬記住不能蓋掉partition table,可以分兩次copy,用winhex把編好的bin先copy第一個sector的446bytes,再copy第二個sector的512bytes,事實上第二支code只要copy有東西的地方就可以了,後面零的地方可以不用理他。但在copy前要確認usb已經被低階格式化過了,都清成零才不會錯)。這支程式的架構是分為兩段code。每段code分別為一個sector也就是512 bytes的大小,放在usb最前面MBR開始連續兩個sectors的地方。一開機,MBR的512bytes會被load到記憶體0x0000:0x7C00的位置。這支程式接著會把接續著MBR後面的第二支程式copy到0x9000:0000的位置。並且jmp到第二支code,把權力交給他之後他會到硬碟上去把第一個partition的boot sector 0x0000003F抓到9000:0200並且檢查byte number 3也就是第四個byte處(9000:0203),檢查是否為NTFS。這會有一個小問題,以我這顆usb為例,開機時被認為硬碟,所以int 13h的dl硬碟代號就是80h。而PC上面的硬碟就會被認為是第二顆,就是81h。若是有的usb隨身碟開機的時候被認做是floppy則dl會是零。則硬碟會是80h。這要自己判斷,可以先假設為80h,若是int 13,call完後carry flag on,則為錯誤,這時就可以判斷為被當作是floppy。判斷完了以後,就把要load in的os所在partition的bootsector抓出來放到0x0000:0x7c00處,並且jmp過去,他bootsector裡面的NTLoader(Windows PE) or SYSlinux(GParted linux)就會幫你做loader OS的動作。看完了以後就知道,我是用偷懶的方式,用chainloading load進相對的bootloader。真正的bootloader是不好寫的,以linux為例,要去/doc底下看boot protocol。才知道linux kernel要抓到哪個相對的位置。

首先下面這個是Makefile
CC=gcc
LD=ld
OBJCOPY=objcopy

CFLAGS=-c
TRIM_FLAGS=-R .pdr -R .comment -R.note -S -O binary

LDFILE_BOOT=solrex_x86_boot.ld
LDFILE_DOS=solrex_x86_dos.ld
LDFLAGS_BOOT=-e c -T$(LDFILE_BOOT)
LDFLAGS_DOS=-e c -T$(LDFILE_DOS)

all: boot.img LOADER.BIN

boot.bin: boot.S
$(CC) $(CFLAGS) boot.S
$(LD) boot.o -o boot.elf $(LDFLAGS_BOOT)
$(OBJCOPY) $(TRIM_FLAGS) boot.elf $@

LOADER.BIN: loader.S
$(CC) $(CFLAGS) loader.S
$(LD) loader.o -o loader.elf $(LDFLAGS_DOS)
$(OBJCOPY) $(TRIM_FLAGS) loader.elf $@
#下面這邊可以看到編完的loader.bin是接在boot.bin的後面
boot.img: boot.bin LOADER.BIN
dd if=boot.bin of=boot.img bs=512 count=1
dd if=LOADER.BIN of=boot.img seek=1 bs=512 count=1

# You must have the authority to do mount, or you must use "su root" or
# "sudo" command to do "make copy"
copy: boot.img

clean:
@rm -f *.o *.elf *.bin *.BIN

再下來是solrex_x86_boot.ld,因為一開機時是0x0000:0x7c00所以要offset 0x7c00。
SECTIONS
{
. = 0x7c00;
.text :
{
_ftext = .;
} = 0
}

solrex_x86_dos.ld(因為是放在0x9000:0x0000所以offset 0x0000)
其實這邊如果為了要在dos下面的debug.exe可以run,則可以改成0x0100。
前面的100是program segment prefix。放一些類似命令列參數的資料。
SECTIONS
{
. = 0x0000;
.text :
{
_ftext = .;
} = 0
}


再來就是第一支code boot.S,真的不長啦,很簡單!
.code16
.set BaseOfStack, 0x7c00 /* Stack base address, inner */
.set BaseOfLoader, 0x9000 /* Section loading address of LOADER.S */
.set OffsetOfLoader, 0x0000 /* Loading offset of LOADER.S */
.text
jmp LABEL_START /* Start to boot. */
BS_DrvNum: .byte 0x80 /* Driver number of interrupt 13 */


/* Initial registers. */
LABEL_START:
mov %cs,%ax
mov %ax,%ds
mov %ax,%es
mov %ax,%ss
mov $BaseOfStack, %sp


/* Reset floppy 把磁頭歸零*/
xor %ah,%ah
xor %dl,%dl /* %dl=0: floppy driver 0 */
int $0x13 /* BIOS int 13h, ah=0: Reset driver 0 */
//我們這邊是用int 13h extension所以si那邊指向一個結構。
mov $0x80,%dl
mov $0x42,%ah
mov $(HardPacket),%si
int $0x13

jmp $BaseOfLoader,$OffsetOfLoader

//因為第二支code是放在usb mbr後面的第二個sector,所以是0x00000001
HardPacket:
size_packet: .byte 0x10
reserved: .byte 0
SectorToTran: .2byte 1
Offset: .2byte OffsetOfLoader
Segment: .2byte BaseOfLoader
SectorLow: .4byte 0x00000001
SectorHigh: .4byte 0

.org 510 /* Skip to address 0x510. */
.2byte 0xaa55 /* Write boot flag to 1st sector(512 bytes) end */



Loader.S
.code16
.set BaseOfLoader, 0x0000
.set OffsetOfLoader, 0x7C00
.text
LabelStart:

mov %cs,%ax
mov %ax,%ds
//我們的硬碟是第二顆,因為usb是第一顆,所以硬碟代號是0x81h
mov $(HardDPacket),%si
mov $0x81,%dl
mov $0x42,%ah
int $0x13
//把硬碟bootsector放到9000:0200以後開始比對,第四個byte 0x0203。
mov $0x0203,%si
cld
mov $CompS,%di
mov %ds,%ax
mov %ax,%es
mov $4,%cx //因為四個字"NTFS"

CompNTFS:
cmp $0,%cx
jz EQUAL
dec %cx
lodsb
cmp %bl,%al
jz KeepGoingComp
jmp DIFFERENT

KeepGoingComp:
inc %di
jmp CompNTFS
//如果比對後等於"NTFS",則載入第一個partition也就是windows PE。
EQUAL:
mov $HardPacket,%di
add $8,%di
mov $0,%bx
mov %bx,%ds
mov $0x7c00,%si
add $0x1C6,%si
mov %ds:(%si),%eax
mov %eax,%es:(%di)
jmp DONE
//不同則是第二個partition。
DIFFERENT:
mov $HardPacket,%di
add $8,%di
mov $0,%bx
mov %bx,%ds
mov $0x7c00,%si
add $0x1D6,%si
mov %ds:(%si),%eax
mov %eax,%es:(%di)

DONE:
mov %cs,%bx
mov %bx,%ds
mov $(HardPacket),%si /* ds:si--> offset of disk information */
mov $0x80,%dl /* Drive number of floppy disk */
mov $0x42,%ah

int $0x13

jmp $BaseOfLoader,$OffsetOfLoader
//要比對的那顆硬碟的bootsector,載入後放在9000:0200。硬碟上的第一個partition原則上
//初始位置都是在0x0000003F
HardDPacket:
.byte 0x10
.byte 0
.2byte 1
.2byte 0x0200
.2byte 0x9000
.4byte 0x0000003F
.4byte 0

//這個就是我們最後載入OS loader的詳細資訊了。
HardPacket:
size_packet: .byte 0x10
reserved: .byte 0
SectorToTran: .2byte 1
Offset: .2byte 0x0000
Segment: .2byte 0x07C0
SectorLow: .4byte 0x00000000 //這個位置會依照partition table裡面的值來填
SectorHigh: .4byte 0


CompS: .ascii "NTFS"
.org 512

其實寫bootloader最麻煩的就是debug,原本的code我裡面加了很多的print message所以比較長,這邊為了要容易理解,所以把那些code全部都拿掉了。如果想要debug可以把link script裡".=0x7c00"這行改成".=0x0100",之後副檔名設為.com就可以在dos底下run,就算不改成.com也可以直接debug。改成".=0x0100"是因為com格式的檔案,在被載到記憶體run的時候最前面有一個128 bytes Program Segment Prefix,可知dos在分配記憶體空間時,後面的offset都是從零開始。且在com檔裡code segment 和 data segment是合在一起的。執行檔案大小不可超過64k。

這邊再記錄一下linker的原理,以免忘掉。因為最近又要開始趕touch panel,恐怕以後沒有時間再摸這塊了。這邊".=0x7c00"可以想想看他的原理,先看看開機的時候cs=0x0000, ip=0x7c00。因為寫好的code裡面變數儲存都是根據相對的位置。假設一個變數宣告在整個檔案的0x3位置。而在開機的時候binary被load進0000:7c00,此時變數的相對位置便為0000:7c03。
這個資訊在compile時期是看不到的,在object file裡頭看到的還是0x3相對位置,在link以後才會變成0x7c03。可以用objdump來查看原本被組譯的二進位檔,如:"objdump boot.o -d -M data16,addr16"這些檔案是沒有絕對位置的資訊。當link後的binary,可以用winhex或是khexedit來看。

##說明上面兩段code的link運作原理。
起先因為我們知道一開機的時候,MBR裡面的code是被放到cs:ip = 0000:7c00的地方。所以第一段code的solrex_x86_boot.ld linker script裡面".=0x7c00"就是這樣來的。後來,我們的第二段code是接在MBR後面的一個sector。預設會load到9000:0000,因此solrex_x86_dos.ld裡面會寫成".=0x0000"。

Thursday, January 07, 2010

Spin lock & work queue 整理

SPIN LOCK
essential linux device driver的第二章A peek inside the kernel裡頭的Concurreny in the kernel有提到使用spin lock的四種case。寫得非常好。可以參考看看。

我的理解是這樣的,spin lock存在的目的是為了避免context switch的發生,什麼時候要避免使用context switch呢?就是處理共用變數的時間非常短。這時就不需要把process抓去睡覺,取而代之的是使用spin lock等待鎖的釋放,只要spin lock等待的時間少於兩次context switch(swap process out & swap process in)的時間。這樣就划的來了。而在單cpu上面只要你佔用了processor此時你要了一個lock,剛好的這個lock已經被其他的context(process or interrupt)所佔據。無論如何,當下你一定要進行context switch才能把執行權限放給其他的context把事情做完,並且把鎖釋放,這樣你才有機會獲得這把鎖。因此才有了以下不同的spin lock在單cpu裡面實做的變形,而不是真的在那邊spin。(在單處理器裡,spin lock的實做就是disable interrupt & disable preemption這樣就可以避免使用semaphore導致context switch)

下面把這三個function的source code攤出來看。
spin_lock() spin_lock_irq() spin_lock_irqsave()

在我的2.6.29的kernel上面spin lock的實做在uni-processor的source code裡頭是長這樣(include/linux/spinlock_api_up.h):

#ifndef __LINUX_SPINLOCK_API_UP_H
#define __LINUX_SPINLOCK_API_UP_H

#ifndef __LINUX_SPINLOCK_H
# error "please don't include this file directly"
#endif

/*
* include/linux/spinlock_api_up.h
*
* spinlock API implementation on UP-nondebug (inlined implementation)
*
* portions Copyright 2005, Red Hat, Inc., Ingo Molnar
* Released under the General Public License (GPL).
*/

#define in_lock_functions(ADDR) 0

#define assert_spin_locked(lock) do { (void)(lock); } while (0)

/*
* In the UP-nondebug case there's no real locking going on, so the
* only thing we have to do is to keep the preempt counts and irq
* flags straight, to suppress compiler warnings of unused lock
* variables, and to add the proper checker annotations:
*/
#define __LOCK(lock) \
do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)

#define __LOCK_BH(lock) \
do { local_bh_disable(); __LOCK(lock); } while (0)

#define __LOCK_IRQ(lock) \
do { local_irq_disable(); __LOCK(lock); } while (0)

#define __LOCK_IRQSAVE(lock, flags) \
do { local_irq_save(flags); __LOCK(lock); } while (0)

#define __UNLOCK(lock) \
do { preempt_enable(); __release(lock); (void)(lock); } while (0)

#define __UNLOCK_BH(lock) \
do { preempt_enable_no_resched(); local_bh_enable(); __release(lock); (void)(lock); } while (0)

#define __UNLOCK_IRQ(lock) \
do { local_irq_enable(); __UNLOCK(lock); } while (0)

#define __UNLOCK_IRQRESTORE(lock, flags) \
do { local_irq_restore(flags); __UNLOCK(lock); } while (0)

#define _spin_lock(lock) __LOCK(lock)
#define _spin_lock_nested(lock, subclass) __LOCK(lock)
#define _read_lock(lock) __LOCK(lock)
#define _write_lock(lock) __LOCK(lock)
#define _spin_lock_bh(lock) __LOCK_BH(lock)
#define _read_lock_bh(lock) __LOCK_BH(lock)
#define _write_lock_bh(lock) __LOCK_BH(lock)
#define _spin_lock_irq(lock) __LOCK_IRQ(lock)
#define _read_lock_irq(lock) __LOCK_IRQ(lock)
#define _write_lock_irq(lock) __LOCK_IRQ(lock)
#define _spin_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
#define _read_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
#define _write_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
#define _spin_trylock(lock) ({ __LOCK(lock); 1; })
#define _read_trylock(lock) ({ __LOCK(lock); 1; })
#define _write_trylock(lock) ({ __LOCK(lock); 1; })
#define _spin_trylock_bh(lock) ({ __LOCK_BH(lock); 1; })
#define _spin_unlock(lock) __UNLOCK(lock)
#define _read_unlock(lock) __UNLOCK(lock)
#define _write_unlock(lock) __UNLOCK(lock)
#define _spin_unlock_bh(lock) __UNLOCK_BH(lock)
#define _write_unlock_bh(lock) __UNLOCK_BH(lock)
#define _read_unlock_bh(lock) __UNLOCK_BH(lock)
#define _spin_unlock_irq(lock) __UNLOCK_IRQ(lock)
#define _read_unlock_irq(lock) __UNLOCK_IRQ(lock)
#define _write_unlock_irq(lock) __UNLOCK_IRQ(lock)
#define _spin_unlock_irqrestore(lock, flags) __UNLOCK_IRQRESTORE(lock, flags)
#define _read_unlock_irqrestore(lock, flags) __UNLOCK_IRQRESTORE(lock, flags)
#define _write_unlock_irqrestore(lock, flags) __UNLOCK_IRQRESTORE(lock, flags)

#endif /* __LINUX_SPINLOCK_API_UP_H */

其中

#define _spin_lock(lock) __LOCK(lock)
#define __LOCK(lock) \
do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)

所以在uni-processor裡頭spin_lock的實做根本就只有把preempt關掉。再看看preempt_disable的實做在/include/linux/preempt.h

#ifdef CONFIG_PREEMPT
#define preempt_disable() \
do { \
inc_preempt_count(); \
barrier(); \
} while (0)
#else
#define preempt_disable() do { } while (0)

#define inc_preempt_count() add_preempt_count(1)

# define add_preempt_count(val) do { preempt_count() += (val); } while (0)

#define preempt_count() (current_thread_info()->preempt_count)

上面都在同一個檔案裡,這邊只擷取用的到的部份。
所以preempt_disable如果在CONFIG_PREEMPT的環境就是把preempt_count加一而已。
如果是在non-preempt就根本什麼都沒做。所以如果你知道只有在process context會使用到spin_lock就可以使用最傳統的。這時儘管是preemptible kernel如果有timer interrupt進來。time slice時間已經用完了,由於切成non_preemptible的緣故,所以也不會被換出去。

接下來再看看其他幾個重要的spin_lock變形:
spin_lock_irq

#define _spin_lock_irq(lock) __LOCK_IRQ(lock)
#define __LOCK_IRQ(lock) \
do { local_irq_disable(); __LOCK(lock); } while (0)

include/linux/irqflags:

#define local_irq_disable() \
do { raw_local_irq_disable(); trace_hardirqs_off(); } while (0)

arch/x86/include/asm/irqflags:

static inline void raw_local_irq_disable(void)
{
native_irq_disable();
}
static inline void native_irq_disable(void)
{
asm volatile("cli": : :"memory");
}

所以在x86裡面spin_lock_irq追到最後就是把cli關掉然後在disable preemption而已。這個時候比較吶悶的是為何中斷已經關掉還要disable preemption,有可能在沒有interrupt進來的情快下被搶佔嗎?沒有timer interrupt還有可能被搶佔?後來想一想,有一個可能是這樣,儘管關掉了外部中斷,內部有可能發生像page fault的這種trap,假設現在page fault的情況是read only導致的,這時系統會copy-on-write製造新的page給這個process使用,這時若發生了記憶體空間不足的問題。就會被抓去睡覺,如此就導致了搶佔的發生。上面是個人的理解,有錯請指教。

spin_lock_irqsave

#define _spin_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
#define __LOCK_IRQSAVE(lock, flags) \
do { local_irq_save(flags); __LOCK(lock); } while (0)

include/linux/irqflags.h

#define local_irq_save(flags) \
do { \
typecheck(unsigned long, flags); \
raw_local_irq_save(flags); \
trace_hardirqs_off(); \
} while (0)

arch/x86/include/asm/irqflags.h

#define raw_local_irq_save(flags) \
do { (flags) = __raw_local_irq_save(); } while (0)

static inline unsigned long __raw_local_irq_save(void)
{
unsigned long flags = __raw_local_save_flags();

raw_local_irq_disable();

return flags;
}
static inline unsigned long __raw_local_save_flags(void)
{
return native_save_fl();
}
static inline unsigned long native_save_fl(void)
{
unsigned long flags;

asm volatile("# __raw_save_flags\n\t"
"pushf ; pop %0"
: "=g" (flags)
: /* no input */
: "memory");

return flags;
}

spin_lock_irqsave和spin_lock_irq的差別:多了把flag存起來的行為。
一般來講只要有interrupt和process context共享spin lock以及process & process context間共享spin lock這兩者同時發生且preemptible的情形,都會spin_lock_irqsave。若是只有process&interrupt context共享lock或是像上例但是non-preemptible kernel,則只需要local_irq_save。

至於spin_lock_bh則是disable softirq,詳細的使用情形則沒有研究。

WORK QUEUE
work queue是一種延遲作業的機制。一般來說預設是使用內建的worker thread。(event0/1/2/3)
使用方法如下:

1. 如果你認為使用預設的worker thread無法處理龐大工作量。此時可以create一個專有的worker thread。
Create_workqueue(const char *name):為每個processer都create一個worker thread。
Create_singlethread_workque(const char *name):只為當下的cpu create一個worker thread。
記得這邊的name就是thread的name,往後可以在程式執行起來以後由ps aux看到。

2. 之後宣告可以有兩種
compile time:DECLARE_WORK(name, void (*function)(void*), void *data)
run time:
INIT_WORK(struct work_struct *work, void (*function)(void*), void *data)
PREPARE_WORK(struct work_struct *work, void (*function)(void*), void *data)
PREPARE_WORK書上(linux device driver 3rd)說沒有初始化work_struct。

3. 之後就是把work排進worker thread開始執行
有兩種,第一種是排進預設的event worker thread:
schedule_work(), schedule_delayed_work()
第二種是排進你自己create的worker thread:
queue_work(struct workqueue_struct *queue, struct work_struct *work)
queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work, unsigned long delay)
第一個參數workqueue_struct就是你自己create的worker thread。

4. 若你想要快點把worker thread裡頭你自己的work執行,可以使用
flush_workqueue(struct workqueue_struct *queue)
若是想直接cancel你所排定的工作cancel_delayed_work(struct work_struct *work)
還是想要直接刪除你的kernel thread:destroy_workqueue(struct workqueue_struct *queue)

Wednesday, October 21, 2009

利用timer interrupt讓兩個Process做context switch一個印出A一個印出B

好不容易有空了,趕快回到自己的興趣抽空學習撰寫OS底層的code。

我在Linux kernel完全剖析的這本書裡面看到一個範例:

他的功能是讓兩個Process利用timer interrupt互相做context switch一個印出A,

當context switch到另一個process則印出B。

當下感到很有興趣。

於是就試著改寫成用usb碟開機的版本。

最後要燒錄到usb disk的image map是長成下面這樣:


boot.bin是組成boot.img的前面1個sector也就是前面512bytes,他是由boot.S編譯而來,

在燒錄到usb隨身碟以後boot.bin就順理成章的變成MBR(master boot record)。

接著head.bin組成後面的11個sectors,他是由head.S編譯而來的。

先不要管code到底在幹麻,首先應該要了解整個code在image裡面的擺放方式,以及在記憶體內

移動的路徑。



知道了code在memory裡移動的路線之後,接著了解一下boot.img是怎麼製作出來的。


# Author: Gavin Guo <mimi0213kimo@gmail.com>
#
# This file is licensed under the GNU General Public License;

CC=gcc
LD=ld
OBJCOPY=objcopy

CFLAGS=-c
#gcc -c的選項是把.S檔編譯成.o檔
TRIM_FLAGS=-R .pdr -R .comment -R.note -S -O binary
#-R的意思是remove掉不需要的section,後面接的是section的名稱
#-S的意思是把symbol table和relocation table拿掉
#-O的意思是要編成怎樣的target format,這邊是binary的格式,也就不帶任何資訊

LDFILE_BOOT=gavin_x86_boot.ld
LDFILE_DOS=gavin_x86_dos.ld
LDFLAGS_BOOT=-e c -T$(LDFILE_BOOT)
LDFLAGS_DOS=-e c -T$(LDFILE_DOS)
#ld的參數接上-e c,e代表的是entry point,c是進入點的symbol,因為code裡面沒有c
#所以會叫說找不到entry point。
#-T是選擇要用的linker script檔案,裡面敘述base address是多少。

all: boot.img head.bin

boot.bin: boot.S
$(CC) $(CFLAGS) boot.S
$(LD) boot.o -o boot.elf $(LDFLAGS_BOOT)
$(OBJCOPY) $(TRIM_FLAGS) boot.elf $@

head.bin: head.S
$(CC) $(CFLAGS) head.S
$(LD) head.o -o head.elf $(LDFLAGS_DOS)
$(OBJCOPY) $(TRIM_FLAGS) head.elf $@

boot.img: boot.bin head.bin
dd if=boot.bin of=boot.img bs=512 count=1
dd if=head.bin of=boot.img seek=1 bs=512 count=11

# You must have the authority to do mount, or you must use "su root" or
# "sudo" command to do "make copy"
copy: boot.img

clean:
@rm -f *.o *.elf *.bin *.BIN *.img

distclean:
@rm -f *.img


objcopy的manual
ld的manual
台大某人寫的objcopy用法
《自写系统》的学习笔记之一 ——实现最小的“操作系统”(1)

gavin_x86_boot.ld長這樣:

SECTIONS
{
. = 0x0000;
.text :
{
_ftext = .;
} = 0
}


gavin_x86_dos.ld長這樣:

SECTIONS
{
. = 0x0000;
.text :
{
_ftext = .;
} = 0
}


看到make file和linker script後就知道boot.img是怎麼做出來的。
接著開始trace source code。先給出boot.S:

BOOTSEG = 0x07c0
SYSSEG = 0x1000

//這邊是16位元的code,所以.code16
.code16
.text

//要記住gavin_x86_boot.ld裡面,base是設為0x0000
jmp $BOOTSEG, $start_code
//因為一開始的offset是0x7c00,所以先jmp到0x07c0:start_code
//把cs改成0x07c0,這樣sp比較好算大小
start_code:
movw %cs, %ax
movw %ax, %ss
movw $0x1000, %sp
xorw %ax, %ax
movw %ax, %ds
//上面把ds設為0,底下的Packet就用自己算的比較不會錯
movw $(Packet + 0x7c00), %si
movb $0x80, %dl
movb $0x42, %ah
int $0x13
//上面ah=0x42h,通常大於8g的硬碟都要用0x42 extension的方法讀。
//這幾行run完以後,11 sectors就存到0x10000了。
cli
//先關掉中斷,待會要準備進入protected mode
cld
//清掉direction flag,到時候movsb就是遞增方式讀取
//從ds:si copy 到es:di
//所以下面這幾行把11 sectors從0x10000 copy到 0x0000
movw $SYSSEG, %ax
movw %ax, %ds
xorw %ax, %ax
movw %ax, %es
xorw %di, %di
xorw %si, %si
//0x1600怎麼算出來的,11x0x200=0x1600
movw $0x1600, %cx
rep movsb
// rep的counter就是%cx

//因為ds剛剛改掉了,所以ds這邊要清成0
xorw %ax, %ax
movw %ax, %ds
lgdtw gdt_48 + 0x7c00
//gdt_48 + 0x7c00這邊是參考ds的位置
inb $0x92, %al
orb $0b00000010, %al
outb %al, $0x92
//上面開啟A20 gate

movl %cr0, %eax
orl $1, %eax
movl %eax, %cr0
//把cr0的0bit on,所以會進入保護模式
//因為進入保護模式,所以要把pipeline裡面的16bits的code清掉
//,後面就要馬上接一個jmp
ljmpl $0x8, $(start_32 + 0x7c00)

.code32
start_32:
/*
mov $0x10, %ax
mov %ax, %gs
mov $0x0768, %ax
xor %edi, %edi
mov %ax, %gs:(%edi)
jmp .
*/
//jmp到code segment的base address,因為code segment存在gdt table的第
//8個bytes所以是8。之後就是jmp到0x0000開始head.s的code
ljmpl $0x8, $0x0

.p2align 2
//align 2是2^2=4所以底下的code會4bytes對齊,ex:0,4,8,12這樣
//cpu存取的時候只需要一次就可以,若是在跨界,則需要存取兩次。
//因為gdt, ldt在座context switch時需要大量的讀取,所以要做
//align加速讀取的速度。

//這邊如果看不懂就去查gdt code,data segment descriptor怎麼填
gdt:
//gdt第一個entry為空,intel定的
.long 0, 0
code:
.word 0xffff
.word 0x0000
.word 0x9800
.word 0x00cf
data:
.word 0xffff
.word 0x0000
.word 0x9200
.word 0x00cf

/* Video Descriptor */
//這個selector的base address是0xb8000,以方便我門印出字串做debug
video:
.word 0xffff
.word 0x8000
.word 0x920b
.word 0x00c0
gdt_end:


gdt_48:
.word (gdt_end - gdt - 1) //gdt的長度
.long gdt + 0x7c00 //gdt放在哪邊
//下面int $0x13h, ah=0x42h所以看不懂的話就去查ralf brown's interrupt list
Packet:
size_packet: .byte 0x10 //0x10代表這個Packet有16個bytes
reserved: .byte 0 //這個byte沒有用
SectorToTran: .2byte 11 //總共要抓11個sectors的data
Offset: .2byte 0x0000 //抓來後放在0x1000:0x0000這個地方
Segment: .2byte 0x1000
SectorLow: .4byte 1 //從usb disk的第二個sector開始抓,
SectorHigh: .4byte 0 //因為sector從0開始算

.org 510
.2byte 0xaa55



heads.S:

/*
* head.s contains the 32-bit startup code.
* Two L3 task multitasking. The code of tasks are in kernel area,
* just like the Linux. The kernel code is located at 0x10000.
*/
KRN_BASE = 0x0
TSS0_SEL = 0x20
LDT0_SEL = 0x28
TSS1_SEL = 0x30
LDT1_SEL = 0x38

.text
startup_32:
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
/*
mov $0x0768, %ax
xor %edi, %edi
mov %ax, %gs:(%edi)
*/
lss stack_ptr,%esp

# setup base fields of descriptors.
movl $KRN_BASE, %ebx
movl $gdt, %ecx
lea tss0, %eax
movl $TSS0_SEL, %edi
call set_base
lea ldt0, %eax
movl $LDT0_SEL, %edi
call set_base
lea tss1, %eax
movl $TSS1_SEL, %edi
call set_base
lea ldt1, %eax
movl $LDT1_SEL, %edi
call set_base

call setup_idt
call setup_gdt
movl $0x10,%eax # reload all the segment registers
mov %ax,%ds # after changing gdt.
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
lss stack_ptr,%esp

# setup up timer 8253 chip.
movb $0x36, %al
movl $0x43, %edx
outb %al, %dx
movl $11930, %eax # timer frequency 100 HZ
movl $0x40, %edx
outb %al, %dx
movb %ah, %al
outb %al, %dx

# setup timer & system call interrupt descriptors.
movl $0x00080000, %eax
movw $timer_interrupt, %ax
movw $0x8E00, %dx
movl $0x08, %ecx
lea idt(,%ecx,8), %esi
movl %eax,(%esi)
movl %edx,4(%esi)
movw $system_interrupt, %ax
movw $0xef00, %dx
movl $0x80, %ecx
lea idt(,%ecx,8), %esi
movl %eax,(%esi)
movl %edx,4(%esi)

# unmask the timer interrupt.
movl $0x21, %edx
inb %dx, %al
andb $0xfe, %al
outb %al, %dx

# Move to user mode (task 0)
pushfl
andl $0xffffbfff, (%esp)
popfl
movl $TSS0_SEL, %eax
ltr %ax
movl $LDT0_SEL, %eax
lldt %ax
movl $0, current
sti
//這邊不是很好理解,而且可能發生一種情形。比方說,sti打開interrupt以後。有可能
//馬上就會有interrupt進來,因為已經填了tr和ldt,所以當interrupt進來後,會把
//所有的register存到tss segment descriptor。但要記得,現在的stack是kernel
//mode的,所以ss & sp會被存到tss裡面。所以即使現在stack裡沒有ss sp flag cs
//ip。到時候在timer interrupt被ljmp $TSS0_SEL, $0回來的時候,還是會從下面
//這邊的push開始run,之後還是會把user mode的堆疊啊,flag啊,cs ip給push進去
pushl $0x17
pushl $stack0_ptr
pushfl
pushl $0x0f
pushl $task0
// debug char 'h'
mov $0x0018, %ax
mov %ax, %gs
mov $0x0768, %ax
xor %edi, %edi
mov %ax, %gs:(%edi)
iret

/****************************************/
setup_gdt:
lgdt lgdt_opcode
ret

setup_idt:
lea ignore_int,%edx
movl $0x00080000,%eax
movw %dx,%ax /* selector = 0x0008 = cs */
movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
lea idt,%edi
mov $256,%ecx
rp_sidt:
movl %eax,(%edi)
movl %edx,4(%edi)
addl $8,%edi
dec %ecx
jne rp_sidt
lidt lidt_opcode
ret

# in: %eax - logic addr; %ebx = base addr ;
# %ecx - table addr; %edi - descriptors offset.
set_base:
addl %ebx, %eax
addl %ecx, %edi
movw %ax, 2(%edi)
rorl $16, %eax
movb %al, 4(%edi)
movb %ah, 7(%edi)
rorl $16, %eax
ret

write_char:
push %gs
pushl %ebx
pushl %eax
mov $0x18, %ebx
mov %bx, %gs
movl scr_loc, %ebx
shl $1, %ebx
movb %al, %gs:(%ebx)
shr $1, %ebx
incl %ebx
cmpl $2000, %ebx
jb 1f
movl $0, %ebx
1: movl %ebx, scr_loc
popl %eax
popl %ebx
pop %gs
ret

/***********************************************/
/* This is the default interrupt "handler" :-) */
.align 2
ignore_int:
push %ds
pushl %eax
movl $0x10, %eax
mov %ax, %ds
movb $0x20, %al
outb %al, $0x20
movl $67, %eax /* print 'C' */
call write_char
popl %eax
pop %ds
iret

/* Timer interrupt handler */
.align 2
timer_interrupt:
push %ds
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
movl $0x10, %eax
mov %ax, %ds
movb $0x20, %al
outb %al, $0x20
movl $1, %eax
cmpl %eax, current
je 1f
movl %eax, current
ljmp $TSS1_SEL, $0
jmp 2f
1: movl $0, current
ljmp $TSS0_SEL, $0
2: popl %eax
popl %ebx
popl %ecx
popl %edx
pop %ds
iret
//注意上面這邊的ljmp是真的會jmp過去另外一個task。假設被timer中斷的是task1,重點
//在於當ljmp執行當下,要記得的被儲存的ss,sp是kernel的且cs ip是指向ljmp的下一行
//以task1來說是"2: popl %eax"。所以可以當作被儲存的是task1被中斷在kernel
//mode的狀態,整個被搬到TSS segment descriptor,之後回來就是"2: popl %eax"
/* system call handler */
.align 2
system_interrupt:
push %ds
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
movl $0x10, %edx
mov %dx, %ds
call write_char
popl %eax
popl %ebx
popl %ecx
popl %edx
pop %ds
iret

/*********************************************/
current:.long 0
scr_loc:.long 0

.align 2
.word 0
lidt_opcode:
.word 256*8-1 # idt contains 256 entries
.long idt + KRN_BASE # This will be rewrite by code.
.align 2
.word 0
lgdt_opcode:
.word (end_gdt-gdt)-1 # so does gdt
.long gdt + KRN_BASE # This will be rewrite by code.

.align 2
idt: .fill 256,8,0 # idt is uninitialized

gdt: .quad 0x0000000000000000 /* NULL descriptor */
.quad 0x00c09a00000007ff /* 8Mb 0x08, base = 0x0000 */
.quad 0x00c09200000007ff /* 8Mb 0x10 */
.quad 0x00c0920b80000002 /* screen 0x18 - for display */

.quad 0x0000e90000000068 # TSS0 descr 0x20
.quad 0x0000e20000000040 # LDT0 descr 0x28
.quad 0x0000e90000000068 # TSS1 descr 0x30
.quad 0x0000e20000000040 # LDT1 descr 0x38
end_gdt:
.fill 128,4,0
stack_ptr:
.long stack_ptr
.word 0x10

/*************************************/
.align 2
ldt0: .quad 0x0000000000000000
.quad 0x00c0fa00000003ff # 0x0f, base = 0x10000
.quad 0x00c0f200000003ff # 0x17
tss0:
.long 0 /* back link */
.long stack0_krn_ptr, 0x10 /* esp0, ss0 */
.long 0, 0 /* esp1, ss1 */
.long 0, 0 /* esp2, ss2 */
.long 0 /* cr3 */
.long task0 /* eip */
.long 0x200 /* eflags */
.long 0, 0, 0, 0 /* eax, ecx, edx, ebx */
.long stack0_ptr, 0, 0, 0 /* esp, ebp, esi, edi */
.long 0x17,0x0f,0x17,0x17,0x17,0x17 /* es, cs, ss, ds, fs, gs */
.long LDT0_SEL /* ldt */
.long 0x8000000 /* trace bitmap */

.fill 128,4,0
stack0_krn_ptr:
.long 0

/************************************/
.align 2
ldt1: .quad 0x0000000000000000
.quad 0x00c0fa00000003ff # 0x0f, base = 0x10000
.quad 0x00c0f200000003ff # 0x17
tss1:
.long 0 /* back link */
.long stack1_krn_ptr, 0x10 /* esp0, ss0 */
.long 0, 0 /* esp1, ss1 */
.long 0, 0 /* esp2, ss2 */
.long 0 /* cr3 */
.long task1 /* eip */
.long 0x200 /* eflags */
.long 0, 0, 0, 0 /* eax, ecx, edx, ebx */
.long stack1_ptr, 0, 0, 0 /* esp, ebp, esi, edi */
.long 0x17,0x0f,0x17,0x17,0x17,0x17 /* es, cs, ss, ds, fs, gs */
.long LDT1_SEL /* ldt */
.long 0x8000000 /* trace bitmap */

.fill 128,4,0
stack1_krn_ptr:
.long 0

/************************************/
task0:
movl $0x17, %eax /*這邊之所以要換掉ds是因為之後的stack0_ptr*/
movw %ax, %ds /*相對位置是用ldt裡面的第二個entry,所存的*/
/*base address為基準*/
mov $65, %al /*65就是0x41所以 print 'A' */
int $0x80
movl $0x08ffffff, %ecx
//因為task0的ecx的loop值大概只有task1的一半,所以
//印出A的數量大概會是B的兩倍
1: loop 1b
jmp task0

.fill 128,4,0
stack0_ptr:
//stack0的userspace stack所在位置。
.long 0

task1:
movl $0x17, %eax
movw %ax, %ds
mov $66, %al /* 0x42所以print 'B' */
int $0x80
movl $0x0fffffff, %ecx
1: loop 1b
jmp task1

.fill 128,4,0
stack1_ptr:
//上面是task1的user space stack所在的位置。
.long 0

/*** end ***/


可以看出主要的組成就是上面這五隻檔案:
Makefile, gavin_x86_boot.ld, gavin_x86_dos.ld, boot.S, head.S

剛開始做實驗的時候會覺得很奇怪,為什麼按下鍵盤只會出現一個C(也就是ignore_int),

後來發現因為keyboard interrupt要回去重新清除flag。下完io以後才能再繼續收到interrupt。

在這個實驗的過程,可以多多利用objdump xxx.elf(xxx.o) -d -M addr(16)32,data(16)32。如果已經

build成binary可以用hexedit。若是不知道如何使用=>man hexedit。

Monday, June 08, 2009

Multi-Point Touch Panel Driver

之前曾經做個一個假的模擬multi-touch gesture的demo。這次做的就是真的了,何謂假的multi-touch呢?採用只會回報單點的touch controller,去實驗各種不同的手勢,然後做個一百個人,一個人假設有一百筆的採樣。之後用統計學去分類各種不同的手勢的點電壓變化。藉此做出模倣實際multi-touch真的會送出兩點的這種行為。但,實際上,這種方法無法還原真正的兩點。只能觀察出不同gesture所具有不同的電壓變化。就是只能有multi-touch而不能有multi-point的應用。

先給出multi-point一張完整的架構圖



















完整的source code如下:


/******************************************************************************

* AsusTsUsb.c -- Driver for Multi-Touch USB Touchscreens

*

* This program is free software; you can redistribute it and/or

* modify it under the terms of the GNU General Public License as

* published by the Free Software Foundation; either version 2 of the

* License, or (at your option) any later version.

*

* This program is distributed in the hope that it will be useful, but

* WITHOUT ANY WARRANTY; without even the implied warranty of

* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU

* General Public License for more details.

*

* You should have received a copy of the GNU General Public License

* along with this program; if not, write to the Free Software

* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*

*/



#include <linux/device.h>

#include <linux/hid.h>

#include <linux/module.h>

#include <linux/usb.h>

#include <linux/usb/input.h>


#define DRIVER_VERSION "v0.1"

#define DRIVER_AUTHOR "Gavin Guo, mimi0213kimo@gmail.com"

#define DRIVER_DESC "Asus USB Multi-Touch Touch Panel driver"

#define DRIVER_LICENSE "GPL"



MODULE_AUTHOR(DRIVER_AUTHOR);

MODULE_DESCRIPTION(DRIVER_DESC);

MODULE_LICENSE(DRIVER_LICENSE);


#define USB_VENDOR_ID_ASUS 0x0486

#define USB_DEVICE_ID_ASUS 0x0185

#define VAIO_RDESC_CONSTANT 0x0001



static int swap_xy;

module_param(swap_xy, bool, 0644);

MODULE_PARM_DESC(swap_xy, "If set X and Y axes are swapped.");


struct asus_mt_usb {

unsigned char *data;

struct input_dev *input1, *input2;

struct asmt_device_info *type;

struct usb_device *udev;

dma_addr_t data_dma;

struct urb *urb;

char name[128];

char phys0[64];

char phys1[64];

int x1, y1;

int x2, y2;

int touch1, touch2, press;

int p2;

};

struct asmt_device_info {

int min_xc, max_xc;

int min_yc, max_yc;

int min_press, max_press;

int rept_size;


void (*process_pkt) (struct asus_mt_usb *asmt, unsigned char *pkt, int len);


/*

* used to get the packet len. possible return values:

* > 0: packet len

* = 0: skip one byte

* < 0: -return value more bytes needed

*/

int (*get_pkt_len) (unsigned char *pkt, int len);


int (*read_data) (struct asus_mt_usb *asmt, unsigned char *pkt);

int (*init) (struct asus_mt_usb *asmt);

};

static int asus_read_data(struct asus_mt_usb *dev, unsigned char *pkt)

{

/*

if (!(pkt[0] & 0x80) ((pkt[1] pkt[2] pkt[3]) & 0x80))

return 0;


dev->x = ((pkt[0] & 0x1F) << 7) (pkt[2] & 0x7F);

dev->y = ((pkt[1] & 0x1F) << 7) (pkt[3] & 0x7F);

dev->touch = pkt[0] & 0x20;

*/

dev->x1 = ((pkt[4] & 0x0F) << 8) (pkt[3] & 0xFF);

dev->y1 = ((pkt[6] & 0x0F) << 8) (pkt[5] & 0xFF);

dev->touch1 = pkt[1] & 0x03;


dev->p2 = pkt[13] & 0x02;


if(dev->p2) {

dev->x2 = ((pkt[10] & 0x0F) << 8) (pkt[9] & 0xFF);

dev->y2 = ((pkt[12] & 0x0F) << 8) (pkt[11] & 0xFF);

dev->touch2 = pkt[7] & 0x03;

}

return 1;

}


static struct asmt_device_info type = {

.min_xc = 0x0,

.max_xc = 0x0fff,

.min_yc = 0x0,

.max_yc = 0x0fff,

.rept_size = 8,

.read_data = asus_read_data,

};


static void usbtouch_process_pkt(struct asus_mt_usb *asmt,

unsigned char *pkt, int len)

{

struct asmt_device_info *type = asmt->type;


if (!type->read_data(asmt, pkt))

return;


input_report_key(asmt->input1, BTN_TOUCH, asmt->touch1);

input_report_key(asmt->input2, BTN_TOUCH, asmt->touch2);


if (swap_xy) {

input_report_abs(asmt->input1, ABS_X, asmt->y1);

input_report_abs(asmt->input1, ABS_Y, asmt->x1);

input_report_abs(asmt->input2, ABS_X, asmt->y2);

input_report_abs(asmt->input2, ABS_Y, asmt->x2);

} else {

input_report_abs(asmt->input1, ABS_X, asmt->x1);

input_report_abs(asmt->input1, ABS_Y, asmt->y1);


if(asmt->p2) {

input_report_abs(asmt->input2, ABS_X, asmt->x2);

input_report_abs(asmt->input2, ABS_Y, asmt->y2);

}

}

/*

* if (type->max_press)

*

* input_report_abs input_report_abs(asmt->input, ABS_PRESSURE, asmt->press);

*/

input_sync(asmt->input1);

if(asmt->p2)

input_sync(asmt->input2);

}


#define USB_REQ_SET_REPORT 0x09

static int usb_set_report_feature(struct usb_interface *intf, unsigned char type,

unsigned char id, void *buf, int size)

{

return usb_control_msg(interface_to_usbdev(intf),

usb_sndctrlpipe(interface_to_usbdev(intf), 0),

USB_REQ_SET_REPORT,

USB_TYPE_CLASS USB_RECIP_INTERFACE,

(type << 8) + id,

intf->cur_altsetting->desc.bInterfaceNumber, buf,

size, HZ);

}


static void asus_mt_irq(struct urb *urb)

{

//printk("%s: that's ok\n", __FUNCTION__ );

struct asus_mt_usb *asmt = urb->context;

//struct usbhid_device *usbhid = hid->driver_data;

int retval;

unsigned char* data = asmt->data;

switch (urb->status) {

case 0:

// success

break;

case -ETIME:

// this urb is timing out

dbg("%s - urb timed out - was the device unplugged?",

__FUNCTION__);

return;

case -ECONNRESET:

case -ENOENT:

case -ESHUTDOWN:

return;

default:

goto resubmit;

}


printk("data = %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", data[0],\

data[1],data[2],data[3],\

data[4],data[5],data[6],data[7],data[8],data[9],data[10],data[11],data[12],data[13]);

//printk("%s:urb->actual_length = %d\n", __FUNCTION__, urb->actual_length );

asmt->type->process_pkt(asmt, asmt->data, urb->actual_length);

resubmit:

retval = usb_submit_urb(urb, GFP_ATOMIC);

if (retval)

err("%s - usb_submit_urb failed with result: %d",

__FUNCTION__, retval);

}


static int asus_probe(struct usb_interface *intf, const struct usb_device_id *id)

{

printk("%s\n", __FUNCTION__);

struct usb_host_interface *interface = intf->cur_altsetting;

struct usb_device *dev = interface_to_usbdev(intf);

struct input_dev *input_dev1, *input_dev2;

int n = 0, insize = 14;


struct asus_mt_usb *asmt = kzalloc(sizeof(struct asus_mt_usb), GFP_KERNEL);

asmt->type = &type;

asmt->udev = dev;


if (dev->manufacturer)

strlcpy(asmt->name, dev->manufacturer, sizeof(asmt->name));


if (dev->product) {

if (dev->manufacturer)

strlcat(asmt->name, " ", sizeof(asmt->name));

strlcat(asmt->name, dev->product, sizeof(asmt->name));

}


if (!strlen(asmt->name))

snprintf(asmt->name, sizeof(asmt->name),

"USB Touchscreen %04x:%04x",

le16_to_cpu(dev->descriptor.idVendor),

le16_to_cpu(dev->descriptor.idProduct));


usb_make_path(dev, asmt->phys0, sizeof(asmt->phys0));

strlcat(asmt->phys0, "/input0", sizeof(asmt->phys0));

usb_make_path(dev, asmt->phys1, sizeof(asmt->phys1));

strlcat(asmt->phys1, "/input1", sizeof(asmt->phys1));


if (!asmt->type->process_pkt) {

printk("process_pkt is null\n");

asmt->type->process_pkt = usbtouch_process_pkt;

}

usb_set_intfdata(intf, asmt);

input_dev1 = input_allocate_device();

input_dev2 = input_allocate_device();

input_dev1->name = asmt->name;

input_dev2->name = asmt->name;

usb_to_input_id(dev, &input_dev1->id);

usb_to_input_id(dev, &input_dev2->id);

asmt->input1 = input_dev1;

asmt->input2 = input_dev2;

if(!asmt !input_dev1 !input_dev2) {

printk("Memory is not enough\n");

goto fail1;

}


input_dev1->dev.parent = &intf->dev;

input_set_drvdata(input_dev1, asmt);


input_dev1->evbit[0] = BIT_MASK(EV_KEY) BIT_MASK(EV_ABS);

input_dev1->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);

input_set_abs_params(input_dev1, ABS_X, asmt->type->min_xc, asmt->type->max_xc, 0, 0);

input_set_abs_params(input_dev1, ABS_Y, asmt->type->min_yc, asmt->type->max_yc, 0, 0);


input_dev2->dev.parent = &intf->dev;

input_set_drvdata(input_dev2, asmt);


input_dev2->evbit[0] = BIT_MASK(EV_KEY) BIT_MASK(EV_ABS);

input_dev2->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);

input_set_abs_params(input_dev2, ABS_X, asmt->type->min_xc, asmt->type->max_xc, 0, 0);

input_set_abs_params(input_dev2, ABS_Y, asmt->type->min_yc, asmt->type->max_yc, 0, 0);


asmt->data = usb_buffer_alloc(dev, insize, GFP_KERNEL,

&asmt->data_dma);

if(!asmt->data) {

printk("asmt->data allocating fail");

goto fail;

}

for (n = 0; n < interface->desc.bNumEndpoints; n++) {

struct usb_endpoint_descriptor *endpoint;

int pipe;

int interval;


endpoint = &interface->endpoint[n].desc;

if (!usb_endpoint_xfer_int(endpoint))

continue;


interval = endpoint->bInterval;


if (usb_endpoint_dir_in(endpoint)) {

if (asmt->urb)

continue;

if (!(asmt->urb = usb_alloc_urb(0, GFP_KERNEL)))

goto fail;

pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

//insize = usb_maxpacket(dev, pipe, usb_pipeout(pipe));

usb_fill_int_urb(asmt->urb, dev, pipe, asmt->data,

insize, asus_mt_irq, asmt, interval);

asmt->urb->transfer_dma = asmt->data_dma;

asmt->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;

}

}


char *buf = kmalloc(8, GFP_KERNEL); /* 8 bytes are enough for both products */

buf[0] = 0x07;

buf[1] = 0x02;

if(usb_set_report_feature(intf, 2, 7, buf, 8))

printk("set report true\n");

else

printk("set report false\n");


if (usb_submit_urb(asmt->urb, GFP_ATOMIC)) {

printk("usb submit urb error\n");

return -EIO;

}


input_register_device(asmt->input1);

input_register_device(asmt->input2);

return 0;

fail:

usb_free_urb(asmt->urb);

asmt->urb = NULL;

usb_buffer_free(dev, insize, asmt->data, asmt->data_dma);

fail1:

input_free_device(input_dev1);

input_free_device(input_dev2);

kfree(asmt);

return 1;

}


static void asus_disconnect(struct usb_interface *intf)

{

struct asus_mt_usb *asmt = usb_get_intfdata(intf);

usb_set_intfdata(intf, NULL);


if (asmt) {

input_unregister_device(asmt->input2);

input_unregister_device(asmt->input1);

usb_kill_urb(asmt->urb);

//input_unregister_device(asmt->input);

usb_free_urb(asmt->urb);

usb_buffer_free(interface_to_usbdev(intf), 14,

asmt->data, asmt->data_dma);

kfree(asmt);

}

}


static const struct usb_device_id asus_devices[] = {

{ USB_DEVICE(USB_VENDOR_ID_ASUS, USB_DEVICE_ID_ASUS) },

{ }

};

MODULE_DEVICE_TABLE(usb, asus_devices);


static struct usb_driver asus_driver = {

.name = "asus",

.probe = asus_probe,

.disconnect = asus_disconnect,

.id_table = asus_devices,

};


static int asus_init(void)

{

printk("asus_init\n");

return usb_register(&asus_driver);

}


static void asus_exit(void)

{

printk("asus_exit\n");

usb_deregister(&asus_driver);

}


module_init(asus_init);

module_exit(asus_exit);




先敘述一下流程,點從firmware丟上來,首先經過UHCI再來hub driver。
經過hub後會判斷是否為hid device若是則會經過hid parser,因為我們的device為hid device,所已預設一定會經過hid parser。經由hid parser去parse也就算了,問題是此時的hid parser還沒有成熟到可以parse這個multi-touch hid的digitizer封包。也因為這樣,所以usb kernel裡頭有一個ignore list,可以把device的vendor id & device id放到這個list內。這樣在mapping的時候,就不會map到hid parser driver。

很有趣的是,當map到hid parser,還會在做一次mapping,這次就是mapping到hid bus。hid bus上面也有一個blacklist。你要加到他的blacklist才不會map到他預設的driver。

嗯嗯,等到match到我們的driver,會先進入probe,然後create兩個input device並且掛上call back function。這兩個input device會match到evdev handler。

要支援multi-point,在firmware方面是用set report的方式把multi-point的功能打開,不然預設他只會送出一個點。為了要找怎麼下command給touch panel controller,trace了一下FW的source code發現應該是用set report的方式;所以我便到了kernel的根目錄底下做搜尋grep -r "set" ./drivers/usb/ grep report

發現了這個.c檔可以做參考./drivers/usb/misc/iowarrior.c。進去裡面看了以後發覺還真的是不錯。



#define GET_TIMEOUT 5
#define USB_REQ_GET_REPORT 0x01
//#if 0
static int usb_get_report(struct usb_device *dev,
struct usb_host_interface *inter, unsigned char type,
unsigned char id, void *buf, int size)
{
return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
USB_REQ_GET_REPORT,
USB_DIR_IN USB_TYPE_CLASS
USB_RECIP_INTERFACE, (type << 8) + id,
inter->desc.bInterfaceNumber, buf, size,
GET_TIMEOUT*HZ);
}
//#endif

#define USB_REQ_SET_REPORT 0x09

static int usb_set_report(struct usb_interface *intf, unsigned char type,
unsigned char id, void *buf, int size)
{
return usb_control_msg(interface_to_usbdev(intf),
usb_sndctrlpipe(interface_to_usbdev(intf), 0),
USB_REQ_SET_REPORT,
USB_TYPE_CLASS USB_RECIP_INTERFACE,
(type << 8) + id,
intf->cur_altsetting->desc.bInterfaceNumber, buf,
size, HZ);
}

我可以用到usb_set_report這個function。後來測了一下type和id有沒有填沒有差,因為FW不會去檢查這兩個欄位。所以code可以像下面這樣子寫:



char *buf = kmalloc(8, GFP_KERNEL);
buf[0] = 7;
buf[1] = 2;
usb_set_report(intf, 0, 0, buf, 8);



其實這支driver沒有甚麼比較難的地方,大概就是這樣。其他的code自己看一看就知道是為什麼了。
只是剛開始要無中生有,需要去trace相關的source code找出我們需要的部分,比較難。

真的要掛上這支driver要在X window起來以前,所以要到/etc/inittab裡頭,把run level改成3。
開機就會先進入command mode,然後再sudo insmod hid-asusmt.ko。如此,就可以趕在X window起來之前把driver上起來。

然後還要記得的是加入xorg.conf到/etc/X11/xorg.conf,如果你不知道xorg.conf裡頭的/dev/input/evdevxx。不知道要設多少,可以去cat /proc/bus/input/devices查詢,也可以順便看看,driver有沒有上好這個devices。若是driver沒有上,這邊就沒有input devices的資訊。

Section "ServerLayout"

Identifier "X.org Configured"

Screen 0 "Screen0" 0 0

InputDevice "Mouse0" "CorePointer"

InputDevice "Keyboard0" "CoreKeyboard"

InputDevice "Test0"

InputDevice "Test1"

EndSection

Section "Files"

ModulePath "/usr/lib/xorg/modules"

FontPath "catalogue:/etc/X11/fontpath.d"

FontPath "built-ins"

EndSection

Section "Module"

Load "extmod"

Load "dbe"

Load "glx"

Load "dri2"

Load "dri"

EndSection

Section "InputDevice"

Identifier "Keyboard0"

Driver "kbd"

EndSection

Section "InputDevice"

Identifier "Mouse0"

Driver "mouse"

Option "Protocol" "auto"

Option "Device" "/dev/input/mice"

Option "ZAxisMapping" "4 5 6 7"

EndSection

Section "InputDevice"

Identifier "Test0"

Driver "evdev"

Option "Device" "/dev/input/event8"

EndSection

Section "InputDevice"

Identifier "Test1"

Driver "evdev"

Option "Device" "/dev/input/event9"

EndSection

Section "Monitor"

Identifier "Monitor0"

VendorName "Monitor Vendor"

ModelName "Monitor Model"

EndSection

Section "Device"

### Available Driver options are:-

### Values: <i>: integer, <f>: float, <bool>: "True"/"False",

### <string>: "String", <freq>: "<f> Hz/kHz/MHz"

### [arg]: arg optional

#Option "NoAccel" # [<bool>]

#Option "SWcursor" # [<bool>]

#Option "ColorKey" # <i>

#Option "CacheLines" # <i>

#Option "Dac6Bit" # [<bool>]

#Option "DRI" # [<bool>]

#Option "NoDDC" # [<bool>]

#Option "ShowCache" # [<bool>]

#Option "XvMCSurfaces" # <i>

#Option "PageFlip" # [<bool>]

Identifier "Card0"

Driver "intel"

VendorName "Intel Corporation"

BoardName "Mobile 945GME Express Integrated Graphics Controller"

BusID "PCI:0:2:0"

EndSection

Section "Screen"

Identifier "Screen0"

Device "Card0"

Monitor "Monitor0"

SubSection "Display"

Viewport 0 0

Depth 1

EndSubSection

SubSection "Display"

Viewport 0 0

Depth 4

EndSubSection

SubSection "Display"

Viewport 0 0

Depth 8

EndSubSection

SubSection "Display"

Viewport 0 0

Depth 15

EndSubSection

SubSection "Display"

Viewport 0 0

Depth 16

EndSubSection

SubSection "Display"

Viewport 0 0

Depth 24

EndSubSection

EndSection



執行/usr/bin/startx進入X window。
若是還沒有安裝MPX得先遵循下面的步驟:
This is readme for MPX enabling on Moblin.

1 Install the latest Moblin image

2 update the RPM packages in the attached .zip file by “rpm –Uvh xxxx.rpm”

3 reboot

4 plug the second USB mouse

5 execute “xinput list”, then you could find the device id of newly plugged second USB mouse. In my system it’s “HID 413c:3010”

6 execute “xinput –create-master “mouse2””

7 execute “xinput –reattach “HID 413c:3010” “mouse2 pointer””



Then you would find 2 pointers on the screen, which means MPX works J

在我們的兩個node裡面第二個node的名稱叫做Test1,所以必須要把xinput -reattach之後的“HID 413c:3010”改成"Test1",這樣就能夠使用兩個點了。如果要移除第二個點則xinput -remove-master "gavin pointer"。即可。

Wednesday, April 22, 2009

Multi-Touch Demo on T91 with Intel fantastic Moblin

因為老闆的要求,所以要在新的moblin上面porting multi-touch的幾種Gesture。
由於是demo的緣故所以用ioctl pollling的方式,沒有實做新的input event並且用送event的方式實踐。下面是這個實做的diagram。

首先談談panel的構造,我們使用的是傳統的四線式單點touch panel,使用自己做的硬體電路模擬實際的多點情形。Controller裡FW送上來的data是長這樣:

g_MouseReport[0] = 1;
g_MouseReport[1] = (g_uZ1 &amp; 0xFF);
g_MouseReport[2] = (g_uZ1 >> 8) &amp; 0x0F;
g_MouseReport[3] = 0;

g_MouseReport[4] = (g_uX1 &amp; 0xFF);
g_MouseReport[5] = (g_uX1 >> 8) &amp; 0x0F;
g_MouseReport[6] = (g_uY1 &amp; 0xFF);
g_MouseReport[7] = (g_uY1 >> 8) &amp; 0x0F;

g_MouseReport[8] = 1;
g_MouseReport[9] = (g_uZ2 &amp; 0xFF);
g_MouseReport[10] = (g_uZ2 >> 8) &amp; 0x0F;
g_MouseReport[11] = 1;
g_MouseReport[12] = (g_uX2 &amp; 0xFF);
g_MouseReport[13] = (g_uX2 >> 8) &amp; 0x0F | 0x10;
g_MouseReport[14] = (g_uY2 &amp; 0xFF);
g_MouseReport[15] = (g_uY2 >> 8) &amp; 0x0F | 0x10;

會收到兩個report為一筆資料,一個report data有八個bytes,兩組report data可以合成一組七個bytes的資料,以利上面的演算法做計算。g_MouseReport陣列的單位是byte,第零個和第八個代表的是資料的結束或者是開始,如果是1代表還有資料在傳送,若是零則代表一筆gesture資料的結束。每筆report data的第四個byte也就是3和11所代表的意義是兩筆data的前後順序,如果是0代表第一筆,1代表第二筆,因此可以由這個值來區分前後。其他的bytes分別封裝了X1,Y1,Z1,X2,Y2,Z2。Z代表壓力的大小,這些座標值的精準度為12 bits,知道這個就可以看懂,上面的code為何要這麼寫。

洋洋灑灑的寫了一堆,重點是我們要如何拿到這些data?首先,要trace USB hub,在drivers/usb/core/hub.c裡面,看了以後你就知道,東西是如何從下面的UHCI, EHCI傳送資料上來,之後在hub_port_connect_change會幫一個新的device做註冊,使用usb_new_device這個function。再接著,你會看到一個device進來後會先mapping到一支generic_driver,這是每個usb device的必經之路,這支driver,會對usb device做descriptor的詢問動作,然後得到device的基本資料,再拿這些資訊去對所有usb bus上面的interface driver做mapping。這就是一個usb device進來以後是如何能在系統裡正常運作的過程。

很可愛的是,我們這邊的Device宣告為hid device,但,傳送上來的資料卻和report descriptor裡面宣告的資料是不符合的,我們自己傳送兩筆8 bytes的report descriptor資料,裡面也宣告為x軸或者是y軸,但是都是需要上面所提到的算法,做重新的組合,因此,我們的HID deviced不能經過HID protocol,不然parse出來的資料會亂七八糟。就是為了要解決這個問題,而且時間又不太充裕的情況下,我想了兩個方法,第一個是去看makefile,想辦法在hid driver被註冊前,先註冊我們的Driver,同時也用Vendor ID and Device ID做比對的項目,這樣,我們的Driver就會提早被device認到,也就不會走到HID protocol這個死胡同裡。但是這樣的缺點是要build in,而且在開發的過程極其麻煩,每次改code要確定能不能work就要整個kernel重build,然後再重開機,實在是太耗時間了。第二個方法,我在usb bus的match函式裡頭,加入了判斷的式子:


int usb_match_one_id(...) {
...
if(dev=>descriptor.idVendor == 0x0486 && dev->descriptor.idProduct == 0x0180)
intf->desc.bInterfaceClass = 0;
...
}


這判斷式的用意就是要把我們device的HID標籤拿掉,讓他不會進入HID protocol。但,缺點是,USB protocol被我更動了,這樣,open source community那群人應該不會饒了我吧!其實,好像也沒那麼嚴重,我只是要demo而已,所以最後就這樣做下去了。測試看看,果不期然,這樣我的touch panel就不會被HID給抓走。而在我driver還沒掛上的時候自己埋的printk,dmesg顯示沒有被mapping的現象。

現在HID這邊已經解決了,接下來就是要撰寫自己的USB driver。
恩,我就在Driver資料夾底下的input和usb/input裡面晃呀晃,後來看到drivers/usb/input底下找到了一支Vojtech Pavlik大師(你如果常常看code可以發現input底下的code很多都出自於他的手)寫的usbmouse.c驅動,他裡面就是為了支援boot device的,而不用為了要支援mouse和keyboard而大費周章的把HID protocol掛上。所以自己寫了隻code去parse boot device。正好,大師的作品可以派上用場。所以我就把他短短兩百多行的Code trace一下,不久寫出自己的第一隻usb driver。也是我的第一隻driver。

首先看看我的Makefile:
他是長這樣

obj-m += asusmt.o
KDIR=/home/bboy/linux-2.6.28
PWD=$(shell pwd)

KVERSION = $(shell uname -r)

all:
#cp ../linux-source-2.6.21.4-eeepc/drivers/usb/input/asusmt.c ./
make -C $(KDIR) M=$(PWD) clean
make -C $(KDIR) M=$(PWD) modules
#下面先移除asusmt舊的module
sudo rmmod asusmt
#這邊在插入asusmt.ko新編譯好的module
sudo insmod asusmt.ko

clean:
make -C $(KDIR) M=$(PWD) clean

install:
make -C $(KDIR) M=$(PWD) modules_install

妳可以把Makefile寫成這樣KDIR是你放kernel source code的地方,如果Driver安裝在不同的kernel上,則需要根據不同的kernel source重新編譯。

接著我貼上USB driver端所有的程式碼:
trace這段code可以從init開始看,insmod後就會執行init,rmmod的時候會執行exit。device拔除後會執行disconnect,還有重要的是probe,當driver和device mapping成功後,第一個執行的function就是probe,裡面包含了/dev/asus_mt的創建,還有urb的初始化,和urb的submit,都在這裡面。最後就是irq那個urb的callback function。當有data進來的時候就會從irq那隻把urb丟進來。

/******************************************************************************
* AsusTsUsb.c -- Driver for Multi-Touch USB Touchscreens
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
//上面這四行後來把他們comment掉其實也沒事,不知道那邊會用到,我也是直接從
//Vojtech Pavlik的code copy過來的。
#include <linux/usb/input.h>
#include <linux/hid.h>
#include <asm/uaccess.h>

//這邊就照著他們的標準寫一些基本的information。
#define DRIVER_VERSION "v0.1"
#define DRIVER_AUTHOR "Gavin Guo, mimi0213kimo@gmail.com"
#define DRIVER_DESC "Asus USB Multi-Touch Touch Panel driver"
#define DRIVER_LICENSE "GPL"


MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE(DRIVER_LICENSE);

//asus_mt是用在註冊node時所使用的字串
#define ASUS_MT_STRING "asus_mt"
//點進來的時候我是用100個大小的queue去存他
#define MAXQUEUE 100
//兩個8個bytes的data,最後會parse成7個bytes的data往上傳。
#define DATANUM 7
//parse好的資料暫時存放在axies_data array
u32 axies_data[DATANUM];
//存放動態get到的major number。
static int asus_mt_major;
//註冊class時要用到的point。
static struct class *asus_mt_class;
//queue的head和tail,head用來新增data到queue,tail用來拿出data。
static int head = 0, tail = 0;
//ready用來判斷是否queue內已有資料進來。
static int ready = 0;
//當我們實作等待資料的架構時,要先宣告一個wait。
wait_queue_head_t wait;
//這個就是我們存放資料的queue
typedef struct DataQueue {
u32 axies_data[DATANUM];
}DataQueue;

DataQueue queue[MAXQUEUE];
//DECLARE_MUTEX(cosem);
/*
axies_data[0] = button
axies_data[1] = Z1
axies_data[2] = X1
axies_data[3] = Y1
axies_data[4] = Z2
axies_data[5] = X2
axies_data[6] = Y2
*/

struct AsTouch_usb {
unsigned char *data;
dma_addr_t data_dma;
struct urb *irq;
struct usb_device *udev;
struct input_dev *input;
char name[128];
char phys[64];
};

//這個struct存放的是要比較的項目,其中flag代表的是要比較的項目,
//flag下面是要比較的實際內容,其中USB_DEVICE_ID_MATCH_DEVICE代表要比較idVendor與
//idProduct,而實際的內容存放在下面。之所以除了vendor id和device id外還要比較
//SUBCLASS_BOOT和MOUSE是因為目前這個device上面有兩種interface,一個是mouse另一個
//是keyboard,mouse是模擬成我們的touch panel,keyboard用不到,但為了防止keyboard
//亂送資料,所以多加幾個mapping的項目,使之只能mapping到mouse。這樣driver就不會接
//收到keyboard送出來的report descriptor。
static struct usb_device_id AsusTsUsb_devices[] = {
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
USB_DEVICE_ID_MATCH_INT_SUBCLASS |
USB_DEVICE_ID_MATCH_INT_PROTOCOL,
.idVendor = 0x0486,
.idProduct = 0x0180,
.bInterfaceSubClass = USB_INTERFACE_SUBCLASS_BOOT,
.bInterfaceProtocol = USB_INTERFACE_PROTOCOL_MOUSE
},
{ }
};
//insert_queue用來新增資料到queue
static int insert_queue()
{
if ( (head + 1) == tail ) {
printk("DataQueue is full, some data is losed\n");
/* Push tail to 1 step forward, prevent tail from reading new coming data */
//tail++;
return -1;
}else {
head = (head + 1) % MAXQUEUE;
int i = 0;
for( ; i < DATANUM; i++ ){
queue[head].axies_data[i] = axies_data[i];
}
printk("insert queue[%d].axies_data %04x %04x %04x %04x %04x %04x %04x\n", head,
queue[head].axies_data[0],queue[head].axies_data[1],queue[head].axies_data[2],
queue[head].axies_data[3],queue[head].axies_data[4],queue[head].axies_data[5],
queue[head].axies_data[6]);
}
return 0;
}
//用來從queue copy資料到user space
static int delete_queue(unsigned long arg)
{
if(head == tail) {
goto empty;
} else {
tail = (tail + 1) % MAXQUEUE;

printk("delete queue[%d].axies_data %04x %04x %04x %04x %04x %04x %04x\n", tail,
queue[tail].axies_data[0],queue[tail].axies_data[1],queue[tail].axies_data[2],
queue[tail].axies_data[3],queue[tail].axies_data[4],queue[tail].axies_data[5],
queue[tail].axies_data[6]);
if(copy_to_user((unsigned int *)arg, queue[tail].axies_data, sizeof(u32)*7 )) {
printk("error copy_to_user\n");
return -EFAULT;
}
return 0;
}

empty:
printk("New data have not been coming yet..\n");
return -1;
}
//ioctl function可以從user space接收cmd,並且依據command做必要的行為,
//arg是從user space傳下來的引數,可以傳一個整數下來,或者是傳一個指標下來。
static int asus_mt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
ready = 0;
//wait_event_interruptible(wait, ready);
printk("asus_mt...ioctl\n");
//printk("cmd = %d\n", cmd);
//cli();
//if(down_interruptible(&cosem))
// return -ERESTARTSYS;
switch(cmd){

case 0:
printk("delete queue\n");
delete_queue(arg);
break;
case 1:
printk("query if queue has data\n");
if(head == tail)
*(unsigned int*)arg = (unsigned int)0;
else
*(unsigned int*)arg = (unsigned int)1;
break;
//default:
// return -EFAULTr
}
//up(&cosem);
//sti();
printk("reading success asus_mt_ioctl\n");
return 0;
}
//open在這邊沒有實做
static int asus_mt_open(struct inode *inode, struct file *file)
{
/*struct AsTouch_usb *dev;

dev = container_of(inode->i_cdev, struct AsTouch_usb, cdev);

file->private_data = dev;
*/
return 0;
}

//release也是沒有實做
static int asus_mt_release(struct inode *inode, struct file *file)
{}
//我們如果要實做檔案的operation就是實做在這個struct裡
static struct file_operations asus_mt_fops = {
.owner = THIS_MODULE,
.ioctl = asus_mt_ioctl,
.open = asus_mt_open,
.release = asus_mt_release,
};
//當我們實做urb時,就要把下面這個irq call back函式填入urb的complete function欄位,
//接著呼叫usb_submit_urb後,若是有report data進來,就會透過這個function把urb丟進來,
//資料就在下面AsTouch->data這個欄位。
static void AsusTsUsb_irq(struct urb *urb)
{
printk("%s: that's ok\n", __FUNCTION__ );
struct AsTouch_usb *AsTouch = urb->context;
int retval;
unsigned char* data = AsTouch->data;
//下面這邊做的就是一些很routine的判斷。
switch (urb->status) {
case 0:
// success
break;
case -ETIME:
// this urb is timing out
dbg("%s - urb timed out - was the device unplugged?",
__FUNCTION__);
return;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
return;
default:
goto resubmit;
}

/*if( !(data[3] | 0x00) ) {
if(down_interruptible(&cosem))
return -ERESTARTSYS;
}*/
//這邊要把資料還原
if( !(data[3] | 0x00) ) {
//printk("data[3] == 0x00\n");
axies_data[0] = data[0];
/* Initialize Z1 */
axies_data[1] = ( data[2] << 8 ) | data[1];
/* Initialize X1 */
axies_data[2] = ( data[5] << 8 ) | data[4];
/* Initialize Y1 */
axies_data[3] = ( data[7] << 8 ) | data[6];
}
else {
//printk("data[3] == 0x01\n");
/* Initialize Z2*/
axies_data[4] = ( data[2] << 8 ) | data[1];
/* Initialize X2 */
axies_data[5] = ( data[5] << 8 ) & 0x0fff | data[4];
/* Initialize Y2 */
axies_data[6] = ( data[7] << 8 ) & 0x0fff | data[6];

//printk("axies_data = On=%04x z1=%04x x1=%04x y1=%04x z2=%04x x2=%04x y2=%04x \n", axies_data[0],\
axies_data[1],axies_data[2],axies_data[3],\
axies_data[4],axies_data[5],axies_data[6]);


}
if( data[3] | 0x00 ) {
// if(down_interruptible(&cosem))
// return -ERESTARTSYS;
insert_queue();
ready = ready + 1;
//wake_up_interruptible(&wait);
// up(&cosem);
//int rep = 0;
/*
for( ; rep < 7; rep++ ) {
axies_data[rep] &= 0x00000000;
}
*/
}
//printk("%s:urb->actual_length = %d\n", __FUNCTION__, urb->actual_length );
resubmit:
retval = usb_submit_urb(urb, GFP_ATOMIC);
if (retval)
err("%s - usb_submit_urb failed with result: %d",
__FUNCTION__, retval);
}
//open這個function其實也用不到,你看裡面的code就知道,
//原本預設上是等user space來open我們的device才會submit urb,
//但,這邊為了debug方便,在probe的時候就submit urb了。
//所以代表,只要device和driver兩個有mapping起來就會submit urb,
//預設上是不能這樣做,會造成系統資源的浪費,irq一直進來。
//一直處理一些用不到的資料。
static int AsusTsUsb_open(struct input_dev *input)
{
printk("%s: that's ok\n", __FUNCTION__ );
/*
struct AsTouch_usb *AsTouch = input->private;

AsTouch->irq->dev = AsTouch->udev;

if (usb_submit_urb(AsTouch->irq, GFP_ATOMIC))
return -EIO;

return 0;
*/
}

static void AsusTsUsb_close(struct input_dev *input)
{
//struct AsTouch_usb *AsTouch = input->private;

//usb_kill_urb(AsTouch->irq);
}

static int AsusTsUsb_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
//probe就是重頭戲了,雖然下面都是每個usb interface driver要做的routine工作。
int ret = 0;
struct device *temp_class;
printk("%s: that's ok\n", __FUNCTION__ );
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
struct AsTouch_usb *astouch;
int pipe, maxp;
//init_waitqueue_head(&wait);
//register_chrdev做的就是向系統申請一個major number並且命名為ASUS_MT_STRING,
//同時把asus_mt_fops送進去,這樣我們的ioctl就可以被user space存取了。
//但記得這邊還不會在/dev底下出現asus_mt,要經過class_create和device_create後
//node才會跑出來。對這邊如果有問題可以參考ldd ch3的那個char driver。
asus_mt_major = register_chrdev(0, ASUS_MT_STRING,
&asus_mt_fops);

if( asus_mt_major < 0 )
{
printk("Unable to get a major for asus multi-touch driver!\n");
return asus_mt_major;
}

asus_mt_class = class_create(THIS_MODULE, ASUS_MT_STRING);

if (IS_ERR(asus_mt_class)) {
printk(KERN_ERR "Error creating Asus Multi-Touch class.\n");
ret = PTR_ERR(asus_mt_class);
goto err_out1;
}

temp_class = device_create(asus_mt_class, NULL,
MKDEV(asus_mt_major, 0),
NULL, ASUS_MT_STRING);

if (IS_ERR(temp_class)) {
printk(KERN_ERR "Error creating Asus Multi-Touch class device.\n");
ret = PTR_ERR(temp_class);
goto err_out2;
}
interface = intf->cur_altsetting;

if (interface->desc.bNumEndpoints != 1)
return -ENODEV;

endpoint = &interface->endpoint[0].desc;
if (!usb_endpoint_is_int_in(endpoint))
return -ENODEV;

pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));

astouch = kzalloc(sizeof(struct AsTouch_usb), GFP_KERNEL);
astouch->data = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &astouch->data_dma);
if (!astouch->data)
goto fail;

astouch->irq = usb_alloc_urb(0, GFP_KERNEL);
if (!astouch->irq)
goto fail;

astouch->udev = dev;

if (dev->manufacturer)
strlcpy(astouch->name, dev->manufacturer, sizeof(astouch->name));

if (dev->product) {
if (dev->manufacturer)
strlcat(astouch->name, " ", sizeof(astouch->name));
strlcat(astouch->name, dev->product, sizeof(astouch->name));
}

if (!strlen(astouch->name))
snprintf(astouch->name, sizeof(astouch->name),
"ASUS USB Multi-Touch Mouse %04x:%04x",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));

usb_make_path(dev, astouch->phys, sizeof(astouch->phys));
strlcat(astouch->phys, "/input0", sizeof(astouch->phys));
//看到了吧,這邊的AsusTsUsb_irq就是那個要被填到complete function的callback function
usb_fill_int_urb(astouch->irq, dev, pipe, astouch->data,
(maxp > 8 ? 8 : maxp),
AsusTsUsb_irq, astouch, endpoint->bInterval);
astouch->irq->transfer_dma = astouch->data_dma;
astouch->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
usb_set_intfdata(intf, astouch);
//在這之前就把urb的資料根據傳進來的interface作好,然後接下來把urb submit出去,
//就可以等著接收report data。
astouch->irq->dev = astouch->udev;
if (usb_submit_urb(astouch->irq, GFP_ATOMIC))
return -EIO;

return 0;

err_out2:
class_destroy(asus_mt_class);
err_out1:
unregister_chrdev(asus_mt_major, ASUS_MT_STRING);
return ret;

fail: usb_buffer_free(dev, 8, astouch->data, astouch->data_dma);
kfree(astouch);
return -ENOMEM;

}
//這邊的disconnect裡面做的不外呼把/dev底下的asus_mt節點給摧毀掉,還有把urb給kill掉。
//但何時會執行disconnect?? 原來是在usb device設備被拔除的時候,你可以試試看把usb
//touch panel的線拔除,此時/dev/asus_mt也會跟著不見。也就不能在對/dev/asus_mt做
//read, write了。
static void AsusTsUsb_disconnect(struct usb_interface *intf)
{
struct AsTouch_usb *AsTouch = usb_get_intfdata(intf);
printk("%s\n", __FUNCTION__ );
device_destroy(asus_mt_class, MKDEV(asus_mt_major, 0));
class_destroy(asus_mt_class);
unregister_chrdev(asus_mt_major, ASUS_MT_STRING);
usb_set_intfdata(intf, NULL);
if (AsTouch) {
usb_kill_urb(AsTouch->irq);
//input_unregister_device(AsTouch->input);
usb_free_urb(AsTouch->irq);
usb_buffer_free(interface_to_usbdev(intf), 8,
AsTouch->data, AsTouch->data_dma);
kfree(AsTouch);
}
}

MODULE_DEVICE_TABLE(usb, AsusTsUsb_devices);

static struct usb_driver AsusTsUsb_driver = {
.name = "AsusTsUsb",
.probe = AsusTsUsb_probe,
.disconnect = AsusTsUsb_disconnect,
.id_table = AsusTsUsb_devices,
};
//一開始driver被insert時就從這邊run起。
static int __init AsusTsUsb_init(void)
{
printk("%s - called", __FUNCTION__);
return usb_register(&AsusTsUsb_driver);
}
//rmmod時就會從這邊run完結束,所以剛剛在disconnect裡的node銷毀,不能放在這邊run,
//要不然如果拔掉usb touch panel則/dev/asus_mt還會存在,若是user space程式對
//他做ioctl問題就大了。
static void __exit AsusTsUsb_exit(void)
{
printk("%s - called", __FUNCTION__);
usb_deregister(&AsusTsUsb_driver);
}

module_init(AsusTsUsb_init);
module_exit(AsusTsUsb_exit);


恩,接著看ap層要怎麼寫,這是從clutter ap擷取出來的一段code。

g_signal_connect (timeline, "new-frame", G_CALLBACK (GestureRecognition), actor);

這裡面的GestureRecognition就是clutter的callback function,他可以設定callback的timer,時間長短。這裏為什麼要放在call back裡面做我們的讀取function呢?如果你在gui的其他地方做自己的while loop去讀取ioctl,會造成gui掛掉不會更新的情形,因為gui本身也是一組while loop,你不能在gui的讀取while loop結束後,或者是還沒結束前就進入你的while loop,這樣都會造成gui沒辦法更新的現象,因為程式都一直在你的wile loop裡邊run。所以要把while loop內的東西,放到timer的call back函式裡頭做。而原本ioctl裡邊用的wait_event_interruptible也不能用,因為,他讀不到資料就會去睡覺,這樣你的gui要等到有資料進來才會更新。沒資料進來,若是切到別的視窗再回來,就會畫面一片空白,因為沒有資料來醒她。

所以這邊ioctl cmd=1就代表詢問有無資料,如果head==tail則代表queue為空,所以return 0;GestureRecognition收到後就會結束,若是head!=tail則回傳1,while loop接著下cmd=0一直讀,直到queue data被讀完為止。所以signal call back的速度可以不用設的太快,因為這邊是low speed device,如果這邊是high speed device,就要設快點,要不然不久queue就滿了,資料就truncate掉。當然queue的大小也會決定資料的準確率,但是需要越完整的資料,就需要更大的queue。

嗯嗯,接下來看GestureRecognition是怎麼寫的:

void GestureRecognition(ClutterTimeline *timeline,
gint frame_num,
gpointer data)
{

int fd = open("/dev/asus_mt", O_RDONLY);
printf("Enter GestureRecognition\n");

CHiddenWindow PacketProcess;

static unsigned int array[7];
if( fd == -1 )
{
printf("read file error\n");
}

int i;
for( i = 0 ; i < 7 ; i++ )
{
array[i] = 0;
}

unsigned int check = 0;
//確定queue有data在開始抓
while((ioctl(fd,1,&check) != -1) && check == 1) {
//開始把data從queue裡一個個讀進array
if (ioctl(fd,0,array) == -1) {
printf("read ioctl array error\n");
}else {
//下面這邊就是去做gesture的判斷,裡面用了一堆統計的方法
//這邊就不列出來了
PacketProcess.Indicate(array);
PacketProcess.MonitorThread();

}
}
close(fd);
}

記得在用open file和ioctl之前要先include下面的.h:
#include <fcntl.h>
#include <sys/ioctl.h>

下面再給一張完整的結構圖: