枚举的过程也就是RC的系统软件通过配置空间访问来确定以及扫描整个总线拓扑的过程。
PCIe的拓扑结构如下:
• Root Complex是树的根,它一般实现了一个主桥设备(host bridge), 一条内部PCIe总线(BUS 0),以及通过若干个PCI bridge扩展出一些root port。host bridge可以完成CPU地址到PCI域地址的转换,pci bridge用于系统的扩展,没有地址转换功能;
• Switch是转接器设备,目的是扩展PCIe总线。switch中有一个upstream port和若干个downstream port, 每一个端口都相当于一个pci bridge
• PCIe ep device是叶子节点设备,比如pcie网卡,显卡等
对CPU来说,最开始仅仅知道Bus0的存在,Bus0下面都有什么设备,PCIE树是怎么样的是不知道的。因此首先从Bus0,Dev0开始,先去读Dev0中Fun0的DID&VID(一定是从Fun0开始),看其是否返回0,如果不为0则表示设备存在,继续下一步。若返回FFFF,则Dev0中没有Fun0(任何设备的第一种功能一定是0),因此该设备不存在,继续探查Bus0,Dev1,Fun0
PCIe枚举过程一般分为三步:
1.创建根节点
2.注册初始化根节点以及枚举根节点下所有设备
3.为根节点下设备分配资源
1、创建根节点:
在dw_pcie_host_init中通过devm_pci_alloc_host_bridge()分配host bridge结构体
pci_register_host_bridge()注册上一步的host bridge,主要是为host bridge数据结构注册对应的设备,创建了一个根总线pci_bus, 也为该pci_bus数据结构注册一个设备并填充初始化的数据。
1、注册了bridge对应的Device,其目录为/sys/devices/pciNNNN:NN,NNNN:NN表示Domain Number、Bus Number:
2、创建了根总线的pci_bus对象
3、将根总线的Device注册到sysfs中:
它们对应的目录位于/sys/devices/pciNNNN:NN/pci_bus/XXXX:XX/…/ZZZZ:ZZ。
pci数据结构
1、pci_host_bridge
Host Bridge连接CPU和PCI系统
2、pci_bus
pci_bus包括节点信息、父总线pci_bus、设备链表、设备操作函数等信息
3、pci_bus_type
与pci_bus不同,该结构体是设备总线驱动模型里的总线
4、pci_driver
pci设备驱动
5、pci_dev
描述PCI设备,比如PCI-to-PCI桥设备等
2、枚举根节点下所有设备:
枚举流程关键函数调用关系
+-> dw_pcie_host_init
+-> pci_host_probe
+-> pci_scan_root_bus_bridge
+-> pci_scan_child_bus()
+-> pci_scan_child_bus_extend()
+-> for dev range(0, 256) //枚举device
pci_scan_slot()枚举单个设备,里面循环的读取每个逻辑function的id
+-> pci_scan_single_device()
+-> pci_scan_device()
+-> pci_bus_read_dev_vendor_id()
+-> pci_alloc_dev()
+-> pci_setup_device()
+-> pci_device_add()//将pci_dev填充数据添加到pci总线设备列表中,并注册相应的ko
+-> for each pci bridge //枚举bridge
+-> pci_scan_bridge_extend()
pci_scan_slot(): 一条pcie总线最多32个设备,每个设备最多8个function, 所以这里pci_scan_child_bus枚举了所有的pcie function, 调用了pci_scan_device 256次尝试获取对应的vendor id和device id, pci_scan_slot调用pci_scan_single_device()配置当前总线下的所有pci设备。
drivers/pci/probe.c +2826
drivers/pci/probe.c +2622
同时,在扫描完所有的device之后还将aspm进行了初始化
pci_scan_slot(bus, devfn)
±> pci_scan_single_device(bus, devfn)
±> pci_scan_device(bus, devfn)
±> pci_alloc_dev(bus)
±> pci_setup_device(dev)
±> pci_device_add(dev, bus)
pci_scan_single_device(): 进一步调用 pci_scan_device() 和 pci_add_device() 。pci_scan_device 先去通过配置空间访问接口读取设备的vendor id, 如果60s没读到,说明没有找到该设备。 如果找到该设备,则通过 pci_alloc_dev 创建 pci_dev 数据结构,并对pci的配置空间进行一些配置。pci_add_device 软件将 pci dev 添加到设备list中。
pci_scan_device()
±> pci_bus_generic_read_dev_vendor_id()
±> pci_bus_wait_crs()
±> pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, l)
具体获取vendor id和device id的时候, 通过pci_bus_read_config_dword函数读取0x00地址,因为每次读一个DW(32bit)所以传入PCI_VENDOR_ID可以把vendor id和device id读出来
扫描完bus上pcie device信息之后通过pci_setup_device() 获取 pci 设备信息,中断号,BAR地址 和 大小 (使用 pci_read_bases 就是往BAR地址写1来计算的),并保存到pci_dev->resources中。
pci_setup_device函数会将能够读取到vendor id和device id的设备填充信息,包括class,memory,IO-space address以及IRQ line等,通过往bar中写入全“1”,再读出来确定bar的大小也是在该函数中调用的
![[图片]](https://img-blog.csdnimg.cn/63e6b56a54154ff591b4e51e3a3e54c4.png
从配置空间header中读取irq_line到dev中
![[图片](https://img-blog.csdnimg.cn/cd3c76640dd84f09ad3745d96ffe2a2a.png)
至此,现在我们已经扫描完了host bridge下的bus和dev了,还有bridge没有扫描,
pci_scan_bridge_extend() 就是用于扫描 pci桥和 pci桥下的所有设备, 这个函数会被调用2次,第一次是处理 BIOS 已经配置好的pci桥, 这个是为了兼容各个架构所做的妥协。通过2次调用 pci_scan_bridge_extend 函数,完成所有的pci桥的处理
3、为根节点下设备分配资源
pcie枚举完成后,pci总线号已经分配,pcie ecam的映射、 pcie设备信息、BAR的个数及大小等也已经ready, 但此时并没有给各个pci device的BAR, pci bridge的mem, I/O, prefetch mem的base/limit寄存器分配资源。
这时就需要走到pcie的资源分配流程,整个资源分配的过程就是从系统的总资源里给每个pci device的bar分配资源,给每个pci桥的base, limit的寄存器分配资源。
pcie资源分配的入口在pci_bus_assign_resources()
如果定义了宏pci_has_flag,则会使用现有配置,不进行对齐操作直接对资源进行分配
在调用pci_bus_assign_resources()之前,先调用pci_bus_size_bridges()
pci_bus_size_bridges(): 用深度优先递归确定各级pci桥上base/limit的大小,会记录在pci_dev->resource[PCI_BRIDGE_RESOURCES]中。
资源分配流程关键函数调用关系
+-> pci_bus_size_bridges(bus);
+-> pci_bus_assign_resources(bus);
+-> __pci_bus_assign_resources
+-> pbus_assign_resources_sorted
/* pci_dev->resource[]里记录有想申请的资源的大小,
* 把这些资源按对齐的要求排序
* 比如资源A要求1K地址对齐,资源B要求32地址对齐
* 那么资源A排在资源B前面, 优先分配资源A
*/
list_for_each_entry(dev, &bus->devices, bus_list)
__dev_sort_resources(dev, &head);
// 分配资源
+-> __assign_resources_sorted
+-> assign_requested_resources_sorted(head, &local_fail_head);
+-> pci_assign_resource
+-> ret = _pci_assign_resource(dev, resno, size, align);
// 分配地址空间
+-> __pci_assign_resource
+-> pci_bus_alloc_resource
+-> pci_bus_alloc_from_region
/* Ok, try it out.. */
+-> ret = allocate_resource(r, res, size, ...);
+-> err = find_resource(root, new, size,...);
+-> __find_resource
// 从资源链表中分配地址空间
// 设置pci_dev->resource[]
new->start = alloc.start;
new->end = alloc.end;
// 把对应的PCI地址写入BAR
+-> pci_update_resource(dev, resno);
+-> pci_std_update_resource
/* 把CPU地址转换为PCI地址: PCI地址 = CPU地址 - offset
* 写入BAR
*/
pcibios_resource_to_bus(dev->bus, ®ion, res);
new = region.start;
reg = PCI_BASE_ADDRESS_0 + 4 * resno;
pci_write_config_dword(dev, reg, new);
pcie的资源枚举过程可以概况如下:
-
获取上游pci 桥设备所管理的系统资源范围
-
使用DFS对所有的pci ep device进行bar资源的分配 (drivers/pci/setup-res.c +25)
-
使用DFS对当前pci桥设备的base和limit的值,并对这些寄存器进行更新(drivers/pci/setup-bus.c +665)
![[图片](https://img-blog.csdnimg.cn/665d19769575410c80dcc25e7fb2d1a9.png)
根据当前bridge windows可分为三种情况:
drivers/pci/setup-bus.c +574
drivers/pci/setup-bus.c +611
drivers/pci/setup-bus.c +630