标题：8259A中断控制器
Overview
前言  8259A芯片是一个中断管理芯片，中断的来源除了来自于硬件自身的NMI中断和来自于软件的INT n指令造成的软件中断之外，还有来自于外部硬件设备的中断，这些中断是可屏蔽的。这些中断也都通过PIC(Programmable Interrupt Controller)进行控制，并传递给CPU。
一个8259A芯片的可以接最多8个中断源，但由于可以将2个或多个8259A芯片级连（cascade），并且最多可以级连到9个，所以最多可以接64个中断源。如今绝大多数的PC都拥有两个8259A，这样 最多可以接收15个中断源。
通过8259A可以对单个中断源进行屏蔽。在一个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。8259A工作原理  当一个中断请求从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为止。
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提供的，不需要用户进行另外设置。所以就从根本上保证了兼容。
Programming the 8259As
每一个8259A芯片都有两个I/O ports，程序员可以通过它们对8259A进行编程。
Master 8259A的端口地址是0x20，0x21；Slave 8259A的端口地址是0xA0，0xA1。8259As的口令  　　程序员可以向8259A写两种命令字：
Initialization Command Word (ICW）；这种命令字被用作对8259A芯片的初始化。
Operation Command Word (OCW）：这种命令被用来向8259A发布命令，以对其进行控制。OCW可以在8259A被初始化之后的任何时候被使用。Master 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) 
Addresses/Registers for Master 8259ASlave 8259A下表的内容是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) 
Addresses/Registers for Slave 8259A
由于8259A芯片不仅能够用于IBM PC/X86，也可以被用作MCS-80/85，对于这两者，在操作模式上有一些不一样，对于某些寄存器的设置也有所不同。我们后面仅仅讨论X86模式相关的内容。
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 
Initialization Command Word 1 (ICW1） 
对于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
Initialization Command Word 2 (ICW2） 
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 
Initialization Command Word 3 for Master 8259A (ICW3） 
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 
Initialization Command Word 3 for Slaves (ICW3） 
Slave 8259A的ICW3的bit3:7被保留，必须被设为0；而bit0:2被设置为此Slave 8259A被接在Master 8259A的哪个IRQ上。比如，在IBM PC上，Slave 8259A被接在Master 8259A的IRQ2上，则此ICW3应被设为0x02。
ICW4
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
Initialization Command Word 4 (ICW4） 
在80x86模式下，我们不需要使用8259A的特殊功能，因此我们将bit1:4都设为0，这意味使用默认的Full Nested Mode，不使用Buffer，以及手动EOI模式；我们只需要将bit-0设为1，这也正是我们ICW0处提到的我们为什么必须要ICW4的原因。所以ICW4的值应该被设为0x01。
所以我们可以用下列代码初始化2个8259A芯片：
inline void init_8259a(void)
{
/* icw1 */
outb( 0x20,0x11 ); /* master port A */
outb( 0xA0,0x11 ); /* slave port A */
/* icw2 */
outb( 0x21,0x20 ); /* master offset of 0x20 in the IDT */
outb( 0xA1,0x28 ); /* slave offset of 0x28 in the IDT */
/* icw3 */
outb( 0x21,0x04 ); /* slaves attached to IR line 2 */
outb( 0xA1,0x02 ); /* this slave in IR line 2 of master */
/* icw4 */
outb( 0x21,0x01 ); /* set as master */
outb( 0xA1,0x01 ); /*set as slave */
}
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 
Operation Control Word 1 (OCW1） 
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 
Operation Control Word 2 (OCW2） 
通过将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( 0xA0,0x20 ); /* 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 
Operation Control Word 3 (OCW3） 
通过将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）;
}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所中断。这不是我们所需要的。