x86架构中断基础介绍

BIOS/UEFI基础——x86架构中断基础介绍

说明

本文讲的是Intel的x86架构下的中断。

参考的文档主要是《64-ia-32-architectures-software-developer-manual.pdf》,《PCI Express体系结构导读》、《x86/x64体系探索及编程》、《82093AA I/O ADVANCED PROGRAMMABLE INTERRUPT CONTROLLER (IOAPIC).pdf》、《MultiProcess Specification.pdf》

参考的代码主要来自EDKII源码(https://github.com/jiangwei12/edk2)。

因水平有限,对中断的理解并非完全,如有错误,实属无奈......

 

分类

x86架构的中断类型和实现方式有很多种,这里做一个大致的分类。

从模块上来分,x86中有8259中断控制器Local APICI/O APIC,另外在PCI /PCIE中还存在MSI中断

对于这种分类,8259和APIC两者是旧和新的区分;Local APIC和I/O APIC是系统位置上的区分,前者属于CPU的一部分,后者属于Chipset的一部分(当然对于SoC来说区分就没有这么明显了);而MSI和前面这几个基本属于两个体系的东西,不过MSI的实现还是需要依赖于APIC,这个在后续会说明。

 

从类型上来分,有硬件中断和软件中断之分,有可屏蔽中断和不可屏蔽中断之分。

这部分的分类,前者是按照中断源来分的,可以是软件主动触发(通过INT等指令(存疑,这个不是很理解,比如int13h是用来表示读写硬盘的,但是参考文档中描述的是SIMD exception,根本是两回事儿,感觉两者并不是同一个东西...)),也可以是模块内部或者外部硬件触发的;

后者主要根据针对中断是否要被处理(大部分中断都可以通过设置来配置成可屏蔽或不可屏蔽)。

 

从实现上来分,有IDT(Interrupt Descriptor Table)和IVT(Interrupt Vector Table)之分。

这部分的分类主要是根据x86模式来分的,IVT主要用于实模式,而IDT主要用于保护模式及之后的模式。

IVT一般放在系统地址从0x0开始的4K空间,且一个Vector到实现代码之间的连接是比较直接的,Vector指针直接指向代码段。而IDT表中的项还有一些转换才指向到代码,这也跟保护模式相适应。

IVT或者IDT都可以通过IDTR寄存器来获取,而通过指令LIDT/SIDT可以读写IDTR寄存器,下面是IDTR到IDT的对应:

上图是在保护模式下,当在实模式下时对应的应该是IVT,IDTR中的Base Address在系统初始化的时候值为0,所以IVT才会放在0x0这个地址:

下面是IVT的基本配置:

上图中第二列中描述的中断在实模式下并不是全部存在,不过还是比较奇怪比如INT10H这里描述的是浮点错误,在实模式和虚拟8086模式下都是yes,但是它在实模式下不是显示相关的处理函数吗?

 

之后的介绍主要是按照模块来分。

 

8259中断控制器

8259控制器在系统中的布局大致如下:

实际的使用中,一般会使用两片8259芯片级联,一个主一个从。每一片有8个中断引脚,由于级联的关系,从片的INTR引脚会连到主片的一个中断引脚(IRQ2)中,所以实际的中断就是15个,分别是第一片上的IRQ0,IRQ1,IRQ3-IRQ7,和第二片的IRQ8-IRQ15。

这些中断引脚基本上的用法都固定,并且对应也都是一些比较老的设备。

8259控制器中对中断的响应优先级是有规定的,一般号越小优先级越高,不过IRQ8-IRQ15都是接在IRQ2上的,所以它们的优先级都当成2来看,所以优先级也比较高。

8259控制器(包括之后介绍的Local APIC)中,有几个比较重要的寄存器:

IRR:Interrupt Request Register,用来标志对应IRQ上发出了中断请求,它是一个8位的寄存器,刚好对应8个中断引脚,中断一次可以有多个,即可以有多个bit可以被置位;

ISR:Interrupt Service Register,用来记录IRQ的中断服务状态,它也是一个8位的寄存器,如果某个bit被置位,就表示中断请求正在被执行;

IMR:Interrupt Mask Register,用来屏蔽对应的中断请求,对应bit被置位表示屏蔽;

上述的寄存器并不能直接的读写。8259控制器在系统空间中使用I/O映射,对应的端口是20h,21h和A0h、A1h,分别对应主片和从片(这些值是硬件确定的软件配置的?)。

通过读写这些寄存器来访问和初始化控制器,写寄存器的值被称为ICW或者OCW,前者全称Initialization Command Word,用来初始化8259控制器,后者全称Operational Control Word,通过它就可以用来读写上述的寄存器以及其它的一些东西。

初始化

前面已经讲到了ICW,而8259控制器的初始化就是通过写ICW来实现的,大致的流程如下:

具体的初始化代码,在EDKII中可以参考8259.inf模块。

Interrupt8259SetVectorBase()函数就是一个初始化的过程。

这里需要注意的是寄存器的使用,写ICW1使用的是x0h这个端口,写ICW2-ICW4使用的是x1h这个端口。(x的值是2或者A)

此外,Interrupt8259WriteMask()函数设置了Mask和触发方式等。

对于触发方式还涉及到寄存器ELCR1和ELCR2,对应的I/O分别是4D0h和4D1h,两个寄存器都是8位的,共16位位对应到16个IRQ,0表示边沿触发1表示电平触发。

另外还安装了一个EFI_LEGACY_8259_PROTOCOL,通过它就可以获取对应的中断向量,并为此设置中断处理函数。下面是8254Timer.inf模块中的示例:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
//
// Get the interrupt vector number corresponding to IRQ0 from the 8259 driver
//
TimerVector = 0 ;
Status      = mLegacy8259->GetVector (mLegacy8259, Efi8259Irq0, (UINT8 *) &TimerVector);
ASSERT_EFI_ERROR (Status);
 
//
// Install interrupt handler for 8254 Timer #0 (ISA IRQ0)
//
Status = mCpu->RegisterInterruptHandler (mCpu, TimerVector, TimerInterruptHandler);
ASSERT_EFI_ERROR (Status);

上述代码为计时器提供了IRQ0处理函数,就是使IRQ0对应的中断向量指向了处理函数。

在中断发生后,经过一系列的处理,最终8259控制器向处理器发送一个中断向量,CPU在IDT表中找到这个向量对应的函数并执行。

需要说明的是,CPU检测中断,加载中断处理程序,从中断处理程序返回,这些动作都是硬件上的,软件并不参与,软件只需要设置中断向量,并配置中断处理函数,当然中断处理函数本身也是软件代码......

具体的CPU处理中断的过程可以参考http://www.cppblog.com/aaxron/archive/2011/11/16/160280.html。

 

以上是一个简介,8259芯片的具体介绍可以参考dos.csail.mit.edu/6.828/2010/readings/hardware/8259A.pdf" target="_blank">https://pdos.csail.mit.edu/6.828/2010/readings/hardware/8259A.pdf。

 

Local APIC

APIC的全称是Advanced Programmable Interrupt Controller,它算是8259控制器的升级版本。

引入它可以适应多处理器环境。

APIC包括了Local APIC和I/O APIC两部分内容,Local APIC是总的控制器,位于CPU内部;I/O APIC主要用于处理外部设备的中断。

下面是APIC在处理器中的逻辑框图:

上面的处理器单元在多线程处理器中指的是逻辑处理器,每个逻辑处理器都有自己的Local APIC,每个Local APIC都对应一组寄存器。这组寄存器可以是映射到系统地址(MMIO方式)中,也可以是在MSR寄存器中,这取决于Local APIC的模式,目前一般有xAPIC和x2APIC两种模式,后者使用MSR寄存器。

 

Local APIC寄存器介绍

下面首先了解这组寄存器:

上面的寄存器是以MMIO的形式展现。对于MSR形式的,是从0x802开始的一系列MSR,这里不再配图。

这些寄存器的位数存在32位、64位和256位几种情况。

其中256位的寄存器是以下的几个:

ISR:In-Service Register;

TMR:Trigger Mode Register;

IRR:Interrupt Request Register;

这些寄存器是跟中断个数对应的,系统中最多有256个中断,而上述寄存器中的每一个位都表示一个中断的状态。

当一个中断触发之后,对应的IRR位就被置位,不过此时中断并没有被CPU处理,只是在排队中,直到CPU要开始处理这个中断时,该位清零,而对应ISR位被设置,表示CPU开始处理这个中断了。当中断处理完成之后,会写EOI(End Of Interrupt)寄存器(也在上面的表中),这样Local APIC就会清零ISR对应的位。

TMR寄存器表示中断的触发方式。

另外还有几个需要详细介绍的寄存器:

 

Local APIC ID寄存器

Local APIC ID寄存器里面保存着APIC ID,它是逻辑处理器在系统中的唯一标识,这个APIC ID在多线程处理器下是很有用的。

Local APIC ID寄存器在xAPIC模式和x2APIC模式下略有不同:

xAPIC和x2APIC在表示Local APIC ID的位数上有差别。

另外,这些位还有一些细分,总共可以分为Cluster ID / Package ID / Core ID / SMT ID,这四层从高位到低位,而在系统中范围是从大到小的。Cluster是一组物理处理器,Package表示一个物理处理器,Core表示处理器中的一个核,SMT表示一个逻辑处理器。

这四层的架构其实包含了Intel多线程处理器中的两大个特性,即Hyper-Threading(又叫Simultaneous Multi-Threading,SMT)和Multi-Core。前者表示的是单个核里面里面有两个执行单元(线程),而后者表示一个处理器里面有多个核。所以提到Intel的处理器说4核8线程,就是应用了上述两者技术的结果,即一个处理器里面有4个核,每个核有两个线程。

参考《64-ia-32-architectures-software-developer-manual.pdf》中的说明会更清楚一些:

 

?
1
2
3
4
5
6
7
8
9
10
? Cluster — Some multi-threading environments consists of multiple clusters of multi-processor systems. The
CLUSTER_ID sub-field is usually supported by vendor firmware to distinguish different clusters. For non
clustered systems, CLUSTER_ID is usually 0 and system topology is reduced to three levels of hierarchy.
? Package — A multi-processor system consists of two or more sockets, each mates with a physical processor
package . The PACKAGE_ID sub-field distinguishes different physical packages within a cluster.
? Core — A physical processor package consists of one or more processor cores. The CORE_ID sub-field distin
guishes processor cores in a package . For a single-core processor, the width of this bit field is 0 .
? SMT — A processor core provides one or more logical processors sharing execution resources. The SMT_ID
sub-field distinguishes logical processors in a core. The width of this bit field is non-zero if a processor core
provides more than one logical processors.
下面是上述概念的对应的MP架构拓扑图:

 


包含两个Package的可以看成是一个Cluster。

至于为什么要有这个Cluster的概念,并不是很清楚......

 

Local APIC版本寄存器

这是一个只读寄存器,它反映了Local APIC的特性,比如说是哪种模式的APIC(并不是xAPIC还是x2APIC,只有内部和外部APIC之分),比如支持对多多少个LVT(LVT后面会介绍)。

下面是它的具体信息:


 

LVT寄存器

Local APIC版本寄存器中确定了LVT的个数,目前的CPU一般支持7个LVT寄存器。LVT全称Local Vector Table,这些寄存器可以接收(主要是LINT0和LINT1)和产生本地中断源。

下面就是这些寄存器的图示:

需要说明的是LINT0和LINT1这两个寄存器,它们对应到Local APIC模块的INTR和NMI引脚,外部的中断会引发这两个寄存器的中断发起

对于具体位的说明如下:

1. Vector(bit0-bit7):就是中断号,通过它在IDT中寻找对应的中断描述符,进而找到中断处理函数;

2. Delivery Mode(bit8-bit10):交付模式,有以下可取的值:

1)Fixed模式(000b),就是根据Vector找到中断处理函数并执行;

2)SMI模式(010b),触发的是SMI中断,这个时候Vector的值需要是00h;

3)NMI模式(100b),触发的是NMI中断,就不需要管Vector的值了;

4)INIT模式(101b),处理器执行INIT(不清楚处理器具体做了什么);

5)ExtINT模式(111b),表示中断源来自外部,比如8259控制器;

其它都是reserved。

典型的,可以将LINT0和LINT1配置成ExtINT和NMI交付模式。

3. Delivery Status(bit12),0表示当前没有中断交付或者中断已经交付给CPU处理,1表示中断已交付但是CPU还未处理;

4. Interrupt Input Pin Polarity(bit13),它只用于LINT0和LINT1,用于设置触发模式,0表示高电平触发1表示低电平触发;

5. Remote IRR Flag(bit14),它只用于LINT0和LINT1,使用在Fixed,电平触发模式中,1表示Local APIC接收并处理由INTR和NMI交付的中断,0表示接收到EOI命令;

6. Trigger Mode(bit15):它只用于LINT0和LINT1,0表示边沿触发,1表示电平触发;

7. Mask(bit16):1表示屏蔽中断响应;

8. Timer Mode(bit17-bit18):它只用于LVT Timer寄存器,用来设置Timer Count的计数模式。00b表示一次计数,01h表示循环计数,10表示指定TSC值计数;

LVT寄存器在上电和复位之后的值都是0x00010000。

CMCI寄存器、Thermal Monitor寄存器和Performance Monitor寄存器只能使用Fixed、SMI或NMI交付模式;

LINT0和LINT1寄存器只能使用Fixed、ExtINT或NMI交付模式;

 

ICR寄存器

ICR全称Interrupt Command Register,它用于逻辑处理器之间的通信,使用的中断称为IPI(Inter-Processor Interrupt)。

 

 

Local APIC初始化

 

Local APIC的初始化包括两个部分,一个上述寄存器的配置,一个是IDT表的配置。

在EDKII代码中,初始化在CpuDex.inf模块中:

 

?
1
2
3
4
5
6
7
8
9
//
// Setup IDT pointer, IDT and interrupt entry points
//
InitInterruptDescriptorTable ();
 
//
// Enable the local APIC for Virtual Wire Mode.
//
ProgramVirtualWireMode ();

 

ProgramVirtualWiredMode()函数主要配置了LINT0、LINT1和Spurious Interrupt Vector Register,最后一个寄存器是用来使能Local APIC的。

这里的Virtual Wire Mode是多处理器系统的一种中断形式,Local APIC下的Virtual Wire Mode结构如下:

关于多处理器系统的介绍,可以参考http://download.intel.com/design/archives/processors/pro/docs/24201606.pdf。

CpuDxe.inf并不会把所有的中断都配置好,这里更可以说是搭好了框架,后续不同模块根据其实现母的还会有中断配置和处理函数添加。

 

I/O APIC

前面已经讲过,APIC有两个部分,一个是Local APIC,一个是I/O APIC,前者位于CPU中,后者位于芯片组中,两者之间通过系统总线来通信。

相比8259控制器通过INTR引脚与处理器通信的方式,I/O APIC则直接通过它的MMIO来与CPU通信。

这通过“Local APIC初始化”那一章节中的Virtual Wire Mode配置图中就可看出来。

下面说明下通过MMIO访问的那些寄存器,它们分为两类,直接访问的寄存器和间接访问的寄存器。

直接访问的寄存器就是下面几个:

这里的xy的值需要另外确定,这个跟平台相关,一般就是00h。

间接方位寄存器有下面几个:

间接寄存器的访问需要通过直接寄存器来完成,可以看到直接寄存器两个分别是index和data,所以过程就是往index中写入需要读取的间接寄存器的偏移,然后通过data得到间接寄存器的值。下面是一个代码示例:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
   Read a 32-bit I/O APIC register.
 
   If Index is >= 0x100, then ASSERT().
   
   @param  Index  Specifies the I/O APIC register to read.
 
   @return  The 32-bit value read from the I/O APIC register specified by Index.
**/
UINT32
EFIAPI
IoApicRead (
   IN UINTN  Index
   )
{
   ASSERT (Index < 0x100 );
   MmioWrite8 (PcdGet32 (PcdIoApicBaseAddress) + IOAPIC_INDEX_OFFSET, (UINT8)Index);
   return MmioRead32 (PcdGet32 (PcdIoApicBaseAddress) + IOAPIC_DATA_OFFSET);
}

 

间接寄存器跟Local APIC中的寄存器组形式类似。10-3Fh称为RedirectionTable寄存器,跟Local APIC中的LVT类似。

RT寄存器一般有24个,每个占64位。

关于这些寄存器的说明,可以参考http://www.intel.com/design/chipsets/datashts/29056601.pdf。

 

没有找到相关I/O APIC的初始化代码。不过有一个BaseIoApicLib.inf库有配置Redirection Table寄存器。

另外,对应有一个模块HpetTimerDxe.inf,里面有设置I/O APIC和添加中断处理函数的代码:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
//
// Initialize I/O APIC entry for HPET Timer Interrupt
// Fixed Delivery Mode, Level Triggered, Asserted Low
//
IoApicConfigureInterrupt (mTimerIrq, PcdGet8 (PcdHpetLocalApicVector), IO_APIC_DELIVERY_MODE_LOWEST_PRIORITY, TRUE, FALSE);
 
// 中间代码省略 
 
//
// Install interrupt handler for selected HPET Timer
//
Status = mCpu->RegisterInterruptHandler (mCpu, PcdGet8 (PcdHpetLocalApicVector), TimerInterruptHandler);
ASSERT_EFI_ERROR (Status);

 

MSI

 

MSI全称Message Signaled Interrupt,它是PCI/PCIE体系的一部分。

在PCI总线中,所有需要提交中断请求的设备,必须能够通过INTx引脚(这个引脚会连接到8259或者I/O APCI上)提交中断请求,而MSI机制是一种可选机制(说是可选,但是不确定在PCI上要怎么实现);

而在PCIE总线中,PCIE设备必须支持MSI或者MSI-X(MSI的升级版本)中断请求机制,而可以不支持INTx中断。目前一般的PCIE设备都使用MSI机制来提交中断。

MSI使用了MSI Capability结构来实现中断请求。PCIE设备在提交MSI请求时,总是想这种Capability结构中的Message Address的地址写Message Data,从而组成一个寄存器写TLP,向处理器提交中断请求。

MSI Capability的结构如下:

它有几种不同的类型,根据位数和是否带MASK来区分。

其中:

Capability ID:它的值是0x05,表示的是MSI的ID号;

Next Pointer:指向下一个Capability,这是PCIE配置结构的组成形式,这里可以不关注;

Message Control:状态和控制寄存器,具体位的意义如下:

Message Address/Message Upper Address:存放目的地址。

Message Data:用来存放MSI报文使用的数据。

Mask Bits:一个PCIE设备使用MSI机制时,最多可以使用32个中断,对应到这里的32位,BIT置1时表示屏蔽中断。

Pending Bits:该部分只读,也是32位,对应到可用的32个中断。

Mask Bits和Pending Bits配合使用,当系统软件(中断为什么需要软件来触发,那硬件中断怎么办?)将Mask Bits的某一位从1改写为0,PCIE设备发送MSI报文想处理器提交中断请求,同事讲Pending Bits中对应的位清0(什么时候是1呢?)。

 

补充说明

本文主要介绍的是中断,但是对于异常(Exception),x86平台下的处理机制也是一样的。


转自http://www.2cto.com/kf/201702/561719.html

你可能感兴趣的:(x86,x86,英特尔,中断,UEFI)