setup.s 解读——Linux-0.11 剖析笔记(三)
CPU 接受中断通过 8259A 中断控制器来实现。
pic_init(); // init interrupt controller
#define IO_PIC1 0x20 // Master (IRQs 0-7 )
#define IO_PIC2 0xA0 // Slave (IRQs 8-15)
// mask all interrupts
outb(IO_PIC1 + 1, 0xFF);
outb(IO_PIC2 + 1, 0xFF);
接着开始对 8259A 设置初始化命令字。
初始化命令字 ICW1-ICW4 需要按规定的顺序进行设置。
A0 线用于选择操作的寄存器。
在 PC/AT 微机系统中。
- 当 A0 = 0 时芯片的端口地址是 0x20(主芯片)和 0xA0(从芯片);
- 当 A0 = 1 时芯片的端口地址是 0x21(主芯片)和 0xA1(从芯片)。
// Set up master (8259A-1)
// 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);
当发送的字节第 5 比特位 (D4) = 1,并且地址线 A0 = 0 时,表示是对 ICW1 编程。
ICW1 被设置为 0x11:0001 0001。表示中断请求是边沿触发、多片 8259A 级联并且需要发送 ICW4。
// ICW2: Vector offset
outb(IO_PIC1 + 1, IRQ_OFFSET);
ICW2 用于设置芯片送出的中断号的高 5 位。
在设置了 ICW1 之后,当 A0 = 1 时表示对 ICW2 进行设置。
在使用 8086/88 处理器的系统或兼容系统中 T7~T3 是中断号的高 5 位,与 8259A 芯片自动设置的低 3 位(8259A 按 IR0~IR7 三位编码值自动填入)组成一个 8 位的中断号。
8259A 在收到第 2 个中断响应脉冲 INTA 时会把此中断号送到数据总线上,以供 CPU 读取。
主片的 ICW2 设置为 0x20,表示主片中断请求 07 级对应的中断号是 0x200x27。
// ICW3: (master PIC) bit mask of IR lines connected to slaves
// (slave PIC) 3-bit # of slave's connection to master
outb(IO_PIC1 + 1, 1 << IRQ_SLAVE);
主芯片的端口地址是 0x21。
对于主片,Si = 1,表示 IRi 接从片的 INT 引脚。
即,主片 S7~S0 各比特位对应级联的从片。
哪位为 1 则表示主片的该中断请求引脚 IR 上信号来自从片, 否则对应的 IR 引脚没有连从片。
把 8259A 主片的 ICW3 设置为 0x04,即 S2 = 1,其余各位为 0。
表示主芯片的 IR2 引脚连接一个从芯片。
中断优先级的排列次序为:0 级最高,1 级次之,接下来是从片上的 815 级,最后是主片的 37 级。
// 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);
当 ICW1 的位 0 (IC4) 置位时,表示需要 ICW4。
地址线 A0 = 1。
8259A 主芯片的 ICW4 命令字的值均为 0x11。
表示 8259A 芯片被设置成特殊全嵌套、非缓冲、非自动结束中断方式,并且用于 8086 及其兼容系统(intel x86 模式)。
// Set up slave (8259A-2)
outb(IO_PIC2, 0x11); // ICW1
ICW1 被设置为 0x11:0001 0001。表示中断请求是边沿触发、多片 8259A 级联并且需要发送 ICW4。
outb(IO_PIC2 + 1, IRQ_OFFSET + 8); // ICW2
从片的 ICW2 设置成 0x28,表示从片中断请求 815 级对应的中断号是 0x280x2f。
outb(IO_PIC2 + 1, IRQ_SLAVE); // ICW3
从芯片的端口地址是 0xA1。
对于从片,ID2~ID0 3 个比特位对应各从片的标识号,即连接到主片的中断级。
当某个从片接收到级联线(CAS2—CAS0)输入的值与自己的 ID2~ID0 相等时,表示此从片被选中。
此时该从片应该向数据总线发送自己当前被选中的中断请求的中断号。
从芯片的 ICW3 被设置为 0x02,即其标识号为 2。
表示此从片连接到主片的 IR2 引脚。
// NB Automatic EOI mode doesn't tend to work on the slave.
// Linux source code says it's "to be investigated".
outb(IO_PIC2 + 1, 0x3); // ICW4
表示 8259A 芯片被设置成普通全嵌套、非缓冲、自动结束中断方式,并且用于 8086 及其兼容系统(intel x86 模式)。
这里解释说非自动结束中断方式在从片上不工作,有待研究。
在对 8259A 设置了初始化命令字后,芯片就已准备好接收设备的中断请求信号了。
在 8259A 工作期间,我们也可以利用操作命令字 OCW1~OCW3 来监测 8259A 的工作状况,或者随时改变初始化时设定的 8259A 的工作方式。
操作命令字 OCW1-OCW3 的设置没有规定其先后顺序,使用时可根据需要灵活选择不同的操作命令字写入到 8259A 中。
- OCW1 用于对 8259A 中中断屏蔽寄存器 IMR 进行读、写操作;
- 地址线 A0 需为 1;
- OCW2 用于定义优先权工作方式;
- OCW3 用于设置或清除特殊屏蔽方式和读取寄存器状态(IRR 和 ISR);
- 当 D4、D3 = 01,且地址线 A0 = 0 时,表示对 OCW3 进行编程。
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
0x68 即 0110 1000,表示由 SMM 位决定是否工作在特殊屏蔽方式、设置特殊屏蔽、不查询。
0x0a 即 0000 1010,表示 ESMM、SMM 的值不起用,不查询,读取 IRR 中的值。
static uint16_t irq_mask = 0xFFFF & ~(1 << IRQ_SLAVE);
if (irq_mask != 0xFFFF) {
pic_setmask(irq_mask);
}
这里的 irq_mask 的值为:1111 1111 1111 1011,即屏蔽中断号 2,即用户主从片连接的中断引脚。
static void
pic_setmask(uint16_t mask) {
irq_mask = mask;
if (did_init) {
outb(IO_PIC1 + 1, mask);
outb(IO_PIC2 + 1, mask >> 8);
}
}
使用 OCW1 对主从片的中断屏蔽寄存器进行读写。
中断向量和中断服务例程的对应关系主要是由 IDT(中断描述符表)。