pcie的pci_create_root_bus 分析

本文详细解析了PCI Root Bus初始化过程,包括创建Root Bus、分配Host Bridge、设置资源及注册设备等步骤。揭示了PCI总线在Linux内核中的实现机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在pci_create_root_bus 中 首先会申请一个pci的root bus
    struct pci_bus *b;
    b = pci_alloc_bus(NULL);
    if (!b)
        return NULL;

    b->sysdata = sysdata;
    b->ops = ops;
    b->number = b->busn_res.start = bus;
#ifdef CONFIG_PCI_DOMAINS_GENERIC
    b->domain_nr = pci_bus_find_domain_nr(b, parent);
#endif

然后会再申请一个host bridge
    bridge = pci_alloc_host_bridge(b);
    if (!bridge)
        goto err_out;
从pci_alloc_host_bridge 中可以看到pcie的root bus是挂在host bridge下面的
static struct pci_host_bridge *pci_alloc_host_bridge(struct pci_bus *b)
{
    struct pci_host_bridge *bridge;

    bridge = kzalloc(sizeof(*bridge), GFP_KERNEL);
    if (!bridge)
        return NULL;

    INIT_LIST_HEAD(&bridge->windows);
    bridge->bus = b;
    return bridge;
}
在pci_alloc_host_bridge 中先申请一个pci_host_bridge 结构体,然后将bridge->bus 指向b,这个b就是前面申请的pci root bus,因此验证了前面的说明一个rc是有host bridge -> root bus -> root port 组成的.

    bridge->dev.parent = parent;
    bridge->dev.release = pci_release_host_bridge_dev;
因为这里的pci_create_root_bus 可能是switch 设备的bus,因为要对bridge->dev.parent 赋值parent,如果是root bus的话,parent 就等于NULL。
acpi_pci_root_create->pci_create_root_bus 调用时,明显看到parent是NULL。
再调用
    dev_set_name(&bridge->dev, "pci%04x:%02x", pci_domain_nr(b), bus);
来设定device的name,这样就可以在/sys/devices 下面看到以pci开始的device了,
root@ubuntu:/sys/devices# ls
LNXSYSTM:00  pci0002:80  pci0006:c0  pci000c:20  pnp0      tracepoint
armv8_pmuv3  pci0004:88  pci0007:90  pci000d:30  software  virtual
breakpoint   pci0005:00  pci000a:10  platform    system

这里的domain就是pci 的segment了,具体也是在pci_create_root_bus 中通过
#ifdef CONFIG_PCI_DOMAINS_GENERIC
    b->domain_nr = pci_bus_find_domain_nr(b, parent);
#endif
来赋值的.
int pci_bus_find_domain_nr(struct pci_bus *bus, struct device *parent)
{
    return acpi_disabled ? of_pci_bus_find_domain_nr(parent) :
                   acpi_pci_bus_find_domain_nr(bus);
}
由于我们使用的acpi因此走acpi_pci_bus_find_domain_nr
int acpi_pci_bus_find_domain_nr(struct pci_bus *bus)
{
    struct pci_config_window *cfg = bus->sysdata;
    struct acpi_device *adev = to_acpi_device(cfg->parent);
    struct acpi_pci_root *root = acpi_driver_data(adev);

    return root->segment;
}
这就比较清楚的看到在pci中所谓的domain就是pci的segment。
    error = pcibios_root_bridge_prepare(bridge);
    if (error) {
        kfree(bridge);
        goto err_out;
    }

pcibios_root_bridge_prepare 这个函数在arm64 下只有在非apci mode下才意义,如果是acpi mode相当与空函数
int pcibios_root_bridge_prepare(struct pci_host_bridge *bridge)
{
    if (!acpi_disabled) {
        struct pci_config_window *cfg = bridge->bus->sysdata;
        struct acpi_device *adev = to_acpi_device(cfg->parent);
        ACPI_COMPANION_SET(&bridge->dev, adev);
    }

    return 0;
}
注册bridge 对应的device,并给bus下面的bridage赋值.
    error = device_register(&bridge->dev);
    if (error) {
        put_device(&bridge->dev);
        goto err_out;
    }
    b->bridge = get_device(&bridge->dev);
给bus下的device 赋值和bus相同的domain
    pci_set_bus_msi_domain(b);
给root bus赋值
    b->dev.class = &pcibus_class;
    b->dev.parent = b->bridge;
    dev_set_name(&b->dev, "%04x:%02x", pci_domain_nr(b), bus);
    error = device_register(&b->dev);
这样就可以在/sys/bus/pci/devices 下面看到pci bus下面挂了多少个devices.
root@ubuntu:/sys/bus/pci/devices# ls
0002:80:00.0  0005:00:00.0  0007:90:00.0  000a:10:00.0  000d:30:00.0
0004:88:00.0  0006:c0:00.0  0007:91:00.0  000c:20:00.0

在开机log中可以通过下面的log找到系统中有多少个host bridge
    if (parent)
        dev_info(parent, "PCI host bridge to bus %s\n", dev_name(&b->dev));
    else
        printk(KERN_INFO "PCI host bridge to bus %s\n", dev_name(&b->dev));

[   13.411622] PCI host bridge to bus 0002:80
[   13.411622] PCI host bridge to bus 0002:80
可以看到系统中有两个host bridge ???。
    /* Add initial resources to the bus */
    resource_list_for_each_entry_safe(window, n, resources) {
        list_move_tail(&window->node, &bridge->windows);
        res = window->res;
        offset = window->offset;
        if (res->flags & IORESOURCE_BUS)
            pci_bus_insert_busn_res(b, bus, res->end);
        else
            pci_bus_add_resource(b, res, 0);
        if (offset) {
            if (resource_type(res) == IORESOURCE_IO)
                fmt = " (bus address [%#06llx-%#06llx])";
            else
                fmt = " (bus address [%#010llx-%#010llx])";
            snprintf(bus_addr, sizeof(bus_addr), fmt,
                 (unsigned long long) (res->start - offset),
                 (unsigned long long) (res->end - offset));
        } else
            bus_addr[0] = '\0';
        dev_info(&b->dev, "root bus resource %pR%s\n", res, bus_addr);
    }
通过这段code 给bus添加资源,可以通过在开机log 中查找dev_info(&b->dev, "root bus resource %pR%s\n", res, bus_addr);
[   13.419865] pci_bus 0002:80: root bus resource [mem 0xa8800000-0xaffeffff window]
[   13.419865] pci_bus 0002:80: root bus resource [mem 0xa8800000-0xaffeffff window]
[   13.434941] pci_bus 0002:80: root bus resource [io  0x0000-0xffff window]
[   13.434941] pci_bus 0002:80: root bus resource [io  0x0000-0xffff window]
[   13.448617] pci_bus 0002:80: root bus resource [bus 80-87]
[   13.448617] pci_bus 0002:80: root bus resource [bus 80-87]
来得到root bus的资源,从log看bus的资源分为3类,及mem/io/bus

最后将bus的node添加到pci_root_buses 中
 list_add_tail(&b->node, &pci_root_buses);
而pci_root_buses是一个list,其定义为extern struct list_head pci_root_buses;    /* list of all known PCI buses */


<think>我们正在讨论`pci_scan_root_bus_bridge`接口,它是Linux内核中PCI子系统的一部分。根据引用[1]中的函数原型,我们可以开始分析。 ### 功能 `pci_scan_root_bus_bridge`的主要功能是**扫描并初始化一个PCI主机桥(host bridge)下游的整个PCI总线层次结构**。具体来说: 1. **创建根总线**:为PCI主机桥创建对应的根总线(root bus)结构体`struct pci_bus`。 2. **枚举设备**:递归扫描该总线下的所有PCI设备(包括子桥),并构建设备树。 3. **分配资源**:在扫描过程中,为每个设备分配I/O和内存地址空间(包括BARs的设置),并设置PCI桥的窗口(通过调用如`pci_bus_size_bridge`等函数)。 4. **注册设备**:将发现的设备注册到内核设备模型中,使驱动能够匹配。 该函数通常在系统启动时由体系结构相关的代码(如x86的`pcibios_init`)或特定平台的主机桥驱动调用,是PCI子系统初始化的核心入口点之一[^1]。 ### 使用方法 #### 函数原型 ```c int pci_scan_root_bus_bridge(struct pci_host_bridge *bridge); ``` - **参数**:`bridge`是一个已经初始化的`struct pci_host_bridge`对象,代表主机桥。 - **返回值**:成功时返回0或负的错误码。 #### 关键步骤 1. **准备主机桥**:调用者需要预先分配并初始化`struct pci_host_bridge`,包括设置其`bus`域(总线号范围)、资源列表(通过`pci_add_resource`等添加I/O、内存等资源窗口)以及操作回调(如`.map_irq`)[^2]。 2. **调用扫描函数**:将准备好的主机桥对象传入`pci_scan_root_bus_bridge`。 3. **后续处理**:扫描完成后,通常需要调用`pci_assign_unassigned_resources`进行资源分配,以及`pci_bus_add_devices`注册设备。 #### 示例代码(伪代码) ```c #include <linux/pci.h> static int my_pci_host_probe(struct platform_device *pdev) { struct pci_host_bridge *bridge; struct resource *io_res, *mem_res; // 1. 分配主机桥对象 bridge = pci_alloc_host_bridge(0); if (!bridge) return -ENOMEM; // 2. 设置总线号范围(例如0~255) bridge->busnr = 0; bridge->bus_range = (struct resource) { .start = 0, .end = 255, .flags = IORESOURCE_BUS, }; // 3. 添加主机桥资源窗口(I/O和内存空间) io_res = platform_get_resource(pdev, IORESOURCE_IO, 0); pci_add_resource(&bridge->windows, io_res); // 引用[1]中的resource_entry mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); pci_add_resource(&bridge->windows, mem_res); // 4. 设置回调(如中断映射) bridge->map_irq = my_map_irq; // 5. 扫描根总线 ret = pci_scan_root_bus_bridge(bridge); if (ret < 0) { pci_free_host_bridge(bridge); return ret; } // 6. 分配资源和注册设备(通常在扫描后调用) struct pci_bus *root_bus = bridge->bus; pci_assign_unassigned_bus_resources(root_bus); pci_bus_add_devices(root_bus); return 0; } ``` ### 内部流程 1. **总线创建**:初始化`struct pci_bus`对象,将其与主机桥关联。 2. **设备扫描**:遍历总线上的所有设备槽(slot),通过`pci_scan_slot`检测设备并创建`struct pci_dev`。 3. **桥处理**:如果发现PCI桥设备(根据Header Type),则递归调用`pci_scan_bridge_extend`创建子总线,并继续扫描。 4. **资源计算**:在扫描桥设备时,会调用`pci_bus_size_bridge`(如前所述)计算下游资源需求[^3]。 5. **设备注册**:扫描完成后,设备暂不注册,需显式调用`pci_bus_add_devices`。 ### 注意事项 - **资源冲突**:主机桥的资源窗口(I/O、内存)必须由平台代码提供,且不能与其他主机桥重叠。 - **错误处理**:如果扫描失败(如总线冲突),需释放主机桥对象。 - **热插拔支持**:该函数主要用于启动时扫描;热插拔场景通常使用`pci_rescan_bus`。 ### 总结 `pci_scan_root_bus_bridge`是Linux内核PCI初始化的核心函数,负责构建从主机桥开始的整个PCI设备树。开发者通常在主机桥驱动中调用它,并配合资源分配和注册函数完成设备枚举[^1][^2]。 §§相关问题§§ 1. 如何为PCI主机桥分配资源窗口? 2. PCI设备扫描完成后为什么需要调用`pci_assign_unassigned_bus_resources`? 3. PCI主机桥驱动开发的基本步骤是什么?</think>### `pci_scan_root_bus_bridge` 接口详解 `pci_scan_root_bus_bridge` 是 Linux 内核 PCI 子系统中的核心函数,用于**初始化并扫描 PCI 主机桥(Host Bridge)及其下游的整个 PCI 总线层次结构**。以下是其功能和使用方法的详细分析(基于 Linux 内核 5.x 实现)。 --- #### 一、核心功能 1. **主机桥初始化** 创建并初始化 `struct pci_host_bridge` 对象,该结构体表示物理主机桥(如 CPU 与 PCIe 根复合体的连接点)。 2. **根总线扫描** 扫描主机桥下游的根总线(Root Bus),递归枚举所有 PCI/PCIe 设备: - 检测设备存在性(通过配置空间访问) - 为每个设备创建 `struct pci_dev` 对象 - 读取设备 BARs(基址寄存器)以获取资源需求[^1] 3. **资源分配准备** 收集下游设备的 I/O 空间、内存空间和中断需求,为后续资源分配(如 `pci_assign_unassigned_resources()`)提供数据基础。 4. **层次结构构建** 构建 PCI 总线树形结构: ```plaintext Host Bridge → Root Bus → Device/Bridge → Sub-Bus → ... ``` --- #### 二、函数原型与参数 ```c int pci_scan_root_bus_bridge(struct pci_host_bridge *bridge); ``` - **参数**: `bridge`:预分配的 `struct pci_host_bridge` 对象,包含: ```c struct pci_host_bridge { struct device dev; // 关联的设备对象 struct pci_bus *bus; // 下游总线对象 struct resource_entry windows; // 资源窗口列表(I/O/MEM) int (*map_irq)(const struct pci_dev*, u8, u8); // 中断映射回调 ... }; ``` - **返回值**: - `0`:成功 - 负值:错误码(如 `-ENOMEM`) --- #### 三、典型使用流程 开发者通常在**主机控制器驱动**中调用此函数,例如 PCIe 根端口驱动: ```c #include <linux/pci.h> static int my_host_driver_probe(struct platform_device *pdev) { struct pci_host_bridge *bridge; struct resource *res; // 1. 分配主机桥对象 bridge = pci_alloc_host_bridge(0); if (!bridge) return -ENOMEM; // 2. 设置总线号范围(例如 0-255) bridge->busnr = 0; bridge->bus_range = (struct resource) { .start = 0, .end = 255, .flags = IORESOURCE_BUS, }; // 3. 添加主机桥资源窗口(I/O 和内存空间) res = platform_get_resource(pdev, IORESOURCE_IO, 0); pci_add_resource(&bridge->windows, res); // 引用[1]中的resource_entry res = platform_get_resource(pdev, IORESOURCE_MEM, 0); pci_add_resource(&bridge->windows, res); // 4. 设置中断映射回调 bridge->map_irq = my_map_irq_callback; // 5. 执行根总线扫描 int ret = pci_scan_root_bus_bridge(bridge); if (ret) { pci_free_host_bridge(bridge); return ret; } // 6. 后续资源分配和设备注册 struct pci_bus *root_bus = bridge->bus; pci_assign_unassigned_bus_resources(root_bus); // 分配资源 pci_bus_add_devices(root_bus); // 注册设备到驱动模型 return 0; } ``` --- #### 四、关键内部操作 1. **总线枚举** 调用 `pci_scan_child_bus()` 递归扫描下游设备,包括 PCI-PCI 桥的处理。 2. **桥资源计算** 对发现的每个桥设备,调用 `pci_bus_size_bridge()` 计算下游资源需求(如 I/O 窗口大小)[^2]。 3. **配置空间访问** 通过主机桥的 `pci_ops` 方法(如 `.read()`/`.write()`)访问物理设备的配置空间。 4. **设备树集成** 在支持设备树(DT)的体系结构(如 ARM)中,解析 DT 节点获取总线拓扑信息。 --- #### 五、使用注意事项 1. **资源窗口必须预先配置** 主机桥的 I/O 和 MEM 资源窗口(`resource_entry`)需在调用前由平台代码提供,否则扫描会失败。 2. **中断映射要求** 必须实现 `.map_irq` 回调,将 PCI 中断引脚(INTx#)映射到系统 IRQ 编号。 3. **错误处理** 扫描失败时需手动释放资源: ```c pci_free_host_bridge(bridge); ``` 4. **热插拔支持** 对于支持热插拔的系统(如服务器),需配合 `pci_rescan_bus()` 实现动态扫描。 --- #### 六、典型应用场景 - 系统启动时的 PCI 子系统初始化 - 嵌入式 SoC 的 PCIe 控制器驱动 - 虚拟化环境中的虚拟 PCI 主机桥(如 QEMU virtio-pci) - 服务器热插拔控制器驱动 > 通过 `lspci -tv` 可查看该函数构建的总线拓扑: > ``` > -[0000:00]-+-00.0 Host bridge > +-01.0 PCI Bridge → [01:00.0] GPU > +-02.0 PCI Bridge → [02:00.0] NVMe SSD > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值