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>

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