efi code里面有关于init 8259的code,也就是legacy8259protocol的实作
对8259的编程,我们是对其相应端口发送ICW(初始化命令字)来完成的,
总共需要发送4个ICW,而且必须按次序发送,这里提一下8259a编程的一些东西,2片8259a级连,一个为主片,一个为从片,从片的INT端口与主片的IRQ2相连,主片的访问端口为0x20和0x21,从片为0xa和0xa1
在legacy bios中IRQ0-IRQ7被分配了0x8-0xf的中断矢量(中断号),
IRQ-8-IRQF
被分配为0x70-0x78,这也可以看做是实模式下是这样来分的,但当cpu转到保护模式的时候,中断号0x8-0xf都被cpu用来处理错误了,所以我们必须从新对8259进行编程,设置中断号的起始地址,
EFI_LEGACY_8259_PROTOCOL 中的SetVectorBase就是干这种事情的,
从字面的意思也可以看出来,“设置中断矢量的基地址”
在EFI BIOS中我们将master interrupt base 设置为0x58,slave interr
base设置为0x70,linux中我们是将master interrupt base设置0xf0,slave设置为0xf8
设置这些base address的时候,我们就要对8259进行编程,也就是要发四个ICW了
EFI_STATUS SetVectorBase(
IN
EFI_LEGACY_8259_PROTOCOL *This,
IN UINT8
MasterBase,
IN UINT8
SlaveBase
)
{
//8259 Master
if (MasterBase != gMasterBase)
{
IoWrite8(0x20,ICW1); //Start
8259 Master Initialization.
IoWrite8(0x21,MasterBase); //Set
Interrupt Offset
IoWrite8(0x21,ICW3_M); //Set
Slave IRQ.
IoWrite8(0x21,ICW4); //Set
8259 mode. See ICW4 comments with #define.
gMasterBase = MasterBase;
}
//8259 Slave
if (SlaveBase != gSlaveBase)
{
IoWrite8(0xa0,ICW1); //Start
8259 Slave Initialization.
IoWrite8(0xa1,SlaveBase); //Set
Interrupt Offset
IoWrite8(0xa1,ICW3_S); //Set
Slave IRQ.
IoWrite8(0xa1,ICW4); //Set
8259 mode. See ICW4 comments with #define.
gSlaveBase = SlaveBase;
}
return EFI_SUCCESS;
}
ICW1:发送到0x20(主片)及0xa0(从片)端口
7 6 5 4 3 2 1 0
0 0 0 1 M 0 C I
I 位:若置位,表示ICW4 会被发送。(ICW4 等下解释)
C 位:若清零,表示工作在级联环境下。
M 位:指出中断请求的电平触发模式,在PC 机中,它应当被置零,表示采用“边沿触发模
式”。
ICW2:发送到0x21(主片)及0xa1(从片)端口
7 6 5 4 3 2 1 0
A7 A6 A5 A4 A3 0 0 0
ICW2 用来指示出IRQ0 使用的中断号是什么,因为最后三位均是零,因此要求IRQ0 的中
断号必须是8,的倍数,这又是一个很巧妙的设计。因为IRQ1 的中断号就是IRQ0 的中断
号+1,IRQ2 的中断号就是IRQ0 的中断号+2,……,IRQ7 的中断号就是IRQ0 的中断号+1,
刚好填满一个8 个的中断向量号空间。
ICW3:发送到0x21(主片)及0xa1(从片)端口
ICW3 只有在级联工作的时候才会被发送,它主要用来建立两处PIC 之间的连接,对于主片
与从片,它结构是不一样的。
(主片结构:)
7 6 5 4 3 2 1 0
IRQ7 IRQ6 IRQ5 IRQ4 IRQ3 IRQ2 IRQ1 IRQ0
上面,如果相应的位被置1,则相应的IRQ 线就被用于与从片连接,若清零则表示被连接到
外围设备。
(从片结构:)
7 6 5 4 3 2 1 0
0 0 0 0 0 IRQ
IRQ 位指出了是主片的哪一个IRQ 连到了从片,这需要同主片上发送的上面的主片结构字
一致。
ICW4:发送到0x21(主片)及0xa1(从片)端口
7 6 5 4 3 2 1 0
0 0 0 0 0 0 EOI 80x86
80x86 位:若置位表示工作在80x86 架构下。
EOI 位:若置位表示自动结束,在PC 上这位需要被清零。
在EFI BIOS code中的定义为
#define
ICW1 0x11
#define
ICW3_M 1<<2
#define
ICW3_S 2
#define
ICW4 1
与上面的说明是一致的
EFI_LEGACY_8259_PROTOCOL中SetMode这个function,是设置现在的8259处于实模式还是处于保护模式下,要中断屏蔽那些IRQ,中断触发的方式是什么样的(边沿还是水平)
其中这个会用到2个全局变量
UINT16 gIrqMask[2] =
{0xffff,0xffff}; UINT16 gIrqTrigger[2] =
{0,0}; 一个是控制中断屏蔽regisgter的,一个是控制中断触发的方式,数组为2,一个用于real
mode,一个用于protect mode
看一下setmode的函数实作
EFI_STATUS SetMode(
IN
EFI_LEGACY_8259_PROTOCOL *This,
IN
EFI_8259_MODE Mode,
IN UINT16
*Mask
OPTIONAL,
IN
UINT16 *EdgeLevel OPTIONAL
)
{
if (Mode >= Efi8259MaxMode) return
EFI_INVALID_PARAMETER;
gMode = Mode;
if (Mask) gIrqMask[Mode] = *Mask;
if (EdgeLevel) gIrqTrigger[Mode] =
*EdgeLevel;
ProgramIrqMaskTrigger();
return EFI_SUCCESS;
}
可以猜想 ProgramIrqMaskTrigger()就是操作8259
register的函数
VOID ProgramIrqMaskTrigger()
{
IoWrite8(0x21,(UINT8)gIrqMask[gMode]);
IoWrite8(0xa1,(UINT8)(gIrqMask[gMode]>>8));
//
// 4d0 can not be accessed as by IoWrite16, we
have to split
//
IoWrite8(0x4d0,(UINT8)gIrqTrigger[gMode]);
IoWrite8(0x4d1,(UINT8)(gIrqTrigger[gMode]>>8));
}
其中
IoWrite8(0x21,(UINT8)gIrqMask[gMode]);
IoWrite8(0xa1,(UINT8)(gIrqMask[gMode]>>8));
为操作8259a OCW命令register
OCW1:中断屏蔽,发送到0x21(主片)或0xa1(从片)端口
7 6 5 4 3 2 1 0
IRQ7 IRQ6 IRQ5 IRQ4 IRQ3 IRQ2 IRQ1 IRQ0
如果相应的位置1,则表示屏蔽相应的IRQ 请求。
EFI_LEGACY_8259_PROTOCOL中GetMask和SetMask,也是操作mask和trigger的,原理和以上相同
GetVector字面意思,得到中断号,也就是上面说的我们设置IRQ0对应的中断号为0x58,我们现在要得到这个0x58,就靠这个函数了,无非就是倒过来运算而已
Master的IRQ对应得中断好号为IRQ+masterbase
Slave上的IRQ为IRQ+slavebase-8 而已
EnableIRQ() DisableIRQ()这些函数的都是操作OCW1
GetInterruptLine()为读配置空间,得到IRQ编号
EndOfInterrupt()在中断处理函数里面发送EOI命令
如果 EOI 被设为自动的,那么ISR 中的位总是被清零的(在EOI 被置位的情况下,8259A
只要向CPU 发送了中断号就会将ISR 中的相应位清零),也就是如果有中断来,芯片就会马
上再向CPU 发出中断请求,即使CPU 正在处理IRQ0 的中断,CPU 并不知道谁的优先级高,
它只会简单的响应8259A 送来的中断,因此,这种情况下低优先级的中断就可能会中断高
优先级的中断服务程序。所以在PC 中,我们总是将EOI 位清零,而在中断服务程序结束的
时候才发送EOI 消息。
注意一点,发EOI命令的时候,先发送给从片,再发送给主片