PCIe枚举源码分析

枚举的过程也就是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, &region, res);
                          new = region.start;
                          reg = PCI_BASE_ADDRESS_0 + 4 * resno;
                          pci_write_config_dword(dev, reg, new);

pcie的资源枚举过程可以概况如下:

  1. 获取上游pci 桥设备所管理的系统资源范围

  2. 使用DFS对所有的pci ep device进行bar资源的分配 (drivers/pci/setup-res.c +25)
    [图片]

  3. 使用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
[图片]

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值