当前位置: 代码迷 >> 综合 >> PCIe 学习笔记(三)
  详细解决方案

PCIe 学习笔记(三)

热度:63   发布时间:2024-01-09 06:34:12.0
pci note 3

-v0.1 2014.11.22 sherlock draft
-v0.2 2015.1.24  sherlock add pci_scan_bridge anlysis

pci_scan_child_bus(b)

这个函数完成pci总线的枚举,完成整个pci树各个总线号的分配。但是并没有分配各个pci桥,

pci device的BAR和mem, I/O, prefetch mem的base/limit寄存器

unsigned int pci_scan_child_bus(struct pci_bus *bus)
{unsigned int devfn, pass, max = bus->busn_res.start;struct pci_dev *dev;/* 开始pci枚举, 一个pci bus上最多有256个设备,每个设备最多有8个function* 所以这里最多可以扫描32个device。实际上每个function也是有配置空间的* 所以function可以看作是个逻辑的设备*/for (devfn = 0; devfn < 0x100; devfn += 8)pci_scan_slot(bus, devfn);/* pci虚拟化相关的东西 */max += pci_iov_bus_range(bus);...if (!bus->is_added) {dev_dbg(&bus->dev, "fixups for bus\n");/* 对于arm64来说,是个空函数 */pcibios_fixup_bus(bus);bus->is_added = 1;}for (pass = 0; pass < 2; pass++)list_for_each_entry(dev, &bus->devices, bus_list) {if (pci_is_bridge(dev))/* 开始递归的做pci枚举,在下面的函数中会再次* 调用pci_scan_child_bus。总线号的确定也在这个* 函数中*/max = pci_scan_bridge(bus, dev, max, pass);}...return max;
}pci_scan_slot(struct pci_bus *bus, int devfn)...--> pci_scan_single_device(bus, devfn).../* 扫描devfn设备, 探测设备:读vendor id, 读BAR的大小 。* 软件行为:为device分配struct pci_dev; 根据探测设备得到的数据,填* 写pci_dev里的一些值。若是按照笔记1中的硬件拓扑图现在扫描的device* 应该是pcie root port*/--> pci_scan_device(bus, devfn)/* 读devfn的vendor id, 会调用到pci_scan_root_bus的参数ops传入的read操作 */--> pci_bus_read_dev_vendor_id()/* 分配pci_dev结构 */--> pci_alloc_dev(bus).../* 处理pci配置头中的class code, Header Type, Interrupt Pin* Interrupt Line等内容,并设定新分配的pci_dev中的对应项。这里* 也会探测device的BAR大小,并设定pci_dev->resource[]。我们关注* 的重点也是pci_bus,pci_dev中相应的元素怎么变化。*/--> pci_setup_device(dev)...--> pci_device_add(dev, bus)	int pci_setup_device(struct pci_dev *dev).../* 的到device配置空间中head type的值,可以是普通pci device, pci桥等* 这个值在下面的switch-case中决定程序是走哪个分支。如果当前的设备是* pcie root port,那么走PCI_HEADER_TYPE_BRIDGE分支。设备配置空间* head type的值一般是pci host controller驱动中配置过的, 可以参考* /drivers/pci/host/pcie-designware.c*/--> pci_read_config_byte(dev, PCI_HEADER_TYPE, &hdr_type)/* device的parent是上游总线之上的那个pci桥 */--> dev->dev.parent = dev->bus->bridge;/* 读出class revision的值 */--> pci_read_config_dword(dev, PCI_CLASS_REVISION, &class)dev->revision = class & 0xff;dev->class = class >> 8;		    /* upper 3 bytes */dev->current_state = PCI_UNKNOWN;class = dev->class >> 8;switch (dev->hdr_type) {		    /* header type */case PCI_HEADER_TYPE_NORMAL:		    /* standard header */if (class == PCI_CLASS_BRIDGE_PCI)goto bad;pci_read_irq(dev);/** 得到BAR的大小,并存在pci_dev->resource[]中,主要是对pci* device起作用。当输入参数dev是pci桥时,得到的值似乎对后面影响* 不大,这点需要确认下?*/pci_read_bases(dev, 6, PCI_ROM_ADDRESS);pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device);break;case PCI_HEADER_TYPE_BRIDGE:		    /* bridge header */if (class != PCI_CLASS_BRIDGE_PCI)goto bad;pci_read_irq(dev);dev->transparent = ((dev->class & 0xff) == 1);pci_read_bases(dev, 2, PCI_ROM_ADDRESS1);set_pcie_hotplug_bridge(dev);pos = pci_find_capability(dev, PCI_CAP_ID_SSVID);if (pos) {pci_read_config_word(dev, pos + PCI_SSVID_VENDOR_ID, &dev->subsystem_vendor);pci_read_config_word(dev, pos + PCI_SSVID_DEVICE_ID, &dev->subsystem_device);}break;...}
现在回到pci_scan_child_bus中的pci_scan_bridge()函数。
/** 如果一个pci_dev是pci桥的话,以它的上游总线bus和这个设备dev本身为参数,扫描这个* 桥。这个函数中实现pci树的递归扫描,在这个函数中为整个pci树上的各个子总线分配* 总线号。这里pci_scan_bridge会调用两次,第一次处理BIOS中已经配置的东西,不清楚* intel在BIOS中做了怎样的处理。*/
pci_scan_bridge(bus, dev, max, pass);.../* 这里上来就读桥设备的主bus号,是因为有些体系架构下可能在BIOS中已经对这些* 做过配置。在ARM64中暂时没有看到有这样做的情况。*/--> pci_read_config_dword(dev, PCI_PRIMARY_BUS, &buses);.../* 配置了在kernel中分配bus号,应该不会进入到if中 */--> if ((secondary || subordinate) && !pcibios_assign_all_busses() &&!is_cardbus && !broken) {...} else {/* 第一次掉用pci_scan_bridge就此返回了 */if (!pass) {...goto out;}/* Clear errors */pci_write_config_word(dev, PCI_STATUS, 0xffff);/* 确定是否已用max+1这个bus号 */child = pci_find_bus(pci_domain_nr(bus), max+1);if (!child) {/* 分配新的struct pci_bus */child = pci_add_new_bus(bus, dev, max+1);if (!child)goto out;pci_bus_insert_busn_res(child, max+1, 0xff);}max++;/* 合成新的bus号,准备写入pci桥的配置空间 */buses = (buses & 0xff000000)| ((unsigned int)(child->primary)     <<  0)| ((unsigned int)(child->busn_res.start)   <<  8)| ((unsigned int)(child->busn_res.end) << 16);.../* 写入该pci桥primary bus, secondary bus, subordinate bus */pci_write_config_dword(dev, PCI_PRIMARY_BUS, buses);if (!is_cardbus) {child->bridge_ctl = bctl;/* 递归调用pci_scan_child_bus, 扫描这个子总线下的设备*/max = pci_scan_child_bus(child);} else {/* 不关心cardbus */...}/** Set the subordinate bus number to its real value.* 每次递归结束把实际的subordinate bus写入pci桥的配置空间* subordinate bus表示该pci桥下最大的总线号*/pci_bus_update_busn_res_end(child, max);pci_write_config_byte(dev, PCI_SUBORDINATE_BUS, max);}...
out:pci_write_config_word(dev, PCI_BRIDGE_CONTROL, bctl);return max;