接前一篇文章:
本回解析 QEMU 中PCI设备触发中断的流程。
PCI总线 的IRQ路由设置是在pc_init1函数中调用pci_bus_map_irqs和pci_bus_irqs函数完成的。
先来看一下hw/i386/pc_piix.c的pc_init1函数:
- /* PC hardware initialisation */
- static void pc_init1(MachineState *machine,
- const char *host_type, const char *pci_type)
- {
- PCMachineState *pcms = PC_MACHINE(machine);
- PCMachineClass *pcmc = PC_MACHINE_GET_CLASS(pcms);
- X86MachineState *x86ms = X86_MACHINE(machine);
- MemoryRegion *system_memory = get_system_memory();
- MemoryRegion *system_io = get_system_io();
- PCIBus *pci_bus = NULL;
- ISABus *isa_bus;
- int piix3_devfn = -1;
- qemu_irq smi_irq;
- GSIState *gsi_state;
- BusState *idebus[MAX_IDE_BUS];
- ISADevice *rtc_state;
- MemoryRegion *ram_memory;
- MemoryRegion *pci_memory = NULL;
- MemoryRegion *rom_memory = system_memory;
- ram_addr_t lowmem;
- uint64_t hole64_size = 0;
-
- ……
- if (pcmc->pci_enabled) {
- Object *phb;
- ……
- pci_bus_map_irqs(pci_bus,
- xen_enabled() ? xen_pci_slot_get_pirq
- : pc_pci_slot_get_pirq);
- ……
- }
- ……
- if (xen_enabled()) {
- pci_device_set_intx_routing_notifier(
- pci_dev, piix_intx_routing_notifier_xen);
-
- /*
- * Xen supports additional interrupt routes from the PCI devices to
- * the IOAPIC: the four pins of each PCI device on the bus are also
- * connected to the IOAPIC directly.
- * These additional routes can be discovered through ACPI.
- */
- pci_bus_irqs(pci_bus, xen_intx_set_irq, pci_dev,
- XEN_IOAPIC_NUM_PIRQS);
- }
- ……
- }
pci_bus_map_irqs函数在中,代码如下:
- void pci_bus_map_irqs(PCIBus *bus, pci_map_irq_fn map_irq)
- {
- bus->map_irq = map_irq;
- }
pci_bus_irqs函数在hw/pci/pci.c中,代码如下:
- void pci_bus_irqs(PCIBus *bus, pci_set_irq_fn set_irq,
- void *irq_opaque, int nirq)
- {
- bus->set_irq = set_irq;
- bus->irq_opaque = irq_opaque;
- bus->nirq = nirq;
- g_free(bus->irq_count);
- bus->irq_count = g_malloc0(nirq * sizeof(bus->irq_count[0]));
- }
传给pci_bus_irqs函数的实参XEN_IOAPIC_NUM_PIRQS表示的实际是PCI链接设备的数目,PCI连接到中断控制器的配置是BIOS或者内核通过PIIX3的PIRQ[A-D]4个引脚配置的。
pc_pci_slot_get_pirq函数在hw/i386/pc_piix.c中,代码如下:
- /*
- * Return the global irq number corresponding to a given device irq
- * pin. We could also use the bus number to have a more precise mapping.
- */
- static int pc_pci_slot_get_pirq(PCIDevice *pci_dev, int pci_intx)
- {
- int slot_addend;
- slot_addend = PCI_SLOT(pci_dev->devfn) - 1;
- return (pci_intx + slot_addend) & 3;
- }
pc_pci_get_pirq函数得到设备连接到的PCI连接设备。假设设备用的引脚为x,设备的功能号为y,则其连接到(x+y) & 3。注意这种关系不是必需的,只是一种建议的连接方式。
这里也顺带提一下xen_pci_slot_get_pirq函数。xen_pci_slot_get_pirq函数在hw/i386/xen/xen-hvm.c中,代码如下:
- int xen_pci_slot_get_pirq(PCIDevice *pci_dev, int irq_num)
- {
- return irq_num + (PCI_SLOT(pci_dev->devfn) << 2);
- }
接下来分析一个PCI设备到底是如何向 虚拟机 中的操作系统触发中断的。PCI设备调用pci_set_irq函数触发中断。pci_set_irq函数在hw/pci/pci.c中,代码如下:
- void pci_set_irq(PCIDevice *pci_dev, int level)
- {
- int intx = pci_intx(pci_dev);
- pci_irq_handler(pci_dev, intx, level);
- }
其中第2个参数表示是拉高还是拉低电平。
(1)pci_set_irq函数首先调用pci_intx函数得到设备使用的INTX引脚。
pci_intx函数在include/hw/pci/pci_device.h中,代码如下:
- static inline int pci_intx(PCIDevice *pci_dev)
- {
- return pci_get_byte(pci_dev->config + PCI_INTERRUPT_PIN) - 1;
- }
(2)然后调用pci_irq_handler函数。
pci_irq_handler函数在hw/pci/pci.c中,代码如下:
- /* 0 <= irq_num <= 3. level must be 0 or 1 */
- static void pci_irq_handler(void *opaque, int irq_num, int level)
- {
- PCIDevice *pci_dev = opaque;
- int change;
-
- assert(0 <= irq_num && irq_num < PCI_NUM_PINS);
- assert(level == 0 || level == 1);
- change = level - pci_irq_state(pci_dev, irq_num);
- if (!change)
- return;
-
- pci_set_irq_state(pci_dev, irq_num, level);
- pci_update_irq_status(pci_dev);
- if (pci_irq_disabled(pci_dev))
- return;
- pci_change_irq_level(pci_dev, irq_num, change);
- }
1)pci_irq_handler函数首先会判断当前中断线状态是否改变。如果没有改变就直接返回。
pci_irq_state函数在同文件(hw/pci/pci.c)中,代码如下:
- static inline int pci_irq_state(PCIDevice *d, int irq_num)
- {
- return (d->irq_state >> irq_num) & 0x1;
- }
2)如果中断线状态改变了,就会调用pci_set_irq_state以及pci_update_irq_status函数设置设备状态。
pci_set_irq_state函数也在hw/pci/pci.c中,代码如下:
- static inline void pci_set_irq_state(PCIDevice *d, int irq_num, int level)
- {
- d->irq_state &= ~(0x1 << irq_num);
- d->irq_state |= level << irq_num;
- }
pci_update_irq_status函数也在hw/pci/pci.c中,代码如下:
- /* Update interrupt status bit in config space on interrupt
- * state change. */
- static void pci_update_irq_status(PCIDevice *dev)
- {
- if (dev->irq_state) {
- dev->config[PCI_STATUS] |= PCI_STATUS_INTERRUPT;
- } else {
- dev->config[PCI_STATUS] &= ~PCI_STATUS_INTERRUPT;
- }
- }
3)然后调用pci_change_irq_level函数来触发中断。
pci_change_irq_level函数也在hw/pci/pci.c中,代码如下:
- static void pci_change_irq_level(PCIDevice *pci_dev, int irq_num, int change)
- {
- PCIBus *bus;
- for (;;) {
- int dev_irq = irq_num;
- bus = pci_get_bus(pci_dev);
- assert(bus->map_irq);
- irq_num = bus->map_irq(pci_dev, irq_num);
- trace_pci_route_irq(dev_irq, DEVICE(pci_dev)->canonical_path, irq_num,
- pci_bus_is_root(bus) ? "root-complex"
- : DEVICE(bus->parent_dev)->canonical_path);
- if (bus->set_irq)
- break;
- pci_dev = bus->parent_dev;
- }
- pci_bus_change_irq_level(bus, irq_num, change);
- }
1)pci_change_irq_level函数会得到当前设备对应的PCI总线。代码片段如下:
bus = pci_get_bus(pci_dev);
2)然后调用其回调函数map_irq。代码片段如下:
irq_num = bus->map_irq(pci_dev, irq_num);
对于根(root)总线来说,这个回调函数就是上边pc_int1函数中初始化的pc_pci_slot_get_pirq函数(或xen_pci_slot_get_pirq函数)。其会返回实际的中断线。
3)然后调用PCI总线的set_irq回调。代码片段如下:
pci_bus_change_irq_level(bus, irq_num, change);
pci_bus_change_irq_level函数也在hw/pci/pci.c中,代码如下:
- static void pci_bus_change_irq_level(PCIBus *bus, int irq_num, int change)
- {
- assert(irq_num >= 0);
- assert(irq_num < bus->nirq);
- bus->irq_count[irq_num] += change;
- bus->set_irq(bus->irq_opaque, irq_num, bus->irq_count[irq_num] != 0);
- }
最终,经过这一系列的函数调用,PCI设备就向虚拟机内的操作系统触发了一个中断。