我们继续考虑在x86系统中配置来自外部设备的中断。
在第1部分(
中断控制器的发展 )中,我们研究了中断控制器的理论基础和通用术语;在第2部分(
Linux内核启动选项 )中,我们研究了操作系统在实践中如何在控制器之间做出选择。 在这一部分中,我们将研究BIOS如何为芯片组中的中断控制器配置IRQ路由。
没有现代的BIOS开发公司(AwardBIOS / AMIBIOS / Insyde)公开其程序的源代码。 但幸运的是,有一个
Coreboot ,一个用免费软件替换专有BIOS的项目。 在他的代码中,我们将看到如何配置芯片组中的中断路由。

理论
首先,刷新并补充我们的理论知识。 在
第1部分中,我们为PIC和APIC的情况确定了一条通用的中断路径。
图片:
APIC:
在这些图中,抽象地显示了PCI设备→PIR映射;实际上,它有些复杂。 实际上,每个PCI设备都有4条中断线(INTA#,INTB#,INTC#,INTD#)。 每个PCI设备最多可以具有8个功能,并且每个功能已经具有一个INTx#中断。 设备的每个功能将拉入INTx#的哪一行是固定在硬件中,还是由设备的配置决定。

本质上,功能是单独的逻辑块。 例如,在一个PCI设备中,可能具有Smbus控制器功能,SATA控制器功能,LPC桥功能。 在操作系统方面,每个功能都是具有自己的PCI Config配置空间的单独设备。
在PCI设备中最简单(也是最常见)的情况下,只有一个功能,该中断通过INTA#线进行。 但是一般来说,该设备甚至可能具有4个以上的功能(如我们之前在8个功能中所述),然后其中一些功能必须植入到一条INTx#线路上(PCI中断可以划分该线路)。 同样,对于芯片组中包含的PCI设备,通过写入特殊寄存器,通常可以指示哪些功能使用哪些INTx#行(以及是否完全使用它们)。
系统化我们的知识,我们表示通过INTx#→PIRQy→IRQz从任何PCI功能中断的路径(路由),其中:
- INTx#-该功能将使用的PCI设备的INT#行(INTA#,INTB#,INTC#,INTD#)
- PIRQy-连接INTx#线的PIR的PIRQ线(PIRQA,PIRQB等)
- IRQz-中断控制器(APIC / PIC)上的IRQ线(0、1、2 ...),它连接到PIRQy线
为什么不能仅在INTA#→PIRQA,INTB#→PIRQB等地方连接?
为什么要费心设置路由呢? 假设我们决定不打扰,并将所有PCI设备的所有中断线传送到同一PIRQ线。 这么说吧:
- INTA号→PIRQA
- INTB编号→PIRQB
- 国际标准编号→PIRQC
- INTD#→PIRQD
如上所述,最常见的情况是PCI设备具有一种功能,并且其中断连接到INTA#线路(因为为什么设备开发人员应该以不同的方式启动它?)。 因此,如果我们突然决定像我们写的那样开始所有线路,那么来自设备的几乎所有中断都将被划分为PIRQA线路。 假设她在IRQ16结束比赛。 然后,每当处理器被告知IRQ16线上发生了中断时,它都必须询问连接到IRQ16线(PIRQA)的所有设备的驱动程序是否有中断。 如果有很多这样的设备,这自然不会加速系统对中断的响应。 在这种情况下,PIRQB-PIRQD线路将大部分处于空闲状态。 为了清楚起见,该图说明了该问题:

但是一切都可以像这样完成:

图片有点令人困惑,但关键是我们仅将INTx#线与PIRQy连接到循环(PIRQA,PIRQB,PIRQC,PIRQD,PIRQA,PIRQB,PIRQC,PIRQD,PIRQA,PIRQB,PIRQC,PIRQD等)。 ..)
应该注意的是,这里不仅要考虑到在每条PIRQ线上都装载了相同数量的PCI功能。 毕竟,有些功能很少会产生中断,而有些则是永久性的(例如,以太网控制器)。 在这种情况下,即使为具有这种功能的中断分配单独的PIRQ线路也可以证明是合理的。
基于上述内容,BIOS开发人员除其他任务外,还要确保PIRQ线路均匀加载中断。
BIOS到底应该做什么?
我们在图中进行系统化:

- 1)指出PCI设备的每个功能都拉到INTx#的哪一行
对于外部PCI设备,不执行此项目,但是对于芯片组中包含的PCI设备的功能,则可能是正确的。 - 2)为每个PCI设备配置INTx#→PIRQy映射
值得注意的是,可以有四个以上的标准PIRQy信号(PIRQA,PIRQB,PIRQC,PIRQD)。 例如8:PIRQA-PIRQH。
PIRQy信号在所选中断控制器(APIC / PIC)的IRQz线上传输。 由于我们希望支持所有可能的加载方法(请参阅
第2部分 ),因此我们需要填写两个映射:
- 3a)填写映射PIRQy→IRQz1进行通信PIR→I / O APIC
但是通常这不是必需的,因为PIRQy线路固定在APIC线路上。 常见的解决方案是PIRQA→IRQ16,PIRQB→IRQ17,...最简单的解决方案,因为 通过将PIRQy线路放置在≥16的控制器线路上,您不必担心与ISA设备不可分割的中断发生冲突。 - 3b)填写映射PIRQy→IRQz2进行通信PIR→PIC
如果我们使用通过PIC控制器的路由,则必须提供此信息。 没有像APIC这样的明确解决方案,因为对于PIC,应该意识到与ISA设备不可分割的中断发生冲突的可能性。
最后第四项是帮助OS确定中断路由所必需的。 设备本身通常不使用这些寄存器。
- 4)填写每个PCI功能的中断线/中断引脚寄存器
通常,中断引脚寄存器是自动填充的,并且通常是只读的,因此填充很可能只需要填写中断线寄存器即可。 如果我们使用通过PIC控制器的路由而没有为操作系统提供任何有关路由中断的表,则必须提供这一点(再次参见第2部分 )。 如果提供了表,并且此映射与路由表($ PIR / ACPI)一致,则操作系统通常会将其保留。
应该注意的是,我们尚未触及$ PIR / MPtable / ACPI表,并在将控制权转移给系统加载程序之前考虑如何根据路由中断配置芯片组寄存器。 中断表是另一篇文章的主题(可能是将来的主题)。
因此,研究了理论基础,最后我们开始实践!
练习
作为本系列文章的示例,我使用一个定制板,该板带有Intel Haswell i7处理器和LynxPoint-LP芯片组。 在此板上,我与SeaBIOS一起启动了coreboot。 Coreboot提供了特定于硬件的初始化,而SeaBIOS有效负载提供了用于操作系统的BIOS接口。 在本文中,我将不介绍配置coreboot的过程,而只是尝试通过示例说明应在芯片组中进行哪种BIOS设置以路由来自外部设备的IRQ中断。
由于coreboot项目正在积极开发中,因此本文始终是最新的,因此我们将使用最新的固定版本
4.9 (2018年12月20日发行)的示例来考虑代码。
最接近我的主板是带有Panther版本的Google Beltino。 该主板的主文件夹为
“ src \ mainboard \ google \ Beltino”文件夹。 所有设置都集中在此处,以及该板专用的代码。
因此,让我们开始整理配置以上各项的位置:
1)指出PCI设备的每个功能都拉到INTx#的哪一行
此信息通过DxxIP寄存器(设备xx中断引脚寄存器(IP))在rcba_config结构的
“ src / mainboard / google / beltino / romstage.c”文件中定义。 该寄存器指示每个器件功能的哪个引脚INTx#(A / B / C / D)输出中断。
可能的选项(请参见文件
“ src / southbridge / intel / lynxpoint / pch.h” ):
0h = No interrupt 1h = INTA# 2h = INTB# 3h = INTC# 4h = INTD#
假设多个功能使用相同的引脚。
假定功能可能不会将该引脚用于中断(无中断)。
正如我们在本文开头所看到的一样。
完整代码负责我们指定的商品:
RCBA_SET_REG_32(D31IP, (INTC << D31IP_TTIP) | (NOINT << D31IP_SIP2) | (INTB << D31IP_SMIP) | (INTA << D31IP_SIP)), RCBA_SET_REG_32(D29IP, (INTA << D29IP_E1P)), RCBA_SET_REG_32(D28IP, (INTA << D28IP_P1IP) | (INTC << D28IP_P3IP) | (INTB << D28IP_P4IP)), RCBA_SET_REG_32(D27IP, (INTA << D27IP_ZIP)), RCBA_SET_REG_32(D26IP, (INTA << D26IP_E2P)), RCBA_SET_REG_32(D22IP, (NOINT << D22IP_MEI1IP)), RCBA_SET_REG_32(D20IP, (INTA << D20IP_XHCI)),
为了更好地理解,请考虑以下示例:
范例1:设备0x1d(十进制29)具有一项功能(EHCI控制器)。
在这种情况下,请将中断分配给INTA#。
00:1d.0-INTA#
RCBA_SET_REG_32(D29IP, (INTA << D29IP_E1P)),
范例2:设备0x1f(十进制31)具有热传感器控制器(00:1f.6),SATA控制器2(00:1f.2),SMBus控制器(00:1f.3),SATA控制器1(00:1f) .2)。 我们只想使用SMBus控制器,SATA控制器1和热传感器控制器。
00:1f.2-INTA#(SATA控制器1)
00:1f.3-INTB#(SMBus控制器)
00:1f.2-无中断(未使用SATA控制器2)
00:1f.6-INTC#(热传感器控制器)
对于此配置,您应该编写:
RCBA_SET_REG_32(D31IP, (INTC << D31IP_TTIP) | (NOINT << D31IP_SIP2) | (INTB << D31IP_SMIP) | (INTA << D31IP_SIP)),
范例3:在一个设备中,我们需要的功能数量超过4个。在0x1c设备中,每个功能都负责PCI Express端口。 为了使端口0-5正常工作,并使中断在各行之间平均分配,您可以配置以下命令:
00:1c.0-INTA#(PCI Express端口0)
00.1c.1-INTB#(PCI Express端口1)
00.1c.2-INTC#(PCI Express端口2)
00.1c.3-INTD#(PCI Express端口3)
00.1c.4-INTA#(PCI Express端口4)
00.1c.5-INTB#(PCI Express端口5)
00.1c.6-无中断(未使用端口)
00.1c.7-无中断(未使用端口)
RCBA_SET_REG_32(D28IP, (INTA << D28IP_P1IP) | (INTB << D28IP_P2IP) | (INTC << D28IP_P3IP) | (INTD << D28IP_P4IP) | (INTA << D28IP_P5IP) | (INTB << D28IP_P6IP) | (NOINT << D28IP_P7IP) | (NOINT << D28IP_P8IP)),
2)为每个PCI设备配置INTx#→PIRQy映射
此信息也在文件
“ src \ mainboard \ google \ Beltino \ romstage.c”中定义在rcba_config结构中,但已经通过DxxIR(设备xx中断路由寄存器)寄存器。
该寄存器中的信息显示每条INTx#中断线连接到哪条PIRQx线(A / B / C / D / E / F / G / H)。
RCBA_SET_REG_32(D31IR, DIR_ROUTE(PIRQG, PIRQC, PIRQB, PIRQA)), RCBA_SET_REG_32(D29IR, DIR_ROUTE(PIRQD, PIRQD, PIRQD, PIRQD)), RCBA_SET_REG_32(D28IR, DIR_ROUTE(PIRQA, PIRQB, PIRQC, PIRQD)), RCBA_SET_REG_32(D27IR, DIR_ROUTE(PIRQG, PIRQG, PIRQG, PIRQG)), RCBA_SET_REG_32(D22IR, DIR_ROUTE(PIRQA, PIRQA, PIRQA, PIRQA)), RCBA_SET_REG_32(D21IR, DIR_ROUTE(PIRQE, PIRQF, PIRQF, PIRQF)), RCBA_SET_REG_32(D20IR, DIR_ROUTE(PIRQC, PIRQC, PIRQC, PIRQC)), RCBA_SET_REG_32(D23IR, DIR_ROUTE(PIRQH, PIRQH, PIRQH, PIRQH)),
范例1:正如我们已经发现的,0x1c设备(十进制中的28个)是PCIe端口。
我们建立“直接”连接:
- INTA号→PIRQA
- INTB编号→PIRQB
- 国际标准编号→PIRQC
- INTD#→PIRQD
RCBA_SET_REG_32(D28IR, DIR_ROUTE(PIRQA, PIRQB, PIRQC, PIRQD))
范例2:设备0x1d(十进制29)-INTA#上的一个功能(EHCI控制器),未使用其他行。
将INTA#线连接到PIRQD:
RCBA_SET_REG_32(D29IR, DIR_ROUTE(PIRQD, PIRQD, PIRQD, PIRQD))
在这种情况下,只有第一个PIRQD记录(对于INTA#)才有意义,其余的则没有意义。
3a)填写映射PIRQy→IRQz1(PIR→APIC)
正如我们已经说过的,映射通常在这里是固定的,这种情况也不例外。
- PIRQA→IRQ16
- PIRQB→IRQ17
- ...
- PIRQH→IRQ23
3b)填写映射PIRQy→IRQz2(PIR→PIC)
在coreboot中,用于填充这些寄存器的内容在主板文件夹“ src \ mainboard \ google \ Beltino \”中的
devicetree.cb文件中定义。
devicetree.cb(用于在Linux内核中以类似概念进行通信的名称devicetree,“ cb”是coreboot的缩写)是一个特殊文件,反映了该主板的配置:使用了哪个处理器,芯片组,其中包括哪些设备,关闭等 此外,可以在此文件中指定芯片组配置的特殊信息。 这只是我们需要的情况:
register "pirqa_routing" = "0x8b" register "pirqb_routing" = "0x8a" register "pirqc_routing" = "0x8b" register "pirqd_routing" = "0x8b" register "pirqe_routing" = "0x80" register "pirqf_routing" = "0x80" register "pirqg_routing" = "0x80" register "pirqh_routing" = "0x80"
这些行指定映射PIRQy→IRQz2。 在代码中,在解析devicetree.cb文件之后,它们被转换为“ config-> pirqX_routing”变量。
变量“ config-> pirqa_routing = 0x8b”将意味着PIRQA连接到PIC控制器的IRIC11中断线(0x0b = 11),但是,较高的位(即0x80)表示不执行中断路由。 老实说,以我的经验,这是一个错误,默认情况下,值得打开PIC路由,如果必要,将该位设置为1,操作系统本身将能够切换到I / O APIC。
也就是说,在这种情况下,编写以下内容会更正确:
register "pirqa_routing" = "0x0b" register "pirqb_routing" = "0x0a" register "pirqc_routing" = "0x0b" register "pirqd_routing" = "0x0b" register "pirqe_routing" = "0x80"
我们没有启用最后4个中断,因为 IRQ0中断始终在系统计时器下使用,并且显然不可用(请参阅
常规IBM-PC兼容中断信息 )。
但是,如果我们仔细观察第2点,我们会发现某些PCI设备使用PIRQE-PIRQH线路,因此断开它们的连接是断开设备的正确方法。
所以最好这样写:
register "pirqa_routing" = "0x03" register "pirqb_routing" = "0x04" register "pirqc_routing" = "0x05" register "pirqd_routing" = "0x06" register "pirqe_routing" = "0x0a" register "pirqf_routing" = "0x0b" register "pirqg_routing" = "0x0e" register "pirqh_routing" = "0x0f"
相应寄存器的实际填充发生在函数pch_pirq_init中的文件
src \ southbridge \ intel \ lynxpoint \ lpc.c中。
负责寄存器填充的代码段:
config_t *config = dev->chip_info; pci_write_config8(dev, PIRQA_ROUT, config->pirqa_routing); pci_write_config8(dev, PIRQB_ROUT, config->pirqb_routing); pci_write_config8(dev, PIRQC_ROUT, config->pirqc_routing); pci_write_config8(dev, PIRQD_ROUT, config->pirqd_routing); pci_write_config8(dev, PIRQE_ROUT, config->pirqe_routing); pci_write_config8(dev, PIRQF_ROUT, config->pirqf_routing); pci_write_config8(dev, PIRQG_ROUT, config->pirqg_routing); pci_write_config8(dev, PIRQH_ROUT, config->pirqh_routing);
寄存器地址常量在同一
pch.h文件中描述
#define PIRQA_ROUT 0x60 #define PIRQB_ROUT 0x61 #define PIRQC_ROUT 0x62 #define PIRQD_ROUT 0x63 #define PIRQE_ROUT 0x68 #define PIRQF_ROUT 0x69 #define PIRQG_ROUT 0x6A #define PIRQH_ROUT 0x6B
将此芯片组的映射PIRQy→IRQz2写入PIRQy_ROUT寄存器中的LPC PCI设备(地址00:1f.0)。 应该注意的是,通常不允许每个PIC使用全部15条IRQz2线路,而只允许使用一部分(例如3、4、5、6、7、9、10、11、12、14、15)。 这些寄存器的描述应包含有关哪些IRQ可用于将PIRQ线中的中断分配给它们的信息。 因此,仅当在线IRQ3,IRQ4,IRQ5,IRQ6,IRQ10,IRQ11,IRQ14,IRQ15上的PIRQ分配可用时,我们上面提出的映射才有可能。 但是,如果我们仔细查看pch_pirq_init函数之前的注释,我们将看到它是:
4)填写每个PCI功能的中断线/中断引脚寄存器
在PCI配置空间(每个PCI均具有符合标准的功能),我们有2个感兴趣的寄存器:
- 3Ch:中断线-您需要在其中写入IRQz2号(0到15之间的数字),该中断号是使用PIC控制器时函数最终提取的中断号
- 3Dh:中断引脚-显示功能使用哪一行INTx#(A / B / C / D)
让我们从最后一个开始。 中断引脚寄存器将根据我们在第1段中所做的芯片组设置(DxxIP寄存器)自动填充,并且为只读。
因此,剩下的就是为每个PCI功能的IRQz2中断填充中断线寄存器。
了解映射PIRQy→IRQz2(项目3b)以及映射INTx#→PIRQy(项目2)后,您可以轻松地为每个函数填写中断线寄存器,知道它使用哪个INTx#中断(项目1)。
在coreboot中,中断行寄存器也填充在pch_pirq_init函数的
src \ southbridge \ intel \ lynxpoint \ lpc.c文件中:
for (irq_dev = all_devices; irq_dev; irq_dev = irq_dev->next) { u8 int_pin=0, int_line=0; if (!irq_dev->enabled || irq_dev->path.type != DEVICE_PATH_PCI) continue; int_pin = pci_read_config8(irq_dev, PCI_INTERRUPT_PIN); switch (int_pin) { case 1: int_line = config->pirqa_routing; break; case 2: int_line = config->pirqb_routing; break; case 3: int_line = config->pirqc_routing; break; case 4: int_line = config->pirqd_routing; break; } if (!int_line) continue; pci_write_config8(irq_dev, PCI_INTERRUPT_LINE, int_line); }
出于某种原因,此代码暗示在任何情况下映射都是INTA#→PIRQA,INTB#→PIRQB,INTC#→PIRQC,INTD#→PIRQD。 尽管实际上我们发现它可能有所不同(请参见第2段)。
通常,“ Eric Biederman曾经说过”,我们将其复制到任何地方:
$ grep "Eric Biederman once said" -r src/ src/southbridge/intel/fsp_bd82x6x/lpc.c: /* Eric Biederman once said we should let the OS do this. src/southbridge/intel/i82801gx/lpc.c: /* Eric Biederman once said we should let the OS do this. src/southbridge/intel/i82801ix/lpc.c: /* Eric Biederman once said we should let the OS do this. src/southbridge/intel/lynxpoint/lpc.c: /* Eric Biederman once said we should let the OS do this. src/southbridge/intel/sch/lpc.c: /* Eric Biederman once said we should let the OS do this.
通常,coreboot并不真正在乎旧版中断支持。 对此错误感到惊讶不值得。 加载现代操作系统时,这不会打扰您,但是如果您突然需要使用“ acpi = off nolapic”选项加载Linux,则几乎无法做到这一点。
结论
总之,我们将重复芯片组中用于路由PCI中断所需的典型信息:
- 指示每个PCI功能拉哪条INTx#行
- 为每个PCI设备配置INTx#→PIRQy映射
- 填充映射PIRQy→IRQz1(PIR→APIC)和映射PIRQy→IRQz2(PIR→PIC)
- 填写每个PCI功能的PCI配置空间的中断线/中断引脚寄存器。