7 对各种锁定键的支持(harib14g)
好,让我们开始吧。回头再看一遍14.4节中的编码表(不想看表格的同学还是可以自己按键盘看编码哦),我们可以得到:
0x3a
: CapsLock
0x45
: NumLock
0x46
: ScrollLock
因此当我们接收到上述按键编码时,只要将binfo—>leds中对应的位置改写就可以了。这和key_shift基本上是一样的,很容易实现。
到这里,我们已经实现了锁定键模式的切换,不过现在还是有一个问题,模式是可以切换了,但是键盘上面的指示灯却不会发生变化。这样就可能会发生下述情况:明明CapsLock灯没亮,但在系统中却是处于CapsLock模式。这个问题我们最好想办法解决它。
关于点亮/熄灭键盘上指示灯的方法,在这里有记载。
http://community.osdev.info/?(AT)keyboardkeyboard)
关于LED的控制
对于NumLock和CapsLock等LED的控制,可采用下面的方法向键盘发送指令和数据。
读取状态寄存器,等待bit 1的值变为0。
向数据输出(0060)写入要发送的1个字节数据。
等待键盘返回1个字节的信息,这和等待键盘输入所采用的方法相同(用IRQ等待或者用轮询状态寄存器bit 1的值直到其变为0都可以)。
返回的信息如果为0xfa,表明1个字节的数据已成功发送给键盘。如为0xfe则表明发送失败,需要返回第1步重新发送。
要控制LED的状态,需要按上述方法执行两次,向键盘发送EDxx数据。其中,xx的bit 0代表ScrollLock,bit 1代表NumLock,bit 2代表CapsLock(0表示熄灭,1表示点亮)。bit 3~7为保留位,置0即可。
■■■■■
有了这些信息,我们总算看到希望了,于是我们写了以下程序。
本次的bootpack.c节选
#define KEYCMD_LED 0xed
void HariMain(void)
{
(中略)
struct FIFO32 fifo, keycmd;
int fifobuf[128], keycmd_buf[32];
(中略)
int key_to = 0, key_shift = 0, key_leds = (binfo->leds >> 4) & 7, keycmd_wait = -1;
(中略)
fifo32_init(&keycmd, 32, keycmd_buf, 0);
(中略)
/*为了避免和键盘当前状态冲突,在一开始先进行设置*/
fifo32_put(&keycmd, KEYCMD_LED);
fifo32_put(&keycmd, key_leds);
for (;;) {
if (fifo32_status(&keycmd) > 0 && keycmd_wait < 0) { /*从此开始*/
/*如果存在向键盘控制器发送的数据,则发送它 */
keycmd_wait = fifo32_get(&keycmd);
wait_KBC_sendready();
io_out8(PORT_KEYDAT, keycmd_wait);
} /*到此结束*/
io_cli();
if (fifo32_status(&fifo) == 0) {
task_sleep(task_a);
io_sti();
} else {
i = fifo32_get(&fifo);
io_sti();
if (256 <= i && i <= 511) { /* 键盘数据 */
(中略)
/*从此开始*/ if (i == 256 + 0x3a) { /* CapsLock */
key_leds ^= 4;
fifo32_put(&keycmd, KEYCMD_LED);
fifo32_put(&keycmd, key_leds);
}
if (i == 256 + 0x45) { /* NumLock */
key_leds ^= 2;
fifo32_put(&keycmd, KEYCMD_LED);
fifo32_put(&keycmd, key_leds);
}
if (i == 256 + 0x46) { /* ScrollLock */
key_leds ^= 1;
fifo32_put(&keycmd, KEYCMD_LED);
fifo32_put(&keycmd, key_leds);
}
if (i == 256 + 0xfa) { /*键盘成功接收到数据*/
keycmd_wait = -1;
}
if (i == 256 + 0xfe) { /*键盘没有成功接收到数据*/
wait_KBC_sendready();
io_out8(PORT_KEYDAT, keycmd_wait);
/*到此结束*/ }
(中略)
} else if (512 <= i && i <= 767) { /*鼠标数据*/
(中略)
} else if (i <= 1) { /*光标用定时器*/
(中略)
}
}
}
}
程序的工作原理是这样的。首先,我们创建了一个叫keycmd的FIFO缓冲区,它不是用来接收中断请求的,而是用来管理由任务A向键盘控制器发送数据的顺序的。如果有数据要发送到键盘控制器,首先会在这个keycmd中累积起来。
keycmd_wait变量,用来表示向键盘控制器发送数据的状态。当keycmd_wait的值为-1时,表示键盘控制器处于通常状态,可以发送指令;当值不为-1时,表示键盘控制器正在等待发送的数据,这时要发送的数据被保存在keycmd_wait变量中。
在for循环的开头,当keycmd中有数据,且keycmd_wait为-1时,向键盘发送1个字节的数据,在开始发送数据的同时,keycmd_wait变为非-1的值。随后,当从键盘接收到0xfa返回信息时,keycmd_wait恢复为-1,继续发送下一个数据。当从键盘接收到的返回信息为0xfe时,则重新发送刚才的数据。
在for循环前面,我们向键盘控制器设置了指示灯的状态,也许这一段是可有可无的,不过这样可以保证key_leds的值和实际的键盘指示灯状态绝对不会发生冲突的情况,因此保险起见还是设置了。
■■■■■
好了,我们来“make run”。本来想贴一张运行时的截图,不过这里发生变化的不是屏幕画面,而是键盘的指示灯,所以很遗憾,没有办法给大家展示这个令人感动的场面了。
嗯?不管怎么按CapsLock键,在笔者的Windows上都无法点亮指示灯,不过NumLock和ScrollLock却是正常的。哦,按Shift+CapsLock指示灯就亮了,好奇怪啊,我们明明不是这样设计的呢。
由于实在无法理解这一现象,笔者又重新“make run”了harib14f,按下NumLock进行实验,咦?harib14f中也可以点亮NumLock指示灯呢1。看起来在这个QEMU模拟器中,键盘的指示灯貌似并不是由模拟器管理,而是由Windows管理的。
1 harib14f中还尚未实现对指示灯的控制。——译者注
于是我们在真机环境下再挑战一下。哇,这次完美了,harib14f无法改变键盘指示灯的状态,而harib14g则可以正常控制键盘指示灯。撒花!
好,今天就到这里吧,明天我们继续来做命令行窗口哦!