鍵盤&鼠標中斷實現
本節主要實現鍵盤中斷和鼠標中斷,鍵盤中斷實現將鍵盤數據顯示到屏幕;鼠標中斷實現鼠標位置的移動。
鍵盤中斷通過主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
參考鏈接: