接前一篇文章:
上一回重点讲了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设备触发中断的流程。