资源描述
键盘体系
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
展开阅读全文