来源 http://people.freebsd.org/~jhb/papers/bsdcan/2007/article/article.html
摘要
在拥有多个独立设备的计算机里一个重要的元素是一个设备通知cpu它需要通过中断引起注意的能力。操作系统可见的对于pci设备的中断技术是非常复杂的,特别是在x86 pc系统上。这篇文章会涉及PCI INTx中断在x86上实现的多种方式以及系统BIOS与操作系统之间交流实现的方法。他同样会涉及较新的用来解决一些INTx中断的局限的Message Signaled Interrupts。最后,这篇文章会提供在x86平台上FreeBSD对INTx和MSI中断的实现的概述。
介绍
中断是现代机器上的设备支持的一个重要部分。他允许系统采用事件驱动的算法来取代轮询算法。这继而允许了诸如cpu时间等系统资源的更高效的利用,特别是在同时拥有多个设备操作的系统上。
操作系统在处理设备中断时的一个必须执行的任务是决定当一个中断触发后哪一个设备需要关注。这个映射可以通过多种方法完成。一些系统会将这个映射信息包含在一个被硬编码进操作系统或者固件的静态表中。其他系统可能会利用可编程硬件来完成动态映射,还有一些系统结合了这两种方法。
PCI允许设备使用两种不同的方式生效中断。第一种方式包含了一个专有的边带信号,它被称为INTx中断。第二种方式包含了如同DMA操作一样通过数据总线发送的特殊的内存写操作,它被称为消息信号中断(MSI)。MSI中断只在较新的设备上有效,因为它是最近才进入到PCI标准中的。
首先,先规定一个命名法。在PCI中,设备这个单词实际上指的是一个拥有一至八个功能的硬件的一部分。例如一个典型的x86芯片可能会包含多个USB控制器,而这些控制器是一个单独的PCI设备的多个功能。一个PCI扩张插槽是一个PCI设备,而且一个单独的扩张卡可能会包含多个功能。但是,从操作系统的视角来看,每个PCI设备的功能都是一个逻辑的操作系统设备。例如,在Darwin和FreeBSD,每个多功能PCI设备的功能都会收到它自己的设备对象并且能够被不同的驱动服务。为了避免混淆,文章的剩余部分将会使用“slot”来表示一个PCI设备,使用“设备”来表示一个逻辑操作系统设备。
旧的PCI INTx中断
如同上面提到的,PCI INTx中断通过使用边带信号实现。每个PCI slot都有四个中断信号: INTA#, INTB#, INTC#, INTD#。每个PCI功能可能会使用其中的一个。功能通过标准的配置空间的一个寄存器指示它使用哪一个信号。
PCI中断线是电平触发的并且在信号被拉低的时候被生效,这允许了多个中断信号共享同一个物理信号线。
x86 cpu上的中断
异常,不可屏蔽中断(NMIs),处理器键中断,以及设备中断在x86 cpus上都是用相同的中断机制。操作系统向cpu提供中断描述符表。当一个中断被确认,cpu决定关联的IDT索引或者向量。它能够通过向外部的中断控制器查询来获得IDT向量。如果中断被一个消息触发,那么消息里就会包含IDT向量。异常和NMI被分配了静态的IDT向量。一旦cpu得到了IDT向量,它使用这个向量作为进入IDT的一个索引。然后它就会触发IDT slot里存放的处理器。因此,为了一个操作系统在x86 cpu上处理设备中断,它必须知道哪一个设备驱动处理句柄和给定的IDT 向量关联。
因此,在一端我们有了一个PCI中断线会被需要关注的PCI功能发送信号。在另一端,我们有了一个CPU接收中断向量。在中间的就被总称为中断控制器。
中断控制器
在x86平台上的中断控制器负责接收设备发来的中断信号,将信号映射为一个IDT向量,然后用这个向量向系统中的一个或多个cpu发起中断。但是,在x86平台上有一些奇怪的事情。首先,由于在PC-AT上的原始的中断控制器和总线的限制,独立的可编程中断路由被加到平台中来适应PCI中断信号和PC-AT中断控制器。第二,随着x86平台不断发展,一组全新的中断控制器,被称作高级可编程中断控制器(APICs),被引入进来。为了保持后向兼容性,拥有APICs的系统仍然包含了PC-AT中断控制器并且任一系统都可以在现代系统中处理中断。甚至两者可以在同一时间被使用,尽管不鼓励这样的行为。
8259A 主从PICs(PC-AT)
原始的PC-AT包括两个8259A可编程中断控制键链接在一起。如同PC-AT上的其他部分,这样的设置称为了实际上的x86平台的标准。每个8259A有8个中断输入信号被分配给8个连续的IDT向量。两个PIC因此一共有16个中断输入。在PC-AT上这些信号被称为ISA IRQs 从0到15。但是,第二个8259A(称为从PIC)被连接到第一个8259A(称为主PIC)的第三个输入引脚。因此,ISA IRQ 2
实际上对设备中断是不可用的,实际上对于设备中断来说一共只有15个中断输入是可以使用的。
原始的PC-AT会为它的设备使用一个ISA总线。ISA中断是边沿触发的,当设备将信号从低升到高时中断被确认。这限制了ISA中断被多个设备共享,所以每个ISA设备都要求在8259A上有一个专有的中断。所有的PC-AT兼容系统都包括一个使用了IRQ 0的ISA定时器,一个使用了IRQ1的键盘控制器,以及一个使用了IRQ8的实时时钟。如果存在一个可选的浮点协处理器,那么它会使用IRQ13。当PCI被引进进来的时候,大部分的PC-AT系统包括了两个使用IRQ3和IRQ4的串口,一个使用IRQ8的软盘控制器,一个使用IRQ7的打印机端口,一个使用IRQ12的PS/2鼠标,以及两个使用IRQ14和IRQ15的IDE控制器。这给PCI中断只留下了IRQ5,9,10,11可以使用。
让事情变得更复杂的是,系统系统包含了另外的ISA设备比如额外的串口或者打印机端口或者声卡。每一个这样的额外ISA设备同样要求一个专有的IRQ。比如声卡一般会使用IRQ5,。因此,PCI中断可以使用的IRQ集合并不是固定的。实际上,添加或者移除一个ISA外部设备会在重启的时候改变这个集合。为了处理这个复杂性,可编程中断路由被加入进来。
可编程中断路由(PCI链接设备)
一个可编程中断路由被用来路由PCI中断到另一个中断控制器的中断输入。一个路由包含了多个输入信号和输出信号。每一个输出信号被挂接到一个中断控制器的输入上。每一个输入信号可以被路由到其中的一个输出信号。多个输入信号可以被路由到同一个输出信号,多个PCI中断信号可以被路由到一个输入信号上。
系统软件,比如BIOS或者操作系统,会负责编程中断路由。编程路由包括路由每一个在使用中的输入信号到一个输出信号。比如,一个PC-AT兼容系统可能会将可编程中断路由上的输出引脚路由到在8259A上的IRQ3,4,5,7,9,10,11。如果IRQs3,4,7正在被ISA设备使用,那么每一个中断路由上的输入引脚必须被路由到IRQs5,9,10,11中的一个。注意路由一个单独的输入引脚(也被称作一个PCI连接设备)到一个IRQ会路由连接到这个输入引脚的所有PCI中断到这个IRQ。也就是说, 你不能地任意地路由独立的PCI中断。相反地,你只能路由连接到一个输入引脚的PCI中断组。这个组是被硬件设定的并且不能被系统软件更改。而且,如果一个系统的输入引脚超过了可用的IRQ,那么至少有一些输入引脚会被路由到相同的IRQ,导致这些输入引脚上的PCI中断集合会共享同一个IRQ。
在图1中有一个使用可编程中断路由和8259A连接的例子。在左边有6个PCI slot。每个slot的INTA#被连接到中断路由器的输入引脚。注意到一些PCI中断被连接到中断路由器上的同一个输入引脚。比如前两个中断都被连接到了LINKA输入引脚。中断路由器也有4个输出引脚被连接到对应于两个8259A控制器上的IRQ5,9,10,11的输入引脚。假设LNKA被路由到O0,LNKB被路由到O1,LNKC被路由到O2,LNKD被路由到O0。那么PCI slot0,1,5的中断会被路由到IRQ5,。PCI slot2 的中断会被路由到IRQ11,PCI slot3,4的中断会被路由到IRQ10。
I/O APICs
许多x86系统,包括最近的系统,包含了中断控制器的第二种集合,称为APIC。在这些系统中,每个CPU包括了一个本地APIC用来接受中断消息并且使用它们来向CPU生效中断。包含一个或多个I/O APIC的芯片负责将设备的中断信号转换为发送给一个或多个本地APIC的消息。
8259A和I/O APIC之间的最大一个区别是I/O APIC上的所有引脚都是独立的。对8259A来说,8个输入引脚被映射到8个连续的IDT向量,并且所有的中断被发送给同一个CPU。I/O APIC,在另一方面,并没有控制器范围的设定。相反地,每个引脚被独立编程。每个引脚被操作系统分配自己的IDT向量并且能被映射到一个或者多个CPU。I/O APIC还可以包含可变数量的引脚。典型地,一个I/O APIC包含16,24,32个输入引脚。
PCI中断信号有多种方式路由到I/O APIC中断引脚。最早的APIC系统包括了一个有16个输入引脚的单独的I/O APIC,它只是简单地重复了两个8259A的功能。在这些系统中,16个输入引脚被用作16个ISA IRQ,而PCI中断使用一个PIR路由到一个ISA IRQ。然而,大部分APIC系统对PCI中断信号采用专有的I/O APIC输入引脚。在这些系统中,第一个I/O APIC的前16个引脚留作16个ISA IRQ。PCI中断信号连接到第一个I/O APIC的其他引脚(如果它包含超过16个引脚)和其他额外I/O APIC(如果有的话)的输入引脚上。一些最新的系统开始利用PIR路由一些PCI中断到一个专门留给PCI中断的I/O APIC 输入引脚集合上。
图2包含了一个PCI中断直接连接到有24个引脚的I/O APIC的例子。如同之前的图片,左边有6个PCI slot,并且每个slot的INTA#引脚被连接到I/O APIC的输入引脚上。如果多个中断线被连接到同一个I/O APIC输入引脚上,系统在使用APIC的时候还可以共享PCI中断。
PCI 中断路由
PCi中断路由包括当一个给定的PCI中断信号被确定后计算出哪一个平台相关的中断被生效。在x86机器上,这包括了一个给定的PCI中断信号被生效后计算出中断控制器上的哪一个输入引脚被生效。
PCI-PCI 桥Swizzle
大部分的PCI中断路由信息是平台相关的。但是PCI规格确实定义了一种特殊情况PCI中断路由是平台无关的。特别地,如果一个附加的卡包含了它自己的PCI-PCI桥,那么在PCI-PCI桥之后的PCI slot的中断引脚会被映射到PCI-PCI桥的上游侧的中断引脚。
如果你分配值0-3给INTA#-INTD#,那么这种映射可以被描述为:
其中是PCI-PCI桥的上游侧的中断引脚而和是跨过桥被路由的中断信号的PCI slot和引脚。因此,slot 0的INTA#被映射为桥上的INTA#,对slot 1,INTA#被映射为桥上的INTB#,而INTD#被映射为桥上的INTA#。
尽管PCI规格只定义了这种在附加卡上的PCI-PCI桥的路由,一些x86系统也为不是在附加桥上,而是在主板上的PCI-PCI桥之后的PCI slot使用这种映射。这时规则是如果BIOS没有为PCI-PCI桥之后的PCI总线提供另外的路由信息,那么上述的映射应当被用来路由跨PCI-PCI桥路由到父PCI总线上的中断。
通过$PIR路由
x86 BIOS提供的第一个PCI中断路由表示$PIR表,这个名字来源于它的前4个字节“$PIR”。这个表描述了PCI中断信号是怎样连接到可编程中断路由的输入引脚。而且,他提供了一些细节可以被操作系统用来直接编程中断路由或者向PCI BIOS询问从而路由独立链接。注意$PIR只知道ISA IRQ,所以一般来说它无法和APIC一起使用。这个规则的一个例外是一些早期的APIC系统只通过他们单一的I/O APIC路由ISA IRQ并且仍然使用一个可编程中断路由器和一个$PIR表来路由PCI中断到ISA IRQ。
$PIR路由
$PIR表的主体由一个可变大小的slot 条目数组组成。每个slot 条目包含了关于一个单独的PCI slot的细节,比如这个slot是否是main chassis上的嵌入设备或者slot代表了母板上的一个物理slot。每个slot 条目还包含了一个4个引脚条目的数组,这些引脚条目包含了slot的INTA#,INTB#,INTC#,INTD#的路由信息。每个引脚条目包含了一个字节,它含有链接标识符和对这个引脚条目可用的ISA IRQ位图。因为每个slot条目总是包含了所有4个引脚条目,但是不是所有的嵌入设备的slot都使用了所有的4个引脚,因此一个引脚可以将链接标识符设为0来标记没有被连接。
每个非零连接标识符对应的是可编程中断路由器上的一个特定的输入引脚。因此,所有的拥有相同链接标识符的(slot,pin)条目都在物理上连接到了同一个输入引脚并且将总是共享同一个中断。而且,将一个链接路由到一个 IRQ会将所有的连接到这个链接的(slot pin)路由到那个IRQ。
使用$PIR将一个PCI中断路由到ISA IRQ是很直接的过程。首先,要被路由的PCI中断的总线,slot,引脚被用来在$PIR表中查询一个(slot,pin)条目。这个PCI中断对应的链接然后就从(slot,pin)条目中被取出来。若这个条目已经被路由到一个IRQ,那么这次路由就完成了。否则,必须选出一个IRQ而且这个链接必须路由到这个IRQ。
一旦操作系统为一个没有被路由的链接选择了一个IRQ,一共有两种方法可以供链接路由到这个IRQ。首先,操作系统可以手动的编程中断路由器来路由这个链接。第二, PCI BIOS可以提供BIOS调用来路由一个独立的链接到一个特定的IRQ。
为了帮助第一种方法,$PIR表包含了PCI位置(总线,slot,功能)和中断路由器的设备ID。设备ID可以用来决定一个路由器驱动。对一些路由器,举例来说,可编程模型是在(slot,pin)条目中的链接描述符是一个独立的字节寄存器的偏移, 要使用的IRQ会被写入到这个寄存器中来路由这个连接。这种方法要求操作系统维持几个不同的中断路由器驱动,并且新的路由器直到一个新的驱动被写入或者一个已经存在的驱动被更新过之前都不被支持。
FreeBSD的$PIR实现
略
通过MP表路由
当APIC被引入x86,一个新的表被用来描述PCI中断到I/O APIC输入引脚的路由。这个表称为MP表。MP表包含了几个不同类型的条目,包括枚举处理器和I/O APIC的条目。它还包含了描述所有连接到I/O APIC输入引脚上的设备中断的条目,这些条目用来路由PCI中断。
每个MP表中的中断条目包括了设备中断作为源,一个I/O APIC输入引脚作为目标。一个源中断包含了一个总段类型(比如ISA或者PCI),一个总线ID以及一个IRQ值。对于PCI中断,总线ID是中断属于的slot所在的总线号。IRQ值包括了PCI slot和intpin。它的低二位包含了intpin(0表示INTA#,etc),第2至第6位包括了slot。目标是一个I/O APIC的APIC ID和一个输入引脚号码。
到这里,使用MP表路由一个PCI中断已经是很简单了。内核简单地简单地走查MP表知道发现一个条目的源中断符合PCI总线,slot和intpin。他然后就使用目的I/O APIC输入引脚作为PCI中断的目标。
在图4中有一个mptable的中断条目输出的例子。这个系统包括了三个I/O APIC,它们的APIC ID是8,9,10。ISA IRQ被连接到第一个I/O APIC的前6个引脚。PCI总线0和4上的中断被连接到第一个I/O APIC的其他引脚。
通过ACPI路由
高级配置和电源接口(ACPI)开发的目的部分是为了x86机器提供一个统一的配置管理接口。$PIR表和MP表都在配置方法之内而且表被合并到了ACPI总括里。因此,ACPI提供了一个统一的接口取代$PIR和MP表来路由PCI中断。有4个主要的组件来支持ACPI中断路由:全局系统中断(GSIs),全局_PIC方法,PCI中断链接设备,以及每PCI总线的_PRT方法。
全局系统中断
ACPI使用一个cookie系统来命名全局系统中断。每一个中断控制器的输入引脚使用一个非常简单的策略分配到了一个GSI。在8259A的情况下,GSI直接映射到ISA IRQ。因此,IRQ0就是GSI0,以此类推。
ACPI的情况稍微复杂一点,但是仍然很简单。每个I/O APIC被BIOS分配一个基GSI。在I/O APIC上的每一个输入引脚通过在基GSI上增加引脚号码从而映射到一个GSI号码。因此,如果一个I/O APIC有一个基GSI是N,那么引脚1的GSI号是N+1,以此类推。基GSI是0的I/O APIC映射ISA IRQ到它的前16个输入引脚。因此,ISA IRQ总是有效地1:1映射到GSI上。
_PIC方法
全局_PIC方法是一个全局的ACPI函数,操作系统在boot过程中调用这个函数来通知ACPI它计划使用哪一个中断硬件的集合。它接收一个输入参数来指示哪一个使用哪一个中断模式。对x86平台,参数有两个可能的值:0表示PIC模式(就是8259A),1表示APIC模式。boot的时候模式默认为PIC模式。经典地,_PIC的时间将中断模式保存到一个全局的变量,变量之后会被_PRT方法调用来决定返回哪一个路由表。
PCI中断链接设备
每一个可编程中断路由器上的输入引脚都被表示为ACPI命名空间中的一个设备。这些设备被叫做PCI哈总段链接设备并且并标记了一个PNP0C0F的PnpID。每个链接的目的地址作为设备资源列表的一个IRQ资源被设置。它通过_CRS,_PRS,_SRS方法来管理,正如其他ACPI设备如在LPC总线上的内建串口管理IRQ资源。
略
_PRT 方法
略
FreeBSD INTx 实现
略
PCI 消息信号中断
旧的PCI INtx 中断工作,但是他们有几个限制。第一,每一个PCI 功能只允许一个单一的中断。第二,PCI INTx 中断使用的是和PCI数据交互中用到的地址线,数据线独立的信号。
单一中断限制会成为高性能设备的一个瓶颈。举例来说,在一些以太网设配器上,发送和接收单元以并行的方式运行,但是单一的中断强制驱动串行地处理两个单元的时间。另一个设备可以从多个处理程序上受益的例子是一个设备非常频繁的产生特定的性能紧急的事件而同时还会产生其他不那么频繁的中断。一个以太网适配器也可以作为这种情况的一个例子。一个繁忙的以太网设备会产生在处理通信的时候产生一些接收和发送的完成信号,同时它还会为一些其他事件产生中断,比如连接状态改变。如果设备能够将接收和发送完成事件分割成独立的中断,这些事件中断处理程序就能够更小巧和快速,就会允许更小的开销。
为PCI INTx中断使用一个和正常的地址和数据线分离的信号会产生一些问题。首先,在许多x86系统上这要求在母板上分离的物理路径来连接信号到中断控制器的输入引脚上。第二,平台和操作系统为了路由中断不得不一起工作。然而,最大的问题是使用独立的信号,中断可能会在触发中断的事件的所有效果对CPU可见之前到达CPU。结果,所有的PCI设备驱动程序的中断处理程序必须以读取PCI设备上的一个寄存器开始。这个读取直到任何在CPU和PCI之间的悬停的交互没有完成之前不会完成,因此保证了触发中断的事件的所有效果对CPU都是可见的。这会增加中断处理程序的延迟和工作,即使中断处理程序并不需要读取一个PCI寄存器。比如,如果一个以太网设备为发送和接收完成事件拥有专有的中断处理程序,这些处理程序在一般情况下只需要走查在RAM中的描述符ring。强制他们以一个虚设的读取开始只会增加开销和延迟。
从PCI2.2开始,一个称为消息信号中断(MSI)的新的中断机制被引入来解决这些问题。有了MSI,每一个PCI功能能够有用一个或多个中断消息。每一个消息有相关联的地址和数据寄存器,这些寄存器会被操作系统赋值。当一个PCI功能使用MSI生效一个中断,它会执行一个PCI写操作向地址寄存器指定的地址写入数据寄存器里指定的数据。平台必须保证有些东西在监听MSI消息使用的地址的写入,并且把它们转换成中断请求然后发送给一个或多个CPU。
消息的地址和数据域的格式是平台相关的。对x86平台,消息数据包含了中断发生时要触发的IDT向量。因此,MSI中断可以绕过这个复杂的中断路由,操作系统可以直接将MSI中断链接到IDT向量。