一. 編寫(xiě)目的:
描述uclinux內核中pc機鍵盤(pán)驅動(dòng)的體系結構和工作原理,用于指導針對具體的嵌入式鍵盤(pán)的驅動(dòng)程序的編寫(xiě)。
二. 參考資料:
1.《Linux內核源代碼情景分析(下冊)》第8.7和8.8章節,page330~412
2.內核源代碼文件:
../linux-2.4.x/drivers/char/keyboard.c
../linux-2.4.x/include/asm-i386/keyboard.h
../linux-2.4.x/drivers/char/pc_keyb.c
../linux-2.4.x/drivers/input/*.*
../linux-2.0.x/drivers/char/keyboard.c
3.網(wǎng)絡(luò )文章:《書(shū)寫(xiě)基于內核的linux鍵盤(pán)記錄器》
三. pc鍵盤(pán)驅動(dòng)工作流程:
1. 鍵盤(pán)初始化
該工作主要是由tty初始化函數tty_init()調用鍵盤(pán)驅動(dòng)程序模塊的初始化函數kbd_init()實(shí)現。Kbd_init()函數主要調用initialize_kbd()函數完成工作。
主要完成工作為,鍵盤(pán)的自檢,檢測,啟動(dòng),寄存器設置等;并且向系統注冊鍵盤(pán)中斷服務(wù)函數。
2. 鍵盤(pán)中斷響應過(guò)程
當用戶(hù)按鍵或者釋放鍵時(shí),鍵盤(pán)向系統產(chǎn)生中斷信號,系統自動(dòng)進(jìn)入鍵盤(pán)中斷服務(wù)函數處理,該部分工作主要由鍵盤(pán)中斷服務(wù)函數keyboard_interrupt()完成。
主要完成工作為:從鍵盤(pán)狀態(tài)寄存器讀取鍵盤(pán)狀態(tài),從鍵盤(pán)緩沖區讀取數據,根據讀取的狀態(tài)和數據,進(jìn)行鍵碼轉換等工作,將結果存入一個(gè)tty的“flip_buffer”的數據緩沖區。在中斷服務(wù)函數最后,進(jìn)行鍵盤(pán)后端處理函數(其實(shí)是控制臺終端的tasklet,即console_tasklet())調度。
3. 鍵盤(pán)后端處理(不屬于鍵盤(pán)驅動(dòng)程序處理范疇)
在鍵盤(pán)中斷服務(wù)函數結束之前,會(huì )將鍵盤(pán)后端處理函數(其實(shí)是控制臺終端的tasklet,即console_tasklet())掛入后端處理隊列,系統在調度的時(shí)候最終執行該函數。
在該函數中將完成一些鍵盤(pán)的相關(guān)工作,例如將flip_buffer中的鍵盤(pán)數據加以處理,將結果存入tty的read_buffer數據緩沖區。
4. 鍵盤(pán)數據最終結果傳遞到用戶(hù)進(jìn)程(不屬于鍵盤(pán)驅動(dòng)程序處理范疇)
在tty的file_operations數據結構的read函數指針指向read_charn()函數,該函數從read_buffer數據緩沖區獲取數據,返回給用戶(hù)進(jìn)程。
如此一次鍵盤(pán)回話(huà)完成。
5.
四. 源文件具體分析:
有一點(diǎn)必須注意,在linux-2.0.x的內核中,鍵盤(pán)驅動(dòng)主要工作都是在
../linux-2.0.x/drivers/char/keyboard.c
文件中完成,而沒(méi)有別的文件,不象linux-2.4.x內核除了文件:
../linux-2.4.x/drivers/char/keyboard.c
還有以下這些文件:
../linux-2.4.x/drivers/char/pc_keyb.c
../linux-2.4.x/include/asm-i386/keyboard.h
我們這里主要分析的時(shí)候linux-2.4.x內核的鍵盤(pán)驅動(dòng)程序。
另外還有一些相關(guān)代碼:
../linux-2.4.x/drivers/char/vt.c
../linux-2.4.x/drivers/char/tty_io.c
../linux-2.4.x/drivers/char/tty_ioctl.c
../linux-2.4.x/drivers/input/*.*
../linux-2.4.x/include/linux/kbd_kern.h
../linux-2.4.x/drivers/char/console.c
1.../linux-2.4.x/drivers/char/pc_keyb.c
(1) void __init pckbd_init_hw(void)
鍵盤(pán)初始化函數,該函數由Kbd_init()調用(Kbd_init()調用的是kbd_init_hw(),但是在i386中,kbd_init_hw被#define為pckbd_init_hw)。
主要完成工作:
a. 根據kbd_controller_present判斷鍵盤(pán)控制器是否存在
b. 調用kbd_request_region()分配資源
c. 調用kbd_clear_input()清除鍵盤(pán)控制器緩沖區數據
d. 鍵盤(pán)如果未復位初始化,則調用函數initialize_kbd()進(jìn)行初始化
e. 設置kbd_rate函數指針為pckbd_rate()函數
f. 調用kbd_request_irq(),將鍵盤(pán)中斷服務(wù)函數keyboard_interrupt()注冊到系統。
(2) static char * __init initialize_kbd(void)
鍵盤(pán)初始化函數,該函數由pckbd_init_hw()調用。
主要完成工作:
a. 調用kbd_write_command_w(KBD_CCMD_SELF_TEST),進(jìn)行鍵盤(pán)自檢
b. 調用kbd_write_command_w(KBD_CCMD_KBD_TEST),進(jìn)行鍵盤(pán)檢測
c. 調用kbd_write_command_w(KBD_CCMD_KBD_ENABLE),使能鍵盤(pán)
d. 調用kbd_write_output_w(KBD_CMD_RESET)復位鍵盤(pán),并且調用函數kbd_wait_for_input()接受復位狀態(tài)字節并且判斷復位是否成功,如果復位成功,繼續,否則函數返回
e. 調用kbd_write_output_w(KBD_CMD_DISABLE),在設置鍵盤(pán)工作模式之前,停止鍵盤(pán)工作。調用kbd_wait_for_input()接受停止鍵盤(pán)狀態(tài),如果停止成功,繼續,否則函數返回
f. 調用kbd_write_command_w(KBD_CCMD_WRITE_MODE)和kbd_write_output_w(…)設置鍵盤(pán)工作模式。
g. 對于powerpc鍵盤(pán)的一些模式設置
h. 調用kbd_write_output_w_and_wait(KBD_CMD_ENABLE),在完成鍵盤(pán)工作模式設置之后,使能鍵盤(pán)工作
i. 最后調用kbd_write_output_w_and_wait(KBD_CMD_SET_RATE),set the typematic rate to maximum
(3) static int kbd_write_output_w_and_wait(int data)
發(fā)送數據到鍵盤(pán)數據端口函數
主要完成工作:
a. 調用kbd_write_output_w(data)函數,往鍵盤(pán)數據端口發(fā)送數據
b. 調用kbd_wait_for_input()等待鍵盤(pán)的回應數據。
(4) static int kbd_write_command_w_and_wait(int data)
發(fā)送命令到鍵盤(pán)數據端口函數
主要完成工作:
a. 調用kbd_write_command_w(data)函數,往鍵盤(pán)命令(控制)端口發(fā)送命令
b. 調用kbd_wait_for_input()等待鍵盤(pán)的回應數據
(5) static void kbd_write_output_w(int data)
發(fā)送數據到鍵盤(pán)的數據端口函數,主要由kbd_write_output (data)完成工作,kbd_write_output (data)函數和體系結構非常密切,一般由匯編代碼實(shí)現
(6) static void kbd_write_command_w(int data)
發(fā)送命令到鍵盤(pán)的命令(控制)端口函數,主要由kbd_write_command(data)完成工作,kbd_write_command(data)函數和體系結構非常密切,一般由匯編代碼實(shí)現。
(7) static int __init kbd_wait_for_input(void)
延時(shí)等待鍵盤(pán)返回數據函數,即循環(huán)等待的時(shí)候kbd_read_data()調用函數從鍵盤(pán)的讀取數據。
(8) static void __init kbd_clear_input(void)
發(fā)送清除鍵盤(pán)數據,即調用函數kbd_read_data()不停地從鍵盤(pán)讀取數據,知道沒(méi)有數據為止。
(9) static int __init kbd_read_data(void)
從鍵盤(pán)讀取數據
主要完成工作:
a. 調用kbd_read_status()函數從鍵盤(pán)狀態(tài)寄存器讀取鍵盤(pán)地狀態(tài),該函數和體系結構關(guān)系密切,一般由匯編代碼實(shí)現
b. 判斷鍵盤(pán)緩沖區是否有數據,如果有則調用函數kbd_read_input()從鍵盤(pán)數據寄存器讀取數據,該函數和體系結構關(guān)系密切,一般由匯編代碼實(shí)現
c. 判斷讀取地數據是否有效
(10) line657~679不懂
(11) static int pckbd_rate(struct kbd_repeat *rep)
在pckbd_init_hw()函數中被賦值給函數指針kbd_rate,被../linux-2.4.x/drivers/char/vt.c 文件vt_ioctl()調用
(12) static int write_kbd_rate(unsigned char r)
被函數pckbd_rate(),是一個(gè)內部函數
(13) static unsigned char parse_kbd_rate(struct kbd_repeat *r)
被函數pckbd_rate(),是一個(gè)內部函數
(14) void pckbd_leds(unsigned char leds)
在文件../linux-2.4.x/include/asm-i386/keyboard.h被定義成宏:kbd_leds()。被鍵盤(pán)中斷后端處理函數kbd_bh()函數調用。
主要功能:
調用函數send_data()設置鍵盤(pán)的led燈,如果失敗設置鍵盤(pán)不存在。
(15) static int send_data(unsigned char data)
發(fā)送字節data到鍵盤(pán),并且等待鍵盤(pán)的回應。
(16) static void keyboard_interrupt(int irq, void *dev_id, struct pt_regs *regs)
鍵盤(pán)中斷服務(wù)函數(最關(guān)鍵),主要是調用函數handle_kbd_event()完成工作。在整個(gè)函數執行過(guò)程必須關(guān)閉中斷。
注意:ps鼠標和鍵盤(pán)共用鍵盤(pán)中斷服務(wù)函數
(17) static unsigned char handle_kbd_event(void)
中斷事件處理函數,該函數由keyboard_interrupt()調用
主要完成以下工作:
a. 調用kbd_read_status()讀取鍵盤(pán)狀態(tài)端口
b. 循環(huán)執行以下操作,知道根據狀態(tài)寄存器判斷沒(méi)有數據,或者已經(jīng)讀取了1000個(gè)數據:
調用kbd_read_input()讀取數據
根據狀態(tài)寄存器的值,判斷是ps鼠標中斷,則調用鼠標中斷事件處理函數handle_mouse_event(),如果是鍵盤(pán)中斷,則調用handle_keyboard_event(unsigned char scancode)。
重新讀取狀態(tài)寄存器
c.
(18) static inline void handle_keyboard_event(unsigned char scancode)
該函數為鍵盤(pán)中斷事件處理函數,由handle_kbd_event()函數調用,主要工作由handle_scancode()函數實(shí)現。
主要完成工作:
a. 調用do_acknowledge(scancode)發(fā)送通知數據收到信息給鍵盤(pán)
b. 調用handle_scancode()函數處理scancode。Handle_scancode()函數在文件../linux-2.4.x/drivers/char/keyboard.c中實(shí)現
c. 調用函數tasklet_schedule(&keyboard_tasklet),將剩余工作放到bh,即將鍵盤(pán)后端函數keyboard_tasklet掛入tasklet,系統自動(dòng)會(huì )調度運行該函數。
(19) static inline void handle_mouse_event(unsigned char scancode)
由于ps鼠標不在該范疇內,在此不加以分析。
(20) static int do_acknowledge(unsigned char scancode)
該函數處理當從鍵盤(pán)接收到一個(gè)數據時(shí),往鍵盤(pán)發(fā)送ACK信息。
主要完成工作:
a. 根據reply_expected判斷是否需要往鍵盤(pán)發(fā)送ACK信息
b. 如果需要,根據具體的scancode進(jìn)行處理
(21) int pckbd_pm_resume(struct pm_dev *dev, pm_request_t rqst, void *data)
該函數是關(guān)于PS鼠標的函數,在此不分析
(22) char pckbd_unexpected_up(unsigned char keycode)
進(jìn)行一些不預期的按鍵釋放等處理,相應清除一些標志
(23) int pckbd_translate(unsigned char scancode, unsigned char *keycode,char raw_mode)
將scancode轉換為keycode,該函數在../linux-2.4.x/include/asm-i386/keyboard.h 文件中被定義成kbd_translate,在handle_scancode()函數中被調用。具體實(shí)現參考源代碼
(24) int pckbd_getkeycode(unsigned int scancode)
根據scancode和keycode數組e0_keys[128]或者high_keys[]數組獲取keycode,該函數在../linux-2.4.x/include/asm-i386/keyboard.h中被定義為宏kbd_getkeycode,在文件../linux-2.4.x/drivers/char/keyboard.c中被getkeycode()函數調用。
主要功能:
根據scancode獲取pc鍵盤(pán)的功能鍵碼
(25) int pckbd_setkeycode(unsigned int scancode, unsigned int keycode)
根據scancode將eo_keys[128]或者high_keys[]對應項的值設置為keycode,該函數在../linux-2.4.x/include/asm-i386/keyboard.h中北定義為宏kbd_getsetkeycode,在文件../linux-2.4.x/drivers/char/keyboard.c中被getsetkeycode()函數調用。
主要功能:
根據設置pc鍵盤(pán)的功能鍵碼為scancode
(26) static void kb_wait(void)
循環(huán)等待,在等待的時(shí)候調用函數handle_kbd_event()監視鍵盤(pán)狀態(tài),超時(shí)或者狀態(tài)滿(mǎn)足則退出循環(huán)等待。
(27)
2.../linux-2.4.x/drivers/char/keyboard.c
(1) void handle_scancode(unsigned char scancode, int down)
該函數在pc鍵盤(pán)驅動(dòng)中是非常關(guān)鍵的一個(gè)函數,主要是將scancode轉換為tty所能接受的碼制,例如ascii碼,unicode等,具體根據需求而定。并且將轉換結果存入flip_buffer;在控制臺的后端處理函數將從flip_buffer讀取數據到read_buffer;而tty驅動(dòng)程序的讀取函數(read:read_chan())從read_buffer讀取數據,從而完成鍵盤(pán)的一個(gè)回話(huà)。
具體實(shí)現請參考《linux內核源代碼情景分析(下冊)》的page375開(kāi)始的內容
(2) int __init kbd_init(void)
該函數在pc鍵盤(pán)驅動(dòng)中也是非常關(guān)鍵的一個(gè)函數,鍵盤(pán)驅動(dòng),鍵盤(pán)的初始化都由該函數實(shí)現,主要功能通過(guò)調用函數kbd_init_hw()實(shí)現,而kbd_init_hw()是一個(gè)宏,具體實(shí)現函數為pckbd_init_hw()。
該函數被tty驅動(dòng)的初始化函數即../linux-2.4.x/drivers/char/tty_io.c文件中的tty_init()函數調用。
具體實(shí)現:
a. 初始化kbd_table數組,即每個(gè)控制臺的鍵盤(pán)狀態(tài)。
b. 獲取ttytab指針(也可以說(shuō)是一個(gè)數組,數組成員為每個(gè)控制臺對應的tty)。
c. 調用kbd_init_hw()函數,實(shí)現初始化
d. 使能鍵盤(pán)中斷服務(wù)后端函數運行,并且掛接keyboard_tasklet()(其實(shí)就是kbd_bh()函數)為鍵盤(pán)中斷服務(wù)后端執行函數
e. 調用函數pm_register()將鍵盤(pán)注冊到電源管理設備列表,最后一個(gè)參數為回調函數,在此好像是NULL
(3) static void kbd_bh(unsigned long dummy)
鍵盤(pán)中斷服務(wù)程序的后端服務(wù)函數,主要完成console changing, led setting and copy_to_cooked等比較花時(shí)間的工作。
主要功能:
完成鍵盤(pán)的numlock,capslock和scrolllock的led指示燈設置。
(4) int getkeycode(unsigned int scancode)
該函數在../linux-2.4.x/drivers/char/vt.c文件line255處被do_kbkeycode_ioctl()調用。
(5) int setkeycode(unsigned int scancode, unsigned int keycode)
該函數在../linux-2.4.x/drivers/char/vt.c文件line262處被do_kbkeycode_ioctl()調用。
(6) static inline unsigned char getleds(void)
該函數被鍵盤(pán)中斷后端處理函數kbd_bh()調用
(7) void register_leds(int console, unsigned int led,unsigned int *addr, unsigned int mask)
(8) void setledstate(struct kbd_struct *kbd, unsigned int led)
該函數在../linux-2.4.x/drivers/char/vt.c文件Line690的vt_ioctl()函數中被調用。
具體功能:
設置鍵盤(pán)NumLock, CapsLock,或ScrollLock的led燈的狀態(tài)。
(9) unsigned char getledstate(void)
該函數在../linux-2.4.x/drivers/char/vt.c文件Line683的vt_ioctl()函數中被調用。
具體功能:
讀取鍵盤(pán)NumLock, CapsLock,或ScrollLock的led燈的狀態(tài)。
以下函數都是handle_scancode()函數處理scancode的時(shí)候調用的內部函數:
(10) void put_queue(int ch)
(11) static void puts_queue(char *cp)
上面這兩個(gè)函數(8),(9)比較關(guān)鍵,因為就是由這兩個(gè)函數將轉換結果存入flip_buffer,并且將控制臺的后端服務(wù)函數,即bh函數掛入tasklet。
(12) void compute_shiftstate(void)
該函數被許多地方調用,例如:
../linux-2.4.x/drivers/char/console.c
../linux-2.4.x/drivers/char/vt.c
主要功能:
設置shift_state全局變量,該變量是應該是和pc鍵盤(pán)上的“shift”鍵的狀態(tài)相關(guān)聯(lián)。
(13) unsigned char handle_diacr(unsigned char ch)
(14) static void SAK(void)
(15) static void spawn_console(void)
(16) static void compose(void)
(17) static void boot_it(void)
(18) static void scroll_back(void)
(19) static void scroll_forw(void)
(20) static void send_intr(void)
(21) static void incr_console(void)
(22) static void decr_console(void)
(23) static void lastcons(void)
(24) static void bare_num(void)
(25) static void num(void)
(26) static void hold(void)
(27) static void show_ptregs(void)
(28) static void caps_on(void)
(29) static void caps_toggle(void)
(30) static void enter(void)
(31) static void applkey(int key, char mode)
(32) void to_utf8(ushort c)
(33) static void do_slock(unsigned char value, char up_flag)
(34) static void do_lock(unsigned char value, char up_flag)
(35) static void do_ascii(unsigned char value, char up_flag)
(36) static void do_meta(unsigned char value, char up_flag)
(37) static void do_dead2(unsigned char value, char up_flag)
(38) static void do_shift(unsigned char value, char up_flag)
(39) static void do_cur(unsigned char value, char up_flag)
(40) static void do_pad(unsigned char value, char up_flag)
(41) static void do_fn(unsigned char value, char up_flag)
(42) static void do_cons(unsigned char value, char up_flag)
(43) static void do_dead(unsigned char value, char up_flag)
(44) static void do_self(unsigned char value, char up_flag)
(45) static void do_spec(unsigned char value, char up_flag)
(46) static void do_ignore(unsigned char value, char up_flag)
五. 鍵盤(pán)驅動(dòng)和系統上層的接口,以下函數就是在根據具體硬件的時(shí)候特別關(guān)注的函數:
鍵盤(pán)驅動(dòng)和系統上層的接口主要就是和tty驅動(dòng)的接口,用戶(hù)和鍵盤(pán)打交道一般都是通過(guò)tty的接口函數進(jìn)行。
1. kbd_init()-鍵盤(pán)初始化函數:tty驅動(dòng)程序的初始化函數tty_init()調用鍵盤(pán)初始化函數kbd_init()函數
2. Put_queue()或者puts_queue()函數:數據緩沖區和tty的接口,鍵盤(pán)驅動(dòng)(具體地說(shuō)是鍵盤(pán)中斷服務(wù)函數)將從鍵盤(pán)讀取地數據存入flip_buffer數據緩沖區,而用戶(hù)調用tty驅動(dòng)的read函數(即read_chan())則從flip_buffer讀取數據。
3. ../linux-2.4.x/drivers/char/tty_ioctl.c 文件n_tty_ioctl()函數調用的地方。
4. ../linux-2.4.x/drivers/char/vt.c 文件vt_ioctl()調用的地方。
(1) void setledstate(struct kbd_struct *kbd, unsigned int led)
(2) unsigned char getledstate(void)
(3) int getkeycode(unsigned int scancode)
(4) int setkeycode(unsigned int scancode, unsigned int keycode)
(5) void compute_shiftstate(void)
(6) static int pckbd_rate(struct kbd_repeat *rep)
5. ../linux-2.4.x/drivers/chr/console.c文件redraw_screen()調用的地方
(1) void compute_shiftstate(void)
(2)
6. 另外注意的函數:
(1) static void kbd_bh(unsigned long dummy)
(2) void pckbd_leds(unsigned char leds)
7.
六.