接前一篇文章:
本回解析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设备就向虚拟机内的操作系统触发了一个中断。