编写目的:

描述uclinux内核中pc机键盘驱动的体系结构和工作原理,用于指导针对具体的嵌入式键盘的驱动程序的编写。

二.   参考资料:

1.《Linux内核源代码情景分析(下册)》第8.78.8章节,page330412

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.网络文章:《书写基于内核的linux键盘记录器》

三.   pc键盘驱动工作流程:

1. 键盘初始化

该工作主要是由tty初始化函数tty_init()调用键盘驱动程序模块的初始化函数kbd_init()实现。Kbd_init()函数主要调用initialize_kbd()函数完成工作。

主要完成工作为,键盘的自检,检测,启动,寄存器设置等;并且向系统注册键盘中断服务函数。

2. 键盘中断响应过程

当用户按键或者释放键时,键盘向系统产生中断信号,系统自动进入键盘中断服务函数处理,该部分工作主要由键盘中断服务函数keyboard_interrupt()完成。

主要完成工作为:从键盘状态寄存器读取键盘状态,从键盘缓冲区读取数据,根据读取的状态和数据,进行键码转换等工作,将结果存入一个tty的“flip_buffer”的数据缓冲区。在中断服务函数最后,进行键盘后端处理函数(其实是控制台终端的tasklet,即console_tasklet())调度。

3. 键盘后端处理(不属于键盘驱动程序处理范畴)

在键盘中断服务函数结束之前,会将键盘后端处理函数(其实是控制台终端的tasklet,即console_tasklet())挂入后端处理队列,系统在调度的时候最终执行该函数。

在该函数中将完成一些键盘的相关工作,例如将flip_buffer中的键盘数据加以处理,将结果存入ttyread_buffer数据缓冲区。

4. 键盘数据最终结果传递到用户进程(不属于键盘驱动程序处理范畴)

ttyfile_operations数据结构的read函数指针指向read_charn()函数,该函数从read_buffer数据缓冲区获取数据,返回给用户进程。

如此一次键盘回话完成。

5.  

四.   源文件具体分析:

有一点必须注意,在linux-2.0.x的内核中,键盘驱动主要工作都是在

../linux-2.0.x/drivers/char/keyboard.c

文件中完成,而没有别的文件,不象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

我们这里主要分析的时候linux-2.4.x内核的键盘驱动程序。

另外还有一些相关代码:

../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)

键盘初始化函数,该函数由Kbd_init()调用(Kbd_init()调用的是kbd_init_hw(),但是在i386中,kbd_init_hw#definepckbd_init_hw)。

主要完成工作:

a. 根据kbd_controller_present判断键盘控制器是否存在

b. 调用kbd_request_region()分配资源

c. 调用kbd_clear_input()清除键盘控制器缓冲区数据

d. 键盘如果未复位初始化,则调用函数initialize_kbd()进行初始化

e. 设置kbd_rate函数指针为pckbd_rate()函数

f.   调用kbd_request_irq(),将键盘中断服务函数keyboard_interrupt()注册到系统。

(2)      static char * __init initialize_kbd(void)

键盘初始化函数,该函数由pckbd_init_hw()调用。

主要完成工作:

a. 调用kbd_write_command_w(KBD_CCMD_SELF_TEST),进行键盘自检

b. 调用kbd_write_command_w(KBD_CCMD_KBD_TEST),进行键盘检测

c. 调用kbd_write_command_w(KBD_CCMD_KBD_ENABLE),使能键盘

d. 调用kbd_write_output_w(KBD_CMD_RESET)复位键盘,并且调用函数kbd_wait_for_input()接受复位状态字节并且判断复位是否成功,如果复位成功,继续,否则函数返回

e. 调用kbd_write_output_w(KBD_CMD_DISABLE),在设置键盘工作模式之前,停止键盘工作。调用kbd_wait_for_input()接受停止键盘状态,如果停止成功,继续,否则函数返回

f.   调用kbd_write_command_w(KBD_CCMD_WRITE_MODE)kbd_write_output_w(…)设置键盘工作模式。

g. 对于powerpc键盘的一些模式设置

h. 调用kbd_write_output_w_and_wait(KBD_CMD_ENABLE),在完成键盘工作模式设置之后,使能键盘工作

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)

发送数据到键盘数据端口函数

主要完成工作:

a. 调用kbd_write_output_w(data)函数,往键盘数据端口发送数据

b. 调用kbd_wait_for_input()等待键盘的回应数据。

(4)      static int kbd_write_command_w_and_wait(int data)

发送命令到键盘数据端口函数

主要完成工作:

a. 调用kbd_write_command_w(data)函数,往键盘命令(控制)端口发送命令

b. 调用kbd_wait_for_input()等待键盘的回应数据

(5)      static void kbd_write_output_w(int data)

发送数据到键盘的数据端口函数,主要由kbd_write_output (data)完成工作,kbd_write_output (data)函数和体系结构非常密切,一般由汇编代码实现

(6)      static void kbd_write_command_w(int data)

发送命令到键盘的命令(控制)端口函数,主要由kbd_write_command(data)完成工作,kbd_write_command(data)函数和体系结构非常密切,一般由汇编代码实现。

(7)      static int __init kbd_wait_for_input(void)

延时等待键盘返回数据函数,即循环等待的时候kbd_read_data()调用函数从键盘的读取数据。

(8)      static void __init kbd_clear_input(void)

发送清除键盘数据,即调用函数kbd_read_data()不停地从键盘读取数据,知道没有数据为止。

(9)      static int __init kbd_read_data(void)

从键盘读取数据

主要完成工作:

a. 调用kbd_read_status()函数从键盘状态寄存器读取键盘地状态,该函数和体系结构关系密切,一般由汇编代码实现

b. 判断键盘缓冲区是否有数据,如果有则调用函数kbd_read_input()从键盘数据寄存器读取数据,该函数和体系结构关系密切,一般由汇编代码实现

c. 判断读取地数据是否有效

(10)   line657679不懂

(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(),是一个内部函数

(13)   static unsigned char parse_kbd_rate(struct kbd_repeat *r)

被函数pckbd_rate(),是一个内部函数

(14) void pckbd_leds(unsigned char leds)

在文件../linux-2.4.x/include/asm-i386/keyboard.h被定义成宏:kbd_leds()被键盘中断后端处理函数kbd_bh()函数调用。

主要功能:

       调用函数send_data()设置键盘的led灯,如果失败设置键盘不存在。

(15)   static int send_data(unsigned char data)

发送字节data到键盘,并且等待键盘的回应。

(16) static void keyboard_interrupt(int irq, void *dev_id, struct pt_regs *regs)

键盘中断服务函数(最关键),主要是调用函数handle_kbd_event()完成工作。在整个函数执行过程必须关闭中断。

注意:ps鼠标和键盘共用键盘中断服务函数

(17) static unsigned char handle_kbd_event(void)

中断事件处理函数,该函数由keyboard_interrupt()调用

主要完成以下工作:

a. 调用kbd_read_status()读取键盘状态端口

b. 循环执行以下操作,知道根据状态寄存器判断没有数据,或者已经读取了1000个数据:

调用kbd_read_input()读取数据

根据状态寄存器的值,判断是ps鼠标中断,则调用鼠标中断事件处理函数handle_mouse_event(),如果是键盘中断,则调用handle_keyboard_event(unsigned char scancode)

重新读取状态寄存器

c.  

(18) static inline void handle_keyboard_event(unsigned char scancode)

该函数为键盘中断事件处理函数,由handle_kbd_event()函数调用,主要工作由handle_scancode()函数实现。

主要完成工作:

a. 调用do_acknowledge(scancode)发送通知数据收到信息给键盘

b. 调用handle_scancode()函数处理scancodeHandle_scancode()函数在文件../linux-2.4.x/drivers/char/keyboard.c中实现

c. 调用函数tasklet_schedule(&keyboard_tasklet),将剩余工作放到bh,即将键盘后端函数keyboard_tasklet挂入tasklet,系统自动会调度运行该函数。

(19) static inline void handle_mouse_event(unsigned char scancode)

由于ps鼠标不在该范畴内,在此不加以分析。

(20) static int do_acknowledge(unsigned char scancode)

该函数处理当从键盘接收到一个数据时,往键盘发送ACK信息。

主要完成工作:

a. 根据reply_expected判断是否需要往键盘发送ACK信息

b. 如果需要,根据具体的scancode进行处理

(21) int pckbd_pm_resume(struct pm_dev *dev, pm_request_t rqst, void *data)

该函数是关于PS鼠标的函数,在此不分析

(22) char pckbd_unexpected_up(unsigned char keycode)

进行一些不预期的按键释放等处理,相应清除一些标志

(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()函数中被调用。具体实现参考源代码

(24) int pckbd_getkeycode(unsigned int scancode)

根据scancodekeycode数组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键盘的功能键码

(25) int pckbd_setkeycode(unsigned int scancode, unsigned int keycode)

根据scancodeeo_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键盘的功能键码为scancode

(26) static void kb_wait(void)

循环等待,在等待的时候调用函数handle_kbd_event()监视键盘状态,超时或者状态满足则退出循环等待。

(27)    

2../linux-2.4.x/drivers/char/keyboard.c

(1)            void handle_scancode(unsigned char scancode, int down)

该函数在pc键盘驱动中是非常关键的一个函数,主要是将scancode转换为tty所能接受的码制,例如ascii码,unicode等,具体根据需求而定。并且将转换结果存入flip_buffer;在控制台的后端处理函数将从flip_buffer读取数据到read_buffer;而tty驱动程序的读取函数(readread_chan())从read_buffer读取数据,从而完成键盘的一个回话。

具体实现请参考《linux内核源代码情景分析(下册)》的page375开始的内容

(2)            int __init kbd_init(void)

该函数在pc键盘驱动中也是非常关键的一个函数,键盘驱动,键盘的初始化都由该函数实现,主要功能通过调用函数kbd_init_hw()实现,而kbd_init_hw()是一个宏,具体实现函数为pckbd_init_hw()

该函数被tty驱动的初始化函数即../linux-2.4.x/drivers/char/tty_io.c文件中的tty_init()函数调用。

具体实现:

a. 初始化kbd_table数组,即每个控制台的键盘状态。

b. 获取ttytab指针(也可以说是一个数组,数组成员为每个控制台对应的tty)。

c. 调用kbd_init_hw()函数,实现初始化

d. 使能键盘中断服务后端函数运行,并且挂接keyboard_tasklet()(其实就是kbd_bh()函数)为键盘中断服务后端执行函数

e. 调用函数pm_register()将键盘注册到电源管理设备列表,最后一个参数为回调函数,在此好像是NULL

(3)            static void kbd_bh(unsigned long dummy)

键盘中断服务程序的后端服务函数,主要完成console changing, led setting and copy_to_cooked等比较花时间的工作。

主要功能:

 完成键盘的numlockcapslockscrolllockled指示灯设置。

(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)

该函数被键盘中断后端处理函数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文件Line690vt_ioctl()函数中被调用。

具体功能:

       设置键盘NumLock, CapsLock,ScrollLockled灯的状态。

(9)            unsigned char getledstate(void)

该函数在../linux-2.4.x/drivers/char/vt.c文件Line683vt_ioctl()函数中被调用。

具体功能:

       读取键盘NumLock, CapsLock,ScrollLockled灯的状态。

以下函数都是handle_scancode()函数处理scancode的时候调用的内部函数:

(10)        void put_queue(int ch)

(11)        static void puts_queue(char *cp)

上面这两个函数(8),(9)比较关键,因为就是由这两个函数将转换结果存入flip_buffer,并且将控制台的后端服务函数,即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键盘上的“shift”键的状态相关联。

(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)

五.   键盘驱动和系统上层的接口,以下函数就是在根据具体硬件的时候特别关注的函数:

键盘驱动和系统上层的接口主要就是和tty驱动的接口,用户和键盘打交道一般都是通过tty的接口函数进行。

1. kbd_init()-键盘初始化函数:tty驱动程序的初始化函数tty_init()调用键盘初始化函数kbd_init()函数

2. Put_queue()或者puts_queue()函数:数据缓冲区和tty的接口,键盘驱动(具体地说是键盘中断服务函数)将从键盘读取地数据存入flip_buffer数据缓冲区,而用户调用tty驱动的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.  

六.