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

88 篇文章 19 订阅
本文详细解析了QEMU中PCI设备触发中断的流程,从pci_bus_map_irqs和pci_bus_irqs函数开始,深入到pci_set_irq、pci_intx等函数,阐述了PCI设备如何通过中断线状态改变向虚拟机操作系统发送中断信号的过程。
摘要由CSDN通过智能技术生成

接前一篇文章:

本回解析 QEMU 中PCI设备触发中断的流程。

PCI总线 的IRQ路由设置是在pc_init1函数中调用pci_bus_map_irqs和pci_bus_irqs函数完成的。

先来看一下hw/i386/pc_piix.c的pc_init1函数:

  1. /* PC hardware initialisation */
  2. static void pc_init1(MachineState *machine,
  3. const char *host_type, const char *pci_type)
  4. {
  5. PCMachineState *pcms = PC_MACHINE(machine);
  6. PCMachineClass *pcmc = PC_MACHINE_GET_CLASS(pcms);
  7. X86MachineState *x86ms = X86_MACHINE(machine);
  8. MemoryRegion *system_memory = get_system_memory();
  9. MemoryRegion *system_io = get_system_io();
  10. PCIBus *pci_bus = NULL;
  11. ISABus *isa_bus;
  12. int piix3_devfn = -1;
  13. qemu_irq smi_irq;
  14. GSIState *gsi_state;
  15. BusState *idebus[MAX_IDE_BUS];
  16. ISADevice *rtc_state;
  17. MemoryRegion *ram_memory;
  18. MemoryRegion *pci_memory = NULL;
  19. MemoryRegion *rom_memory = system_memory;
  20. ram_addr_t lowmem;
  21. uint64_t hole64_size = 0;
  22. ……
  23. if (pcmc->pci_enabled) {
  24. Object *phb;
  25. ……
  26. pci_bus_map_irqs(pci_bus,
  27. xen_enabled() ? xen_pci_slot_get_pirq
  28. : pc_pci_slot_get_pirq);
  29. ……
  30. }
  31. ……
  32. if (xen_enabled()) {
  33. pci_device_set_intx_routing_notifier(
  34. pci_dev, piix_intx_routing_notifier_xen);
  35. /*
  36. * Xen supports additional interrupt routes from the PCI devices to
  37. * the IOAPIC: the four pins of each PCI device on the bus are also
  38. * connected to the IOAPIC directly.
  39. * These additional routes can be discovered through ACPI.
  40. */
  41. pci_bus_irqs(pci_bus, xen_intx_set_irq, pci_dev,
  42. XEN_IOAPIC_NUM_PIRQS);
  43. }
  44. ……
  45. }

pci_bus_map_irqs函数在中,代码如下:

  1. void pci_bus_map_irqs(PCIBus *bus, pci_map_irq_fn map_irq)
  2. {
  3. bus->map_irq = map_irq;
  4. }

pci_bus_irqs函数在hw/pci/pci.c中,代码如下:

  1. void pci_bus_irqs(PCIBus *bus, pci_set_irq_fn set_irq,
  2. void *irq_opaque, int nirq)
  3. {
  4. bus->set_irq = set_irq;
  5. bus->irq_opaque = irq_opaque;
  6. bus->nirq = nirq;
  7. g_free(bus->irq_count);
  8. bus->irq_count = g_malloc0(nirq * sizeof(bus->irq_count[0]));
  9. }

传给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中,代码如下:

  1. /*
  2. * Return the global irq number corresponding to a given device irq
  3. * pin. We could also use the bus number to have a more precise mapping.
  4. */
  5. static int pc_pci_slot_get_pirq(PCIDevice *pci_dev, int pci_intx)
  6. {
  7. int slot_addend;
  8. slot_addend = PCI_SLOT(pci_dev->devfn) - 1;
  9. return (pci_intx + slot_addend) & 3;
  10. }

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中,代码如下:

  1. int xen_pci_slot_get_pirq(PCIDevice *pci_dev, int irq_num)
  2. {
  3. return irq_num + (PCI_SLOT(pci_dev->devfn) << 2);
  4. }

接下来分析一个PCI设备到底是如何向 虚拟机 中的操作系统触发中断的。PCI设备调用pci_set_irq函数触发中断。pci_set_irq函数在hw/pci/pci.c中,代码如下:

  1. void pci_set_irq(PCIDevice *pci_dev, int level)
  2. {
  3. int intx = pci_intx(pci_dev);
  4. pci_irq_handler(pci_dev, intx, level);
  5. }

其中第2个参数表示是拉高还是拉低电平。

(1)pci_set_irq函数首先调用pci_intx函数得到设备使用的INTX引脚。

pci_intx函数在include/hw/pci/pci_device.h中,代码如下:

  1. static inline int pci_intx(PCIDevice *pci_dev)
  2. {
  3. return pci_get_byte(pci_dev->config + PCI_INTERRUPT_PIN) - 1;
  4. }

(2)然后调用pci_irq_handler函数。

pci_irq_handler函数在hw/pci/pci.c中,代码如下:

  1. /* 0 <= irq_num <= 3. level must be 0 or 1 */
  2. static void pci_irq_handler(void *opaque, int irq_num, int level)
  3. {
  4. PCIDevice *pci_dev = opaque;
  5. int change;
  6. assert(0 <= irq_num && irq_num < PCI_NUM_PINS);
  7. assert(level == 0 || level == 1);
  8. change = level - pci_irq_state(pci_dev, irq_num);
  9. if (!change)
  10. return;
  11. pci_set_irq_state(pci_dev, irq_num, level);
  12. pci_update_irq_status(pci_dev);
  13. if (pci_irq_disabled(pci_dev))
  14. return;
  15. pci_change_irq_level(pci_dev, irq_num, change);
  16. }

1)pci_irq_handler函数首先会判断当前中断线状态是否改变。如果没有改变就直接返回。

pci_irq_state函数在同文件(hw/pci/pci.c)中,代码如下:

  1. static inline int pci_irq_state(PCIDevice *d, int irq_num)
  2. {
  3. return (d->irq_state >> irq_num) & 0x1;
  4. }

2)如果中断线状态改变了,就会调用pci_set_irq_state以及pci_update_irq_status函数设置设备状态。

pci_set_irq_state函数也在hw/pci/pci.c中,代码如下:

  1. static inline void pci_set_irq_state(PCIDevice *d, int irq_num, int level)
  2. {
  3. d->irq_state &= ~(0x1 << irq_num);
  4. d->irq_state |= level << irq_num;
  5. }

pci_update_irq_status函数也在hw/pci/pci.c中,代码如下:

  1. /* Update interrupt status bit in config space on interrupt
  2. * state change. */
  3. static void pci_update_irq_status(PCIDevice *dev)
  4. {
  5. if (dev->irq_state) {
  6. dev->config[PCI_STATUS] |= PCI_STATUS_INTERRUPT;
  7. } else {
  8. dev->config[PCI_STATUS] &= ~PCI_STATUS_INTERRUPT;
  9. }
  10. }

3)然后调用pci_change_irq_level函数来触发中断。

pci_change_irq_level函数也在hw/pci/pci.c中,代码如下:

  1. static void pci_change_irq_level(PCIDevice *pci_dev, int irq_num, int change)
  2. {
  3. PCIBus *bus;
  4. for (;;) {
  5. int dev_irq = irq_num;
  6. bus = pci_get_bus(pci_dev);
  7. assert(bus->map_irq);
  8. irq_num = bus->map_irq(pci_dev, irq_num);
  9. trace_pci_route_irq(dev_irq, DEVICE(pci_dev)->canonical_path, irq_num,
  10. pci_bus_is_root(bus) ? "root-complex"
  11. : DEVICE(bus->parent_dev)->canonical_path);
  12. if (bus->set_irq)
  13. break;
  14. pci_dev = bus->parent_dev;
  15. }
  16. pci_bus_change_irq_level(bus, irq_num, change);
  17. }

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中,代码如下:

  1. static void pci_bus_change_irq_level(PCIBus *bus, int irq_num, int change)
  2. {
  3. assert(irq_num >= 0);
  4. assert(irq_num < bus->nirq);
  5. bus->irq_count[irq_num] += change;
  6. bus->set_irq(bus->irq_opaque, irq_num, bus->irq_count[irq_num] != 0);
  7. }

最终,经过这一系列的函数调用,PCI设备就向虚拟机内的操作系统触发了一个中断。

举报

选择你想要举报的内容(必选)
  • 内容涉黄
  • 政治相关
  • 内容抄袭
  • 涉嫌广告
  • 内容侵权
  • 侮辱谩骂
  • 样式问题
  • 其他