编写目的:
描述uclinux内核中pc机键盘驱动的体系结构和工作原理,用于指导针对具体的嵌入式键盘的驱动程序的编写。
二. 参考资料:
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.网络文章:《书写基于内核的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中的键盘数据加以处理,将结果存入tty的read_buffer数据缓冲区。
4. 键盘数据最终结果传递到用户进程(不属于键盘驱动程序处理范畴)
在tty的file_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被#define为pckbd_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) 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(),是一个内部函数
(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()函数处理scancode。Handle_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)
根据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键盘的功能键码
(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键盘的功能键码为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驱动程序的读取函数(read:read_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等比较花时间的工作。
主要功能:
完成键盘的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)
该函数被键盘中断后端处理函数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()函数中被调用。
具体功能:
设置键盘NumLock, CapsLock,或ScrollLock的led灯的状态。
(9) unsigned char getledstate(void)
该函数在../linux-2.4.x/drivers/char/vt.c文件Line683的vt_ioctl()函数中被调用。
具体功能:
读取键盘NumLock, CapsLock,或ScrollLock的led灯的状态。
以下函数都是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.
六.