戏说BIOS之Keyboard
1. Introduction
Keyboard System看起来好像挺简单,但事实上它远比想象中的复杂,硬件上Keyboard System需要两颗cpu完成key stroke的转换以及和Host的通信过程,一颗用于处理keyboard的make&break过程,另一颗作为keyboard controller和host交换信息。一次按键过程在软件的层面上也要经过多次转化才能成为最终被用户理解的ASCII码。这个过程通常需要经历ma=>mv=>set2=>Set1=>ASCII。Keyboard System的架构框图如下图1所示:
这是MB中常见的架构,在NB中这部分已经被放入EC之中成为EC的
一个部分KBC,但是工作原理依旧如此。
2. How It Works?
那么当我们按下一个键,需要做哪些动作,才能让我们看到最终的字符呢? 听我慢慢道来。当我们按下一个键‘k’时(make),键盘内部的8031会将k的set2 scan code‘2Ch’ 通过上图1的串行连接送给8042,8042会查一张set2转set1的表将该set2 scan code转成 set1的‘14h’,而且8042会引发IRQ1通知host,表示有按键事件发生。Host将会读取60Port获取set1的scancode‘14h’,而后host会将‘14h’转化为ASCII码‘k’,当我们松开一个键时过程同按下比较像了,不过键盘内部的8031会先送‘F0h’,然后再送‘2Ch’给8042,8042看到‘f0h’会将Set1的‘14h’的bit7设置为1即94h,以表示这是一个break。Host端也会收到中断IRQ1,可是host通常不处理break code。Make&Break key也被称之为通码和断码。最终host会将set1以及ASCII码放在BDA 之中。Host端对于键盘系统处理分为以下几类:a.字符键b.功能键c.控制键d.双态键e.特殊功能键,对于这几种不同按键host处理方式也会有所不同对于c&d host会在BDA中置flag;对于a host会保存set1和ASCII码在BDA之中(大小写根据控制键的flag确定);b会影响到set1的值;对于e host可能会通过中断调用相关的function。下图2显示host的处理流程:
接下来我将分别用C和ASM演示BIOS处理keyboard system的大致过程代码的原理是通过hook int9接管BIOS的中断处理过程,然后读取EC的6064 port获得kbc的data和status,并转为ASCII码显示出来,有一个要注意的地方就是EOI,EOI是特指8259或者8259兼容设备的中断清除指令。需要在中断服务程序结束之前向8259发送EOI指令,如果在中断程序一开始就发送EOI指令的话,中断服务程序一旦比较大,运行时间较长,可能会产生中断嵌套,严重会造成死机。如果中断服务程序结束之后还没有发送EOI指令的话,那么以后将屏蔽该IRQ以及优先级低于该IRQ的所有中断。我最初就没有送EOI,害得我调试了好久。
C代码如下所示:
#include
#include
#include
#include
#include
#include
void interrupt new_int9_handler(); /* interrupt prototype */
void interrupt (*old_int9_handler)(); /* interrupt function pointer */
unsigned char ESC_Press_Flag = 0;
unsigned char fifo[0x10]={0};
unsigned char start=0;
unsigned char stop=0;
int main(void)
{
printf("Used to test keyboard set1 scancode/n");
printf("@author:peterhu/t/t@Version 1.0/n");
printf("Copyright(C) LGPL/t[ESC] to Quit/n");
/* store old interrupt vector */
old_int9_handler = getvect(9);
/* set up new interrupt handler */
setvect(0x09,new_int9_handler);
while(1)
{
if(ESC_Press_Flag)
break;
while(stop != start)
{
printf("[%.2x],",fifo[stop]);
stop = (++stop)%0x10;
}
}
setvect(0x09,old_int9_handler);
clrscr();
return 1;
}
void interrupt new_int9_handler()
{
unsigned char status;
unsigned char set1;
disable();
status = inportb(0x64);
if(status & 0x01)
{
set1 = inport(0x60);
fifo[start] = set1;
start = (++start) % 0x10;
if(set1 == 0x01)
ESC_Press_Flag = 1;
//printf("[%.2x],",set1);
}
outportb(0x20,0x20);
enable();
}
asm代码如下所示:
data segment
SET1 db 0
H2A db '[','0','0',']','$'
MSG db 'Used to test keyboard set1 scancode',0Ah,0Dh,'@author:peterhu',09h,09h,'@Version 1.0',0Ah,0Dh,'Copyright(C) LGPL',09h,'[ESC] to Quit',0Ah,0Dh,'$'
OLDINT9 dd 0
ESCPR db 0
data ends
code segment
assume cs:code,ds:data
start:
mov ax,data
mov ds,ax
mov ax,1ch
call hex2asi
call show_set1
call show_title
call back_int9
call install_int9
l0:
xor cx,cx
mov cl,SET1
jcxz nokey
xor ax,ax
mov al,SET1
call hex2asi
call show_set1
mov SET1,00h
nokey:
mov cl,ESCPR
jcxz l0
call restore_int9
mov ax,4c00h
int 21h
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; sub routine for store and show set1 scancode
;; for keybord strok maybe something error :/
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
int9_handler:
cli
in al,64h
and al,01h
cmp al,01h
jne exit9
in al,60h
mov ah,00h
cmp al,01h
je escp
jmp exit9
escp:
mov ESCPR,01h
exit9:
mov SET1,al
mov al,20h
out 20h,al
sti
iret
back_int9:
push ax
xor ax,ax
mov es,ax
mov ax,es:[24h]
mov word ptr OLDINT9,ax
mov ax,es:[26h]
mov word ptr OLDINT9+2,ax
pop ax
ret
restore_int9:
push ax
xor ax,ax
mov es,ax
mov ax,es:[24h]
mov ax,word ptr OLDINT9
mov ax,es:[26h]
mov ax,word ptr OLDINT9+2
pop ax
ret
install_int9:
push ax
push ds
push bx
mov ax,0
mov ds,ax
mov bx,24h
cli
mov word ptr[bx],offset int9_handler
mov word ptr[bx+2],seg int9_handler
sti
pop bx
pop ds
pop ax
ret
show_title:
push dx
mov dx,offset MSG
mov ah,9
int 21h
pop dx
ret
show_set1:
push dx
mov dx,offset H2A
mov ah,9
int 21h
pop dx
ret
hex2asi:
push dx
push cx
push si
push bx
mov bx,0
mov si,offset H2A
mov byte ptr [si+1],'0'
mov byte ptr [si+2],'0'
ha1:
mov cx,10h
mov dx,0
div cx
mov cx,ax
jcxz ha3
cmp dx,10d
jnb ha2
add dx,30h
push dx
inc bx
jmp short ha1
ha2:
sub dx,10d
add dx,'A'
push dx
inc bx
jmp ha1
ha3:
cmp dx,10d
jnb ha4
add dx,30h
push dx
inc bx
mov cx,bx
jmp ha5
ha4:
sub dx,10d
add dx,'A'
push dx
inc bx
mov cx,bx
jmp ha5
ha5:
pop ax
mov [si+1],al
inc si
loop s4
ok:
pop bx
pop si
pop cx
pop dx
ret
code ends
end start
上述程序运行状况如下图3所示,一旦有按键动作该程序就会显示set1的scancode(没有给出相应的ASCII码,凑合着用吧J)上述代码可以在这里下载。
REFF:
<
Enjoy it!
That’s all
Peter