键盘&鼠标中断实现
本节主要实现键盘中断和鼠标中断,键盘中断实现将键盘数据显示到屏幕;鼠标中断实现鼠标位置的移动。
键盘中断通过主8259A的IRQ1触发,鼠标中断通过从8259A的IRQ4触发
CPU通过中断向量号来寻址待执行的中断代码
中断向量号 = 起始向量号 + 中断请求号
起始向量号通过中断控制字ICW2被初始化 中断请求号即为8259A引脚编号
打开主IRQ1和从IRQ4中断响应引脚
;OCW(operation control word)
;当OCW[i] = 1 时,屏蔽对应的IRQ(i)管线的信号
;IRQ1对应的是键盘产生的中断
mov al, 11111001b
out 021h, al
call io_delay
;CPU忽略所有来自从8259A芯片的信号
;鼠标是通过从8259A的IRQ4管线向CPU发送信号
mov al, 11101111b
out 0A1h, al
call io_delay
初始化中断控制字ICW2如下所示:
;向主8259A发送ICW2
;20h 对应二进制00100000
;ICW2[0,1,2] = 0
;8259A根据被设置的起始向量号(起始向量号通过中断控制字ICW2被初始化)加上中断请求号计算出中断向量号
;当主8259A对应的IRQ0管线向CPU发送信号时,CPU根据0x20这个值去查找要执行的代码,
;,CPU根据0x21这个值去查找要执行的代码,依次类推。
mov al, 020h
out 021h, al
call io_delay
;向从8259A发送ICW2
;28h 对应二进制00100100
;8259A根据被设置的起始向量号(起始向量号通过中断控制字ICW2被初始化)加上中断请求号计算出中断向量号
;当从8259A对应的IRQ0管线向CPU发送信号时,CPU根据0x28这个值去查找要执行的代码,
;IRQ1管线向CPU发送信号时,CPU根据0x29这个值去查找要执行的代码,依次类推。
mov al, 028h
out 0A1h, al
call io_delay
主控制器起始向量号为0x20 从控制器起始向量号为0x28 因此
键盘中断中断向量号为0x20+0x01=0x21
鼠标中断中断向量号为0x28+0x04=0x2c
因此中断描述符初始化如下:
;中断描述符表
LABLE_IDT:
%rep 0x21
Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate
%endrep
;键盘中断向量(8259A 键盘中断向量0x20,IRQ1 是键盘中断请求,0x20 + IRQ[n] = 0x21
.0x21:
Gate SelectorCode32, KeyboardHandler, 0, DA_386IGate
%rep 10
Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate
%endrep
;从中断控制器8259A 中断向量0x28,IRQ4 是鼠标中断请求,0x28 + IRQ[n] = 0x2c
.0x2c:
Gate SelectorCode32, MouseHandler, 0, DA_386IGate
相应的键盘和鼠标中断处理函数:
;键盘中断程序
LabelKeyboardHandler:
KeyboardHandler EQU LabelKeyboardHandler - $$
; 注意中断切换过程
push es
push ds
pushad
mov eax, esp
push eax
call int_keyboard
pop eax
mov esp, eax
popad
pop ds
pop es
iretd
;鼠标中断程序
LabelMouseHandler:
MouseHandler EQU LabelMouseHandler - $$
; 注意中断切换过程
push es
push ds
pushad
mov eax, esp
push eax
call int_mouse
pop eax
mov esp, eax
popad
pop ds
pop es
iretd
中断处理函数的处理过程
将当前寄存器值入栈
执行中断响应函数
将栈中寄存器值恢复,继续执行中断前指令
键盘中断响应函数
void int_keyboard(char *index){
//0x20是8259A控制端口
//0x21对应的是键盘的中断向量。当键盘中断被CPU执行后,下次键盘再向CPU发送信号时,
//CPU就不会接收,要想让CPU再次接收信号,必须向主PIC的端口再次发送键盘中断的中断向量号
io_out8(0x20, 0x21);
unsigned char data = io_in8(PORT_KEYDATA);
fifo8_put(&keybufInfo, data);
}
将键盘中断产生数据存入队列中,主函数不断检测队列中是否有数据,如果有数据,则将相应数据显示到屏幕中
for(;;){
if (keybufInfo.len > 0) {
io_cli();
static char keyval[4] = {'0','x'};
for(int i=0; i<keybufInfo.len; i++){
char data = fifo8_get(&keybufInfo);
static int x = 0;
char2HexStr(data, keyval);
showString(keyval, x%SCREEN_WIDTH, x/SCREEN_WIDTH*20, COL8_FFFFFF);
x += 32;
}
io_seti();
} else if (mousebufInfo.len > 0) {
io_cli();
for(int t=0;t<mousebufInfo.len;t++){
mouseCursorMoved(&mouseDes, COL8_008484);
}
io_seti();
} else {
io_hlt();
}
}
鼠标电路初始化
内核加载完成,初始化中断描述符表,要想响应鼠标中断,需要首先实现鼠标电路的初始化
//初始化键盘控制电路,鼠标控制电路是连接在键盘控制电路上,通过键盘电路实现初始化
void init_mouse(){
waitKBCReady();
//0x60让键盘电路进入数据接受状态
io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE);
waitKBCReady();
//数据0x47要求键盘电路启动鼠标模式,这样鼠标硬件所产生的数据信息,通过键盘电路端口0x60就可读到
io_out8(PORT_KEYDATA, KBC_MODE);
waitKBCReady();
io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);
waitKBCReady();
//0xf4数据激活鼠标电路,激活后将会给CPU发送中断信号
io_out8(PORT_KEYDATA, MOUSECMD_ENABLE);
}
只有当端口0x64端口数据第二个比特为0时,鼠标电路才能接受来自内核命令。
//鼠标电路对应的一个端口是 0x64, 通过读取这个端口的数据来检测鼠标电路的状态,
//内核会从这个端口读入一个字节的数据,如果该字节的第二个比特位为0,那表明鼠标电路可以
//接受来自内核的命令,因此,在给鼠标电路发送数据前,内核需要反复从0x64端口读取数据,
//并检测读到数据的第二个比特位,直到该比特位为0时,才发送控制信息
void waitKBCReady(){
for(;;){
if((io_in8(PORT_KEYSTA)&0x02)==0){
break;
}
}
}
鼠标中断响应函数
在鼠标中断响应函数中,将鼠标中断产生数据存入队列中
void int_mouse(char *index){
//当中断处理后,要想再次接收中断信号,就必须向中断控制器发送一个字节的数据
io_out8(0x20, 0x20);
io_out8(0xa0, 0x20);
//读取鼠标数据
unsigned char data = io_in8(PORT_MOUSEDATA);
fifo8_put(&mousebufInfo, data);
}
主循环不断检测鼠标队列,依次处理每个数据
for(;;){
if (keybufInfo.len > 0) {
io_cli();
static char keyval[4] = {'0','x'};
for(int i=0; i<keybufInfo.len; i++){
char data = fifo8_get(&keybufInfo);
static int x = 0;
char2HexStr(data, keyval);
showString(keyval, x%SCREEN_WIDTH, x/SCREEN_WIDTH*20, COL8_FFFFFF);
x += 32;
}
io_seti();
} else if (mousebufInfo.len > 0) {
io_cli();
for(int t=0;t<mousebufInfo.len;t++){
mouseCursorMoved(&mouseDes, COL8_008484);
}
io_seti();
} else {
io_hlt();
}
}
读取队列中的数据,每三个为一组进行解析,每三个数据构建结构体MouseDes来表示当前鼠标的移动信息,然后擦除旧的鼠标信息,计算鼠标新的坐标,绘制新的鼠标。
void mouseCursorMoved(MouseDes *mdec, char bc){
unsigned char data = fifo8_get(&mousebufInfo);
if(mouse_decode(mdec, data) != 0){
//擦除之前鼠标位置
fillRect(mdec->x, mdec->y, 16, 16, bc);
//计算鼠标新的坐标
mdec->x += mdec->offX;
mdec->y += mdec->offY;
if(mdec->x < 0){
mdec->x = 0;
}
if(mdec->x > SCREEN_WIDTH-16/2){
mdec->x = SCREEN_WIDTH-16/2;
}
if(mdec->y < 0){
mdec->y = 0;
}
if(mdec->y > SCREEN_HEIGHT-16){
mdec->y = SCREEN_HEIGHT-16;
}
//绘制鼠标
init_mouse_cursor((char *)VGA_ADDR, mdec->x, mdec->y, COL8_008484);
}
}
鼠标移动模型以及处理
//鼠标处理需要连续处理3字节
//phase 表示处理字节阶段
//offX, offY 当前鼠标的偏移
//x,y 鼠标当前所在的坐标位置
typedef struct _MouseDes{
char buf[3], phase;
int offX, offY;
int x, y, btn;
}MouseDes;
每三个数据即可给出鼠标中断的数据信息,主要包括鼠标按键类型(左 滑轮 右)
鼠标上下移动 左右移动数据。第一个字节0xab, a的数值必须在0-3这个范围内,由于a对应的是八比特中的高四位,所以这意味着该字节的第7,8两个比特位必须为0,b对应着八比特位中的低四位,它的值必须在8-F之间,这意味着该字节数据对应的第4个比特位必须为1.把第一个字节转换成二进制,那么它必须满足下面格式(X,*代表0或1):
0 0 X X 1 * \ * * 三个*用来表示鼠标按键,当鼠标的左键,滚轮,右键被按下时,对应的比特位会设置为1.第二个字节用来表示鼠标的左右移动,对该字节进行相应处理后,可以得到鼠标平移的坐标变换。第三个字节的数据表示鼠标的上下移动,对该字节进行相应处理后,可以得到鼠标垂直移动时的坐标数变化。
具体的处理过程如下所示:
int mouse_decode(MouseDes *mdec, unsigned char dat){
int flag = -1;
if(mdec->phase == 0){
if(dat == 0xfa){
mdec->phase = 1;
}
flag = 0;
}
else if(mdec->phase == 1){
if((dat&0xc8) == 0x08){
mdec->buf[0] = dat;
mdec->phase = 2;
}
flag = 0;
}
else if(mdec->phase == 2){
mdec->buf[1] = dat;
mdec->phase = 3;
flag = 0;
}
else if(mdec->phase == 3){
mdec->buf[2] = dat;
mdec->phase =1;
mdec->btn = mdec->buf[0]&0x07;
mdec->offX = mdec->buf[1];
mdec->offY = mdec->buf[2];
if((mdec->buf[0]&0x10) != 0){
mdec->offX |= 0xffffff00;
}
if((mdec->buf[0]&0x20) != 0){
mdec->offY |= 0xffffff00;
}
mdec->offY = -mdec->offY;
flag = 1;
}
return flag;
}
代码位置:https://github.com/ChenWenKaiVN/bb_os_core
参考链接: