接前一篇文章:
上一回重点讲了 PCI 设备中断的相关概念。本回开始讲解代码实现细节。
PCI链接设备到 中断控制器 上的路由信息是通过SeaBIOS配置完成的。SeaBIOS中的pci_bios_init_device函数会初始化piix3/4设备。pci_bios_init_device函数在roms/seabios/src/fw/pciinit.c中,代码如下:
- static void pci_bios_init_device(struct pci_device *pci)
- {
- dprintf(1, "PCI: init bdf=%pP id=%04x:%04x\n"
- , pci, pci->vendor, pci->device);
-
- /* map the interrupt */
- u16 bdf = pci->bdf;
- int pin = pci_config_readb(bdf, PCI_INTERRUPT_PIN);
- if (pin != 0)
- pci_config_writeb(bdf, PCI_INTERRUPT_LINE, pci_slot_get_irq(pci, pin));
-
- pci_init_device(pci_device_tbl, pci, NULL);
-
- /* enable memory mappings */
- pci_config_maskw(bdf, PCI_COMMAND, 0,
- PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_SERR);
- /* enable SERR# for forwarding */
- if (pci->header_type & PCI_HEADER_TYPE_BRIDGE)
- pci_config_maskw(bdf, PCI_BRIDGE_CONTROL, 0,
- PCI_BRIDGE_CTL_SERR);
- }
其中的pci_init_device函数也在同文件(roms/seabios/src/fw/pciinit.c)中,代码如下:
- int pci_init_device(const struct pci_device_id *ids
- , struct pci_device *pci, void *arg)
- {
- while (ids->vendid || ids->class_mask) {
- if ((ids->vendid == PCI_ANY_ID || ids->vendid == pci->vendor) &&
- (ids->devid == PCI_ANY_ID || ids->devid == pci->device) &&
- !((ids->class ^ pci->class) & ids->class_mask)) {
- if (ids->func)
- ids->func(pci, arg);
- return 0;
- }
- ids++;
- }
- return -1;
- }
传给pci_init_device函数的实参为:
pci_init_device(pci_device_tbl, pci, NULL);
其中,第一个参数pci_device_tbl的定义也在同文件(roms/seabios/src/fw/pciinit.c)中,代码如下:
- static const struct pci_device_id pci_device_tbl[] = {
- /* PIIX3/PIIX4 PCI to ISA bridge */
- PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371SB_0,
- piix_isa_bridge_setup),
- PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_0,
- piix_isa_bridge_setup),
- PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_LPC,
- mch_isa_bridge_setup),
-
- /* STORAGE IDE */
- PCI_DEVICE_CLASS(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371SB_1,
- PCI_CLASS_STORAGE_IDE, piix_ide_setup),
- PCI_DEVICE_CLASS(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB,
- PCI_CLASS_STORAGE_IDE, piix_ide_setup),
- PCI_DEVICE_CLASS(PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_STORAGE_IDE,
- storage_ide_setup),
-
- /* PIC, IBM, MPIC & MPIC2 */
- PCI_DEVICE_CLASS(PCI_VENDOR_ID_IBM, 0x0046, PCI_CLASS_SYSTEM_PIC,
- pic_ibm_setup),
- PCI_DEVICE_CLASS(PCI_VENDOR_ID_IBM, 0xFFFF, PCI_CLASS_SYSTEM_PIC,
- pic_ibm_setup),
-
- /* PIIX4 Power Management device (for ACPI) */
- PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3,
- piix4_pm_setup),
- PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_SMBUS,
- ich9_smbus_setup),
-
- /* 0xff00 */
- PCI_DEVICE_CLASS(PCI_VENDOR_ID_APPLE, 0x0017, 0xff00, apple_macio_setup),
- PCI_DEVICE_CLASS(PCI_VENDOR_ID_APPLE, 0x0022, 0xff00, apple_macio_setup),
-
- /* Intel IGD OpRegion setup */
- PCI_DEVICE_CLASS(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA,
- intel_igd_setup),
-
- PCI_DEVICE_END,
- };
由此就引出了piix_isa_bridge_setup函数。它也在同文件(roms/seabios/src/fw/pciinit.c)中,代码如下:
- /* PIIX3/PIIX4 PCI to ISA bridge */
- static void piix_isa_bridge_setup(struct pci_device *pci, void *arg)
- {
- int i, irq;
- u8 elcr[2];
-
- elcr[0] = 0x00;
- elcr[1] = 0x00;
- for (i = 0; i < 4; i++) {
- irq = pci_irqs[i];
- /* set to trigger level */
- elcr[irq >> 3] |= (1 << (irq & 7));
- /* activate irq remapping in PIIX */
- pci_config_writeb(pci->bdf, 0x60 + i, irq);
- }
- outb(elcr[0], PIIX_PORT_ELCR1);
- outb(elcr[1], PIIX_PORT_ELCR2);
- dprintf(1, "PIIX3/PIIX4 init: elcr=%02x %02x\n", elcr[0], elcr[1]);
- }
由代码可见,在piix3/4设备的配置空间0x60开始的地方,写入了PCI链接设备到中断线的路由关系。代码片段为:
- /* activate irq remapping in PIIX */
- pci_config_writeb(pci->bdf, 0x60 + i, irq);
LNKA、LNKB、LNKC、LNKD分别对应10、10、11、11这4条中断线。何以见得?pci_irqs[]在同文件(roms/seabios/src/fw/pciinit.c)中,定义如下:
- /* host irqs corresponding to PCI irqs A-D */
- const u8 pci_irqs[4] = {
- 10, 10, 11, 11
- };
PCI设备到PCI链接设备的路由信息是写在ACPI表中的 ,在此不深入ACPI的具体细节,仅简单介绍一下基本概念。
ACPI的英文全称为 Advanced Configuration and Power Interface ,中文译为 高级配置电源管理接口 。 ACPI提供了处理器硬件和操作系统之间的一组接口,用来对处理器以及设备的电源进行管理,并且可以配置外部设备使用的系统资源 。ACPI使用 一系列描述符表来 管理处理器和设备资源。PCI设备的中断路由是通过build_prt函数完成的,该函数会构造包含128项数组的数据,每一项表示一个设备的路由信息,说明该设备路由到哪个PCI链接设备。映射关系按照如下公式完成:
(slot+pin)&3 -> "LNK[D|A|B|C]"
下一回开始解析QEMU中PCI设备触发中断的流程。