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 区域。这也是设备驱动程序与硬件交互的基础。

相关推荐
weixin_4526006941 分钟前
串行时钟保持芯片D1380/D1381,低功耗工作方式自带秒、分、时、日、日期、月、年的串行时钟保持芯片,每个月多少天以及闰年能自动调节
科技·单片机·嵌入式硬件·时钟·白色家电电源·微机串行时钟
森旺电子4 小时前
51单片机仿真摇号抽奖机源程序 12864液晶显示
单片机·嵌入式硬件·51单片机
不过四级不改名6776 小时前
蓝桥杯嵌入式备赛教程(1、led,2、lcd,3、key)
stm32·嵌入式硬件·蓝桥杯
小A1596 小时前
STM32完全学习——SPI接口的FLASH(DMA模式)
stm32·嵌入式硬件·学习
Rorsion6 小时前
各种电机原理介绍
单片机·嵌入式硬件
善 .9 小时前
单片机的内存是指RAM还是ROM
单片机·嵌入式硬件
超级码农ProMax9 小时前
STM32——“SPI Flash”
stm32·单片机·嵌入式硬件
Asa31910 小时前
stm32点灯Hal库
stm32·单片机·嵌入式硬件
end_SJ12 小时前
初学stm32 --- 外部中断
stm32·单片机·嵌入式硬件
gantengsheng12 小时前
基于51单片机和OLED12864的小游戏《贪吃蛇》
单片机·嵌入式硬件·游戏·51单片机