PCIe_Host驱动分析_设备枚举

往期内容

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

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

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-CSDN博客
    -- 末篇,有专栏内容观看顺序

总线和设备树专栏:

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

目录

分析的文件仍然是:pcie-rockchip.c

详情见上一篇:PCIe_Host驱动分析_地址映射-CSDN博客

1.PCIe控制器的资源

在上一篇文章中,讲了host驱动时如何去解析设备树获取得到设备树的资源:pcie控制器的寄存器地址、region0地址、IO/Memory的映射需要的pcie_addr_base和cpu_addr_base等资源,其中三类资源记录在链表中:

  • 总线资源:就是总线号,从0到0x1f
  • 内存资源:CPU地址基地址为0xfa000000,PCI地址基地址为0xfa000000,大小为0x1e00000
  • IO资源:CPU地址基地址为0xfbe00000,PCI地址基地址为0xfbe00000,大小为0x100000

解析设备树时,把资源记录在这个链表里:

res链表中记录的资源最终会放到pci_bus->bridge->windows链表里,如下图记录:

2.回顾设备配置空间

本节内容参考:PCI_SPEV_V3_0.pdf(资料下载

使用PCI/PCIe的目的,就是为了简单地访问它:像读写内存一样读写PCI/PCIe设备。

提问:

  • 使用哪些地址读写设备?
  • 这些地址的范围有多大?
  • 是像内存一样访问它,还是像IO一样访问它?

每个PCI/PCIe设备都有配置空间,就是一系列的寄存器,对于普通的设备,它的配置空间格式如下。

里面有: -----可以看深入解析非桥PCI设备的访问和配置方法_pcie 寄存器访问从wire 使用配置请求-CSDN博客

  • 设备ID
  • 厂家ID
  • Class Code:哪类设备?存储设备?显示设备?等待
  • 6个Base Address Register:

2.1 设备信息

介绍:

2.2 base address

BAR用于:

  • 声明需要什么类型的空间:内存、IO、32位地址、64位地址?

  • 声明需要的空间有多大

  • 保存主控分配给它的PCI空间基地址

  • 基址寄存器(BAR,Base Address Registers):配置空间中的基址寄存器(BARs)定义了PCIe设备的寄存器(或内存)映射。它指定了设备的内存空间或I/O空间在系统地址空间中的位置。这些寄存器address告诉操作系统设备的寄存器或数据缓冲区在哪个地址范围,从而可以进行内存映射I/O(MMIO)或端口映射I/O操作。

    • 内存映射寄存器:通过配置空间中的BAR,设备的寄存器可以映射到系统的内存地址空间,主机可以通过对这些内存地址进行读写来操作设备的寄存器。

地址空间可以分为两类:内存(Memory)、IO:

  • 对于内存,写入什么值读出就是什么值,可以提前读取
  • 对于IO,它反应的是硬件当前的状态,每个时刻读到的值不一定相同

BAR的格式如下:

  • 用于内存空间
  • 用于IO空间:

在PCIe设备的配置空间中,基址寄存器BAR,Base Address Register)用于指示设备在系统内存地址空间或I/O地址空间中所需的地址范围。这些寄存器告诉操作系统设备需要多少内存或I/O空间,但要知道它们申请的具体空间大小是有一个明确的机制的。

BAR的结构

  • 每个BAR寄存器是32位或64位宽,存储设备的基地址 和一些控制信息。配置空间中通常有6个BAR寄存器(BAR0到BAR5),但设备可以选择只使用其中的一部分。
  • BAR寄存器中的低位被用来存储设备的控制位,而高位则用于设备申请的基地址。BAR寄存器可以指定设备是需要内存映射 (Memory-mapped I/O)还是I/O映射(Port I/O)的地址空间。

BAR申请空间大小的机制

为了知道PCIe设备需要申请的内存或I/O空间的大小,操作系统或主机使用一个简单的探测机制

  1. 写全1到BAR寄存器 :操作系统在设备初始化阶段,会向每个BAR寄存器写入全10xFFFFFFFF)。这一步的作用是屏蔽基地址部分,只保留空间大小的相关位。

  2. 读取BAR寄存器的返回值:设备会根据它需要的内存或I/O空间大小,返回一组值。这些返回的值中会有低几位为固定的0(这些位用于对齐),而高位部分表明设备需要的地址空间的大小。

  3. 计算空间大小:返回的值并不是直接告诉你设备需要的空间大小,而是告诉你它的地址掩码。通过这一返回值,操作系统可以计算设备需要的空间大小。具体计算方法如下:

    • 首先,取出返回值中的有效位(低位为0的位保留对齐要求,不参与大小计算)。
    • 然后,将该值按位取反(bitwise NOT),并加1,得到设备实际需要的空间大小。例如,如果BAR返回的值为0xFFFFFFF0,则所需空间大小为(~0xFFFFFFF0 + 1),即16字节
  4. 分配空间并写回BAR:操作系统根据返回的大小值,为设备分配一个合适的地址范围,然后将这个分配的基地址写入BAR寄存器中。低几位的0确保地址对齐,满足设备的对齐要求。


具体计算示例

假设某PCIe设备的一个BAR寄存器在写入0xFFFFFFFF后返回了0xFFFFF000

  • 解析返回值

    • 高位部分0xFFFFF000表示该设备需要的空间大小。因为设备返回的低12位是0,这意味着该设备需要的空间大小是4KB(2^12 字节)。
  • 计算大小

    • 使用公式:大小 = ~0xFFFFF000 + 1,即大小 = 0x00000FFF + 1 = 0x1000,即4096字节(4KB)

32位 vs. 64位 BAR

  • 32位 BAR:如果BAR是32位的,返回的值就是设备所需的32位地址空间范围。如果设备需要更大的空间,则可能会使用多个BAR寄存器。
  • 64位 BAR :有些PCIe设备可能需要更大的内存空间,在这种情况下,设备会使用连续的两个BAR寄存器,将它们组合成一个64位地址来表示设备所需的地址范围。系统通过检查BAR寄存器中的一个特殊标志(在32位BAR的下两位中),判断它是否为64位BAR。如果是,则会组合连续的两个BAR寄存器来构成64位地址空间。

内存 vs I/O 空间

BAR寄存器还可以指示设备请求的地址空间类型:

  • 内存映射空间:如果BAR的类型字段指示设备需要内存映射地址空间,返回的大小值将告诉系统设备需要的内存大小,操作系统将其映射到系统的内存空间。
  • I/O映射空间:如果BAR指示设备需要I/O空间,返回的值将告诉系统设备需要的I/O端口空间的大小,操作系统将其映射到I/O端口空间。

BAR寄存器用于指定PCIe设备的内存或I/O空间的基址以及所需的地址范围。

操作系统通过向BAR写入0xFFFFFFFF,读取返回值来确定设备需要的空间大小。

通过位操作(取反和加1)计算出设备申请的内存或I/O空间的实际大小。

BAR寄存器既可以是32位也可以是64位,根据设备需求分配相应的地址空间。

这种机制使操作系统能够灵活分配设备所需的地址资源,同时确保设备按照其需要的地址对齐要求运行。

3.设备扫描过程

3.1 构造pci_dev结构体 -- 核心

扫描PCIe总线,对每一个PCIe桥、PCIe设备,都构造出对应的pci_dev:

  • 填充pci_dev的各项成员,比如VID、PID、Class等
  • 分配地址空间、写入PCIe设备
c 复制代码
/*
 * The pci_dev structure is used to describe PCI devices.
 */
struct pci_dev {
    struct list_head bus_list;	/* node in per-bus list */
    struct pci_bus	*bus;		/* bus this device is on */
    struct pci_bus	*subordinate;	/* bus this device bridges to */

    void		*sysdata;	/* hook for sys-specific extension */
    struct proc_dir_entry *procent;	/* device entry in /proc/bus/pci */
    struct pci_slot	*slot;		/* Physical slot this device is in */

    unsigned int	devfn;		/* encoded device & function index */
    unsigned short	vendor;
    unsigned short	device;
    unsigned short	subsystem_vendor;
    unsigned short	subsystem_device;
    unsigned int	class;		/* 3 bytes: (base,sub,prog-if) */
    u8		revision;	/* PCI revision, low byte of class word */
    u8		hdr_type;	/* PCI header type (`multi' flag masked out) */

    ........省略.........

    struct pci_driver *driver;	/* which driver has allocated this device */
    u64		dma_mask;	/* Mask of the bits of bus address this
					   device implements.  Normally this is
					   0xffffffff.  You only need to change
					   this if your device has broken DMA
					   or supports 64-bit transfers.  */
    
    ........省略.........

    unsigned int	irq;
	struct cpumask	*irq_affinity;
    struct resource resource[DEVICE_COUNT_RESOURCE]; /* I/O and memory regions + expansion ROMs */
}

struct pci_dev 是 Linux 内核中用于描述 PCI 设备的核心数据结构之一。它包含了一个 PCI 设备的所有必要信息,内核通过这个结构体来管理和操控 PCI 设备。

1. PCI 设备的总线和系统信息

c 复制代码
struct list_head bus_list;
  • 设备所在总线链表中的节点,便于将设备组织到一个链表中,方便总线上的设备管理。
c 复制代码
struct pci_bus *bus;
  • 设备所在的 PCI 总线对象的指针,通过这个指针可以找到设备所属的总线。
c 复制代码
struct pci_bus *subordinate;
  • 如果这个设备是一个 PCI 桥接设备(Bridge),则这个字段指向它所连接的子总线。通过这个字段可以访问子总线的设备。
c 复制代码
void *sysdata;
  • 系统特定的扩展字段,允许特定架构或平台存储自定义的系统数据。
c 复制代码
struct proc_dir_entry *procent;
  • 设备在 /proc/bus/pci 中的条目,用于用户态通过 /proc 文件系统访问 PCI 设备的信息。
c 复制代码
struct pci_slot *slot;
  • 指向 PCI 插槽的指针,表示设备所在的物理插槽位置。

2. 设备标识信息

c 复制代码
unsigned int devfn;
  • 编码后的设备和功能号,设备编号和功能编号被组合成一个值,遵循 PCI 规范中的设备和功能编号编码规则。
c 复制代码
unsigned short vendor;
unsigned short device;
  • 设备的厂商 ID 和设备 ID,分别用于标识设备制造商和设备本身的型号。
c 复制代码
unsigned short subsystem_vendor;
unsigned short subsystem_device;
  • 子系统的厂商 ID 和子系统设备 ID,这两个字段通常在服务器和嵌入式设备中用于标识子系统设备。
c 复制代码
unsigned int class;
  • 设备的类别码,用来描述设备的功能。它由基础类、子类和编程接口组成,分别表示设备的大类、小类和具体接口。
c 复制代码
u8 revision;
  • PCI 设备的修订版本号,表示该设备的硬件版本。
c 复制代码
u8 hdr_type;
  • PCI 头部类型,表示该设备的配置空间头部类型。还可以用来标记设备是否为多功能设备。

3. 设备中断相关

c 复制代码
unsigned int irq;
  • 设备使用的中断号,用于表示设备分配的中断线。
c 复制代码
struct cpumask *irq_affinity;
  • 中断亲和性掩码,表示设备的中断关联到哪个 CPU 上处理。多核系统中可以为设备的中断设置特定的 CPU 来处理。

4. 设备资源信息

c 复制代码
struct resource resource[DEVICE_COUNT_RESOURCE];

struct resource {   // ioport.h
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	unsigned long desc;
	struct resource *parent, *sibling, *child;
};

1. resource_size_t start
● 资源的起始地址,表示该资源在 CPU 物理地址空间中的起始位置。
● 对于内存资源,这是内存区域的起始地址;对于 I/O 资源,这是 I/O 端口的起始地址。
2. resource_size_t end
● 资源的结束地址,表示该资源在 CPU 物理地址空间中的结束位置。
● start 和 end 通常表示该资源所占用的连续地址范围。
3. const char *name
● 资源的名字,可以是一个描述性字符串,用于标识该资源。它有助于调试和追踪系统中的资源分配情况。
4. unsigned long flags
● 资源的属性标志,使用一系列标志位来描述资源的类型和特性。常见的标志包括:
  ○ IORESOURCE_MEM:表示这是一个内存资源。
  ○ IORESOURCE_IO:表示这是一个 I/O 端口资源。
  ○ IORESOURCE_IRQ:表示这是一个中断资源。
  ○ IORESOURCE_BUSY:表示该资源已被分配并正在使用。
5. unsigned long desc
● 资源的描述字段,通常用于保存有关资源的其他具体信息。该字段具体如何使用取决于资源类型和实现需求。
6. struct resource *parent
● 指向父资源的指针,用于构建资源的层次结构。例如,一个 PCI 设备可能是一个更大内存资源的子资源。
7. struct resource *sibling
● 指向同一级别其他资源的指针,用于表示当前资源在父资源下的兄弟资源。
8. struct resource *child
● 指向子资源的指针,用于构建资源的层次结构。如果一个资源被分配给多个子资源(比如不同的内存区域),这些子资源通过这个字段来进行关联。
  • 设备的资源描述符数组,包含了设备的 I/O 端口、内存映射地址和扩展 ROM 等资源。DEVICE_COUNT_RESOURCE 是该设备能使用的最大资源数量,通常包括 I/O 空间、内存空间和扩展 ROM。

  • 这个结构体广泛用于内核中,尤其是在设备驱动程序和系统资源管理中。对于 PCI 设备、内存管理或 I/O 端口操作来说,resource 结构体可以描述某个资源在系统中占用的地址范围。

  • CPU 视角的物理地址startend 描述的是从 CPU 角度看到的物理地址空间(cpu_addr_phycical)。这些地址并不能直接通过指针访问。如果要从内核代码中访问这些地址,需要通过 ioremap 将物理地址映射到内核的虚拟地址空间中。

  • 示例:

    • 当你想访问某个 PCI 设备的寄存器时,通常你会首先获取设备的资源信息,并用 ioremap 将资源的物理地址映射到虚拟地址空间。
    • structresource *res = pci_resource_start(pdev, BAR0); // 获取 PCI 设备 BAR0 的资源
    • void __iomem *mapped_addr = ioremap(res->start, resource_size(res));
    • 在这段代码中,pci_resource_start 会返回设备的 BAR0 基地址,ioremap 会将这个物理地址映射到虚拟地址空间,之后你可以通过 mapped_addr 访问设备的寄存器。

3.2 代码分析

要找到这4个核心代码:

  • 分配pci_dev
  • 读取PCIe设备的配置空间,填充pci_dev中的设备信息
  • 根据PCIe设备的BAR,得知它想申请什么类型的地址、多大?
  • 分配地址,写入BAR

关键代码分为两部分:

  • 读信息、得知PCIe设备想申请多大的空间
shell 复制代码
rockchip_pcie_probe
    bus = pci_scan_root_bus(&pdev->dev, 0, &rockchip_pcie_ops, rockchip, &res); // \pci\probe.c
        pci_scan_root_bus_msi   //  \pci\probe.c
            pci_scan_child_bus  // \pci\probe.c
                pci_scan_slot   //  \pci\probe.c
                    dev = pci_scan_single_device(bus, devfn);   //  \pci\probe.c 
                        dev = pci_scan_device(bus, devfn);      //  
                            struct pci_dev *dev;
                            dev = pci_alloc_dev(bus);
                            pci_setup_device
                                pci_read_bases(dev, 6, PCI_ROM_ADDRESS);	
                        pci_device_add(dev, bus);

📎probe.c

  • 分配空间
c 复制代码
rockchip_pcie_probe
    pci_bus_size_bridges(bus);      // \pci\setup_bus.c
    pci_bus_assign_resources(bus);  // \pci\setup_bus.c
        __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);

📎setup-bus.c

1.分配pci_dev结构体

1. **rockchip_pcie_probe**

该函数是 Rockchip PCIe 驱动的入口函数。通常会初始化 PCIe 控制器硬件,并调用 PCI 核心框架来进行 PCI 总线扫描。关键调用是:

c 复制代码
bus = pci_scan_root_bus(&pdev->dev, 0, &rockchip_pcie_ops, rockchip, &res);
  • pci_scan_root_bus 是用于扫描 PCI 根总线的函数。
  • pdev->dev 是设备结构体,0 是总线号,rockchip_pcie_ops 是与该硬件相关的操作集合,rockchip 是设备私有数据,res 是与总线相关的资源信息。
  • 此函数返回根总线的 pci_bus 结构体指针,表示 PCI 总线被成功扫描。

2. **pci_scan_root_bus**

pci_scan_root_bus 是 PCI 核心框架中的函数,负责扫描和初始化根总线设备。

c 复制代码
pci_scan_root_bus_msi()

此处调用了 pci_scan_root_bus_msi 来支持 MSI (Message Signaled Interrupts)。该函数将进一步调用 pci_scan_child_bus

3. **pci_scan_child_bus**

pci_scan_child_bus 是扫描子总线的核心函数。

c 复制代码
pci_scan_slot()
  • 在这个函数中,它将扫描每个插槽。每个插槽可以对应多个设备(多功能设备)。

4. **pci_scan_slot**

pci_scan_slot 函数负责扫描某个插槽内的所有功能(function),并检查是否有有效的 PCI 设备存在。

c 复制代码
pci_scan_single_device(bus, devfn)
  • 对于插槽的每个设备功能,调用 pci_scan_single_device 函数,试图扫描设备。

5. **pci_scan_single_device**

pci_scan_single_device 尝试扫描单个 PCI 设备。如果成功,会调用 pci_scan_device 来完成设备的初始化。

c 复制代码
dev = pci_scan_device(bus, devfn);
  • 这一步实际执行设备的扫描。它通过设备函数号 devfn 来识别特定设备,执行后续设备初始化。

6. **pci_scan_device**

pci_scan_device 是实际初始化 PCI 设备的核心函数。此时,PCI 总线已经检测到设备存在。接下来要做的就是分配和设置 pci_dev 结构体。

c 复制代码
struct pci_dev *dev;
dev = pci_alloc_dev(bus);
  • pci_alloc_dev 分配并初始化 pci_dev 结构体,该结构体用于表示一个 PCI 设备。它将设备的信息与总线关联,并初始化一些基本字段。
c 复制代码
pci_setup_device(dev);
  • pci_setup_device 是初始化 PCI 设备的核心函数,读取设备配置空间并设置设备的相关信息。
  • 此函数会读取 PCI 配置空间的各种寄存器,包括 PCI_VENDOR_IDPCI_DEVICE_ID,并根据这些信息填充 pci_dev 结构体。

7. **pci_read_bases(dev, 6, PCI_ROM_ADDRESS)**

pci_setup_device 函数中调用 pci_read_bases,用于读取 PCI 设备的基地址寄存器(BAR),从而知道设备的 I/O 地址和内存映射地址。通常,PCI 设备有多个 BAR(最多 6 个),每个 BAR 用于映射设备的资源。

c 复制代码
pci_read_bases(dev, 6, PCI_ROM_ADDRESS);
  • pci_read_bases 用于读取 PCI 设备的基地址寄存器(Base Address Registers, BARs),并将设备的 I/O 或内存资源注册到内核中。
  • PCI_ROM_ADDRESS 是设备可能包含的扩展 ROM 地址。

8. **pci_device_add(dev, bus)**

c 复制代码
pci_device_add(dev, bus);
  • 当设备初始化完成后,调用 pci_device_add 将设备添加到 PCI 总线设备列表中,并注册到内核设备模型中。这一步实际上让设备进入内核设备框架,使得后续可以绑定驱动并进行管理。

2.读取设备信息

3.读BAR

pci_read_bases函数会调用__pci_read_base__pci_read_base只是读BAR,算出想申请的空间的大小:

  • 读BAR,保留原值

  • 写0xFFFFFFFF到BAR

  • 在读出来,解析出所需要的地址空间大小,记录在pci_dev->resource

    • pci_dev->resource .start = 0;
    • pci_dev->resource .end = size - 1;

4.分配地址空间

  • 从哪里分配得到地址空间?

    • 在设备树里指明了CPU地址、PCI地址的对应关系,这些作为"资源"记录在pci_bus里
    • 读BAR时,在pci_dev->resource\[\]里记录了它想申请空间的大小
  • 分配得到的基地址,要写入BAR

代码调用关系如下:

  • 把要申请的资源, 按照对齐要求排序,然后调用assign_requested_resources_sorted,代码如下:
shell 复制代码
/* 把要申请的资源, 按照对齐要求排序
 * 然后调用assign_requested_resources_sorted
 */

rockchip_pcie_probe
    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);
  • assign_requested_resources_sorted函数做两件事

    • 分配地址空间
    • 把这块空间对应的PCI地址写入PCIe设备的BAR
    • 代码如下:
shell 复制代码
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);

rockchip_pcie_probe 函数中,PCIe 控制器探测和初始化过程中,有一个关键步骤是为 PCIe 设备分配资源并将这些资源的基地址写入 PCI 配置空间中的基地址寄存器(BAR)。下面是对上述调用关系的进一步理解:

1. **pci_bus_size_bridges** & **pci_bus_assign_resources**

  • pci_bus_size_bridges(bus) 用于计算 PCIe 总线上设备的资源需求。

  • pci_bus_assign_resources(bus) 是为设备分配资源的函数。

    • 主要调用了 __pci_bus_assign_resources,进一步处理资源分配和对齐操作。

2. **__pci_bus_assign_resources**

  • __pci_bus_assign_resources 调用了 pbus_assign_resources_sorted 来对资源进行排序和分配。设备的 pci_dev->resource[] 中保存了该设备申请的资源,接下来会根据资源的对齐要求来排序。

    • 对齐要求是指设备的资源地址必须满足一定的对齐标准。例如,某些设备可能需要 1K 对齐,而另一些设备可能只需要 32 字节对齐。对齐要求高的资源优先分配,以确保资源的合理使用。
  • _dev_sort_resources

    • 资源会按对齐要求排序,比如 1K 对齐的资源优先于 32 字节对齐的资源。
    • 排序后,调用 __assign_resources_sorted 来为这些设备分配资源。

3. **assign_requested_resources_sorted**

该函数执行两个关键任务:

  1. 分配地址空间。
  2. 将分配的 PCI 地址写入 PCI 设备的 BAR。

它调用了 pci_assign_resource 来完成这些任务。

4. **pci_assign_resource**

分配地址空间:pci_assign_resource 负责实际的资源分配,它调用 _pci_assign_resource 函数,后者进一步调用 __pci_assign_resource

  • __pci_assign_resource 会计算和分配地址空间,最后通过 allocate_resource 函数从资源链表中分配地址。
c 复制代码
allocate_resource(r, res, size, ...);
  • allocate_resource 尝试从给定的资源池中分配符合要求的内存或 I/O 地址空间,并将地址信息更新到 pci_dev->resource[] 中。

    • res->startres->end 是为资源分配的物理地址范围。

5. **pci_update_resource**

  • 地址空间分配完成后,调用 pci_update_resource 函数将这些资源的基地址写入 PCI 配置空间中的 BAR。

  • 其中,pci_std_update_resource 是具体的写入 BAR 的函数。

  • pci_std_update_resource

    • 它首先将分配给设备的 CPU 地址 转换为 PCI 地址 。由于 CPU 地址和 PCI 地址通常不一致,需根据系统架构中的偏移量(offset)进行转换。

      • PCI 地址 = CPU 地址 - offset
  • pcibios_resource_to_bus

    • pcibios_resource_to_bus 负责将设备的物理地址(CPU 地址)转换为 PCI 总线地址(PCI 地址)。转换后的地址会保存在 region.start 中。
  • pci_write_config_dword(dev, reg, new)

    • 最终通过 pci_write_config_dword 将 PCI 地址写入 PCI 设备的配置空间中对应的 BAR 寄存器。BAR 地址从 PCI_BASE_ADDRESS_0 开始,每个 BAR 占用 4 个字节的配置空间。
    • reg 是 BAR 的寄存器地址(从 PCI_BASE_ADDRESS_0 开始),new 是要写入 BAR 的 PCI 地址。

  1. 资源分配 :通过 pci_bus_size_bridgespci_bus_assign_resources,PCI 桥和设备资源的需求得到计算,并开始为设备分配 I/O 和内存资源。
  2. 对齐排序:在分配资源时,根据对齐要求对设备的资源请求进行排序,高对齐要求的资源优先分配。
  3. 地址空间分配 :调用 pci_assign_resource 和相关函数从资源池中为设备分配地址空间,并更新 pci_dev->resource[]
  4. BAR 写入 :通过 pci_update_resourcepci_write_config_dword,将分配的 PCI 地址写入设备的 BAR 寄存器。

通过这个过程,PCIe 设备的资源得到了有效分配,BAR 寄存器被正确写入,从而确保设备能够正常访问系统的内存和 I/O 区域。这也是设备驱动程序与硬件交互的基础。

相关推荐
国科安芯9 小时前
航天器多路并联大功率电源系统设计与ASP4644均流特性分析
单片机·嵌入式硬件·fpga开发·安全性测试
茯苓gao11 小时前
嵌入式开发笔记:CANopen相关移位运算与通信协议术语详解
网络·嵌入式硬件·学习·信息与通信
梁朝辉14 小时前
STM32H750VBT6中ADCINP与INN什么区别
stm32·单片机·嵌入式硬件
省四收割者15 小时前
一文详解信号完整性(1)
python·嵌入式硬件·数学建模·信息与通信·信号处理·智能硬件
崇山峻岭之间15 小时前
单片机无感FOC驱动之ADC
单片机·嵌入式硬件
Ligocious16 小时前
1.点亮一颗小小的LED
单片机·嵌入式硬件
K成长日志17 小时前
BLE协议之设备地址
网络·物联网·网络协议·嵌入式·蓝牙·ble
凉、介19 小时前
ARMv8 架构下的刷 Cache 操作
c语言·笔记·学习·架构·嵌入式·arm
KaifuZeng20 小时前
通信与接口协议面试八、CAN通信
单片机·嵌入式硬件·面试·通信与接口协议
智源单片机设计21 小时前
基于单片机的直流电机双闭环调速系统设计
单片机·嵌入式硬件