PCI/PCIE协议还是很完美的,所以目前很是流行。所以linux内核主线代码是严格按照协议来的,例如pci_read_bases(dev, 6, PCI_ROM_ADDRESS);轮流获取6个MEM空间大小。
但是由于开发是存在BUG,或者开发者没有彻底理解PCI/PCIE或是别的原因,很多设备并没有严格按照PCI/PCIE协议来实现。
例如,有的设备实际上只有一个有效的BASE_ADDRESS地址,但是实际探测有N个地址,部分无效的地址空间还特别大,导致地址空间不够,后面别的PCI设备无法获取地址。
针对这种情况,linux内核做了妥协,允许驱动初始化流程在某个阶段可以修改前面阶段的一些资源,也就是数据结构。
enum pci_fixup_pass {
pci_fixup_early, /* Before probing BARs */
pci_fixup_header, /* After reading configuration header */
pci_fixup_final, /* Final phase of device fixups */
pci_fixup_enable, /* pci_enable_device() time */
pci_fixup_resume, /* pci_device_resume() */
pci_fixup_suspend, /* pci_device_suspend */
pci_fixup_resume_early, /* pci_device_resume_early() */
};
刚才讲的那种情况,可以在pci_fixup_header阶段修正,方法如下:
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_XXX, PCI_DEVICE_ID_IBM_XXX, fixup_xxx_pci_bar);
//只保留BASE_ADDRESS0的资源信息,其它5个BAR的资源信息全部清除
static void fixup_xxx_pci_bar(struct pci_dev *dev)
for (i = 1; i < DEVICE_COUNT_RESOURCE; i++) {
dev->resource[i].start = dev->resource[i].end = 0;
dev->resource[i].flags = 0;
}
生效的位置:获取BASE_ADDREESS大小之后,分配PCI/PCIE域到BASE_ADDREESS里面之前
void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
/* Fix up broken headers */
pci_fixup_device(pci_fixup_header, dev);