QEMU源码全解析 —— PCI设备模拟(14)

接前一篇文章:

本回解析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;
}

QEMU源码全解析 —— PCI设备模拟(14)_第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设备就向虚拟机内的操作系统触发了一个中断。

你可能感兴趣的:(QEMU,KVM,QEMU,KVM,PCI)