抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

Preface

PCI Local Bus Specification: 2.2.6. Interrupt Pins (Optional)

pci总线规范为pci设备准备了4个中断引脚(INTx#)用于传递中断。设备所使用的中断引脚信息保存在configuration space的Interrupt Pin Register中。这4个中断引脚在设备间的共享的,引脚的分配pci spec推荐遵循一定的规则,详见Implementation Note: Interrupt RoutingInterrup Line Register中记录设备对应的IRQ,可以使用pci总线规范中推荐的规则建立设备INTx#IRQ的联系,见2.2.6. Interrupt Pins (Optional): Interrupt Routing

中断支持

  • 连接的中断线
    • Interrupt Pin Register中记录设备所连接的中断引脚(INTA#, INTB#, INTC#, INTD#)
    • ref:
      • PCI Local Bus Specification: 2.2.6. Interrupt Pins (Optional)
      • PCI Local Bus Specification: 6.2.4. Miscellaneous Registers
    • qemu: qemu/hw/pci/pci.c: pci_intx()
  • 中断处理函数
    • Interrupt Line Register中记录设备所需的中断向量
    • ref:
      • PCI Local Bus Specification: 6.2.4. Miscellaneous Registers
    • qemu: 似乎是根据routing规则算出irq,而不是读取Interrup Line Register。如qemu/hw/isa/piix3.c: pci_slot_get_pirq()注释写到:Return the global irq number corresponding to a given device irq pin.
  • 中断状态
    • 中断禁止:Command Register: bit 10
    • 中断预备:Device Status Register: bit 3
    • ref:
      • PCI LOCAL BUS SPECIFICATION, REV. 3.0: Device Control
      • PCI LOCAL BUS SPECIFICATION, REV. 3.0: Device Status
    • qemu:
      • qemu/hw/pci/pci.c: pci_update_irq_status()
      • qemu/hw/pci/pci.c: pci_irq_disabled()

PCI-to-PCI Bridge Architecture Specification: 11.2. System Initialization介绍了系统初始化的流程,其中就包括中断的初始化:11.2.3. Writing IRQ Numbers into Interrupt Line Register(s)

简单的说就是:系统初始化程序(如BIOS)会感知到pci设备的连接状况,然后将设备的INTx#IRQ绑定(Interrup Routing),分别填入configuration space的Interrupt Pin RegisterInterrupt Line Register中。中断触发时将会通过Interrupt Line Register中的内容寻找中断例程。

pci-pci bridge spec中(PCI-to-PCI Bridge Architecture Specification: Chapter 9 Interrupt Support)对中断的支持进行了较为详细的说明。

qemu中的模拟:qemu/hw/pci/pci.c,约1435行到1525行提供了整套pci中断处理流程的模拟,大体过程如下:

  1. 设备初始化时,如果需要使用pci提供的中断支持,使用pci_allocate_irq申请pci中断,这样该设备中断触发时将交由pci_irq_handler处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    pci_ich9_ahci_realize
    d->ahci.irq = pci_allocate_irq(dev);


    qemu_irq pci_allocate_irq(PCIDevice *pci_dev)
    {
    int intx = pci_intx(pci_dev);

    return qemu_allocate_irq(pci_irq_handler, pci_dev, intx);
    }
  2. pci_irq_handler通过Interrup Pin Register获取设备的INTx信息:pci_intx(pci_dev)。通过Status寄存器更新中断状态等
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    static void pci_irq_handler(void *opaque, int irq_num, int level)
    {
    PCIDevice *pci_dev = opaque;
    int change;

    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);
    }
  3. 获取到intx后在pci_change_irq_level中完成irq查询和中断触发等操作。使用bus->map_irq回调函数查找设备对应的IRQ
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    static void pci_change_irq_level(PCIDevice *pci_dev, int irq_num, int change)
    {
    PCIBus *bus;
    for (;;) {
    bus = pci_get_bus(pci_dev);
    irq_num = bus->map_irq(pci_dev, irq_num);
    if (bus->set_irq)
    break;
    pci_dev = bus->parent_dev;
    }
    pci_bus_change_irq_level(bus, irq_num, change);
    }
  4. 最后在pci_bus_change_irq_level中调用bus->set_irq()根据IRQ触发中断,层层传递至cpu
    1
    2
    3
    4
    5
    6
    7
    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);
    }

bus->map_irqbus->set_irq是在控制器设备初始化时设置(pci_bus_irqs())的,表示pci中断线所连接到的控制器,再由控制器处理然后传递中断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void pc_q35_init(MachineState *machine)
{
...
pci_bus_irqs(host_bus, ich9_lpc_set_irq, ich9_lpc_map_irq, ich9_lpc,
ICH9_LPC_NB_PIRQS);
...
}

void pci_bus_irqs(PCIBus *bus, pci_set_irq_fn set_irq, pci_map_irq_fn map_irq,
void *irq_opaque, int nirq)
{
bus->set_irq = set_irq;
bus->map_irq = map_irq;
bus->irq_opaque = irq_opaque;
bus->nirq = nirq;
bus->irq_count = g_malloc0(nirq * sizeof(bus->irq_count[0]));
}

TODO

  • MSI
  • Q
    • 一个设备初始化后irq就确定了就不改变了?

评论