PCIe_Host驱动分析_地址映射

往期内容

本文章相关专栏往期内容,PCI/PCIe子系统专栏:

  1. 嵌入式系统的内存访问和总线通信机制解析、PCI/PCIe引入
  2. 深入解析非桥PCI设备的访问和配置方法
  3. PCI桥设备的访问方法、软件角度讲解PCIe设备的硬件结构
  4. 深入解析PCIe设备事务层与配置过程
  5. PCIe的三种路由方式
  6. PCI驱动与AXI总线框架解析(RK3399)
  7. 深入解析PCIe地址空间与寄存器机制:从地址映射到TLP生成的完整流程

Uart子系统专栏:

  1. 专栏地址:Uart子系统
  2. Linux内核早期打印机制与RS485通信技术
    – 末片,有专栏内容观看顺序

interrupt子系统专栏:

  1. 专栏地址:interrupt子系统
  2. Linux 链式与层级中断控制器讲解:原理与驱动开发
    – 末片,有专栏内容观看顺序

pinctrl和gpio子系统专栏:

  1. 专栏地址:pinctrl和gpio子系统

  2. 编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用

    – 末片,有专栏内容观看顺序

input子系统专栏:

  1. 专栏地址:input子系统
  2. input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
    – 末片,有专栏内容观看顺序

I2C子系统专栏:

  1. 专栏地址:IIC子系统
  2. 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-优快云博客
    – 末篇,有专栏内容观看顺序

总线和设备树专栏:

  1. 专栏地址:总线和设备树
  2. 设备树与 Linux 内核设备驱动模型的整合-优快云博客
    – 末篇,有专栏内容观看顺序

img

资料

开发板资料:

  • https://wiki.t-firefly.com/zh_CN/ROC-RK3399-PC-PLUS/

分析的文件:

1.回顾

在这里插入图片描述

CPU访问外部的PCIe设备的流程:

  1. 读PCIe设备的配置空间,CPU依靠控制器的Region0
  2. 获取PCIe设备申请空间的大小,CPU分配空间(基地址为addr_cpu)
  3. 控制器将addr_cpu转化为addr_pcie,包装成TPL格式写给设备,进行配置
  4. CPU以后访问外部设备可以发出addr_cpu0,控制器就会自动转化为对应的addr_pcie0地址去访问该外部设备

那么控制器想要发送TPL,将CPU发送来的地址信息(region0)进行解析转化,就得去配置控制器上region0的寄存器:

  1. 配置region1~32的register
  2. 记录地址资源,用来分配给PCIe设备用的(分配给设备的地址资源,上面提到的流程的第2点)

2.设备树文件

在这里插入图片描述

pcie0: pcie@f8000000 {
        compatible = "rockchip,rk3399-pcie";
        #address-cells = <3>;
        #size-cells = <2>;
        ranges = <0x83000000 0x0 0xfa000000 0x0 0xfa000000 0x0 0x1e00000
                  0x81000000 0x0 0xfbe00000 0x0 0xfbe00000 0x0 0x100000>;
        reg = <0x0 0xf8000000 0x0 0x2000000>,
              <0x0 0xfd000000 0x0 0x1000000>;
        reg-s = "axi-base", "apb-base";
;

RK3399访问PCIe控制器时,CPU地址空间可以分为:

  • Client Register Set:地址范围 0xFD000000~0xFD7FFFFF,比如选择PCIe协议的版本(Gen1/Gen2)、电源控制等
  • Core Register Set :地址范围 0xFD800000~0xFDFFFFFF,所谓核心寄存器就是用来进行设置地址映射的寄存器等
  • Region 0:0xF8000000~0xF9FFFFFF , 32MB,用于访问外接的PCIe设备的配置空间
  • Region 1:0xFA000000~0xFA0FFFFF,1MB,用于地址转换
  • Region 2:0xFA100000~0xFA1FFFFF,1MB,用于地址转换
  • ……
  • Region 32:0xFBF00000~0xFBFFFFFF,1MB,用于地址转换

其中Region 0大小为32MB,Region1~31大小分别为1MB。

在设备树里都有体现(下列代码中,其他信息省略了):

  • reg属性里的0xf8000000:Region 0的地址

  • reg属性里的0xfd000000:PCIe控制器内部寄存器的地址

    • Client Register Set:地址范围 0xFD000000~0xFD7FFFFF
    • Core Register Set :地址范围 0xFD800000~0xFDFFFFFF
  • ranges属性里

    • 第1个0xfa000000:Region1~30的CPU地址空间首地址,用于内存读写
    • 第2个0xfa000000:Region1~30的PCI地址空间首地址,用于内存读写
    • 第1个0xfbe00000:Region31的CPU地址空间首地址,用于IO读写
    • 第2个0xfbe00000:Region31的PCI地址空间首地址,用于IO读写
  • Region32呢?在.c文件里用作"消息TLP"

对于memory内存读写:之前写入什么值,再去读的时候就是什么值

对于IO内存读写:之前写入什么值,去读的时候不一定就是原来的那个值

3.PCI驱动程序框架

在这里插入图片描述

4.驱动程序源码分析

  1. 从设备树中获取PCIe控制器寄存器和region0配置空间的基地址,然后才能去通过配置对应的region的寄存器,达到想要发送的TPL的数据内容格式,比如TPL中的某些字段(bus、device number、func、reg等)可以由addr_cpu来提供,其余位设置ob_addr寄存器,让其来补。因此最主要的就是获取设备树中的reg中的region0和寄存器的base_addr
  2. 从设备树中获取region1~32的addr_cpu和addr_pcie的基地址,根据flag去分别解析得到资源。------获取设备树中ranges有关IO/Memory的配置空间资源
  3. 既然知道了region1~32的addr_cpu_base和addr_pcie_base,那么肯定就是要去确定他们之间的隐射关系。这样后续配置好的pcie设备,CPU就可以发出相对应的addr_cpu映射得到addr_pcie,去直接操作设备。(一般情况下addr_cpu和addr_pcie会设置成一样的,TPL由addr_pcie构成,也就由addr_cpu构成的差不多)

4.1 region和寄存器的配置基地址

从设备树中获取控制器的region和寄存器的地址,去初始化,后续才能发出配置读/写的TPL

在这里插入图片描述

0xF8000000就是RK3399的Region0地址,用于 ECAM:ECAM是访问PCIe配置空间一种机制,PCIe配置空间大小是4k。即:只写读写0xF8000000这段空间,就可以只写读写PCIe设备的配置空间。

0xFD000000是RK3399 PCIe控制器本身的寄存器基地址。

Region0用与读写配置空间,它对应的寄存器要设置用于产生对应的TLP,函数调用关系如下:

在这里插入图片描述

rockchip_pcie_probe
    err = rockchip_pcie_init_port(rockchip);  // 初始化,配置读/写类型的TPL
      	rockchip_pcie_write(rockchip,
      			    (RC_REGION_0_ADDR_TRANS_L + RC_REGION_0_PASS_BITS),  //RC_REGION_0_PASS_BITS:25-1
                //CPU发出的地址为[24:0],但是TPL发出需要的(bus<<20) | (dev<<15) | (fun<<12) | (reg)就要28位了
                //其中24:0就是有cpu_addr填充上,而高三位则由控制器的寄存器ob_addr0的27:25来填充上,这样配置的TPL最基础的
      			    PCIE_CORE_OB_REGION_ADDR0);
      	rockchip_pcie_write(rockchip, RC_REGION_0_ADDR_TRANS_H,
      			    PCIE_CORE_OB_REGION_ADDR1);
      	rockchip_pcie_write(rockchip, 0x0080000a, PCIE_CORE_OB_REGION_DESC0);   
        //设置ob_desc0寄存器,默认发出配置 Type1类型的TPL
        
      	rockchip_pcie_write(rockchip, 0x0, PCIE_CORE_OB_REGION_DESC1);   

以配置写为例:

在这里插入图片描述

可以看到,对于对配置空间的写,通过bus、dev、func、reg来构造一个偏移地址busdev,busdev+region的基地址就能实现对PCI设备的配置写

4.2 确定CPU/PCI地址空间

对于memory内存读写:之前写入什么值,再去读的时候就是什么值

对于IO内存读写:之前写入什么值,去读的时候不一定就是原来的那个值

在PCIe设备树里有一个属性ranges,它里面含有多个range,每个range描述了:

  • flags:是内存还是IO
  • PCIe地址
  • CPU地址
  • 长度

先提前说一下怎么解析这些range,函数为for_each_of_pci_range,解析过程如下:

在这里插入图片描述
在这里插入图片描述

rockchip_pcie_probe
	resource_size_t io_base;
    LIST_HEAD(res); // 资源链表

	// 解析设备树获得PCI host bridge的资源(CPU地址空间、PCI地址空间、大小)
	err = of_pci_get_host_bridge_resources(dev->of_node, 0, 0xff, &res, &io_base);
		// 解析 bus-range
		// 设备树里:  bus-range = <0x0 0x1f>;
		// 解析得到: bus_range->start= 0 , 
		//          bus_range->end = 0x1f, 
		//          bus_range->flags = IORESOURCE_BUS;
		// 放入前面的链表"LIST_HEAD(res)"
		err = of_pci_parse_bus_range(dev, bus_range);  
			pci_add_resource(resources, bus_range);

		// 解析 ranges
		// 设备树里: 
        //        ranges = <0x83000000 0x0 0xfa000000 0x0 0xfa000000 0x0 0x1e00000
        //                  0x81000000 0x0 0xfbe00000 0x0 0xfbe00000 0x0 0x100000>;
    	of_pci_range_parser_init
    		parser->range = of_get_property(node, "ranges", &rlen);
		for_each_of_pci_range(&parser, &range) {// 解析range            
            // 把range转换为resource
            // 第0个range
            // 		range->pci_space = 0x83000000,
            //		range->flags     = IORESOURCE_MEM,
            //		range->pci_addr  = 0xfa000000,
            //		range->cpu_addr  = 0xfa000000,
            //		range->size      = 0x1e00000,
            // 转换得到第0个res:
            // 		res->flags = range->flags = IORESOURCE_MEM;
            // 		res->start = range->cpu_addr = 0xfa000000;
            // 		res->end = res->start + range->size - 1 = (0xfa000000+0x1e00000-1);
            // ---------------------------------------------------------------
            // 第1个range
            // 		range->pci_space = 0x81000000,
            //		range->flags     = IORESOURCE_IO,
            //		range->pci_addr  = 0xfbe00000,
            //		range->cpu_addr  = 0xfbe00000,
            //		range->size      = 0x100000,
            // 转换得到第1个res:
            // 		res->flags = range->flags = IORESOURCE_MEM;
            // 		res->start = range->cpu_addr = 0xfbe00000;
            // 		res->end = res->start + range->size - 1 = (0xfbe00000+0x100000-1);
            err = of_pci_range_to_resource(&range, dev, res); 

            // 在链表中增加resource
            // 第0个resource:
            //		注意第3个参数: offset = cpu_addr - pci_addr = 0xfa000000 - 0xfa000000 = 0
            // 第1个resouce
            //		注意第3个参数: offset = cpu_addr - pci_addr = 0xfbe00000 - 0xfbe00000 = 0
            pci_add_resource_offset(resources, res,	res->start - range.pci_addr);

        }

    /* Get the I/O and memory ranges from DT */
    resource_list_for_each_entry(win, &res) {
        rockchip->io_bus_addr = io->start - win->offset;   // 0xfbe00000, cpu addr
        rockchip->mem_bus_addr = mem->start - win->offset; // 0xfba00000, cpu addr
        rockchip->root_bus_nr = win->res->start; // 0
    }

	bus = pci_scan_root_bus(&pdev->dev, 0, &rockchip_pcie_ops, rockchip, &res);

	pci_bus_add_devices(bus);

4.3 建立CPU/PCI地址空间的映射

配置地址转转换单元,调用关系如下:
在这里插入图片描述

rockchip_pcie_probe
    err = rockchip_cfg_atu(rockchip);
                /* MEM映射: Region1~30 */
                // rockchip->mem_bus_addr = 0xfa000000
              	// rockchip->mem_size     = 0x1e00000
              	// 设置Region1、2、……30的映射关系
                for (reg_no = 0; reg_no < (rockchip->mem_size >> 20); reg_no++) {
                    err = rockchip_pcie_prog_ob_atu(rockchip, reg_no + 1,
                                    AXI_WRAPPER_MEM_WRITE,
                                    20 - 1,
                                    rockchip->mem_bus_addr +
                                    (reg_no << 20),
                                    0);
                    if (err) {
                        dev_err(dev, "program RC mem outbound ATU failed\n");
                        return err;
                    }
                }
                
                /* IO映射: Region31 */
                // rockchip->io_bus_addr = 0xfbe00000
              	// rockchip->io_size     = 0x100000
              	// 设置Region31的映射关系
                offset = rockchip->mem_size >> 20;
                for (reg_no = 0; reg_no < (rockchip->io_size >> 20); reg_no++) {
                    err = rockchip_pcie_prog_ob_atu(rockchip,
                                    reg_no + 1 + offset,
                                    AXI_WRAPPER_IO_WRITE,
                                    20 - 1,
                                    rockchip->io_bus_addr +
                                    (reg_no << 20),
                                    0);
                    if (err) {
                        dev_err(dev, "program RC io outbound ATU failed\n");
                        return err;
                    }
                }

                /* 用于消息传输: Region32 */
                rockchip_pcie_prog_ob_atu(rockchip, reg_no + 1 + offset,
                              AXI_WRAPPER_NOR_MSG,
                              20 - 1, 0, 0);

                rockchip->msg_bus_addr = rockchip->mem_bus_addr +
                                ((reg_no + offset) << 20);

何一个Region,都有对应的寄存器:
在这里插入图片描述

所谓建立CPU和PCI地址空间的映射,就是设置Region对应的寄存器,都是使用函数rockchip_pcie_prog_ob_atu
在这里插入图片描述

4.4 总结

1. 从设备树中获取PCIe控制器寄存器和Region 0配置空间的基地址

设备树(Device
Tree)中的reg属性定义了硬件资源的地址映射。对于PCIe控制器来说,通常在reg属性中包含控制器的寄存器基地址和对应配置空间的地址区域。Region
0通常用于PCIe配置空间(Configuration Space)的访问,也即控制器本身的寄存器设置。

步骤:

  • 通过读取设备树中对应PCIe节点的reg属性,获取PCIe控制器寄存器的基地址Region 0的基地址
  • PCIe控制器的寄存器可以配置多个Region(如region0对应配置空间,region1~32用于PCIe映射)。你需要首先通过寄存器配置来初始化并启用对应的Region(如Region
    0)。

在配置PCIe控制器的寄存器时,你需要设置TPL字段(如busdevice numberfuncreg等)。正如你所描述的,CPU地址(addr_cpu可提供这些信息,同时Outbound(OB)地址寄存器(ob_addr)负责映射CPU地址到PCIe地址空间。

2. 从设备树中获取Region 1~32的**addr_cpu****addr_pcie**的基地址

PCIe控制器的多个Region(如Region1~32)通常用于内存或I/O空间的映射,映射的是CPU地址和PCIe地址之间的对应关系。设备树中的ranges属性指定了这些映射关系,包括addr_cpuaddr_pcie的基地址。

步骤:

  • 解析设备树中对应PCIe控制器节点的ranges属性,提取Region 1~32的addr_cpuaddr_pcie的基地址
  • ranges属性中可能包含标志位(flags),用于区分不同类型的资源(如I/O空间或内存空间)。你需要根据这些标志位去判断每个区域是用于I/O、内存或其他类型的访问。

3. 确定**addr_cpu_base****addr_pcie_base**的映射关系

通过设置多个Region(如Region1~32)之间的映射关系,CPU可以通过**addr_cpu**访问PCIe设备的寄存器或内存空间。在配置PCIe设备时,CPU发出的addr_cpu会通过Region的映射机制被转换为addr_pcie,从而在PCIe设备上实现操作。

一般来说,addr_cpuaddr_pcie可以设置成相同,以简化访问映射关系,但这并不是必须的。重要的是,PCIe控制器中的Region寄存器需要正确配置,以确保CPU访问时,能正确地转换成对应的PCIe地址。

TPL构成

TPL(Transaction Pending
List)中的字段会根据PCIe地址(通常是addr_pcie)来构成。如果addr_cpuaddr_pcie是相同的,那么从逻辑上看,TPL可以由addr_cpu直接构成。然而,无论如何,最终的TPL是与PCIe设备交互的PCIe地址,而不是CPU直接使用的虚拟地址或物理地址。因此,在事务处理中,PCIe控制器会处理地址转换,并将相应的PCIe地址信息写入TPL。

  1. 通过设备树的reg获取PCIe控制器寄存器和Region 0配置空间的基地址,并通过这些地址配置PCIe控制器的寄存器,设置TPL中字段(如busdevice numberfunc等)。
  2. 通过设备树的ranges获取Region 1~32的addr_cpuaddr_pcie基地址,并根据flags标志解析出不同类型的资源映射。
  3. 通过PCIe控制器的Region寄存器配置,确定addr_cpuaddr_pcie的映射关系。CPU发出的addr_cpu可以映射到相应的addr_pcie,实现对PCIe设备的访问。
  4. TPL由PCIe地址构成,但如果addr_cpuaddr_pcie相同,则TPL可以近似由addr_cpu构成。

这一过程确保了CPU发出的请求能够通过PCIe控制器映射到正确的PCIe设备地址,并且TPL可以正确地记录和管理事务。

<think>我们正在讨论PCIe驱动开发中的`devm_pci_alloc_host_bridge`函数。这个函数在Linux内核的PCI子系统中扮演着关键角色,特别是在Root Complex(RC)驱动初始化阶段。下面我将详细解释该函数的作用和调用流程,并参考站内引用内容进行说明。 ### 1. **`devm_pci_alloc_host_bridge`函数的作用** `devm_pci_alloc_host_bridge`是Linux内核中用于**分配并初始化一个PCI主机桥(Host Bridge)** 的函数。主机桥是连接CPU和PCIe系统的关键组件,RC驱动通过它管理整个PCIe拓扑。该函数的作用包括: - **分配内存**:为`struct pci_host_bridge`结构体分配内存,该结构体代表一个PCI主机桥。 - **资源初始化**:初始化主机桥的资源列表(如内存、I/O、总线号范围),确保后续枚举过程能正确分配资源。 - **设备资源管理(devm)**:使用`devm_`系列函数(如`devm_kzalloc`[^1])自动管理内存,当驱动卸载或设备移除时自动释放资源,避免内存泄漏。 - **设置回调函数**:为`pci_ops`(PCI配置空间访问操作)等预留接口,供后续驱动填充。 此函数通常在RC驱动的`probe`函数中调用,是驱动初始化的第一步。 ### 2. **函数调用流程** 在PCIe RC驱动开发中,`devm_pci_alloc_host_bridge`的典型调用流程如下(以Xilinx或类似平台驱动为例): ```c // 驱动probe函数示例 static int xilinx_pcie_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; // 步骤1: 分配并初始化主机桥 struct pci_host_bridge *bridge = devm_pci_alloc_host_bridge(dev, sizeof(struct xilinx_pcie)); if (!bridge) return -ENOMEM; // 步骤2: 填充私有数据(如硬件寄存器基地址) struct xilinx_pcie *pcie = pci_host_bridge_priv(bridge); pcie->dev = dev; // 步骤3: 获取硬件资源(如寄存器基址) res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "reg_base"); pcie->reg_base = devm_ioremap_resource(dev, res); // 步骤4: 设置主机桥操作函数(pci_ops) bridge->ops = &xilinx_pcie_ops; // 包含config_read/config_write等 // 步骤5: 设置资源范围(如总线号、内存/I/O窗口) bridge->bus_range = &xilinx_bus_range; // 总线号范围 bridge->sysdata = pcie; bridge->map_irq = xilinx_map_irq; // 中断映射函数 // 步骤6: 扫描PCI总线(触发枚举) pci_scan_root_bus_bridge(bridge); ... } ``` #### 关键步骤解析: 1. **分配主机桥**: - `devm_pci_alloc_host_bridge(dev, priv_size)`:分配`struct pci_host_bridge`及其私有数据区(`priv_size`为私有结构大小,如`xilinx_pcie`)。 - 内部调用`devm_kzalloc`[^1]分配内存,确保自动释放。 2. **获取私有数据**: - `pci_host_bridge_priv(bridge)`:通过指针偏移获取私有数据区地址,用于存储硬件特定信息(如寄存器基址)。 3. **资源初始化**: - 通过`platform_get_resource_byname()`获取设备树中定义的资源(如寄存器基址)。 - 使用`devm_ioremap_resource()`映射寄存器到内核虚拟地址空间。 4. **设置操作函数**: - `bridge->ops`指向`struct pci_ops`,其中包含配置空间读写函数(如`.read = xilinx_pcie_cfg_read`)。这些函数由驱动实现,用于访问EP的配置空间。 5. **设置资源范围**: - 指定总线号范围(`bus_range`)、内存/I/O窗口(`windows`链表)等。这些信息通常来自设备树或ACPI表。 6. **触发枚举**: - `pci_scan_root_bus_bridge(bridge)`:最终调用`pci_scan_root_bus()`启动PCI总线扫描,递归枚举所有EP设备(详见上一回答的扫描流程)。 ### 3. **函数内部实现(Linux内核源码参考)** `devm_pci_alloc_host_bridge`的核心实现位于`drivers/pci/probe.c`(以Linux 5.10内核为例): ```c struct pci_host_bridge *devm_pci_alloc_host_bridge(struct device *dev, size_t priv_size) { struct pci_host_bridge *bridge; size_t alloc_size = sizeof(*bridge) + priv_size; // 使用devm_kzalloc分配内存[^1] bridge = devm_kzalloc(dev, alloc_size, GFP_KERNEL); if (!bridge) return NULL; // 初始化主机桥资源列表 INIT_LIST_HEAD(&bridge->windows); // 资源窗口链表 INIT_LIST_HEAD(&bridge->resources); ... return bridge; } ``` 此函数通过`devm_kzalloc`[^1]分配内存,并初始化链表头(如`windows`用于存储PCI地址空间映射)。 ### 4. **与资源分配的关系** 主机桥的资源管理直接影响后续枚举: - **资源窗口(windows)**:表示主机桥可用的地址空间范围(如内存0x80000000-0xFFFFFFFF)。在枚举时,EP的BAR空间必须分配在这些窗口内。 - **总线号范围**:由`bus_range`指定,例如0-255,表示该桥下游可用的总线号。 在`pci_scan_root_bus_bridge()`中,这些资源将被用于: 1. 创建根总线(`pci_create_root_bus()`)。 2. 调用`pci_scan_child_bus()`扫描设备。 3. 通过`pci_assign_unassigned_bus_resources()`分配BAR空间(确保在`windows`定义的范围内)。 ### 5. **错误处理** 如果分配失败(如内存不足),函数返回`NULL`,驱动应处理错误(如返回`-ENOMEM`)。由于使用`devm`,成功分配后无需显式释放。 ### 总结 `devm_pci_alloc_host_bridge`在PCIe RC驱动中的作用是: 1. **自动管理主机桥内存**(通过`devm_kzalloc`[^1])。 2. **初始化主机桥关键数据结构**(资源列表、操作函数预留位)。 3. **为后续枚举流程(扫描、资源分配)奠定基础**。 它是RC驱动开发的起点,确保主机桥正确初始化后,才能递归枚举EP设备。 §§相关问题§§ 1. PCIe主机桥(Host Bridge)和根复合体(Root Complex)有什么区别? 2. Linux内核中如何实现PCIe配置空间的访问函数(如`pci_ops`)? 3. 设备树(Device Tree)如何描述PCIe主机桥的资源?</think>### `devm_pci_alloc_host_bridge`函数的作用与调用流程 在PCIe驱动开发中,`devm_pci_alloc_host_bridge`是**初始化PCI主机桥(Root Complex)的核心函数**,负责创建并管理主机桥的数据结构。以下是详细解析: --- #### 一、函数作用 1. **内存分配与初始化**: - 自动分配`struct pci_host_bridge`结构体内存(使用`devm_kzalloc`[^1]) - 初始化主机桥关键数据结构: ```c struct pci_host_bridge { struct device *dev; struct list_head windows; // 资源窗口链表(内存/I/O空间) struct pci_ops *ops; // PCI配置空间操作函数 struct pci_bus *bus; // 关联的PCI总线 // ... }; ``` 2. **资源管理**: - 通过`devm_`机制自动释放资源(驱动卸载时自动回收内存) - 初始化资源窗口链表(用于后续BAR空间分配) 3. **桥接设备注册基础**: - 为后续`pci_scan_root_bus()`等枚举函数提供操作入口 - 设置PCI配置空间访问函数(如`read/write`) --- #### 二、典型调用流程 在PCIe RC驱动的`probe()`函数中,典型调用栈如下: ```c // 1. 驱动入口函数(如Xilinx/Intel平台驱动) static int pcie_rc_probe(struct platform_device *pdev) { // 2. 分配主机桥结构体 struct pci_host_bridge *bridge = devm_pci_alloc_host_bridge(dev, priv_size); // 3. 填充私有数据(硬件寄存器地址等) struct pcie_priv *priv = pci_host_bridge_priv(bridge); priv->regs = pcim_iomap_table(pdev)[0]; // 4. 设置PCI操作函数 bridge->ops = &pcie_ops; // 包含cfg_read/cfg_write等 // 5. 添加资源窗口(内存/I/O范围) pci_add_resource(&bridge->windows, &mem_res); // 6. 创建根总线并启动枚举 pci_scan_root_bus_bridge(bridge); // → 触发设备枚举 ... } ``` ##### 关键函数调用栈: ```plaintext pcie_rc_probe() // 驱动probe入口 ├─ devm_pci_alloc_host_bridge() // 分配主机桥结构体 [^1] │ ├─ devm_kzalloc() // 自动内存分配 │ ├─ INIT_LIST_HEAD(&bridge->windows) // 初始化资源链表 │ └─ memset(priv_data, 0, priv_size) // 清零私有数据区 │ ├─ pci_host_bridge_priv() // 获取私有数据指针 ├─ pci_add_resource() // 添加资源窗口 └─ pci_scan_root_bus_bridge() // 启动枚举流程 ├─ pci_create_root_bus() // 创建根总线 └─ pci_scan_child_bus() // 递归扫描设备 ``` --- #### 三、关键操作解析 1. **资源窗口管理**: - 通过`pci_add_resource()`添加主机桥可用的地址空间范围: ```c struct resource mem_res = { .start = 0x80000000, .end = 0x9FFFFFFF, .flags = IORESOURCE_MEM }; pci_add_resource(&bridge->windows, &mem_res); ``` - 后续枚举时EP的BAR空间必须在此范围内分配 2. **配置空间访问**: - 驱动需实现`struct pci_ops`: ```c static const struct pci_ops pcie_ops = { .read = pcie_cfg_read, // 读取配置寄存器 .write = pcie_cfg_write, // 写入配置寄存器 }; ``` - 这些函数通过主机桥的配置访问机制(ECAM/增强配置访问机制)操作EP设备 3. **自动资源释放**: - `devm_`前缀确保资源生命周期与`struct device`绑定 - 驱动卸载时自动调用`devm_kfree()`释放内存[^1] --- #### 四、实际驱动示例(Xilinx PCIe) ```c static int xilinx_pcie_probe(struct platform_device *pdev) { // 分配主机桥(含私有数据区) struct pci_host_bridge *bridge = devm_pci_alloc_host_bridge(dev, sizeof(struct xdma_pcie)); // 获取私有数据指针 struct xdma_pcie *pcie = pci_host_bridge_priv(bridge); // 映射寄存器 pcie->reg_base = devm_platform_ioremap_resource(pdev, 0); // 设置操作函数 bridge->ops = &xilinx_pcie_ops; // 添加内存窗口(从设备树解析) pci_add_resource(&bridge->windows, &res[0]); // 启动枚举 pci_scan_root_bus_bridge(bridge); ... } ``` --- ### 总结 `devm_pci_alloc_host_bridge`的核心作用: 1. **自动管理**主机桥数据结构内存[^1] 2. **初始化**PCIe Root Complex的操作框架 3. **桥接**硬件资源与PCI子系统枚举流程 4. **简化**驱动开发(通过`devm_`资源管理) 此函数是PCIe RC驱动初始化的**必要起点**,为后续设备枚举(`pci_scan_child_bus()`)和资源分配(`pci_assign_unassigned_resources()`)奠定基础。
评论 33
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值