1、keyboard键盘体系author: sniff 从什么开始说比较合理呀?就从硬件开始把:严格来说称不上什么键盘体系,但由于键盘的driver code比较的涩晦,所以就称之为键盘体系了。后注:什么叫后注?也就是写完后想说点什么的意思呀!这篇文挡太长了(本来想写的更长,真的,还有一些文件都没有写上去呀),大家还是用“文挡结构图”来看把,厉害把,这么多,全部手写呀。硬件相关硬件,其实有一些内容,但我实在不想一段段的翻译,大家想要的话,我把english文挡发给大家好了。Keyboard Key 键盘代码键盘模式键盘模式有4种, 在Linux 下你可以用kbd_mode -参数 来设置和显示你的
2、模式: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是
3、我们的最上层。在keyboard.c中,不涉及底层操作,也不涉及到任何体系结构,他主要负责:键盘初始化、键盘tasklet的挂入、按键盘后的处理、keymap的装入、scancode的转化、与TTY设备的通信。Keyboard.c是一个大杂烩,包含了大多数keyboard的key的处理,因此代码才变的庞大和复杂,我们可以修改它,让他变的很小(但这样做并不会使空间节省,因为keyboard.c的许多代码是动态加载的)。Keyboard.c中的int _init kbd_init(void) 函数是键盘代码执行的开始点,但keyboard.c并非是一定执行的,你必须先定义CONFIG_VT(Cha
4、racter 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
5、tasklet中(注意到keyboard_tasklet 是开放出来的,等下可以看到怎么使用它)。 现在就等你按键了,当有按键时,会调用键盘中断处理函数,一般都是体系下的keyboard_interrupt(),此函数会调用到keyboard.c中的handle_scancode() 。 handle_scancode()就是我们的重点,这个函数最终决定了我们按下的key如何处理,在下面会分析他的流程,他主要做:与TTY的通信、keymap的装入、按键的处理。 handle_scancode()处理的结果就是把你按下的key发到不同的处理函数中,一般的,这些函数离最终的结果已经很近了。这其中,
6、大多数的函数都会调用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)之
7、中.用户通过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这个全局变量
8、是很重要的,他维护着庞大的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; /* modef
9、lags=0 */ kbd0.kbdmode = VC_XLATE; /* ASCII模式 */ for (i = 0 ; i MAX_NR_CONSOLES ; i+)/* 为每个控制台分配一个KBD的结构 */ kbd_tablei = kbd0; ttytab = console_driver.table; /* ttytab是一个很重要的指针,他维护着当前各个控制台的tty_struct表(即相当于一个2维表),tty_struct可看成/dev/tty*的输入设备,只有在/dev/tty*打开时它才接收输入数据*/* 以下就是涉及到硬件的操作,由于我们没有KBD硬件,因此我们可能希望
10、屏蔽所产生的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_
11、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_getkeyc
12、ode()/ 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也有自己的一
13、块KBD芯片,而且功能居然和PC的差不多。 那现在我们怎么办呀(我们的板不知道有没有KBD模块芯片)?我们来挑一个最复杂的来分析把。那就是SA1111了。 我们从kbd_init_hw() 进入把:void _init sa1111_init_hw(void) kbd_request_region(); /* 分配kbd端口,其实根本就不用分配了,对PC来说,0x600x68就是KBD的IO口,对sa1100更不用分配了,所以此函数为NULL*/SKPCR |= (16); /* 即0x4000,0000+0x0200处的register操作 */* 以下初始化KBD的4个register,以
14、便开始下面的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,显示在系统启
15、动的时候 */ if (msg) printk(KERN_WARNING initialize_kbd: %sn, 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_RX
16、F) printk(M_%.2x , MSEDATA & 0xff); #endif#if 0 /*/ timer_tableCOMTROL_TIMER.fn = pckbd_timer; timer_tableCOMTROL_TIMER.expires = jiffies + 2*HZ/100; timer_active |= 1 driver_data) /* 我们如果直接操作tty是危险的,因为我们不知道tty是否被打开了,所以我们通过tty-driver_data来判断,tty打开的时候,tty-driver_data是被设置的,tty关闭的时候,他被clear */ /* * We
17、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_buffe
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1