keyboard.doc
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- keyboard
- 资源描述:
-
键盘体系 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展开阅读全文
咨信网温馨提示:1、咨信平台为文档C2C交易模式,即用户上传的文档直接被用户下载,收益归上传人(含作者)所有;本站仅是提供信息存储空间和展示预览,仅对用户上传内容的表现方式做保护处理,对上载内容不做任何修改或编辑。所展示的作品文档包括内容和图片全部来源于网络用户和作者上传投稿,我们不确定上传用户享有完全著作权,根据《信息网络传播权保护条例》,如果侵犯了您的版权、权益或隐私,请联系我们,核实后会尽快下架及时删除,并可随时和客服了解处理情况,尊重保护知识产权我们共同努力。
2、文档的总页数、文档格式和文档大小以系统显示为准(内容中显示的页数不一定正确),网站客服只以系统显示的页数、文件格式、文档大小作为仲裁依据,个别因单元格分列造成显示页码不一将协商解决,平台无法对文档的真实性、完整性、权威性、准确性、专业性及其观点立场做任何保证或承诺,下载前须认真查看,确认无误后再购买,务必慎重购买;若有违法违纪将进行移交司法处理,若涉侵权平台将进行基本处罚并下架。
3、本站所有内容均由用户上传,付费前请自行鉴别,如您付费,意味着您已接受本站规则且自行承担风险,本站不进行额外附加服务,虚拟产品一经售出概不退款(未进行购买下载可退充值款),文档一经付费(服务费)、不意味着购买了该文档的版权,仅供个人/单位学习、研究之用,不得用于商业用途,未经授权,严禁复制、发行、汇编、翻译或者网络传播等,侵权必究。
4、如你看到网页展示的文档有www.zixin.com.cn水印,是因预览和防盗链等技术需要对页面进行转换压缩成图而已,我们并不对上传的文档进行任何编辑或修改,文档下载后都不会有水印标识(原文档上传前个别存留的除外),下载后原文更清晰;试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓;PPT和DOC文档可被视为“模板”,允许上传人保留章节、目录结构的情况下删减部份的内容;PDF文档不管是原文档转换或图片扫描而得,本站不作要求视为允许,下载前可先查看【教您几个在下载文档中可以更好的避免被坑】。
5、本文档所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用;网站提供的党政主题相关内容(国旗、国徽、党徽--等)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
6、文档遇到问题,请及时联系平台进行协调解决,联系【微信客服】、【QQ客服】,若有其他问题请点击或扫码反馈【服务填表】;文档侵犯商业秘密、侵犯著作权、侵犯人身权等,请点击“【版权申诉】”,意见反馈和侵权处理邮箱:1219186828@qq.com;也可以拔打客服电话:0574-28810668;投诉电话:18658249818。




keyboard.doc



实名认证













自信AI助手
















微信客服
客服QQ
发送邮件
意见反馈



链接地址:https://www.zixin.com.cn/doc/9010450.html