day7
獲取按鍵編碼(hiarib04a)
void inthandler21(int *esp)
{struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO; // 獲取系統啟動信息結構體指針unsigned char data, s[4]; // data: 鍵盤數據緩存,s: 格式化字符串緩存io_out8(PIC0_OCW2, 0x61); // 發送EOI命令(0x61)通知PIC中斷處理完成// 具體說明:
// 1. PIC0_OCW2 是主PIC的操作命令字寄存器(端口地址0x20)
// 2. 0x61 的二進制形式是 01100001,其中:
// - 高三位 011 表示「指定IRQ級別的EOI命令」
// - 低五位 00001 表示IRQ1(鍵盤中斷)
// 3. 該操作完成兩個功能:
// a. 清除PIC的中斷服務寄存器(ISR)對應位
// b. 允許PIC繼續接收新的中斷請求data = io_in8(PORT_KEYDAT); // 從鍵盤數據端口讀取掃描碼sprintf(s, "%02X", data); // 將掃描碼轉為16進制字符串boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31); // 清空顯示區域(青灰色背景)putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s); // 顯示白色文本的掃描碼return;
}
這句話用來通知PIC“已經知道發生了IRQ1中斷哦”。如果是IRQ3,則寫成0x63。也就是說,將“0x60+IRQ號碼”輸出給OCW2就可以。執行這句話之后,PIC繼續時刻監視IRQ1中斷是否發生。
如果忘記了執行這句話,PIC就不再監視IRQ1中斷,不管下次由鍵盤輸入什么信息,系統都感知不到了。
從編號為0x0060的設備輸入的8位信息是按鍵編碼。編號為0x0060的設備就是鍵盤。
加快中斷處理(hiarib04b)
struct KEYBUF {unsigned char data, flag;
};
#define PORT_KEYDAT 0x0060struct KEYBUF keybuf;
void inthandler21(int *esp)
{unsigned char data;io_out8(PIC0_OCW2, 0x61); data = io_in8(PORT_KEYDAT);if (keybuf.flag == 0) {keybuf.data = data;keybuf.flag = 1;}return;
}
for (;;) {io_cli();//如果flag是0,
//表示緩沖區為空;如果flag是1,就表示緩沖區中存有數據if (keybuf.flag == 0) {io_stihlt();} else {i = keybuf.data;keybuf.flag = 0;io_sti();sprintf(s, "%02X", i);boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);}}
io_sti();io_hlt();不等于io_stihlt();
如果io_sti()之后產生了中斷,keybuf里就會存入數據,這時候讓CPU進入HLT狀態,keybuf里存入的數據就不會被覺察到。根據CPU的規范,機器語言的STI指令之后,如果緊跟著HLT指令,那么就暫不受理這兩條指令之間的中斷,而要等到HLT指令之后才受理,
緩沖區建立優點:
- 異步處理機制:鍵盤中斷(IRQ1)發生時需要立即響應,但此時系統可能正在處理其他任務。緩沖區作為數據的中轉站,允許中斷處理程序快速保存數據后立即返回
- 防止數據丟失:當用戶快速連續按鍵時,中斷可能連續觸發。緩沖區可以存儲多個按鍵數據(雖然當前實現是單緩沖區,后續可以擴展為隊列)
- 線程安全:通過flag標志位(keybuf.flag)實現簡單的同步機制,避免主程序讀取數據時與中斷程序發生競爭
- 解耦硬件操作:將底層硬件讀取(io_in8)與上層邏輯處理分離,提高代碼的可維護性
自此中斷處理和調用流程
- 中斷向量表初始化?(在?
int.c
?的 PIC 初始化中)
int.
// PIC初始化時設置中斷向量號
io_out8(PIC0_ICW2, 0x20); // IRQ0-7對應INT 0x20-0x27
io_out8(PIC1_ICW2, 0x28); // IRQ8-15對應INT 0x28-0x2f
- 匯編層中斷門處理?(在?
naskfunc.nas
?中實現)
naskfunc.nas
_asm_inthandler21: ; 對應鍵盤中斷(IRQ1)PUSH ESPUSH DSPUSHADMOV EAX,ESPPUSH EAXMOV AX,SSMOV DS,AXMOV ES,AXCALL _inthandler21 ; 調用C語言處理函數POP EAXPOPADPOP DSPOP ESIRETD
- C語言中斷服務程序?(在?
int.c
?中實現)
int.c
// 鍵盤中斷處理程序
void inthandler21(int *esp) {unsigned char data;io_out8(PIC0_OCW2, 0x61); // 通知PIC中斷處理完成data = io_in8(PORT_KEYDAT); // 讀取鍵盤數據// ... 緩沖區處理 ...
}
完整的中斷調用流程:
- 硬件中斷觸發 → 2. CPU查IDT表 → 3. 跳轉至_asm_inthandlerXX → 4. 保存上下文 → 5. 調用C處理函數 → 6. 恢復上下文 → 7. IRETD返回
制作FIFO緩沖區(hiarib04c)
就是將緩沖區的數據部分弄成鏈表,但這里是靜態數組,到下面整理緩沖區就是動態分配內存了
struct KEYBUF {unsigned char data[32];int next;
};
void inthandler21(int *esp)
{unsigned char data;io_out8(PIC0_OCW2, 0x61);data = io_in8(PORT_KEYDAT);if (keybuf.next < 32) {keybuf.data[keybuf.next] = data;keybuf.next++;}return;
}
for (;;) {io_cli();if (keybuf.next == 0) {io_stihlt();} else {i = keybuf.data[0];keybuf.next--;for (j = 0; j < keybuf.next; j++) {keybuf.data[j] = keybuf.data[j + 1];}io_sti();sprintf(s, "%02X", i);boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);}}
但這個程序,中斷處理的時候涉及數據移送操作,如果在數據移送前中斷的話,數據就會亂
所以下面就改善一下
改善FIFO緩沖區(hiarib04d)
其實就是一個雙指針,一個讀數據指針,一個寫數據指針
寫的時候直接覆蓋寫,到了len最大容量就歸0
整理FIFO緩沖區(hiarib04e)
struct FIFO8 {unsigned char *buf;int p, q, size, free, flags;
};
/*可變緩沖區,size存入緩沖區的總字節數,變量free保存緩沖區里美歐數據的字節數,
buf存緩沖區的地址,p代表下一個數據寫入地址(next_w),q代表下一個數據讀出地址
(next_r)。
*/
void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf)
/* 初始化FIFO緩沖區 */
{
fifo->size = size;
fifo->buf = buf;
fifo->free = size; /* 緩沖區的大小 */
fifo->flags = 0;
fifo->p = 0; /* 下一個數據寫入位置 */
fifo->q = 0; /* 下一個數據讀出位置 */
return;
}
#define FLAGS_OVERRUN 0x0001
int fifo8_put(struct FIFO8 *fifo, unsigned char data)
/* 向FIFO傳送數據并保存 */
{
if (fifo->free == 0) {
/* 空余沒有了,溢出 */
fifo->flags |= FLAGS_OVERRUN;
return -1;
}
fifo->buf[fifo->p] = data;
fifo->p++;
if (fifo->p == fifo->size) {
fifo->p = 0;
}
fifo->free--;
return 0;
}
int fifo8_get(struct FIFO8 *fifo)
/* 從FIFO取得一個數據 */
{
int data;
if (fifo->free == fifo->size) {
/* 如果緩沖區為空,則返回 -1 */
return -1;
}
data = fifo->buf[fifo->q];
fifo->q++;
if (fifo->q == fifo->size) {
fifo->q = 0;
}
fifo->free++;
return data;
}
int fifo8_status(struct FIFO8 *fifo)
/* 報告一下到底積攢了多少數據 */
{
return fifo->size - fifo->free;
}
鼠標(harib04f)
#define PORT_KEYDAT 0x0060
#define PORT_KEYSTA 0x0064
#define PORT_KEYCMD 0x0064
#define KEYSTA_SEND_NOTREADY 0x02
#define KEYCMD_WRITE_MODE 0x60
#define KBC_MODE 0x47
void wait_KBC_sendready(void)
{
/* 等待鍵盤控制電路準備完畢 */
for (;;) {
if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) {
break;
}
}
return;
//鍵盤控制電路(keyboard controller, KBC)做好準備動作,等待控制指令的到來。
//因為雖然CPU的電路很快,但鍵盤控制電路卻沒有那么快。
//如果鍵盤控制電路可以接受CPU指令了,CPU從設備號碼0x0064處所讀
//取的數據的倒數第二位(從低位開始數的第二位)應該是0。在確認到這一位是0之前,程序一直通過for語句循環查詢。
}
void init_keyboard(void)
{
/* 初始化鍵盤控制電路
一邊確認可否往鍵盤
控制電路傳送信息,一邊發送模式設定指令,指令中包含著要設定為何種模式。模
式設定的指令是0x60,利用鼠標模式的模式號碼是0x47,*/
wait_KBC_sendready();
io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE);
wait_KBC_sendready();
io_out8(PORT_KEYDAT, KBC_MODE);
return;
}
鍵盤控制器(Keyboard Controller, KBC)
i8042 鍵盤控制器-------詳細介紹 - LinKArftc - 博客園 (cnblogs.com)
在x86架構中,鍵盤控制器(Keyboard Controller, KBC)通過特定端口與CPU通信。以下是代碼中涉及的端口地址、宏定義和函數的詳細解釋:
一、端口地址定義
宏定義 | 端口地址 | 功能描述 |
---|---|---|
PORT_KEYDAT | 0x0060 | 鍵盤數據端口:用于讀取鍵盤掃描碼或發送鍵盤配置參數。 |
PORT_KEYSTA | 0x0064 | 鍵盤狀態端口:讀取鍵盤控制器的狀態(如是否準備好接收命令)。 |
PORT_KEYCMD | 0x0064 | 鍵盤命令端口:向鍵盤控制器發送控制命令(與狀態端口共享地址,操作區分方向)。 |
二、關鍵宏定義
宏定義 | 值 | 功能描述 |
---|---|---|
KEYSTA_SEND_NOTREADY | 0x02 | 狀態寄存器中的“發送未就緒”標志位:若該位為1,表示控制器忙,不能接收新命令。 |
KEYCMD_WRITE_MODE | 0x60 | 鍵盤控制器的命令:寫入操作模式到配置字節。 |
KBC_MODE | 0x47 | 鍵盤控制器的工作模式參數:啟用鍵盤和鼠標接口,并設置掃描碼轉換模式。 |
三、函數解析
1. wait_KBC_sendready()
void wait_KBC_sendready(void) {for (;;) {if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) {break;}}return;
}
- 功能:等待鍵盤控制器準備好接收命令。
- 實現:
- 循環讀取狀態端口(
0x0064
)。 - 檢查狀態寄存器的第1位(
KEYSTA_SEND_NOTREADY
):- 若為0,表示控制器就緒,退出循環。
- 若為1,繼續等待。
- 循環讀取狀態端口(
2. init_keyboard()
void init_keyboard(void) {wait_KBC_sendready();io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE); // 發送模式設置命令wait_KBC_sendready();io_out8(PORT_KEYDAT, KBC_MODE); // 寫入模式參數return;
}
- 功能:初始化鍵盤控制器的工作模式。
- 流程:
- 等待控制器就緒。
- 向命令端口(
0x0064
)發送命令0x60
(KEYCMD_WRITE_MODE
),表示要寫入配置字節。 - 再次等待控制器就緒。
- 向數據端口(
0x0060
)寫入模式參數0x47
(KBC_MODE
),配置控制器行為。
四、配置字節?0x47
?的位定義
位 | 名稱 | 功能 |
---|---|---|
7 | Reserved | 保留位,必須設為0。 |
6 | Translation Mode | 1=啟用掃描碼轉換(將XT鍵盤掃描碼集1轉換為AT鍵盤掃描碼集2)。 |
5 | Second Port Clock | 0=啟用鼠標接口(PS/2 Port 2)。 |
4 | First Port Clock | 0=啟用鍵盤接口(PS/2 Port 1)。 |
3 | Ignore Lock Keys | 0=正常處理鎖定鍵(如Caps Lock)。 |
2 | System Flag | 由BIOS設置的系統標志(通常設為0)。 |
1 | Second Port IRQ | 1=啟用鼠標中斷(IRQ12)。 |
0 | First Port IRQ | 1=啟用鍵盤中斷(IRQ1)。 |
0x47
(二進制?01000111
)的具體配置
位 | 值 | 作用 |
---|---|---|
7 | 0 | 保留位為0,符合規范。 |
6 | 1 | 啟用掃描碼轉換(將XT掃描碼轉換為AT掃描碼,增強兼容性)。 |
5 | 0 | 啟用鼠標接口(允許鼠標數據傳輸)。 |
4 | 0 | 啟用鍵盤接口(允許鍵盤數據傳輸)。 |
3 | 0 | 正常處理Caps Lock等鎖定鍵。 |
2 | 0 | 系統標志為0(通常由BIOS設置)。 |
1 | 1 | 啟用鼠標中斷(IRQ12),允許鼠標事件觸發中斷。 |
0 | 1 | 啟用鍵盤中斷(IRQ1),允許鍵盤事件觸發中斷。 |
五、硬件交互原理
- 端口I/O操作:
io_in8(port)
:從指定端口讀取1字節數據。io_out8(port, data)
:向指定端口寫入1字節數據。- 在x86中,端口地址空間獨立于內存地址空間,需通過
IN
/OUT
指令訪問。
- 鍵盤控制器流程:
- 發送命令:向
PORT_KEYCMD
(0x0064
)寫入命令字節。 - 寫入參數:向
PORT_KEYDAT
(0x0060
)寫入命令參數(如模式字節)。 - 狀態檢查:從
PORT_KEYSTA
(0x0064
)讀取狀態,確保操作安全。
- 發送命令:向
六、代碼的意義
這段代碼的目標是配置鍵盤控制器以支持鍵盤和鼠標輸入,具體作用包括:
- 啟用鍵盤接口(允許接收按鍵掃描碼)。
- 啟用鼠標接口(為后續鼠標驅動做準備)。
- 設置掃描碼轉換模式,確保兼容性。
七、實際應用場景
- 操作系統啟動初期:在初始化輸入設備時調用
init_keyboard()
,確保鍵盤可用。 - 外設驅動開發:理解端口通信是編寫鍵盤/鼠標驅動的基礎。
總結
這段代碼通過操作鍵盤控制器的端口,配置其工作模式,為后續處理鍵盤和鼠標輸入奠定基礎。理解端口地址、狀態標志和配置字節的細節,是開發操作系統輸入子系統的關鍵一步。
發送鼠標激活指令
#define KEYCMD_SENDTO_MOUSE 0xd4
#define MOUSECMD_ENABLE 0xf4
void enable_mouse(void)
{
/* 激活鼠標 */
wait_KBC_sendready();
io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);
wait_KBC_sendready();
io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);
return; /* 順利的話,鍵盤控制其會返送回ACK(0xfa)*/
}
在x86架構中,PS/2鍵盤控制器(KBC)負責管理鍵盤和鼠標的通信。以下是代碼的逐層解析:
一、關鍵宏定義
宏定義 | 值 | 功能描述 |
---|---|---|
KEYCMD_SENDTO_MOUSE | 0xD4 | 鍵盤控制器命令:指示下一個寫入數據端口的字節將發送到鼠標(而非鍵盤)。 |
MOUSECMD_ENABLE | 0xF4 | 鼠標命令:啟用鼠標數據報告模式(開始發送移動/按鍵事件)。 |
二、代碼流程分析
函數 enable_mouse()
void enable_mouse(void) {wait_KBC_sendready(); // 等待KBC就緒io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE); // 發送命令0xD4到命令端口(0x64)wait_KBC_sendready(); // 再次等待KBC就緒io_out8(PORT_KEYDAT, MOUSECMD_ENABLE); // 發送命令0xF4到數據端口(0x60)return; // 正常情況下,KBC會返回ACK(0xFA)
}
三、分步詳解
1. wait_KBC_sendready()
- 作用:確保鍵盤控制器(KBC)可接收新命令。
- 實現:循環讀取狀態端口(
0x64
),直到狀態寄存器的“發送未就緒”位(KEYSTA_SEND_NOTREADY=0x02
)為0。
2. 發送命令 0xD4
到命令端口(0x64
)
- 目的:通知KBC,下一個寫入數據端口(
0x60
)的字節是發送給鼠標的指令(而非鍵盤)。 - PS/2協議:鍵盤和鼠標共享同一控制器,通過此命令區分目標設備。
3. 發送命令 0xF4
到數據端口(0x60
)
- 作用:向鼠標發送“啟用數據報告”指令。此后,鼠標將開始發送移動和按鍵事件數據包。
- 響應:鼠標應返回
0xFA
(ACK確認),表示指令已接收。
四、硬件交互邏輯
-
KBC與設備的通信鏈
CPU → KBC命令端口(0x64) → 發送命令0xD4 → KBC → 轉發到鼠標 CPU → KBC數據端口(0x60) → 發送數據0xF4 → KBC → 轉發到鼠標
-
ACK確認機制
- 鼠標收到有效指令后,會通過KBC返回
0xFA
(需通過中斷或輪詢讀取數據端口獲取)。 - 此代碼未處理ACK,可能依賴后續中斷服務程序(ISR)處理。
- 鼠標收到有效指令后,會通過KBC返回
五、技術細節
1. 鍵盤控制器狀態寄存器(PORT_KEYSTA=0x64)
位 | 名稱 | 說明 |
---|---|---|
0 | Output Buffer Full | 1=數據端口(0x60)有數據待讀取。 |
1 | Input Buffer Full | 1=控制器忙,不能接收命令(代碼檢查此位)。 |
2-7 | 保留 |
2. 鼠標命令 0xF4
的作用
-
啟用數據報告模式:鼠標持續發送數據包,格式為3字節:
Byte 1: Y溢出 | X溢出 | Y符號位 | X符號位 | 保留 | 中鍵 | 右鍵 | 左鍵 Byte 2: X軸移動量(補碼) Byte 3: Y軸移動量(補碼)
六、實際應用場景
- 操作系統初始化階段:在鍵盤初始化后調用此函數,激活鼠標輸入。
- 驅動程序開發:理解PS/2協議是處理傳統輸入設備的基礎。
七、注意事項
- ACK處理:需在中斷處理中捕獲
0xFA
,否則可能錯過鼠標數據。 - 兼容性:現代硬件可能使用USB替代PS/2,需額外判斷設備存在性。
- 錯誤處理:未收到ACK時需重試或報錯。
總結
此代碼通過鍵盤控制器向鼠標發送啟用指令,使其進入數據報告模式。核心步驟為:
- 等待控制器就緒 → 2. 聲明目標為鼠標 → 3. 發送啟用命令。
理解PS/2協議和端口I/O操作是開發輸入設備驅動的關鍵。
PS/2協議解釋
PS/2協議是早期計算機用于連接鍵盤和鼠標的通信標準,盡管逐漸被USB取代,但在許多傳統系統和嵌入式設備中仍有應用。以下是其核心機制和關鍵細節的詳解:
一、物理接口與電氣特性
- 接口定義
-
PS/2接口為6針Mini-DIN,實際使用4針:
引腳 功能 1 數據線(DATA) 3 地線(GND) 4 VCC(+5V) 5 時鐘線(CLK)
-
- 通信方式
- 同步串行傳輸:設備(鍵盤/鼠標)與主機(鍵盤控制器)通過CLK同步,DATA線傳輸數據。
- 主從模式:默認設備控制CLK,主機可接管時鐘(如發送命令時)。
二、數據幀格式
每幀數據包含?11位,按順序如下:
位 | 名稱 | 值 | 說明 |
---|---|---|---|
1 | 起始位 | 0 | 標志數據開始 |
8 | 數據位 | 0/1 | LSB(最低位)優先發送 |
1 | 奇偶校驗位 | 0/1 | 奇校驗(數據位+校驗位的1個數為奇) |
1 | 停止位 | 1 | 標志數據結束 |
示例:鍵盤發送按鍵按下碼?0x1C
(字母’A’的掃描碼)的幀結構:
0
?0011100
?1
?1
?→ 起始位 + 數據(二進制0011100,LSB優先為0011100
) + 奇偶位 + 停止位。
三、通信方向與流程
1. 設備到主機(如按鍵事件)
- 設備檢測到動作(如按鍵按下),生成中斷(IRQ1鍵盤/IRQ12鼠標)。
- 設備拉低CLK并發送數據幀。
- 主機讀取DATA線,在CLK下降沿采樣數據位。
2. 主機到設備(如發送命令)
- 主機拉低CLK至少100μs,通知設備準備接收命令。
- 主機控制CLK,逐位發送命令幀。
- 設備在接收到命令后,返回ACK(
0xFA
)或錯誤碼。
四、關鍵命令與響應
1. 鍵盤常用命令
命令 | 值 | 功能 | 響應 |
---|---|---|---|
重置 | 0xFF | 復位鍵盤 | 0xFA ?+?0xAA |
啟用掃描 | 0xF4 | 開始發送按鍵事件 | 0xFA |
設置LED | 0xED | 控制Num/Caps/Scroll燈 | 0xFA |
2. 鼠標常用命令
命令 | 值 | 功能 | 響應 |
---|---|---|---|
重置 | 0xFF | 復位鼠標 | 0xFA ?+?0xAA |
啟用報告 | 0xF4 | 開始發送移動/按鍵數據 | 0xFA |
設置分辨率 | 0xE8 | 調整移動靈敏度 | 0xFA |
五、數據包格式
1. 鍵盤數據包
- 單字節:傳輸按鍵的掃描碼(按下或釋放)。
- 例如:
0x1C
(A鍵按下),0x9C
(A鍵釋放,前綴0xF0
表示釋放)。
- 例如:
2. 鼠標數據包
-
標準3字節格式(兼容模式):
字節 位7-0 Byte1 Y溢出 Byte2 X軸移動量(8位補碼,-128~127) Byte3 Y軸移動量(8位補碼,-128~127)
六、錯誤處理與重試
- 奇偶校驗錯誤:主機檢測到校驗錯誤時,可發送重傳命令(
0xFE
)。 - 超時:若設備未響應,主機可復位設備或記錄錯誤。
- ACK缺失:未收到
0xFA
時,主機應重發命令或初始化設備。
七、PS/2與USB的對比
特性 | PS/2 | USB |
---|---|---|
熱插拔 | 不支持(可能損壞設備) | 支持 |
中斷機制 | 專用IRQ(低延遲) | 輪詢或中斷傳輸 |
協議復雜度 | 簡單(固定數據包) | 復雜(多種傳輸類型、描述符) |
應用場景 | 傳統設備、嵌入式系統 | 現代外設、即插即用 |
八、操作系統中的實現
- 初始化流程:
- 啟用鍵盤/鼠標接口 → 發送重置命令 → 配置掃描碼模式。
- 中斷處理程序:
- 讀取數據端口(
0x60
) → 解析掃描碼或鼠標包 → 傳遞事件到上層。
- 讀取數據端口(
- 緩沖區管理:
- 維護循環隊列存儲輸入事件,避免數據丟失。
總結
PS/2協議通過同步串行通信實現低延遲輸入,其核心在于時鐘同步、數據幀格式和命令響應機制。
從鼠標接受數據(harib04g)
struct FIFO8 mousefifo;
void inthandler2c(int *esp)
/* 來自PS/2鼠標的中斷 */
{
unsigned char data;
io_out8(PIC1_OCW2, 0x64); /* 通知PIC1 IRQ-12的受理已經完成 */
io_out8(PIC0_OCW2, 0x62); /* 通知PIC0 IRQ-02的受理已經完成 */
data = io_in8(PORT_KEYDAT);
fifo8_put(&mousefifo, data);
return;
}
在x86系統中,處理來自PS/2鼠標的中斷(IRQ12)時,需要與可編程中斷控制器(PIC)交互以確認中斷處理完成。以下是代碼的詳細解釋:
一、代碼功能概述
這段代碼是處理PS/2鼠標中斷的中斷服務程序(ISR),主要完成以下操作:
- 通知PIC中斷處理完成:通過向主PIC和從PIC發送EOI(End of Interrupt)命令。
- 讀取鼠標數據:從鍵盤控制器數據端口獲取鼠標事件數據。
- 存儲數據到緩沖區:將數據存入FIFO隊列供后續處理。
二、關鍵代碼解析
1. 中斷處理流程
void inthandler2c(int *esp) {unsigned char data;// 通知PIC中斷處理完成io_out8(PIC1_OCW2, 0x64); // 從PIC(IRQ12)的EOIio_out8(PIC0_OCW2, 0x62); // 主PIC(IRQ2)的EOI// 讀取鼠標數據data = io_in8(PORT_KEYDAT);// 存入FIFO隊列fifo8_put(&mousefifo, data);return;
}
2. PIC的端口與命令
- PIC1_OCW2:從PIC(PIC1)的操作命令字端口,地址為
0xA0
。 - PIC0_OCW2:主PIC(PIC0)的操作命令字端口,地址為
0x20
。 - EOI命令值:
0x64
和0x62
是特殊EOI命令,通知PIC中斷已處理。
三、PIC的EOI通知機制
1. 主從PIC的級聯
- 主PIC(PIC0):處理IRQ0-IRQ7。
- 從PIC(PIC1):處理IRQ8-IRQ15,其輸出連接到主PIC的IRQ2。
- 中斷傳遞:當從PIC的IRQ12(鼠標中斷)觸發時,主PIC的IRQ2也會被激活。
2. 發送EOI命令的必要性
- 目的:告訴PIC中斷處理已完成,允許其繼續響應新中斷。
- 級聯場景:處理從PIC的中斷(如IRQ12)時,需向主從PIC均發送EOI。
3. 特殊EOI命令詳解
-
OCW2格式:
位 7 ? 6 (SL) 5 (EOI) 4-0 (L3-L0) 值 0 1 1 中斷級別 -
從PIC(IRQ12)的EOI命令
0x64
:- 二進制:
01100100
- SL=1, EOI=1:表示發送特殊EOI命令。
- L3-L0=0100:指定從PIC的中斷級別4(對應IRQ12 = IRQ8 + 4)。
- 二進制:
-
主PIC(IRQ2)的EOI命令
0x62
:- 二進制:
01100010
- SL=1, EOI=1:特殊EOI命令。
- L3-L0=0010:指定主PIC的中斷級別2(IRQ2)。
- 二進制:
四、代碼執行步驟
-
發送EOI到從PIC:
io_out8(0xA0, 0x64); // 通知從PIC,IRQ4(即IRQ12)已處理
- 從PIC收到命令后,清除IRQ4的中斷狀態。
-
發送EOI到主PIC:
io_out8(0x20, 0x62); // 通知主PIC,IRQ2(級聯中斷)已處理
- 主PIC收到命令后,清除IRQ2的中斷狀態。
-
讀取鼠標數據:
data = io_in8(0x60); // 從鍵盤控制器數據端口讀取鼠標數據包
- 數據端口
0x60
用于傳輸鍵盤和鼠標的輸入數據。
- 數據端口
-
存儲數據到緩沖區:
fifo8_put(&mousefifo, data); // 將數據存入隊列供后續解析
- 使用FIFO隊列避免中斷處理過程中數據丟失。
五、關聯硬件行為
- 中斷觸發:鼠標移動或點擊時,鍵盤控制器通過IRQ12通知CPU。
- PIC級聯:從PIC通過主PIC的IRQ2向CPU傳遞中斷。
- 數據包格式:PS/2鼠標數據為3字節,需在后續代碼中解析。
六、常見問題
1. 為何需要同時通知主從PIC?
- 從PIC的中斷通過主PIC的IRQ2級聯,需兩者均確認中斷完成,否則后續中斷會被阻塞。
2. 0x64
和0x62
如何計算?
- 特殊EOI命令公式:
OCW2 = 0x60 | (IRQ_LEVEL & 0x07)
- 從PIC IRQ4:
0x60 | 4 = 0x64
- 主PIC IRQ2:
0x60 | 2 = 0x62
- 從PIC IRQ4:
3. 是否必須使用特殊EOI?
- 常規EOI(
0x20
)僅適用于非特殊模式,級聯中斷需特殊EOI明確指定中斷級別。
總結
這段代碼通過向主從PIC發送特殊EOI命令,確保中斷處理正確完成,并讀取鼠標數據存入緩沖區。理解PIC的級聯機制和OCW2命令格式,是編寫可靠中斷處理程序的關鍵。