我的博客:startcraft
中斷控制芯片的初始化
參考:https://blog.csdn.net/longintchar/article/details/79439466
中斷控制芯片8259A由主從兩個芯片級聯而成
從片的輸出接在主片的IRQ2上,所以一共可以接受15個設備的中斷信號,中斷控制芯片的作用就是管理外接設備的中斷
首先要對芯片進行初始化,初始化該芯片就是初始化一組寄存器ICW寄存器
當地址線的A0=0時主片的端口的0x20,從片的端口是0xA0
ICW1
首先設置ICW1,ICW1寄存器一共有8位,作用分別爲
這裏我們給它設置爲0x11即0001 0001,就是D4和D0爲1
在idt.c中的init_idt()
函數最前面加上
ICW芯片組的初始化是有順序的,必須從ICW1到ICW4
//初始化主從芯片的ICW1
//0001 0001
outb(0x20,0x11);
outb(0xA0,0x11);
outb就是之前在common.c裏實現的cpu對cpu外設備的寫函數
ICW2
然後是初始化ICW2,它的高五位表示送出的中斷號的高五位,低三位由芯片根據當前的IR0-IR7自動填入對應的二進制串
通過前面的設計知道0-31號中斷是cpu保留的,所以這裏主片的IR0就從第32號中斷開始,從片的IR0就是第40號中斷。
如何設置ICW2呢,在ICW1設置之後,地址線A0會變成1,這時代表設置的就是ICW2,當A0爲1時,主片的端口是0x21,從片的端口是0xA1
所以我們在上面的代碼下面加上
//初始化主片的ICW2,讓主片的中斷號從32-39
//0010 0xxx
outb(0x21,0x20);
//初始化從片的ICW2,讓從片的中斷號從40-47
//0010 1xxx
outb(0xA1,0x28);
ICW3
此時地址線A0還是1
主片的ICW3的8位代表的是連接從片的情況,哪一位爲1就代表哪一位的IRx連接了從片,這裏是IR2連接從片所以值爲0x04(0000 0100)
從片的ICW3高五位爲0,低三位表示的是該從片連接的是主片的哪一個IR,所以這裏設置成0x02代表該從片連接的是主片的IR2
加上如下代碼
//設置從片連接主片的IR2
outb(0x21,0x04);
outb(0xA1,0x02);
ICW4
這裏設置成0x01。表示 8259A 芯片被設置成普通全嵌套、非緩衝、非自動結束中斷方式,並且用於 8086 及其兼容系統。
//設置芯片以8086方式工作
outb(0x21,0x01);
outb(0xA1,0x01);
設置完ICW之後芯片就可以工作了,在工作過程中可以設置OCW芯片組來控制工作狀態,OCW的使用不需要按順序了
OCW1
現在設置一下OCW1,OCW1的八位,哪一位爲1代表屏蔽哪一級中斷,現在我們都不屏蔽
//開放所有中斷
outb(0x21,0x00);
outb(0xA1,0x00);
現在初始化就完成了。
IRQ的處理
IRQ的處理和ISR函數的處理類似,首先定義IRQ函數
在idt.h最後加上
// IRQ 處理函數
void irq_handler(pt_regs *regs);
// 定義IRQ
#define IRQ0 32 // 電腦系統計時器
#define IRQ1 33 // 鍵盤
#define IRQ2 34 // 與 IRQ9 相接,MPU-401 MD 使用
#define IRQ3 35 // 串口設備
#define IRQ4 36 // 串口設備
#define IRQ5 37 // 建議聲卡使用
#define IRQ6 38 // 軟驅傳輸控制使用
#define IRQ7 39 // 打印機傳輸控制使用
#define IRQ8 40 // 即時時鐘
#define IRQ9 41 // 與 IRQ2 相接,可設定給其他硬件
#define IRQ10 42 // 建議網卡使用
#define IRQ11 43 // 建議 AGP 顯卡使用
#define IRQ12 44 // 接 PS/2 鼠標,也可設定給其他硬件
#define IRQ13 45 // 協處理器使用
#define IRQ14 46 // IDE0 傳輸控制使用
#define IRQ15 47 // IDE1 傳輸控制使用
// 聲明 IRQ 函數
// IRQ:中斷請求(Interrupt Request)
void irq0(); // 電腦系統計時器
void irq1(); // 鍵盤
void irq2(); // 與 IRQ9 相接,MPU-401 MD 使用
void irq3(); // 串口設備
void irq4(); // 串口設備
void irq5(); // 建議聲卡使用
void irq6(); // 軟驅傳輸控制使用
void irq7(); // 打印機傳輸控制使用
void irq8(); // 即時時鐘
void irq9(); // 與 IRQ2 相接,可設定給其他硬件
void irq10(); // 建議網卡使用
void irq11(); // 建議 AGP 顯卡使用
void irq12(); // 接 PS/2 鼠標,也可設定給其他硬件
void irq13(); // 協處理器使用
void irq14(); // IDE0 傳輸控制使用
void irq15(); // IDE1 傳輸控制使用
然後去idt_s.s中添加相應的處理過程,這部分和isr很相似,唯一不同的是IRQ的宏有兩個參數,因爲IRQx的中斷號不是x
; 構造中斷請求的宏
%macro IRQ 2
[GLOBAL irq%1]
irq%1:
cli
push 0
push %2
jmp irq_common_stub
%endmacro
IRQ 0, 32 ; 電腦系統計時器
IRQ 1, 33 ; 鍵盤
IRQ 2, 34 ; 與 IRQ9 相接,MPU-401 MD 使用
IRQ 3, 35 ; 串口設備
IRQ 4, 36 ; 串口設備
IRQ 5, 37 ; 建議聲卡使用
IRQ 6, 38 ; 軟驅傳輸控制使用
IRQ 7, 39 ; 打印機傳輸控制使用
IRQ 8, 40 ; 即時時鐘
IRQ 9, 41 ; 與 IRQ2 相接,可設定給其他硬件
IRQ 10, 42 ; 建議網卡使用
IRQ 11, 43 ; 建議 AGP 顯卡使用
IRQ 12, 44 ; 接 PS/2 鼠標,也可設定給其他硬件
IRQ 13, 45 ; 協處理器使用
IRQ 14, 46 ; IDE0 傳輸控制使用
IRQ 15, 47 ; IDE1 傳輸控制使用
[GLOBAL irq_common_stub]
[EXTERN irq_handler]
irq_common_stub:
pusha ; pushes edi, esi, ebp, esp, ebx, edx, ecx, eax
mov ax, ds
push eax ; 保存數據段描述符
mov ax, 0x10 ; 加載內核數據段描述符
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
push esp
call irq_handler
add esp, 4
pop ebx ; 恢復原來的數據段描述符
mov ds, bx
mov es, bx
mov fs, bx
mov gs, bx
mov ss, bx
popa ; Pops edi,esi,ebp...
add esp, 8 ; 清理壓棧的 錯誤代碼 和 ISR 編號
iret ; 出棧 CS, EIP, EFLAGS, SS, ESP
.end:
然後就是去idt.c中構造這些IRQ的中斷描述符和實現IRQ處理函數了
init_idt()
{
......
idt_set_gate(32, (uint32_t)irq0, 0x08, 0x8E);
idt_set_gate(33, (uint32_t)irq1, 0x08, 0x8E);
idt_set_gate(34, (uint32_t)irq2, 0x08, 0x8E);
idt_set_gate(35, (uint32_t)irq3, 0x08, 0x8E);
idt_set_gate(36, (uint32_t)irq4, 0x08, 0x8E);
idt_set_gate(37, (uint32_t)irq5, 0x08, 0x8E);
idt_set_gate(38, (uint32_t)irq6, 0x08, 0x8E);
idt_set_gate(39, (uint32_t)irq7, 0x08, 0x8E);
idt_set_gate(40, (uint32_t)irq8, 0x08, 0x8E);
idt_set_gate(41, (uint32_t)irq9, 0x08, 0x8E);
idt_set_gate(42, (uint32_t)irq10, 0x08, 0x8E);
idt_set_gate(43, (uint32_t)irq11, 0x08, 0x8E);
idt_set_gate(44, (uint32_t)irq12, 0x08, 0x8E);
idt_set_gate(45, (uint32_t)irq13, 0x08, 0x8E);
idt_set_gate(46, (uint32_t)irq14, 0x08, 0x8E);
idt_set_gate(47, (uint32_t)irq15, 0x08, 0x8E);
......
}
// IRQ處理函數
void irq_handler (pt_regs *regs)
{
//32-39是主片處理的中斷
if(regs->int_no<40)
//給主片發送重置信號
outb(0x20,0x20);
else
//給從片發送重置信號
outb(0xA0,0x20);
if(interrupt_handlers[regs->int_no])
interrupt_handlers[regs->int_no](regs);
}
這裏的重置信號其實是OCW2控制的
設置時鐘中斷
時鐘中斷是操作系統運行的脈搏,通過定時產生一箇中斷來讓操作系統進行對進程進行調度之類的操作
時鐘中斷由Intel 8253 (8254)芯片產生,所以要先對它進行初始化
driver/timer.c
#include "timer.h"
#include "debug.h"
#include "common.h"
#include "idt.h"
void timer_callback(pt_regs *regs)
{
static uint32_t tick = 0;
printk_color(rc_black, rc_blue, "Tick: %d\n", tick++);
}
void init_timer(uint32_t frequency)
{
// 註冊時間相關的處理函數
register_interrupt_handler(IRQ0, timer_callback);
// Intel 8253/8254 PIT芯片 I/O端口地址範圍是40h~43h
// 輸入頻率爲 1193180/frequency 即每秒中斷次數
uint32_t divisor = 1193180 / frequency;
// D7 D6 D5 D4 D3 D2 D1 D0
// 0 0 1 1 0 1 1 0
// 即就是 36 H
// 設置 8253/8254 芯片工作在模式 3 下
outb(0x43, 0x36);
// 拆分低字節和高字節
uint8_t low = (uint8_t)(divisor & 0xFF);
uint8_t hign = (uint8_t)((divisor >> 8) & 0xFF);
// 分別寫入低字節和高字節
outb(0x40, low);
outb(0x40, hign);
}
timer_callback
函數就產生中斷是調用的中斷處理函數,這裏只是測試就寫了打印
8253/8254芯片的工作模式3下是方波發生器方式,該方式下產生的方波的頻率爲輸入時鐘頻率的N分之一,該芯片輸入時鐘是1193180HZ,N就是我們設置給它的參數,所以我們想它每10ms(100HZ)產生一次IRQ0的信號也就是每10ms產生一個方波,那麼N取值就是1193180/100
對應的頭文件include/timer.h
#ifndef INCLUDE_TIMER_H_
#define INCLUDE_TIMER_H_
#include "types.h"
void init_timer(uint32_t frequency);
#endif // INCLUDE_TIMER_H_
修改init/entry.c測試一下
#include "console.h"
#include "timer.h"
#include "debug.h"
#include "gdt.h"
#include "idt.h"
int kern_entry()
{
init_debug();
init_gdt();
init_idt();
console_clear();
printk_color(rc_black, rc_green, "Hello, OS kernel!\n");
init_timer(100);
//開啓中斷
asm volatile("sti");
}