1、键盘体系 author: sniff 从什么开始说比较合理呀?就从硬件开始把:严格来说称不上什么键盘体系,但由于键盘的driver code比较的涩晦,所以就称之为键盘体系了。 后注:什么叫后注?也就是写完后想说点什么的意思呀!这篇文挡太长了(本来想写的更长,真的,还有一些文件都没有写上去呀),大家还是用“文挡结构图”来看把,厉害把,这么多,全部手写呀。 硬件相关 硬件,其实有一些内容,但我实在不想一段段的翻译,大家想要的话,我把english文挡发给大家好了。 Keyboard Key 键盘代码 键盘模式 键盘模式有4种, 在Linux 下你
2、可以用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代码。 键盘模块的上层漫
3、游: 键盘模块的最上层严格的说应该是TTY设备,这在以后描述。对于使用者而言,keyboard.c是我们的最上层。 在keyboard.c中,不涉及底层操作,也不涉及到任何体系结构,他主要负责:键盘初始化、键盘tasklet的挂入、按键盘后的处理、keymap的装入、scancode的转化、与TTY设备的通信。 Keyboard.c是一个大杂烩,包含了大多数keyboard的key的处理,因此代码才变的庞大和复杂,我们可以修改它,让他变的很小(但这样做并不会使空间节省,因为keyboard.c的许多代码是动态加载的)。 Keyboard.c中的int __init kbd_init(v
4、oid) 函数是键盘代码执行的开始点,但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()会
5、检查硬件及有效性、分配资源和中断、操作寄存器的实现函数调用。 初始化一完成,就把keyboard_tasklet放到CPU tasklet中(注意到keyboard_tasklet 是开放出来的,等下可以看到怎么使用它)。 现在就等你按键了,当有按键时,会调用键盘中断处理函数,一般都是体系下的keyboard_interrupt(),此函数会调用到keyboard.c中的handle_scancode() 。 ★handle_scancode()就是我们的重点,这个函数最终决定了我们按下的key如何处理,在下面会分析他的流程,他主要做:与TTY的通信、keymap的装入、按
6、键的处理。 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终端
7、规程的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表、键盘复合锁定状
8、态、一些功能灯的定义、键盘模式定义、及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 = KB
9、D_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; /* t
10、tytab是一个很重要的指针,他维护着当前各个控制台的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 /* 我们希望把key
11、board_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() 相当与一个统一的界面,不同硬件操作代码是不同的,因为是涉及
12、硬件的操作。 对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 / CON
13、FIG_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()
14、 进入把: 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;
15、 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 硬件,出
16、错会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_interru
17、pt); #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_tabl
18、e[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键盘挂上。这里有一个很重要的问题就是你在对
19、register读的时候,先收到KBD_REPLY_ACK数据标志,然后再受到数据,因此你需要读2次。 另,这些硬件代码实在是太多了,我想,如果大家有什么不懂来问我把,我吃不削写了。这里涉及到很多的 键盘命令/键盘标志,你可以参考pc_keyb.h文件,它包括了特殊的键盘命令操作,你可能需要对键盘的原理有所了解才看的懂。 static char * __init initialize_kbd(void) { int status; #if 0 /*@@@*/ /* * Test the keyboard interface.
20、 * 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"; /*
21、 * 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)
22、 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 assu
23、mption 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 =
24、 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)
25、 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(
26、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_wr
27、ite_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 -- Co
28、rt */ 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_outp
29、ut_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_w
30、rite_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_RE
31、PLY_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中就是调用h
32、andle_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 =
33、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
34、tasklet); /* 一不做,二不修,我们再一次把keyboard_tasklet放到tasklet中,确保bottom half的执行。*/ } handle_scancode()函数: 进入handle_scancode(), 你准备好了吗?,首先我们要看到此函数是kernel EXPORT出去的函数,也就是说我们可以在user-level程序中调用他,不过,如果把这段代码改写一下,在user-level中调用才有意义的多。 Handle_scancode()主要是:判断key是按下还是释放,然后把当前的tty_driver赋给变量tty,tty_driver可看成/
35、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);
36、 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 know
37、ing 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的
38、指针 */ 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 mo
39、de 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
40、包含的是键控代码(u-char) * 注意到keycode 不允许为0 (on m68k 0 是允许的). * 我们明了了键的 up/down 状态,我们要把状态传进去 * 并且如果是MEDIUMRAW 模式,我们要返回keycode的值 */ kbd_processkeycode(keycode, up_flag, 0); } sa1111_translate()函数: 因为scancode 转化成keycode 是这样的重要,所以我才不的不把他拿出来单独讲解了。我们只有知道scancode和keycode 的区别,我们才可以做下面的事情。
41、 我们知道我们从键盘的 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, unsig
42、ned 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 f
43、ew 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
44、 * 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 == 0
45、x100 && 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_scanc
46、ode = 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 2
47、A 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
48、 * 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)
49、 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); #endi
50、f 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
©2010-2025 宁波自信网络信息技术有限公司 版权所有
客服电话:4009-655-100 投诉/维权电话:18658249818