5.14.2.5. PCI/PCI-E

PCI/PCI-E本质上就是一个代理了很多设备的设备。所以它才有那些BDF的复杂概念,好像很灵活,但如果我们从地址分配这个角度看,每个PCI/PCIE根桥就是一个平台设备,这个平台设备有自己的MMIO空间,它的所有动态协议,不过是对这个空间的重新分配(基于设备对应的Bus-Device-Function)。

所以PCI/PCIE根桥的创建本质上分配一个MMIO空间,并用PCI/PCIE作为这个MR的管理设备而已。一般套路是:

  1. 创建一个PCI/PCIE设备作为Root Bridge,比如TYPE_GPEX_HOST(General PCI EXpress)。

  2. 创建ECAM空间(配置空间)* mb = sysbus_mmio_get_region() * mem_region_init_alias(alias...) * memory_region_add_subregion(system_memory, addr, alias)

  3. 创建BAR空间* 同2

剩下的事情就是TYPE_GPEX_HOST驱动的问题了。

备注

我们这里快速补充一下PCI/PCIE上的基本概念:

在谈PCI/PCIE的时候,Host表示CPU子系统,Host Bridge表示把CPU和PCI/PCIE总线连起来的那个IP。这个IP包括三个功能:Bus Master,Bus Target,以及Configure Access Generation。

TYPE_GPEX_HOST的继承树结构::

DEVICE <- SYS_BUS_DEVICE <- PCI_HOST_BRIDAGE <- PCIE_HOST_BRIDGE <- GPEX_HOST

中断的行为类似,先为整个RP分配中断,然后用gpex_set_irq_num()建立PCIE局部irq和全局irq的关系即可。

5.14.2.5.1. PCI/PCI-E驱动

前面是全系统的PCI桥的概念,我们用一个PCI设备的backend来看具体的backend的写法:

static void my_class_init(ObjectClass *oc, void *data) {
  PCIDeviceClass *k = PCI_DEVICE_CLASS(oc);
  k->realize = my_realize;
  k->vendor_id = MY_VENDOR_ID;
  k->device_id = MY_DEVICE_ID;
  k->revision = MY_REVISION;
  k->class_id = PCI_CLASS_XXXX;
}

static const TypeInfo my_pci_device_info = {
  .name          = "my-pci-device"
  .parent        = TYPE_PCI_DEVICE,
  .class_init    = my_class_init,
  .interfaces    = {
    { INTERFACE_CONVENTIONAL_PCI_DEVICE },
    { },
  };
};

这个和其他类没有什么区别,只是父类设置成了TYPE_PCI_DEVICE,在类初始化的时候把父类的基本属性都设置类(vendor id等),realize中可以调用pci模块提供的比如:

pci_config_set_interrupt_pin()/msi_init(),
pci_register_bar()

这些函数,创建相应的pci资源,剩下的工作,留给父类去做就可以了。