引言
- 这一章节我们要来实现 8259A 驱动代码的实现
- 驱动,听起来很难得样子,其实复杂在硬件的了解,其代码本身是非常简单的。所以,有了上一章节对 8259A 的介绍,本章内容就变得非常简单了
x86 中的 8259A 级联
- 前面我们比较的对 8259A 级联方式进行通用性地介绍,现在我们有针对性的看看其在我们具体的 x86 系统中的级联
- 不要问 IRQ 为什么这么排列,当时就这么约定的吧,你非要不按照这么排列也行,就是你做出的系统只能运行在你自己的硬件平台上,无法运行在通用的硬件平台上
8259A 驱动编写
- 在 8259A 内部有两组寄存器,一组是初始化命令寄存器组,用来保存初始化命令字(Initialization Command Words,ICW),ICW 共 4 个,ICW1~ICW4。另一组寄存器是操作命令寄存器组,用来保存操作命令字(Operation Command Word,OCW),OCW 共 3 个,OCW1~OCW3。所以,我们对 8259A 的编程,也分为初始化和操作两部分
8259A 初始化
- 先来介绍一下 ICW1~ICW4 寄存器
- ICW1:用来初始化 8259A 的连接方式和中断信号的触发方式。其格式如下:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
0 | 0 | 0 | 1 | LTIM | ADI | SNGL | IC4 |
- ICW1 需要写入到主片的 0x20 端口和从片的 0xA0 端口
- IC4 表示是否要写入 ICW4,并不是所有的 ICW 初始化控制字都需要用到。IC4 为 1 时表示需要在后面写入 ICW4,为 0 则不需要。注意,x86 系统 IC4 必须为 1
- SNGL 表示 single,若 SNGL 为 1,表示单片,若 SNGL 为 0,表示级联
- ADI 表示 call address interval,用来设置 8085 的调用时间间隔,x86 不需要设置
- LTIM 表示 level/edge triggered mode,用来设置中断检测方式,LTIM 为 0 表示边沿触发,LTIM 为 1 表示电平触发
- 第 4 位的 1 是固定的
- 第 5~7 位专用于 8085 处理器,x86 不需要,直接置为 0 即可
- ICW2:用来设置起始中断向量号,只设置 IRQ0 的中断向量号即可,IRQ1,IRQ2...中断向量号依次 +1。其格式如下:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
T7 | T6 | T5 | T4 | T3 | ID2 | ID1 | ID0 |
- ICW2 需要写入到主片的 0x21 端口和从片的 0xA1 端口
- 只需要填写高 5 位 T3~T7,ID0~ID2 这低 3 位不用咱们负责。由于咱们只填写高 5 位,所以任意数字都是 8 的倍数,这个数字表示的便是设定的起始中断向量号
- ICW3:ICW3 仅在级联的方式下才需要(如果 ICW1 中的 SNGL 为 0),用来设置主片和从片用哪个 IRQ 接口互连。由于主片和从片的级联方式不一样,对于这个 ICW3,主片和从片都有自己不同的结构
- 主片 ICW3 格式
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
S7 | S6 | S5 | S4 | S3 | S2 | S1 | S0 |
- 对于主片,ICW3 中置 1 的那一位对应的 IRQ 接口用于连接从片,若为 0 则表示接外部设备。比如,若主片 IRQ2 接有从片,则主片的 ICW3 为 00000100
- 从片 ICW3 格式
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
0 | 0 | 0 | 0 | 0 | ID2 | ID1 | ID0 |
- 不光是作为主片需要设置 ICW3,作为从片也需要设置 ICW3,而从片 ICW3 只用到低 3 位,低 3 位能表示的数值范围是 0~7,正好可以对应连接到主片IRQ0~IRQ7。比如,若从片级联到主片的 IRQ2,则从片的 ICW3 为 00000010
- ICW3 需要写入主片的 0x21 端口及从片的 0xA1 端口
- ICW4:设置 8259A 的中断嵌套方式。其格式如下:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
0 | 0 | 0 | SFNM | BUF | M/S | AEOI | μPM |
- ICW4 需要写入主片的 0x21 及从片的 0xA1 端口
- ICW4 有些低位的选项基于高位,所以咱们从高位开始介绍
- 第 7~5 位未定义,直接置为 0 即可
- SFNM 表示特殊全嵌套模式(Special Fully Nested Mode),若 SFNM 为 0,则表示全嵌套模式,若 SFNM 为 1,则表示特殊全嵌套模式。全嵌套模式,中断服务程序执行时不响应其它同优先级级或低优先级级中断,必须等中断服务程序执行完毕方可响应其它同优先级中断,而特殊全嵌套模式在中断服务程序正在执行时也响应其它同优先级中断,低优先级中断依旧是不响应的
- BUF 表示本 8259A 芯片是否工作在缓冲模式。BUF 为 0,则工作非缓冲模式,BUF 为 1,则工作在缓冲模式
- 如果工作在缓冲模式下(BUF=1),M/S 用来规定本 8259A 是主片,还是从片。若 M/S=1:主片,若 M/S=0:从片;若工作在非缓冲模式(BUF=0)下,M/S 无效
- AEOI 表示自动结束中断(Auto End Of Interrupt)。AEOI=0:非自动结束中断,AEOI=1:自动结束中断
- μPM 表示微处理器类型(microprocessor),此项是为了兼容老处理器。若 μPM 为 0,则表示 8080 或 8085 处理器,若 μPM 为 1,则表示 x86 处理器
- 代码实现
; 初始化可编程中断控制器 8259A - 级联 pic_init: push ax .8259a_m_init: ; 8259A 主片初始化 mov al, 0x11 ; ICW1:边沿触发,级联,需要 ICW4 out 0x20, al mov al, 0x20 ; ICW2:起始中断向量号为 0x20 out 0x21, al mov al, 0x04 ; ICW3:IRQ2 接从片 out 0x21, al mov al, 0x11 ; ICW4:8086模式,正常 EOI,非缓冲模式,特殊全嵌套模式 out 0x21, al .8259a_s_init: ; 8259A 从片初始化 mov al, 0x11 ; ICW1:边沿触发,级联,需要 ICW4 out 0xA0, al mov al, 0x28 ; ICW2:起始中断向量号为 0x28 out 0xA1, al mov al, 0x02 ; ICW3:设置从片连接到主片 IRQ2 引脚 out 0xA1, al mov al, 0x01 ; ICW4:8086模式,正常 EOI,非缓冲模式,全嵌套模式 out 0xA1, al pop ax ret
8259A 操作
- 学了 ICW 寄存器,现在再来学习几个 OCW 寄存器吧
- OCW1:用来屏蔽连接在 8259A 上的外部设备的中断信号,实际上就是把 OCW1 写入了 IMR 寄存器
- 注意了,由于外部设备的中断都是可屏蔽中断,所以最终还是要受标志寄存器 eflags 中的 IF 位的管束,若 IF=0,可屏蔽中断全部被屏蔽,也就是说,若 IF=0,即使 8259A 把外部设备的中断向量号发过来,CPU 也置之不理
- OCW1 格式如下:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
M7 | M6 | M5 | M4 | M3 | M2 | M1 | M0 |
- OCW1 要写入主片的 0x21 或从片的 0xA1 端口
- M0~M7 对应 8259A 的 IRQ0~IRQ7,某位为 1,对应的 IRQ 上的中断信号就被屏蔽了。否则某位为 0 的话,对应的 IRQ 中断信号则被放行
- OCW2:用来设置中断结束方式和优先级模式,其格式如下:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
R | SL | EOI | 0 | 0 | L2 | L1 | L0 |
- OCW2 要写入到主片的 0x20 及从片的 0xA0 端口
- 如果 R=0,表示固定优先级方式,即 IRQ 接口号越低,优先级越高
- 如果 R=1,表明用循环优先级方式,这样优先级会在 0~7 内循环。如果 SL=0,初始的优先级次序为 IR0>IR1>IR2>IR3>IR4>IR5>IR6>IR7。当某级别的中断被处理完成后,它的优先级别将变成最低,将最高优先级传给之前较之低一级别的中断请求,其他依次类推。可循环方式多用于各中断源优先级相同的情况,优先级通过这种循环可以实现轮询处理。该循环可总结为如果 IR(i)优先级最低,IR(i+1)则优先级最高,其优先级关系如下图所示:
- 在 R=1 且 SL=1 时,还可以通过 L2~L0 指定最低优先级是哪个 IRQ 接口,举个例子,若想指定 IRQ5 为最低优先级,则需要设置 R=1,SL=1,L2~L0=101,新的优先级循环就编程了:IR6>IR7>IR0>IR1>IR2>IR3>IR4>IR5
- EOI,End Of Interrupt,为中断结束命令位(ICW4.AEIO=0时有意义)。EOI=1,结束中断,清 ISR 相应位
- 第 4~3 位的 00 是 OCW2 的标识,可以认为是固定值
- L2~L0 用来指定优先级
- OCW3:设置特殊屏蔽方式及查询方式,其格式如下:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
/ | ESMM | SMM | 0 | 1 | P | PR | RIS |
- OCW3 要写入主片的 0x20 端口或从片的 0xA0 端口
- 第 7 位未用到
- ESMM=0:关闭特殊屏蔽模式,ESMM=1:开启特殊屏蔽模式,
- SMM=0:未工作于特殊屏蔽模式,SMM=1:工作于特殊屏蔽模式,
- 第 4~3 位的 01 是 OCW3 的标识,8259A 通过这两位判断是哪个控制字
- P,Poll command,查询命令,P=0:无意义,P=1:中断查询命令,查询当前最高中断优先级
- RR,Read Register,读取寄存器命令,它和 RIS 位是配合在一起使用的。当 RR 为 1 时才可以读取寄存器
- RIS,,Read Interrupt register Select,读取中断寄存器选择位。RIS=0:读取 IRR 寄存器的值,RIS=1:读取 ISR 寄存器的值
- 有了上面的介绍,让我们来封装几个函数吧
; 手动结束主片中断 write_m_EOI: push ax mov al, 0x20 ; OCW2:固定优先级方式,结束中断,清 ISR 相应位 out 0x20, al ; 端口号:0x20 pop ax ret ; 手动结束从片中断 write_s_EOI: push ax mov al, 0x20 ; OCW2:固定优先级方式,结束中断,清 ISR 相应位 out 0xA0, al ; 端口号:0xA0 pop ax ret ; 读主片 ISR 寄存器的值,返回值存入 al 寄存器 read_m_ISR: mov al, 0x0B ; OCW3:RR=1,RIS=1,读取 ISR 寄存器的值 out 0x20, al ; 端口号:0x20 jmp $+2 ; jmp 指令占 2 个字节,$ 表示当前位置,这里就是起延时作用,给与端口处理时间 in al, 0x20 ; 端口号:0x20 ret ; 读从片 ISR 寄存器的值,返回值存入 al 寄存器 read_s_ISR: mov al, 0x0B ; OCW3:RR=1,RIS=1,读取 ISR 寄存器的值 out 0xA0, al ; 端口号:0xA0 jmp $+2 ; jmp 指令占 2 个字节,$ 表示当前位置,这里就是起延时作用,给与端口处理时间 in al, 0xA0 ; 端口号:0xA0 ret ; 读主片 IRR 寄存器的值,返回值存入 al 寄存器 read_m_IRR: mov al, 0x0A ; OCW3:RR=1,RIS=0,读取 IRR 寄存器的值 out 0x20, al ; 端口号:0x20 jmp $+2 ; jmp 指令占 2 个字节,$ 表示当前位置,这里起延时作用,给与端口处理时间 in al, 0x20 ; 端口号:0x20 ret ; 读从片 IRR 寄存器的值,返回值存入 al 寄存器 read_s_IRR: mov al, 0x0A ; OCW3:RR=1,RIS=0,读取 IRR 寄存器的值 out 0xA0, al ; 端口号:0xA0 jmp $+2 ; jmp 指令占 2 个字节,$ 表示当前位置,这里起延时作用,给与端口处理时间 in al, 0xA0 ; 端口号:0xA0 ret ; 读主片 IMR 寄存器的值,返回值存入 al 寄存器 read_m_IMR: in al, 0x21 ; 端口号:0x21 ret ; 将 al 寄存器的值写入主片 IMR 寄存器中 write_m_IMR: out 0x21, al ; 端口号:0x21 ret ; 读从片 IMR 寄存器的值,返回值存入 al 寄存器 read_s_IMR: in al, 0xA1 ; 端口号:0xA1 ret ; 将 al 寄存器的值写入从片 IMR 寄存器中 write_s_IMR: out 0xA1, al ; 端口号:0xA1 ret ; 设置主片工作在特殊屏蔽模式 set_m_smm: push ax mov al, 0x68 ; ESMM=1,SMM=1 out 0x20, al ; 端口号:0x20 pop ax ret