我的博客: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");
}