【实现】初始化中断控制器
80386把中断号0~31分配给陷阱、故障和非屏蔽中断,而把32~47之间的中断号分配给可屏蔽中断。可屏蔽中断的中断号是通过对中断控制器的编程来设置的。下面描述了对8259A中断控制器初始化过程。
8259A通过两个I/O地址来进行中断相关的数据传送,对于单个的8259A或者是两级级联中的主8259A而言,这两个I/O地址是0x20和0x21。对于两级级联的从8259A而言,这两个I/O地址是0xA0和0xA1。8259A有两种编程方式,一是初始化方式,二是工作方式。在操作系统启动时,需要对8959A做一些初始化工作,即实现8259A的初始化方式编程。8259A中的四个中断命令字(ICW)寄存器用来完成初始化编程,其含义如下:
- ICW1:初始化命令字。
- ICW2:中断向量寄存器,初始化时写入高五位作为中断向量的高五位,然后在中断响应时由8259根据中断源(哪个管脚)自动填入形成完整的8位中断向量(或叫中断类型号)。
- ICW3: 8259的级联命令字,用来区分主片和从片。
- ICW4:指定中断嵌套方式、数据缓冲选择、中断结束方式和CPU类型。
8259A初始化的过程就是写入相关的命令字,8259A内部储存这些命令字,以控制8259A工作。有关的硬件可看附录补充资料。这里只把ucore对8259A的初始化过程(在picirq.c中的pic_init函数实现)描述一下:
//此时系统尚未初始化完毕,故屏蔽主从8259A的所有中断
outb(IO_PIC1 + 1, 0xFF);
outb(IO_PIC2 + 1, 0xFF);
// 设置主8259A的ICW1,给ICW1写入0x11,0x11表示(1)外部中断请求信号为上升沿触发有效,(2)系统中有多片8295A级联,(3)还表示要向ICW4送数据
// ICW1设置格式为: 0001g0hi
// g: 0 = edge triggering, 1 = level triggering
// h: 0 = cascaded PICs, 1 = master only
// i: 0 = no ICW4, 1 = ICW4 required
outb(IO_PIC1, 0x11);
// 设置主8259A的ICW2: 给ICW2写入0x20,设置中断向量偏移值为0x20,即把主8259A的IRQ0-7映射到向量0x20-0x27
outb(IO_PIC1 + 1, IRQ_OFFSET);
// 设置主8259A的ICW3: ICW3是8259A的级联命令字,给ICW3写入0x4,0x4表示此主中断控制器的第2个IR线(从0开始计数)连接从中断控制器。
outb(IO_PIC1 + 1, 1 << IRQ_SLAVE);
//设置主8259A的ICW4:给ICW4写入0x3,0x3表示采用自动EOI方式,即在中断响应时,在8259A送出中断矢量后,自动将ISR相应位复位;并且采用一般嵌套方式,即当某个中断正在服务时,本级中断及更低级的中断都被屏蔽,只有更高的中断才能响应。
// ICW4设置格式为: 000nbmap
// n: 1 = special fully nested mode
// b: 1 = buffered mode
// m: 0 = slave PIC, 1 = master PIC
// (ignored when b is 0, as the master/slave role
// can be hardwired).
// a: 1 = Automatic EOI mode
// p: 0 = MCS-80/85 mode, 1 = intel x86 mode
outb(IO_PIC1 + 1, 0x3);
//设置从8259A的ICW1:含义同上
outb(IO_PIC2, 0x11); // ICW1
//设置从8259A的ICW2:给ICW2写入0x28,设置从8259A的中断向量偏移值为0x28
outb(IO_PIC2 + 1, IRQ_OFFSET + 8); // ICW2
//0x2表示此从中断控制器链接主中断控制器的第2个IR线
outb(IO_PIC2 + 1, IRQ_SLAVE); // ICW3
//设置主8259A的ICW4:含义同上
outb(IO_PIC2 + 1, 0x3); // ICW4
//设置主从8259A的OCW3:即设置特定屏蔽位(值和英文解释不一致),允许中断嵌套;不查询;将读入其中断请求寄存器IRR的内容
// OCW3设置格式为: 0ef01prs
// ef: 0x = NOP, 10 = clear specific mask, 11 = set specific mask
// p: 0 = no polling, 1 = polling mode
// rs: 0x = NOP, 10 = read IRR, 11 = read ISR
outb(IO_PIC1, 0x68); // clear specific mask
outb(IO_PIC1, 0x0a); // read IRR by default
outb(IO_PIC2, 0x68); // OCW3
outb(IO_PIC2, 0x0a); // OCW3
//初始化完毕,使能主从8259A的所有中断
if (irq_mask != 0xFFFF) {
pic_setmask(irq_mask);
}