Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5464127
  • 博文数量: 671
  • 博客积分: 10010
  • 博客等级: 上将
  • 技术积分: 7310
  • 用 户 组: 普通用户
  • 注册时间: 2006-07-14 09:56
文章分类

全部博文(671)

文章存档

2011年(1)

2010年(2)

2009年(24)

2008年(271)

2007年(319)

2006年(54)

我的朋友

分类: LINUX

2007-12-18 16:29:35

PC键盘驱动程序源码分析

一. 编写目的:

描述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.

六.

阅读(1247) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~