PCIe初始化枚举和资源分配流程分析

本文主要是对PCIe的初始化枚举、资源分配流程进行分析,代码对应的是alikernel-4.19,平台是arm64

1. PCIe architecture

1.1 pcie的拓扑结构

在分析PCIe初始化枚举流程之前,先描述下pcie的拓扑结构。
如下图所示:
PCIe初始化枚举和资源分配流程分析_第1张图片

整个PCIe是一个树形的拓扑:
• Root Complex是树的根,它一般实现了一个主桥设备(host bridge), 一条内部PCIe总线(BUS 0),以及通过若干个PCI bridge扩展出一些root port。host bridge可以完成CPU地址到PCI域地址的转换,pci bridge用于系统的扩展,没有地址转换功能;
• Swich是转接器设备,目的是扩展PCIe总线。switch中有一个upstream port和若干个downstream port, 每一个端口都相当于一个pci bridge
• PCIe ep device是叶子节点设备,比如pcie网卡,显卡,nvme卡等。

每个PCIe设备,包括host bridge、pci bridge和ep设备都有一个4k的配置空间。arm使用ecam的方式访问pcie配置空间。

1.2 PCIe的软件层次

PCIe模块涉及到的代码文件很多,在分析pcie的代码前,先对pcie的涉及到的代码梳理下。
这里以arm64架构为例,pcie代码主要分散在三个目录:

drivers/pci/*
driver/acpi/pci*
arch/arm64/kernel/pci.c

将pcie代码按如下层次划分:

    |-->+ pcie hp service driver  +
    |-->+ pcie aer service driver +
    |-->+ pcie pme service driver +
    |-->+ pcie dpc service driver +
    |
+---------------------+   +----------------+
| pcie port bus driver|   | pcie ep driver |
+---------------------+   +----------------+
+------------------------------------------+
|              pcie core driver            |
+------------------------------------------+
+------------------+   +-------------------+
| arch pcie driver |   | acpi pcie driver  |
+------------------+   +-------------------+
____________________________________________
+------------------------------------------+
|               pcie hardware              |
+------------------------------------------+

arch pcie driver:放一些和架构强相关的pcie的函数实现,对应arch/arm64/kernel/pci.c
acpi pcie driver: apci扫描时所涉及到的pcie代码,包括host bridge的解析初始化,pcie bus的创建,ecam的映射等, 对应drivers/acpi/pci*.c
pcie core driver: pcie的子系统代码,包括pcie的枚举流程,资源分配流程,中断流程等,主要对应drivers/pci/*.c
pcie port bus driver: 是pcie port的四个service代码的整合, 四个service主要指的是pcie dpc/pme/hotplug/aer,对应的是drivers/pci/pcie/*
pcie ep driver:是叶子节点的设备驱动,比如显卡,网卡,nvme等。

Linux内核实现

2.1 pcie初始化流程

pcie的代码文件这么多,初始化涉及的调用也很多,从哪一个开始看呢?
内核通过initcore的level决定模块的启动顺序

cat System.map | grep pci | grep initcall

再结合Makefile中object定义的先后顺序

obj-y     += access.o bus.o probe.o remove.o pci.o quirks.o /
             pci-driver.o search.o pci-sysfs.o rom.o setup-res.o

可以看出关键symbol的调用顺序如下:

|-->pcibus_class_init() /* postcore_initcall(pcibus_class_init) */
|
|-->pci_driver_init() /* postcore_initcall(pci_driver_init) */
|    
|-->acpi_pci_init() /* arch_initcall(acpi_pci_init) */
|
|-->acpi_init() /* subsys_initcall(acpi_init) */

pcibus_class_init(): 注册pci_bus class,完成后创建了/sys/class/pci_bus目录。
pci_driver_init(): 注册pci_bus_type, 完成后创建了/sys/bus/pci目录。
acpi_pci_init(): 注册acpi_pci_bus, 并设置电源管理相应的操作。
acpi_init(): apci启动所涉及到的初始化流程,PCIe基于acpi的启动流程从该接口进入。

在linux/Documentation/acpi/namespace.txt中定义了acpi解析的流程

     +---------+    +-------+    +--------+    +------------------------+
     |  RSDP   | +->| XSDT  | +->|  FADT  |    |  +-------------------+ |
     +---------+ |  +-------+ |  +--------+  +-|->|       DSDT        | |
     | Pointer | |  | Entry |-+  | ...... |  | |  +-------------------+ |
     +---------+ |  +-------+    | X_DSDT |--+ |  | Definition Blocks | |
     | Pointer |-+  | ..... |    | ...... |    |  +-------------------+ |
     +---------+    +-------+    +--------+    |  +-------------------+ |
                    | Entry |------------------|->|       SSDT        | |
                    +- - - -+                  |  +-------------------| |
                    | Entry | - - - - - - - -+ |  | Definition Blocks | |
                    +- - - -+                | |  +-------------------+ |
                                             | |  +- - - - - - - - - -+ |
                                             +-|->|       SSDT        | |
                                               |  +-------------------+ |
                                               |  | Definition Blocks | |
                                               |  +- - - - - - - - - -+ |
                                               +------------------------+
                                                           |
                                              OSPM Loading |
                                                          \|/
                                                    +----------------+
                                                    | ACPI Namespace |
                                                    +----------------+

ACPI Namespace就是表示系统上所有可枚举的ACPI设备的层次结构。

现在对acpi_init()流程展开,主要找和pci初始化相关的调用:

acpi_init() /* subsys_initcall(acpi_init) */
    +-> mmcfg_late_init()
    +-> acpi_scan_init()
        +-> acpi_pci_root_init()
            +-> acpi_scan_add_handler_with_hotplug(&pci_root_handler, "pci_root");
                +-> .attach = acpi_pci_root_add
        /*
         * register pci_link_handler to list: acpi_scan_handlers_list.
         * this handler has relationship with PCI IRQ.
         */
        +-> acpi_pci_link_init()
        /* we facus on PCI-ACPI, ignore other handlers' init */
        ...
        +-> acpi_bus_scan()
            /* create struct acpi_devices for all device in this system */
            --> acpi_walk_namespace()
            --> acpi_bus_attach()
                --> acpi_scan_attach_handler()
                    --> acpi_scan_match_handler()
                    --> handler->attach /* attach is acpi_pci_root_add */

mmcfg_late_init(), acpi先扫描MCFG表,MCFG表定义了ecam的相关资源。
acpi_pci_root_init(),定义pcie host bridge device的attach函数, ACPI的Definition Block中使用PNP0A03表示一个PCI Host Bridge。
acpi_pci_link_init(), 注册pci_link_handler, 主要和pcie IRQ相关。
acpi_bus_scan(), 会通过acpi_walk_namespace()会遍历system中所有的device,并为这些acpi device创建数据结构,执行对应device的attatch函数。根据ACPI spec定义,pcie host bridge device定义在DSDT表中,acpi在扫描过程中扫描DSDT,如果发现了pcie host bridge, 就会执行device对应的attach函数,调用到acpi_pci_root_add()。

acpi_pci_root_add的函数很长,完整代码就不贴了, 它主要做了几个动作
(1)通过ACPI的_SEG参数, 获取host bridge使用的segment号, segment指的就是pcie domain, 主要目的是为了突破pcie最大256条bus的限制。
(2)通过ACPI的_CRS里的BusRange类型资源取得该Host Bridge的Secondary总线范围,保存在root->secondary这个resource中
(3)通过ACPI的_BNN参数获取host bridge的根总线号。
执行到这里如果没有返回失败,硬件设备上会有如下打印:

pr_info(PREFIX "%s [%s](domain %04x %pR)\n",
        acpi_device_name(device), acpi_device_bid(device),
        root->segment, &root->secondary);
...
ACPI: PCI Root Bridge [PCI0](domain 0000 [bus 00-7f])

(3) pci_acpi_scan_root, pcie枚举流程的入口

2.2 pcie枚举流程

166 struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root)
167 {
168         int node = acpi_get_node(root->device->handle);
169         struct acpi_pci_generic_root_info *ri;
170         struct pci_bus *bus, *child;
171         struct acpi_pci_root_ops *root_ops;
172
173         ri = kzalloc_node(sizeof(*ri), GFP_KERNEL, node);    
174         if (!ri)
175                 return NULL;
176
177         root_ops = kzalloc_node(sizeof(*root_ops), GFP_KERNEL, node);
178         if (!root_ops) {
179                 kfree(ri);
180                 return NULL;
181         }
182
183         ri->cfg = pci_acpi_setup_ecam_mapping(root);    -------(1)
184         if (!ri->cfg) {
185                 kfree(ri);
186                 kfree(root_ops);
187                 return NULL;
188         }
189
190         root_ops->release_info = pci_acpi_generic_release_info;
191         root_ops->prepare_resources = pci_acpi_root_prepare_resources;
192         root_ops->pci_ops = &ri->cfg->ops->pci_ops;     ----- (2)
193         bus = acpi_pci_root_create(root, root_ops, &ri->common, ri->cfg);  ---- (3)
194         if (!bus)
195                 return NULL;
196
            ....
202
203         return bus;
204 }

(1) pci_acpi_setup_ecam_mapping(), 建立ecam映射。 arm64上访问pcie的配置空间都是通过ecam机制进行访问,将ecam的空间进行映射,这样cpu就可以通过访问内存访问到相应设备的配置空间。

118 static struct pci_config_window *
119 pci_acpi_setup_ecam_mapping(struct acpi_pci_root *root)
120 {
121         struct device *dev = &root->device->dev;
122         struct resource *bus_res = &root->secondary;
123         u16 seg = root->segment;
124         struct pci_ecam_ops *ecam_ops;
125         struct resource cfgres;
126         struct acpi_device *adev;
127         struct pci_config_window *cfg;
128         int ret;
129
130         ret = pci_mcfg_lookup(root, &cfgres, &ecam_ops);
131         if (ret) {
132                 dev_err(dev, "%04x:%pR ECAM region not found\n", seg, bus_res);
133                 return NULL;
134         }
135
136         adev = acpi_resource_consumer(&cfgres);
137         if (adev)
138                 dev_info(dev, "ECAM area %pR reserved by %s\n", &cfgres,
139                          dev_name(&adev->dev));
140         else
141                 dev_warn(dev, FW_BUG "ECAM area %pR not reserved in ACPI namespace\n",
142                          &cfgres);
143
144         cfg = pci_ecam_create(dev, &cfgres, bus_res, ecam_ops);
145         if (IS_ERR(cfg)) {
146                 dev_err(dev, "%04x:%pR error %ld mapping ECAM\n", seg, bus_res,
147                         PTR_ERR(cfg));
148                 return NULL;
149         }
150
151         return cfg;
152 }

130行:pci_mcfg_lookup(), 通过该接口可以获取ecam的资源以及访问配置空间的操作ecam_ops.
ecam_ops默认是pci_generic_ecam_ops, 定义在drivers/pci/ecam.c中,但也可以由厂商自定义,厂商自定义的ecam_ops实现在drivers/pci/controller/目录下, 比如hisi_pcie_ops和ali_pcie_ops,厂商会依据实际的硬件对ecam进行限制。

107 struct pci_ecam_ops ali_pcie_ops = {
108         .bus_shift    = 20,
109         .init         =  ali_pcie_init,
110         .pci_ops      = {
111                 .map_bus    = ali_pcie_map_bus,
112                 .read       = ali_pcie_rd_conf,
113                 .write      = ali_pcie_wr_conf,
114         }
115 };

144行: pci_ecam_create(), 对ecam的地址进行ioremap,如果定义了ecam_ops->init,还会执行到相应的初始化函数中
(2) 设置root_ops的pci_ops, 这里的pci_ops就是对应上面说的ecam_ops->pci_ops, 即配置空间的访问接口
(3) bus = acpi_pci_root_create(root, root_ops, &ri->common, ri->cfg);

struct pci_bus *acpi_pci_root_create(struct acpi_pci_root *root,
878                                      struct acpi_pci_root_ops *ops,
879                                      struct acpi_pci_root_info *info,
880                                      void *sysdata)
881 {
882         int ret, busnum = root->secondary.start;
883         struct acpi_device *device = root->device;
884         int node = acpi_get_node(device->handle);
885         struct pci_bus *bus;
886         struct pci_host_bridge *host_bridge;
887
            ...
906         bus = pci_create_root_bus(NULL, busnum, ops->pci_ops,
907                                   sysdata, &info->resources);  
908         if (!bus)
909                 goto out_release_info;
910
911         host_bridge = to_pci_host_bridge(bus->bridge);
            ...
923         pci_scan_child_bus(bus);
924         pci_set_host_bridge_release(host_bridge, acpi_pci_root_release_info,
925                                     info);
926         if (node != NUMA_NO_NODE)
927                 dev_printk(KERN_DEBUG, &bus->dev, "on NUMA node %d\n", node);
928         return bus;
929
930 out_release_info:
931         __acpi_pci_root_release_info(info);
932         return NULL;
933 }

906行: pci_create_root_bus()用来创建该{segment: busnr}下的根总线。传递的参数: NULL是host bridge设备的parent节点; busnum是总线号; ops->pci_ops对应的是ecam->pci_ops,即配置空间的操作接口; sysdata私有数据,对应的是pcie_create_ecam()所返回的pci_cfg_window, 包括ecam的地址范围,映射地址等; info->resource是一个resource_list, 用来保存总线号,I/O空间,mem空间等信息。

2914 struct pci_bus *pci_create_root_bus(struct device *parent, int bus,
2915                 struct pci_ops *ops, void *sysdata, struct list_head *resources)
2916 {
2917         int error;
2918         struct pci_host_bridge *bridge;
2919
2920         bridge = pci_alloc_host_bridge(0);  
2921         if (!bridge)
2922                 return NULL;
2923
2924         bridge->dev.parent = parent;
2925
2926         list_splice_init(resources, &bridge->windows);
2927         bridge->sysdata = sysdata;
2928         bridge->busnr = bus;
2929         bridge->ops = ops;
2930
2931         error = pci_register_host_bridge(bridge);
2932         if (error < 0)
2933                 goto err_out;
2934
2935         return bridge->bus;
2936
2937 err_out:
2938         kfree(bridge);
2939         return NULL;
2940 }

2920行: 分配struct pci_host_bridge, 一个pci_host_bridge对应一个pci host bridge设备。
2924行:设置该bridge的parent为NULL, 说明host bridge device是最上层设备。
2931行: pci_register_host_bridge()。 注册host bridge device。 该函数比较长,就不贴具体实现了,主要是为host bridge数据结构注册对应的设备,创建了一个根总线pci_bus, 也为该pci_bus数据结构注册一个设备并填充初始化的数据。

         dev_set_name(&bridge->dev, "pci%04x:%02x", pci_domain_nr(bus),
                      bridge->busnr); 
         err = device_register(&bridge->dev);
         dev_set_name(&bus->dev, "%04x:%02x", pci_domain_nr(bus), bus->number);
         err = device_register(&bus->dev);

2935行:返回root_bus, 即pci_host_bridge的bus成员。
到该函数结束我们已经有了一个root_bus device, 一个host bridge device, 也知道了他们的关系:
PCIe初始化枚举和资源分配流程分析_第2张图片

回到上面acpi_pci_root_create()函数中,看923行 pci_scan_child_bus(), 现在开始遍历host bridge主桥下的所有pci设备
函数也比较长,列一些关键的函数调用:

pci_scan_child_bus()
    +-> pci_scan_child_bus_extend()
        +-> for dev range(0, 256)
               pci_scan_slot()
                    +-> pci_scan_single_device()
                        +-> pci_scan_device()
                            +-> pci_bus_read_dev_vendor_id()
                            +-> pci_alloc_dev()
                            +-> pci_setup_device()
                        +-> pci_add_device()
        +-> for each pci bridge
            +-> pci_scan_bridge_extend()

pci_scan_slot(): 一条pcie总线最多32个设备,每个设备最多8个function, 所以这里pci_scan_child_bus枚举了所有的pcie function, 调用了pci_scan_slot 256次, pci_scan_slot调用pci_scan_single_device()配置当前总线下的所有pci设备。
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_setup_device(): 获取pci设备信息,中断号,BAR地址和大小(使用pci_read_bases, 就是往BAR地址写1来计算的),并保存到pci_dev->resources中。

现在我们已经扫描完了host bridge下的bus和dev, 现在开始扫描bridge, 一个bridge也对应一个pci_dev。比如switch中的每一个port对应一个pci bridge。
pci_scan_bridge_extend()就是用于扫描pci桥和pci桥下的所有设备, 这个函数会被调用2次,第一次是处理BIOS已经配置好的pci桥, 这个是为了兼容各个架构所做的妥协。通过2次调用pci_scan_bridge_extend函数,完成所有的pci桥的处理。

1061 static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
1062                                   int max, unsigned int available_buses,
1063                                   int pass)
1064 {
1065         struct pci_bus *child;
             ......
1078         pci_read_config_dword(dev, PCI_PRIMARY_BUS, &buses);
1079         primary = buses & 0xFF;
1080         secondary = (buses >> 8) & 0xFF;
1081         subordinate = (buses >> 16) & 0xFF;
1082
1083         pci_dbg(dev, "scanning [bus %02x-%02x] behind bridge, pass %d\n",
1084                 secondary, subordinate, pass);
1085
             ......
1110         if ((secondary || subordinate) && !pcibios_assign_all_busses() &&
1111             !is_cardbus && !broken) {
                    .......
1145         } else {
1146
1151                 if (!pass) {
1152                         if (pcibios_assign_all_busses() || broken || is_cardbus)
1153
1162                                 pci_write_config_dword(dev, PCI_PRIMARY_BUS,
1163                                                        buses & ~0xffffff);
1164                         goto out;
1165                 }
1166
1167                 /* Clear errors */
1168                 pci_write_config_word(dev, PCI_STATUS, 0xffff);
1169
1175                 child = pci_find_bus(pci_domain_nr(bus), max+1);
1176                 if (!child) {
1177                         child = pci_add_new_bus(bus, dev, max+1);
1178                         if (!child)
1179                                 goto out;
1180                         pci_bus_insert_busn_res(child, max+1,
1181                                                 bus->busn_res.end);
1182                 }
1183                 max++;
1184                 if (available_buses)
1185                         available_buses--;
1186
1187                 buses = (buses & 0xff000000)
1188                       | ((unsigned int)(child->primary)     <<  0)
1189                       | ((unsigned int)(child->busn_res.start)   <<  8)
1190                       | ((unsigned int)(child->busn_res.end) << 16);
1191
                    ......
1200
1201                 /* We need to blast all three values with a single write */
1202                 pci_write_config_dword(dev, PCI_PRIMARY_BUS, buses);
1203
1204                 if (!is_cardbus) {
1205                         child->bridge_ctl = bctl;
1206                         max = pci_scan_child_bus_extend(child, available_buses);
1207                 } else {
                            .......
1239                 }
1240
1241                 /* Set subordinate bus number to its real value */
1242                 pci_bus_update_busn_res_end(child, max);
1243                 pci_write_config_byte(dev, PCI_SUBORDINATE_BUS, max);
1244         }
1245         .......
1268         return max;
1269 }

1078行:一开始读取pci bridge的主bus号,是因为有的体系结构可能已经在BIOS中对这些做过配置。如果需要在kernel中进行scan bridge,就不会进1110行的那个if 分支。
1151行: 第一次pci_scan_bridge的pass参数为0,在这里直接返回。
1187行:生成新的BUS号,准备写入pci配置空间。
1202行: 将该pci bridge的primary bus, secondary bus, subordinate bus写入配置空间。
1206行: 这里又递归调用了pci_scan_child_bus, 扫描该子总线下所有设备。
1242行: 比较关键, 每次递归结束把实际的subordinate bus写入pci桥的配置空间。subordinate bus表示该pci桥下最大的总线号。
最后,在PCIe总线树枚举完成后,返回PCIe总线树中的最后一个pci总线号,PCIe的枚举流程至此结束。
PCIe初始化枚举和资源分配流程分析_第3张图片

总的来说,枚举流程分为3步:

  1. 发现主桥设备和根总线;
  2. 发现主桥设备下的所有pci设备
  3. 如果主桥下面的设备是pci bridge, 那么再次遍历这个pci bridge桥下的所有pci设备,并以此递归,直到将当前的pci总线树遍历完毕,并且返回host bridge的subordinate总线号。

2.3 pcie的资源分配

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的资源分配流程整体比较复杂,主要介绍下总体的流程,对关键的函数再做展开。
pcie资源分配的入口在pci_acpi_scan_root()->pci_bus_assign_resources()
在调用pci_bus_assign_resources()之前,先调用pci_bus_size_bridges()
pci_bus_size_bridges(): 用深度优先递归确定各级pci桥上base/limit的大小,会记录在pci_dev->resource[PCI_BRIDGE_RESOURCES]中。

再进行资源分配pci_bus_assign_resources():

1376 void __pci_bus_assign_resources(const struct pci_bus *bus,
1377                                 struct list_head *realloc_head,
1378                                 struct list_head *fail_head)
1379 {
1380         struct pci_bus *b;
1381         struct pci_dev *dev;
1382
1383         pbus_assign_resources_sorted(bus, realloc_head, fail_head);
1384
1385         list_for_each_entry(dev, &bus->devices, bus_list) {
1386                 pdev_assign_fixed_resources(dev);
1387
1388                 b = dev->subordinate;
1389                 if (!b)
1390                         continue;
1391
1392                 __pci_bus_assign_resources(b, realloc_head, fail_head);
1393
1394                 switch (dev->class >> 8) {
1395                 case PCI_CLASS_BRIDGE_PCI:
1396                         if (!pci_is_enabled(dev))
1397                                 pci_setup_bridge(b);
1398                         break;
1399
1400                 case PCI_CLASS_BRIDGE_CARDBUS:
1401                         pci_setup_cardbus(b);
1402                         break;
1403
1404                 default:
1405                         pci_info(dev, "not setting up bridge for bus %04x:%02x\n",
1406                                  pci_domain_nr(b), b->number);
1407                         break;
1408                 }
1409         }
1410 }

1383行: pbus_assign_resources_sorted, 这个函数先对当前总线下设备请求的资源进行排序

+-> pbus_assign_resources_sorted()
    +-> list_for_each_entry(dev, &bus->devices, bus_list)
            __dev_sort_resources(dev, &head);
+-> __assign_resources_sorted(&head, realloc_head, fail_head);
    +-> assign_requested_resources_sorted(head, fail_head);
        +-> list_for_each_entry(dev_res, head, list)
            pci_assign_resource(dev_res->dev, idx)
                +-> pci_bus_alloc_resource()
                    +-> allocate_resource()
                        +-> find_resource()
                        +-> request_resource()
            +->pci_update_resource()

__dev_sort_resources将pci设备使用的资源进行对齐和排序,然后加入到head流程中。
__assign_resources_sorted中先调用find_resource()获取上游pci bridge的所管理的空间资源范围。 再调用request_resource()为当前pci设备分配pcie地址空间,最后调用pci_update_resource()将初始化pcie bar寄存器,将更新的资源区间写到寄存器。
1392行: 和枚举流程一样,这里也是用深度优先遍历的方法,依次分配各个pcie ep设备的bar资源。
1397行: pci_setup_bridge(),某个总线下所有设备BAR空间分配之后,将初始化该总线桥的配置空间中的memory base寄存器(该总线子树下所有设备使用的PCI总线域地址空间的基地址)和memory limit寄存器(总线子树使用的总地址空间的大小)。

总而言之,pcie的资源枚举过程可以概况如下:

  1. 获取上游pci 桥设备所管理的系统资源范围
  2. 使用DFS对所有的pci ep device进行bar资源的分配
  3. 使用DFS对当前pci桥设备的base和limit的值,并对这些寄存器进行更新。

至此,pci树中所有pci设备的BAR寄存器,以及pci桥的base、limit寄存器都已经初始化完毕。

参考资料

PCI Express体系结构导读

你可能感兴趣的:(PCIe扫盲)