2.5 PIC - 8259A
[ Home ] [ Top ] [ Previous ] [ Next ]
2.5.1 Overview
在本章的第一节我们已经提到,中断的来源除了来自于硬件自身的NMI中断和来自于软件的INT n指令造成的软件中断之外,还有来自于外部硬件设备的中断,这些中断是可屏蔽的。这些中断也都通过PIC(Programmable Interrupt Controller)进行控制,并传递给CPU。在IBM PC极其兼容机上所使用的PIC是Intel 8259A芯片。8259A芯片的功能非常强大,但在IBM PC上,我们只用到比较简单的功能。我们本节也只讨论其在PC上的使用。
一个8259A芯片的可以接最多8个中断源,但由于可以将2个或多个8259A芯片级连(cascade),并且最多可以级连到9个,所以最多可以接64个中断源。早期,IBM PC/XT只有1个8259A,. 但设计师们马上意识到这是不够的,于是到了IBM PC/AT,8259A被增加到2个以适应更多外部设备的需要,其中一个被称作Master,另外一个被称作Slave,Slave以级连的方式连接在Master上。如今绝大多数的PC都拥有两个8259A,这样 最多可以接收15个中断源。
通过8259A可以对单个中断源进行屏蔽。
2.5.2 Principle
在一个8259A芯片有如下几个内部寄存器:
- Interrupt Mask Register (IMR)
- Interrupt Request Register (IRR)
- In Sevice Register (ISR)
IMR被用作过滤被屏蔽的中断;IRR被用作暂时放置未被进一步处理的Interrupt;当一个Interrupt正在被CPU处理时,此中断被放置在ISR中。
除了这几个寄存器之外,8259A还有一个单元叫做Priority Resolver,当多个中断同时发生时,Priority Resolver根据它们的优先级,将高优先级者优先传递给CPU。
当一个中断请求从IR0到IR7中的某根线到达IMR时,IMR首先判断此IR是否被屏蔽,如果被屏蔽,则此中断请求被丢弃;否则,则将其放入IRR中。
在此中断请求不能进行下一步处理之前,它一直被放在IRR中。一旦发现处理中断的时机已到,Priority Resolver将从所有被放置于IRR中的中断中挑选出一个优先级最高的中断,将其传递给CPU去处理。IR号越低的中断优先级别越高,比如IR0的优先级别是最高的。
8259A 通过发送一个INTR(Interrupt Request)信号给CPU,通知CPU有一个中断到达。CPU收到这个信号后,会暂停执行下一条指令,然后发送一个INTA(Interrupt Acknowledge)信号给8259A。8259A收到这个信号之后,马上将ISR中对应此中断请求的Bit设置,同时IRR中相应的bit会被 reset。比如,如果当前的中断请求是IR3的话,那么ISR中的bit-3就会被设置,IRR中IR3对应的bit就会被reset。这表示此中断请 求正在被CPU处理,而不是正在等待CPU处理。
随后,CPU会再次发送一个INTA信号给8259A,要求它告诉CPU此中断请求的中断向量是什么,这是一个从0到255的一个数。8259A根据被设置的起始向量号(起始向量号通过中断控制字ICW2被初始化)加上中断请求号计算出中断向量号,并将其放置在Data Bus上。比如被初始化的起始向量号为8,当前的中断请求为IR3,则计算出的中断向量为8+3=11。
CPU从Data Bus上得到这个中断向量之后,就去IDT中找到相应的中断服务程序ISR,并调用它。如果8259A的End of Interrupt (EOI)通知被设定位人工模式,那么当ISR处理完该处理的事情之后,应该发送一个EOI给8259A。
8259A得到EOI通知之后,ISR寄存器中对应于此中断请求的Bit会被Reset。
如果8259A的End of Interrupt (EOI)通知被设定位自动模式,那么在第2个INTA信号收到后,8259A ISR寄存器中对应于此中断请求的Bit就会被Reset。
在 此期间,如果又有新的中断请求到达,并被放置于IRR中,如果这些新的中断请求中有比在ISR寄存中放置的所有中断优先级别还高的话,那么这些高优先级别 的中断请求将会被马上按照上述过程进行处理;否则,这些中断将会被放在IRR中,直到ISR中高优先级别的中断被处理结束,也就是说知道ISR寄存器中高 优先级别的bit被Reset为止。
2.5.3 IRQ2/IRQ9 Redirection
为什么要将IRQ2重定向到IRQ9上?这仍然是由于兼容性问题造成的。
早期的IBM PC/XT只有一个8259A,这样就只能处理8种IRQ。但很快就发现这根本不能满足需求。所以到了IBM PC/AT,又以级连的方式增加了一个8259A,这样就可以多处理7种IRQ。原来的8259A被称作Master PIC,新增的被称作Slave PIC。但由于CPU只有1根中断线,Slave PIC不得不级连在Master PIC上,占用了IRQ2,那么在IBM PC/XT上使用IRQ2的设备将无法再使用它;但新的系统又必须和原有系统保持兼容,怎么办?
由 于新增加的Slave PIC在原有系统中不存在,所以,设计者从Slave PIC的IRQ中挑出IRQ9,要求软件设计者将原来的IRQ2重定向到IRQ9上,也就是说IRQ9的中断服务程序需要去掉用IRQ2的中断服务程序。 这样,将原来接在IRQ2上的设备现在接在IRQ9上,在软件上只需要增加IRQ9的中断服务程序,由它调用IRQ2的中断服务程序,就可以和原有系统保 持兼容。而在当时,增加的IRQ9中断服务程序是由PC开发商开发的BIOS提供的,所以就从根本上保证了兼容。
2.5.4 Programming the 8259As
每一个8259A芯片都有两个I/O ports,程序员可以通过它们对8259A进行编程。
Master 8259A的端口地址是0x20,0x21;Slave 8259A的端口地址是0xA0,0xA1。
程序员可以向8259A写两种命令字:
- Initialization Command Word (ICW); 这种命令字被用作对8259A芯片的初始化。
- Operation Command Word (OCW): 这种命令被用来向8259A发布命令,以对其进行控制。O CW可以在8259A被初始化之后的任何时候被使用。
下表的内容是Master 8259A的I/O端口地址,以及通过它们所能操作的寄存器。
Address | Read/Write | Function |
0x20 | Write | Initialization Command Word 1 (ICW1) |
Write | Operation Command Word 2 (OCW2) | |
Write | Operation Command Word 3 (OCW3) | |
Read | Interrupt Request Register (IRR) | |
Read | In-Service Register (ISR) | |
0x21 | Write | Initialization Command Word 2 (ICW2) |
Write | Initialization Command Word 3 (ICW3) | |
Write | Initialization Command Word 4 (ICW4) | |
Read/Write | Interrupt Mask Register (IMR) |
下表的内容是Slave 8259A的I/O端口地址,以及通过它们所能操作的寄存器。
Address | Read/Write | Function |
0xA0 | Write | Initialization Command Word 1 (ICW1) |
Write | Operation Command Word 2 (OCW2) | |
Write | Operation Command Word 3 (OCW3) | |
Read | Interrupt Request Register (IRR) | |
Read | In-Service Register (ISR) | |
0xA1 | Write | Initialization Command Word 2 (ICW2) |
Write | Initialization Command Word 3 (ICW3) | |
Write | Initialization Command Word 4 (ICW4) | |
Read/Write | Interrupt Mask Register (IMR) |
2.5.5 Initialization
当 主机Power-on或Reset之后,必须对两个8259A都进行初始化。事实上,BIOS已经这么做了。但不幸的是,BIOS对其进行的初始化的结果 并非我们所需要。比如,我们要开发Protected Mode下OS,我们要设置自己的IDT,那么我们就不能使用BIOS设置的IVT,而在对8259A初始化操作中,我们需要告诉8259A,其相关中断 请求的起始向量号,而我们对IDT的中断向量布局和BIOS设置的IVT的中断向量布局可以是不一样的。这样,我们也需要对两个8259A进行初始化。
任 何时候,只要向某一个8259A的第一个端口(0x20 for Master, and 0xA0 for Slave)写入的命令的bit-4(从0算起)为1,那么这个8259A就认为这是一个ICW1;而一旦一个8259A收到一个ICW1,它就认为一个 初始化序列开始了。你可以通过对照上边的表和后面的表,第一端口可写的有ICW1,OCW2和OCW3。而ICW1的bit-4要求必须是1,但OCW2 和OCW3的bit-4要求必须是0。
8259A的初始化流程协议如下图所示,程序员对其进行初始化时必须遵守此协议:
ICW1
Bit(s) | Function | |
7:5 | Interrupt Vector Addresses for MCS-80/85 Mode. | |
4 | Must be set to 1 for ICW1 | |
3 | 1 | Level Triggered Interrupts |
0 | Edge Triggered Interrupts | |
2 | 1 | Call Address Interval of 4 |
0 | Call Address Interval of 8 | |
1 (SINGL) | 1 | Single PIC |
0 | Cascaded PICs | |
0 (IC4) | 1 | Will be Sending ICW4 |
0 | Don't need ICW4 |
对于X86,bit-0必须被设置为1;由于当今的IBM PC上都有两个级连的8259A,所以bit-1应该被设置为0;由于bit-2是为MCS-80/85服务的,我们将其设置为0;bit-3也设置为0;bit-4被要求必须设置为1;bit5:7是为MCS-80/85服务的,对于X86,应将全部将其设为0。
所以,在X86系统上,ICW1应该被设置为二进制00010001 = 0x11。
ICW2
Bit | 80x86 Mode |
7 | I7 |
6 | I6 |
5 | I5 |
4 | I4 |
3 | I3 |
2 | 0 |
1 | 0 |
0 | 0 |
ICW2被用作指定本8259A中的中断请求的起始中断向量,bit0:3必须被设为0;所以,其起始中断向量必须是8的倍数。比如,我们的OS的设计讲来自于Master 8259A的8个中断请求放在IDT的第32 (从0开始计)个位置到第39个位置,则我们应该将ICW2设为0x20。
这样,当将来此8259A上接收到一个IRQ时,其低3位会被自动填充为IRQ号。比如,其收到一个IRQ6,将6自动填充到后3位,则生成的向量号为0x26。8259A会在收到CPU发来的第二个INTA信号之后,将生成的向量号放到Data Bus上。
ICW3
Master 8259A和Slave 8259A有不同的ICW3格式。
Bit | Function |
7 | IR7 is connected to a Slave |
6 | IR6 is connected to a Slave |
5 | IR5 is connected to a Slave |
4 | IR4 is connected to a Slave |
3 | IR3 is connected to a Slave |
2 | IR2 is connected to a Slave |
1 | IR1 is connected to a Slave |
0 | IR0 is connected to a Slave |
Slave 8259A被接在Master 8259A的那个IRQ上,则相应的位就被设置为1,其余的位都被设置为0。在IBM PC上,Slave 8259A被接在Master 8259A的IRQ2上,则此ICW3的值应该被设置为二进制00000100 = 0x04。
Bit(s) | Function | |
7 | Reserved. Set to 0 | |
6 | Reserved. Set to 0 | |
5 | Reserved. Set to 0 | |
4 | Reserved. Set to 0 | |
3 | Reserved. Set to 0 | |
2:0 | Slave ID | |
000 | Slave 0 | |
001 | Slave 1 | |
010 | Slave 2 | |
011 | Slave 3 | |
100 | Slave 4 | |
101 | Slave 5 | |
110 | Slave 6 | |
111 | Slave 7 |
Bit(s) | Function | |
7 | Reserved. Set to 0 | |
6 | Reserved. Set to 0 | |
5 | Reserved. Set to 0 | |
4 | 1 | Special Fully Nested Mode |
0 | Not Special Fully Nested Mode | |
3:2 | 0x | Non - Buffered Mode |
10 | Buffered Mode - Slave | |
11 | Buffered Mode - Master | |
1 | 1 | Auto EOI |
0 | Normal EOI | |
0 | 1 | 8086/8080 Mode |
0 | MCS-80/85 |
{
/* icw1 */
outb( 0x11, 0x20 ); /* master port A */
outb( 0x11, 0xA0 ); /* slave port A */
outb( 0x20, 0x21 ); /* master offset of 0x20 in the IDT */
outb( 0x28, 0xA1 ); /* slave offset of 0x28 in the IDT */
outb( 0x04, 0x21 ); /* slaves attached to IR line 2 */
outb( 0x02, 0xA1 ); /* this slave in IR line 2 of master */
outb( 0x01, 0x21 ); /* set as master */
outb( 0x01, 0xA1 ); /*set as slave */
}
2.5.6 Operation
一旦按照初始化协议初始化完成之后,程序员就可以在任何时候,以任何顺序向8259A发送操作控制字OCW了。
OCW1
Bit | PIC 2 | PIC 1 |
7 | Mask IRQ15 | Mask IRQ7 |
6 | Mask IRQ14 | Mask IRQ6 |
5 | Mask IRQ13 | Mask IRQ5 |
4 | Mask IRQ12 | Mask IRQ4 |
3 | Mask IRQ11 | Mask IRQ3 |
2 | Mask IRQ10 | Mask IRQ2 |
1 | Mask IRQ9 | Mask IRQ1 |
0 | Mask IRQ8 | Mask IRQ0 |
OCW1是用来做中断请求屏蔽用的操作控制字。如果你想屏蔽那个IRQ,只需要对照上表将相应的Bit置为1,然后发送给相应的8259A就可以了。比如我想屏蔽IRQ10,我只需要将0x0A写到端口0xA1。对应代码如下:
outb(0x0A, 0xA1);
OCW2
Bit(s) | Function | |
7:5 | 000 | Rotate in Auto EOI Mode (Clear) |
001 | Non Specific EOI | |
010 | Reserved | |
011 | Specific EOI | |
100 | Rotate in Auto EOI Mode (Set) | |
101 | Rotate on Non-Specific EOI | |
110 | Set Priority Command (Use Bits 2:0) | |
111 | Rotate on Specific EOI (Use Bits 2:0) | |
4 | Must be set to 0 | |
3 | Must be set to 0 | |
2:0 | 000 | Act on IRQ 0 or 8 |
001 | Act on IRQ 1 or 9 | |
010 | Act on IRQ 2 or 10 | |
011 | Act on IRQ 3 or 11 | |
100 | Act on IRQ 4 or 12 | |
101 | Act on IRQ 5 or 13 | |
110 | Act on IRQ 6 or 14 | |
111 | Act on IRQ 7 or 15 |
通过将bit3:4设置为0,以说明这是一个OCW2。如果bit-6被设为1,则bit0:2有效,其操作则是面向某个IRQ的;否则将bit0:2设为0,其操作是面向整个8259A的所有IRQ的。我们一般只会用到No Specific EOI——因为我们在初始化8259A时,制定的EOI Mode为手动模式,所以当每次对应某个8259A芯片的IRQ的中断服务程序ISR执行结束后,都需要向8259A发送一个EOI,其对应的OCW2的值为0x20。需要注意的是,由于IBM PC有2个级连的8259A,所以我们每次必须分别给两个都发一个。
比如下面示例代码用来向两个8259A芯片发送EOI,它需要在针对来自于两个8259A芯片的中断的服务程序ISR末尾处被调用:
inline void send_eoi(void)
{
/* Send EOI to both master and slave */
outb( 0x20, 0x20 ); /* master PIC */
outb( 0x20, 0xA0 ); /* slave PIC */
}
OCW3
Bit(s) | Function | |
7 | Must be set to 0 | |
6:5 | 00 | Reserved |
01 | Reserved | |
10 | Reset Special Mask | |
11 | Set Special Mask | |
4 | Must be set to 0 | |
3 | Must be set to 1 | |
2 | 1 | Poll Command |
0 | No Poll Command | |
1:0 | 00 | Reserved |
01 | Reserved | |
10 | Next Read Returns Interrupt Request Register | |
11 | Next Read Returns In-Service Register |
通过将Bit-3设为1,Bit-4设为0,以让8259A知道这是一个OCW3。OCW3中对我们最有意义的位是bit0:1,我们可以通过将bit-1设为1来通知8259A,下一个读端口的动作将要读取IRR或ISR寄存器的内容。
比如下面示例C++代码用来读取Master 8259A的IRR寄存器内容到__irr变量中:
void read_irr(unsigned char& __irr)
{
outb(0x02, 0x20);
inb(&__irr, 0x20);
}
2.5.7 Full Nested Mode
为了让我们更加理解8259A的中断控制机理,我们需要说明一下Full Nested Mode。 在我们初始化时,只需要将ICW4的bit-4设为0,我们就选择了Full Nested Mode。
Full Nested Mode其实就是实现按照中断请求的优先级别进行抢断处理的机制——如果当前一个IRQ正在被CPU处理,也就是说,当前CPU正在调用其中断服务程序 ISR;这时8259A又接到了新的IRQ,如果此IRQ的优先级大于正在处理的IRQ,那么,此IRQ就会被提交给CPU以优先处理;否则此IRQ则被 放置在IRR中,直到所有的高优先级中断被处理结束为止。
其处理过程大致如下:
在 ISR寄存器中有一个8-bit的字节,范围为bit[0,7];每一个bit对应一个IRQ(IRQ0-IRQ7对应bit[0,7])。当一个IRQ 被提交给CPU之后(收到来自于CPU的第一个INTA信号之后),其对应的bit会被设置为1。比如IRQ6被提交给CPU之后,IS Register的bit-6会被设置为1。当此8259A收到一个EOI之后(对于手动模式,这意味着一个优先级别最高的中断请求被处理结束),会将 IS Register中被设置的最高优先级IRQ的对应的bit清为0。比如在收到一个EOI时,发现IS Register的bit-3, bit-5, bit-6被设置,那么被清除的则是bit-3(越小优先级别越高)。在清除优先级最高的bit之后,8259A会到IRR中察看是否有优先级别高于当前 正在处理的IRQ中优先级别最高的IRQ,如果有,则将此IRQ提交给CPU处理,同时设置相应的bit。还以上面的例子为例,当bit-3被清除之后, 如果发现在IRR中有一个IRQ4等待被处理,则将其提交给CPU,在收到来自于CPU的第一个INTA信号之后,则将IS Register的bit-4置为1。
在此过程中,如果8259A接到更高优先级别的IRQ,则将其立即提交给CPU。比如,当前正在处理的IRQ为IRQ3,IRQ5,那么IS Register中被设置的bit为bit-3,bit-5;如果此时接到一个IRQ1,则立即将其提交给CPU,在收到来自于CPU的第一个INTA信号之后,则将IS Register的bit-1置为1。
由 此过程我们也可以看出,为了实现这种优先级机制,必须将EOI设为手动模式,也就是说必须将ICW4的bit-1设为0。因为,对于自动EOI模 式,8259A会在收到来自于CPU的第2个INTA信号之后,就自动将IS Register中此IRQ对应的bit清0,而事实上,这个时候此IRQ对应的中断服务程序还没有被CPU调用,也就是说此IRQ还没有被处理结束,而 由于此IRQ对应的bit已经被清除,如果此IRQ是一个优先级很高的话,那么此IRQ的处理完全可以被一个优先级别更低的IRQ所中断。这不是我们所需 要的。