收藏 分销(赏)

keyboard.doc

上传人:s4****5z 文档编号:9010450 上传时间:2025-03-11 格式:DOC 页数:46 大小:232.50KB
下载 相关 举报
keyboard.doc_第1页
第1页 / 共46页
keyboard.doc_第2页
第2页 / 共46页
点击查看更多>>
资源描述
键盘体系 author: sniff 从什么开始说比较合理呀?就从硬件开始把:严格来说称不上什么键盘体系,但由于键盘的driver code比较的涩晦,所以就称之为键盘体系了。 后注:什么叫后注?也就是写完后想说点什么的意思呀!这篇文挡太长了(本来想写的更长,真的,还有一些文件都没有写上去呀),大家还是用“文挡结构图”来看把,厉害把,这么多,全部手写呀。 硬件相关 硬件,其实有一些内容,但我实在不想一段段的翻译,大家想要的话,我把english文挡发给大家好了。 Keyboard Key 键盘代码 键盘模式 键盘模式有4种, 在Linux 下你可以用kbd_mode -参数 来设置和显示你的模式: 1) Scancode mode (raw )raw模式:将键盘端口上读出的扫描码放入缓冲区 2) Keycode mode (mediumraw) mediumraw模式:将扫描码过滤为键盘码放入缓冲区 3) ASCII mode (XLATE ) XLATE模式:识别各种键盘码的组合,转换为TTY终端代码放入缓冲区 4) UTF-8 MODE (UNICODE) Unicode 模式:UNICODE模式基本上与XLATE相同,只不过可以通过数字小键盘间接输入UNICODE代码。 键盘模块的上层漫游: 键盘模块的最上层严格的说应该是TTY设备,这在以后描述。对于使用者而言,keyboard.c是我们的最上层。 在keyboard.c中,不涉及底层操作,也不涉及到任何体系结构,他主要负责:键盘初始化、键盘tasklet的挂入、按键盘后的处理、keymap的装入、scancode的转化、与TTY设备的通信。 Keyboard.c是一个大杂烩,包含了大多数keyboard的key的处理,因此代码才变的庞大和复杂,我们可以修改它,让他变的很小(但这样做并不会使空间节省,因为keyboard.c的许多代码是动态加载的)。 Keyboard.c中的int __init kbd_init(void) 函数是键盘代码执行的开始点,但keyboard.c并非是一定执行的,你必须先定义CONFIG_VT(Character devices -> Virtual terminal)才有效,为什么?因为kbd_init()是在tty_io.c 的 tty_init()中被调用的。 ★Kbd_init()在进行一些基本初始化后,执行kbd_init_hw(),此函数可以看做是一个统一的上层界面,对于不同的体系或相同体系下不同的board,他们的kbd_init_hw()的实现代码是不同的(同过CONFIG_XXX_XXX来实现),他的实现代码就是操作硬件。 kbd_init_hw()会:检查硬件及有效性、分配资源和中断、操作寄存器的实现函数调用。 初始化一完成,就把keyboard_tasklet放到CPU tasklet中(注意到keyboard_tasklet 是开放出来的,等下可以看到怎么使用它)。 现在就等你按键了,当有按键时,会调用键盘中断处理函数,一般都是体系下的keyboard_interrupt(),此函数会调用到keyboard.c中的handle_scancode() 。 ★handle_scancode()就是我们的重点,这个函数最终决定了我们按下的key如何处理,在下面会分析他的流程,他主要做:与TTY的通信、keymap的装入、按键的处理。 handle_scancode()处理的结果就是把你按下的key发到不同的处理函数中,一般的,这些函数离最终的结果已经很近了。这其中,大多数的函数都会调用put_queue() 函数。 ★put_queue()的工作原理就是利用TTY, 它将经过转换的键盘功能码用tty_insert_flip_char()放入到当前TTY的flip buffer中,然后将缓冲区输出任务函数(flush_to_ldisc)添加到控制台任务队列(con_task_queue)并激活控制台软中断执行该任务函数. flush_to_ldisc()翻转读写缓冲区,将缓冲区接收数据传递给tty终端规程的n_tty_receive_buf()接收函数,n_tty_receive_buf()处理输入字符,将输出字符缓冲在终端的循环缓冲区(read_buf)之中.用户通过tty规程的read_chan()读取read_buf中的字符.当多个进程同时读取同一终端时, 使用tty->atomic_read信号灯来竞争读取权. Kbd_init()开始点: int __init kbd_init(void) { int i; struct kbd_struct kbd0; /* 定义一个kbd_struct, 他用于保存当前键盘LED灯状态、缺省keymap表、键盘复合锁定状态、一些功能灯的定义、键盘模式定义、及modeflags模式*/ extern struct tty_driver console_driver; /* console_driver这个全局变量是很重要的,他维护着庞大的TTY/Console对象,承当TTY对外的输入和输出 */ kbd0.ledflagstate = kbd0.default_ledflagstate = KBD_DEFLEDS; /* 缺省不亮灯 */ kbd0.ledmode = LED_SHOW_FLAGS; /* 缺省,用于显示flags */ kbd0.lockstate = KBD_DEFLOCK; /* 缺省,表示用key_map的第一个表,即没有lock键*/ kbd0.slockstate = 0; /* 没有粘键 */ kbd0.modeflags = KBD_DEFMODE; /* modeflags=0 */ kbd0.kbdmode = VC_XLATE; /* ASCII模式 */ for (i = 0 ; i < MAX_NR_CONSOLES ; i++)/* 为每个控制台分配一个KBD的结构 */ kbd_table[i] = kbd0; ttytab = console_driver.table; /* ttytab是一个很重要的指针,他维护着当前各个控制台的tty_struct表(即相当于一个2维表),tty_struct可看成/dev/tty*的输入设备,只有在/dev/tty*打开时它才接收输入数据*/ /* 以下就是涉及到硬件的操作,由于我们没有KBD硬件,因此我们可能希望屏蔽所产生的error信息*/ #ifdef CONFIG_SA1100_ROSETTA button_setup(); /* initialize button hardware */ #else kbd_init_hw(); /*下面会分析他*/ #endif /* 我们希望把keyboard_tasklet 挂到CPU的运行队列中去,下面就是 */ tasklet_enable(&keyboard_tasklet); tasklet_schedule(&keyboard_tasklet); /* 下面register一个电源管理的KBD设备?操作函数为NULL? */ pm_kbd = pm_register(PM_SYS_DEV, PM_SYS_KBC, NULL); return 0; } kbd_init_hw()函数: 我们已经说了,kbd_init_hw() 相当与一个统一的界面,不同硬件操作代码是不同的,因为是涉及硬件的操作。 对arm体系而言,目前的kbd_init_hw() 提供了4种硬件代码,象如果是ADS板,就要提供ADS的相应代码。 SA1100体系的此函数放在文件include/asm-arm/arch-sa1100/keyboard.h 中。大家可以看到在这个文件中除了kbd_init_hw() 外,还有kbd_setkeycode() / kbd_getkeycode()/ kbd_translate()/ ……等函数都是函数名相同,处理代码不同。而这些函数都会在keyboard.c中被调用到。 SA1100定义了“CONFIG_SA1100_BRUTUS / CONFIG_SA1100_GRAPHICSCLIENT/ CONFIG_SA1111 / other board”4种已知硬件的操作代码,对于Assabet就会调用other board的代码,也就是无论什么函数都为NULL(因为没有这个硬件呀!),CONFIG_SA1100_GRAPHICSCLIENT也就是ADS板的处理代码(SmartIO芯片决定的),CONFIG_SA1111也有自己的一块KBD芯片,而且功能居然和PC的差不多。 ★那现在我们怎么办呀(我们的板不知道有没有KBD模块芯片)?我们来挑一个最复杂的来分析把。那就是SA1111了。 我们从kbd_init_hw() 进入把: void __init sa1111_init_hw(void) { kbd_request_region(); /* 分配kbd端口,其实根本就不用分配了,对PC来说,0x60~0x68就是KBD的IO口,对sa1100更不用分配了,所以此函数为NULL*/ SKPCR |= (1<<6); /* 即0x4000,0000+0x0200处的register操作 */ /* 以下初始化KBD的4个register,以便开始下面的test工作 */ KBDCLKDIV = 0; KBDPRECNT = 127; KBDCR = 0x08; mdelay(50); KBDDATA = 0xff; mdelay(50); /* Flush any pending input. */ kbd_clear_input(); if (kbd_startup_reset) { /* int kbd_startup_reset =1,所以肯定执行 */ char *msg = initialize_kbd(); /* initialize_kbd() 真正开始init 硬件,出错会return错误message,显示在系统启动的时候 */ if (msg) printk(KERN_WARNING "initialize_kbd: %s\n", msg); } #if defined CONFIG_PSMOUSE psaux_init(); #endif /* allocate the IRQ, keyboard_interrupt 是处理函数 */ kbd_request_irq(keyboard_interrupt); #if 0 /*@@@*/ for (;;) { if (KBDSTAT & KBD_STAT_RXF) printk("K_%.2x ", KBDDATA & 0xff); if (MSESTAT & MSE_STAT_RXF) printk("M_%.2x ", MSEDATA & 0xff); } #endif #if 0 /*@@@*/ timer_table[COMTROL_TIMER].fn = pckbd_timer; timer_table[COMTROL_TIMER].expires = jiffies + 2*HZ/100; timer_active |= 1 << COMTROL_TIMER; #endif } initialize_kbd()函数: 这个函数如果逐层展开,会比较庞大,我们就分析最上层的把。 首先,要reset键盘,即对键盘的 KBDDATA寄存器写KBD_CMD_RESET数据,然后读状态位,假如超时,那么可能是没有AT键盘挂上。这里有一个很重要的问题就是你在对register读的时候,先收到KBD_REPLY_ACK数据标志,然后再受到数据,因此你需要读2次。 另,这些硬件代码实在是太多了,我想,如果大家有什么不懂来问我把,我吃不削写了。这里涉及到很多的 键盘命令/键盘标志,你可以参考pc_keyb.h文件,它包括了特殊的键盘命令操作,你可能需要对键盘的原理有所了解才看的懂。 static char * __init initialize_kbd(void) { int status; #if 0 /*@@@*/ /* * Test the keyboard interface. * This seems to be the only way to get it going. * If the test is successful a x55 is placed in the input buffer. */ kbd_write_command_w(KBD_CCMD_SELF_TEST); if (kbd_wait_for_input() != 0x55) return "Keyboard failed self test"; /* * Perform a keyboard interface test. This causes the controller * to test the keyboard clock and data lines. The results of the * test are placed in the input buffer. */ kbd_write_command_w(KBD_CCMD_KBD_TEST); if (kbd_wait_for_input() != 0x00) return "Keyboard interface failed self test"; /* * Enable the keyboard by allowing the keyboard clock to run. */ kbd_write_command_w(KBD_CCMD_KBD_ENABLE); #endif /* * Reset keyboard. If the read times out * then the assumption is that no keyboard is * plugged into the machine. * This defaults the keyboard to scan-code set 2. * * Set up to try again if the keyboard asks for RESEND. */ do { kbd_write_output_w(KBD_CMD_RESET); status = kbd_wait_for_input(); if (status == KBD_REPLY_ACK) break; if (status != KBD_REPLY_RESEND) return "Keyboard reset failed, no ACK"; } while (1); if (kbd_wait_for_input() != KBD_REPLY_POR) return "Keyboard reset failed, no POR"; /* * Set keyboard controller mode. During this, the keyboard should be * in the disabled state. * * Set up to try again if the keyboard asks for RESEND. */ do { kbd_write_output_w(KBD_CMD_DISABLE); status = kbd_wait_for_input(); if (status == KBD_REPLY_ACK) break; if (status != KBD_REPLY_RESEND) return "Disable keyboard: no ACK"; } while (1); #if 0 /*@@@*/ kbd_write_command_w(KBD_CCMD_WRITE_MODE); kbd_write_output_w(KBD_MODE_KBD_INT | KBD_MODE_SYS | KBD_MODE_DISABLE_MOUSE | KBD_MODE_KCC); /* ibm powerpc portables need this to use scan-code set 1 -- Cort */ kbd_write_command_w(KBD_CCMD_READ_MODE); if (!(kbd_wait_for_input() & KBD_MODE_KCC)) { /* * If the controller does not support conversion, * Set the keyboard to scan-code set 1. */ kbd_write_output_w(0xF0); kbd_wait_for_input(); kbd_write_output_w(0x01); kbd_wait_for_input(); } #else kbd_write_output_w(0xf0); kbd_wait_for_input(); kbd_write_output_w(0x01); kbd_wait_for_input(); #endif kbd_write_output_w(KBD_CMD_ENABLE); if (kbd_wait_for_input() != KBD_REPLY_ACK) return "Enable keyboard: no ACK"; /* * Finally, set the typematic rate to maximum. */ kbd_write_output_w(KBD_CMD_SET_RATE); if (kbd_wait_for_input() != KBD_REPLY_ACK) return "Set rate: no ACK"; kbd_write_output_w(0x00); if (kbd_wait_for_input() != KBD_REPLY_ACK) return "Set rate: no ACK"; return NULL; } 键盘中断: 在做完init后,接下来就等待key press事件了,如果产生了key press,好,调到keyboard_interrupt()来处理,在sa1111中就是调用handle_kbd_event()函数。 handle_kbd_event()函数:首先去读status port,看是不是有按键事件,然后判断是键盘还是鼠标的,如果是键盘的,读出scancode,然后判断status register的第8位是否为1,如果是1(表示正确,即key press时状态),那么调用handle_keyboard_event(scancode)。 static inline void handle_keyboard_event(unsigned char scancode) { #ifdef CONFIG_VT kbd_exists = 1; if (do_acknowledge(scancode)) /* 我们希望读出的不是键盘的CMD数据,正确,返回1,否则0 */ handle_scancode(scancode, !(scancode & 0x80)); /* 这就是我们要注意的最重要的函数,我们看到scancode & 0x80,为什么要这样,因为KBDDATA register最高位是判断键是press还是release,如果是1就是release。后面的7位才是我们要的数据。 */ #endif tasklet_schedule(&keyboard_tasklet); /* 一不做,二不修,我们再一次把keyboard_tasklet放到tasklet中,确保bottom half的执行。*/ } handle_scancode()函数: 进入handle_scancode(), 你准备好了吗?,首先我们要看到此函数是kernel EXPORT出去的函数,也就是说我们可以在user-level程序中调用他,不过,如果把这段代码改写一下,在user-level中调用才有意义的多。 Handle_scancode()主要是:判断key是按下还是释放,然后把当前的tty_driver赋给变量tty,tty_driver可看成/dev/tty*的输出设备,不要和tty_struct混乱起来。 void handle_scancode(unsigned char scancode, int down) { unsigned char keycode; char up_flag = down ? 0 : 0200; /* 判断,如果是press那么up_flag=0,否则up_flag=0200 */ char raw_mode; /* 键盘模式 */ pm_access(pm_kbd); add_keyboard_randomness(scancode | up_flag); tty = ttytab? ttytab[fg_console]: NULL; /* 把当前的TTY参数传给变量 tty, 由tty来控制操作 */ if (tty && (!tty->driver_data)) { /* 我们如果直接操作tty是危险的,因为我们不知道tty是否被打开了,所以我们通过tty->driver_data来判断,tty打开的时候,tty->driver_data是被设置的,tty关闭的时候,他被clear */ /* * We touch the tty structure via the ttytab array * without knowing whether or not tty is open, which * is inherently dangerous. We currently rely on that * fact that console_open sets tty->driver_data when * it opens it, and clears it when it closes it. */ tty = NULL; } kbd = kbd_table + fg_console; /*kbd_table是kbd[]的指针,所以kbd就是当前tty的kbd_struct的指针 */ if ((raw_mode = (kbd->kbdmode == VC_RAW))) { /* 我们判断当前的kbd模式是否为VC_RAW,我们知道如果是的话,我们就可以把scancode 直接放到tty_flip_buffer中。 */ put_queue(scancode | up_flag); /* we do not return yet, because we want to maintain the key_down array, so that we have the correct values when finishing RAW mode or when changing VT's */ } /* * 把scancode 转化为 keycode。 kbd_translate()函数, * 我们分析sa1111的。分析在下面 */ if (!kbd_translate(scancode, &keycode, raw_mode)) return; if (0) printk(__FUNCTION__ ": scancode=%d keycode=%d raw_mode=%d\n", scancode, keycode, raw_mode); /* * 变量keycode包含的是键控代码(u-char) * 注意到keycode 不允许为0 (on m68k 0 是允许的). * 我们明了了键的 up/down 状态,我们要把状态传进去 * 并且如果是MEDIUMRAW 模式,我们要返回keycode的值 */ kbd_processkeycode(keycode, up_flag, 0); } sa1111_translate()函数: 因为scancode 转化成keycode 是这样的重要,所以我才不的不把他拿出来单独讲解了。我们只有知道scancode和keycode 的区别,我们才可以做下面的事情。 我们知道我们从键盘的 IO口读出的是scancode,那我们怎么知道每个键的scancode 呀?通过:Linux下的showkey –s我们就可以知道每个键的scancode, 如果想知道相应的keycode,你重要用showkey就可以了。(后面的代码我写了一个模仿showkey的程序) 通过测试,我们知道1~88键按下的scancode是“0x**”,而89~128是以 “0xe0(或0xe1) 0x**”表示的。如果是释放,只要加上个128就可以了。 然后我们来读以下的代码: int sa1111_translate(unsigned char scancode, unsigned char *keycode, char raw_mode) { static int prev_scancode = 0; /* special prefix scancodes.. */ if (scancode == 0xe0 || scancode == 0xe1) { prev_scancode = scancode; return 0; } /* 0xFF is sent by a few keyboards, ignore it. 0x00 is error */ if (scancode == 0x00 || scancode == 0xff) { prev_scancode = 0; return 0; } scancode &= 0x7f; if (prev_scancode) { /* * usually it will be 0xe0, but a Pause key generates * e1 1d 45 e1 9d c5 when pressed, and nothing when released */ if (prev_scancode != 0xe0) { if (prev_scancode == 0xe1 && scancode == 0x1d) { prev_scancode = 0x100; return 0; } else if (prev_scancode == 0x100 && scancode == 0x45) { *keycode = E1_PAUSE; prev_scancode = 0; } else { #ifdef KBD_REPORT_UNKN if (!raw_mode) printk(KERN_INFO "keyboard: unknown e1 escape sequence\n"); #endif prev_scancode = 0; return 0; } } else { prev_scancode = 0; /* * The keyboard maintains its own internal caps lock and * num lock statuses. In caps lock mode E0 AA precedes make * code and E0 2A follows break code. In num lock mode, * E0 2A precedes make code and E0 AA follows break code. * We do our own book-keeping, so we will just ignore these. */ /* * For my keyboard there is no caps lock mode, but there are * both Shift-L and Shift-R modes. The former mode generates * E0 2A / E0 AA pairs, the latter E0 B6 / E0 36 pairs. * So, we should also ignore the latter. - aeb@cwi.nl */ if (scancode == 0x2a || scancode == 0x36) return 0; if (e0_keys[scancode]) *keycode = e0_keys[scancode]; else { #ifdef KBD_REPORT_UNKN if (!raw_mode) printk(KERN_INFO "keyboard: unknown scancode e0 %02x\n", scancode); #endif return 0; } } } else if (scancode >= SC_LIM) { /* This happens with the FOCUS 9000 keyboard Its keys PF1..PF12 are reported to generate 55 73 77 78 79 7a 7b 7c 74 7e 6d 6f Moreover, unless re
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传
相似文档                                   自信AI助手自信AI助手

当前位置:首页 > 包罗万象 > 大杂烩

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2025 宁波自信网络信息技术有限公司  版权所有

客服电话:4009-655-100  投诉/维权电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服