PC键盘驱动程序源码分析

来源:百度文库 编辑:神马文学网 时间:2024/04/30 07:28:23
一.   编写目的:描述uclinux内核中pc机键盘驱动的体系结构和工作原理,用于指导针对具体的嵌入式键盘的驱动程序的编写。二.   参考资料:1.《Linux内核源代码情景分析(下册)》第8.7和8.8章节,page330~4122.内核源代码文件:../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.c3.网络文章:《书写基于内核的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.c1.../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.   六.     本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/houen_study/archive/2004/11/29/197638.aspx