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

img

目录

  • 往期内容
  • 1.PCIe控制器的资源
  • 2.回顾设备配置空间
    • 2.1 设备信息
    • 2.2 base address
  • 3.设备扫描过程
    • 3.1 构造pci_dev结构体 -- 核心
    • 3.2 代码分析
      • 1.分配pci_dev结构体
      • 2.读取设备信息
      • 3.读BAR
      • 4.分配地址空间

分析的文件仍然是: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

img

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

img

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

img

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 设备信息

img

介绍:img

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的格式如下:

  • 用于内存空间

img

  • 用于IO空间:

img

在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设备
/** 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 thisdevice implements.  Normally this is0xffffffff.  You only need to changethis if your device has broken DMAor 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 设备的总线和系统信息

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

2. 设备标识信息

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

3. 设备中断相关

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

4. 设备资源信息

struct resource resource[DEVICE_COUNT_RESOURCE];struct resource {   // ioport.hresource_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设备想申请多大的空间
rockchip_pcie_probebus = pci_scan_root_bus(&pdev->dev, 0, &rockchip_pcie_ops, rockchip, &res); // \pci\probe.cpci_scan_root_bus_msi   //  \pci\probe.cpci_scan_child_bus  // \pci\probe.cpci_scan_slot   //  \pci\probe.cdev = 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_devicepci_read_bases(dev, 6, PCI_ROM_ADDRESS);	pci_device_add(dev, bus);

📎probe.c

  • 分配空间
rockchip_pcie_probepci_bus_size_bridges(bus);      // \pci\setup_bus.cpci_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_sortedassign_requested_resources_sorted(head, &local_fail_head);

📎setup-bus.c

1.分配pci_dev结构体

img

1. **rockchip_pcie_probe**

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

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 核心框架中的函数,负责扫描和初始化根总线设备。

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 是扫描子总线的核心函数。

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

4. **pci_scan_slot**

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

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

5. **pci_scan_single_device**

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

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

6. **pci_scan_device**

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

struct pci_dev *dev;
dev = pci_alloc_dev(bus);
  • pci_alloc_dev 分配并初始化 pci_dev 结构体,该结构体用于表示一个 PCI 设备。它将设备的信息与总线关联,并初始化一些基本字段。
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 用于映射设备的资源。

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)**

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

2.读取设备信息

img

3.读BAR

img

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,代码如下:
/* 把要申请的资源, 按照对齐要求排序* 然后调用assign_requested_resources_sorted*/rockchip_pcie_probepci_bus_size_bridges(bus);pci_bus_assign_resources(bus);__pci_bus_assign_resourcespbus_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_sortedassign_requested_resources_sorted(head, &local_fail_head);
  • assign_requested_resources_sorted函数做两件事

    • 分配地址空间
    • 把这块空间对应的PCI地址写入PCIe设备的BAR
    • 代码如下:
assign_requested_resources_sorted(head, &local_fail_head);pci_assign_resourceret = _pci_assign_resource(dev, resno, size, align);// 分配地址空间__pci_assign_resourcepci_bus_alloc_resourcepci_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地址写入BARpci_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 函数从资源链表中分配地址。
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 区域。这也是设备驱动程序与硬件交互的基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/64907.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入浅出:多功能 Copilot 智能助手如何借助 LLM 实现精准意图识别

阅读原文 1. Copilot中的意图识别 如果要搭建一个 Copilot 智能助手,比如支持 知识问答、数据分析、智能托管、AIGC 等众多场景或能力,那么最核心的就是基于LLM进行意图识别分发能力,意图识别的准确率直接决定了 Copilot 智能助手的能力上限…

Jo-im开发:用于WebRTC的ICE中继服务器Coturn搭建

前言 本人计划开发一套具备文本、语音、视频通话功能的IM demo,同时具备多人在线会议功能,按习惯大概会开源版定义名称为Duihao jo-im,本案主要用于实现语音视频通话的基础组件支撑。因为我们选择基于WebRTC实现IM中语音、视频通话&#xff…

【CVE-2024-53375】TP-Link Archer系列路由器认证操作系统命令注入(内附远离和代码利用)

CVE-2024-53375 TP-Link Archer系列路由器认证操作系统命令注入 受影响的设备 使用 HomeShield 功能的 TP-Link 设备容易受到此漏洞的影响。这包括 TP-Link Archer 系列的多款路由器。 经过测试 Archer AXE75(EU)_V1_1.2.2 Build 20240827(发布日期 2024 年 11 月 4 日)…

程控电阻箱应用中需要注意哪些安全事项?

程控电阻箱是一种用于精确控制电路中电流和电压的电子元件,广泛应用于电子实验、测试设备以及精密测量仪器中。在应用程控电阻箱时,为确保安全和设备的正常运行,需要注意以下几个安全事项: 1. 正确连接:确保电阻箱与电…

Promise链式调用

Promise链式调用 上一篇我们实现了通过promise的方式实现获取国家基本信息,本次我们来使用promise链式调用来实现邻国的展现 首先,我们从第一个国家中获取到邻国的国家代码名称 const neighbour data[0].borders[0];然后我们通过fetch来获取邻国信息&a…

Elasticsearch相关知识@1

目录标题 Lucene1. **什么是 Lucene?**2. **Lucene 在 Elasticsearch 中的作用**3. **Lucene 的核心功能**(1) **倒排索引**(2) **分词**(3) **查询解析**(4) **相关性评分** 4. **为什么 Elasticsearch 使用 Lucene?**5. **Lucene 和 Elasticsearch 的区别**6. **总结** 分片…

UE5 渲染管线 学习笔记

兰伯特 SSS为散射的意思 带Bias的可以根据距离自动切换mip的卷积值 而带Level的值mipmaps的定值 #define A8_SAMPLE_MASK .a 这样应该就很好理解了 这个只采样a通道 带Level的参考上面的 朝左上和右下进行模糊 带Bias参考上面

canvas绘制仪表盘刻度盘

canvas画布可以实现在网页上绘制图形的方法,比如图表、图片处理、动画、游戏等。今天我们在vue模板下用canvas实现仪表盘的绘制。 对canvas不熟悉的同学可以先了解下canvas的API文档:canvas API中文网 - Canvas API中文文档首页地图 一、创建模板&#…

Spring Boot 中实现自定义注解记录接口日志功能

👨🏻‍💻 热爱摄影的程序员 👨🏻‍🎨 喜欢编码的设计师 🧕🏻 擅长设计的剪辑师 🧑🏻‍🏫 一位高冷无情的全栈工程师 欢迎分享 / 收藏 / 赞 / 在看…

【超详细实操内容】django的身份验证系统之限制用户访问的三种方式

目录 1、使用request.user.is_authenticated属性 2、装饰器login_required 3、LoginRequiredMixin类 通常情况下,网站都会对用户限制访问,例如,未登录的用户不可访问用户中心页面。Django框架中使用request.user.isauthenticated属性、装饰器loginrequired和LoginRequire…

scss配置全局变量报错[sass] Can‘t find stylesheet to import.

路径没有错误,使用别名即可 后又提示Deprecation Warning: Sass import rules are deprecated and will be removed in Dart Sass 3.0.0. 将import改为use 使用时在$前添加全局变量所在文件,即variable.

基于Qlearning强化学习的机器人路线规划matlab仿真

目录 1.算法仿真效果 2.算法涉及理论知识概要 3.MATLAB核心程序 4.完整算法代码文件获得 1.算法仿真效果 matlab2022a仿真结果如下(完整代码运行后无水印): 训练过程 测试结果 仿真操作步骤可参考程序配套的操作视频。 2.算法涉及理论…

9 RCC使用HSE、HSI配置时钟

一、时钟树 RCC:reset clock control,复位和时钟控制器。HSE是外部的高速时钟信号,可以由有源晶振或者无源晶振提供。如果使用HSE或者HSE经过PLL倍频之后的时钟作为系统时钟SYSCLK,当HSE故障时候,不仅HSE会被关闭,PLL…

认识数据结构之——排序

一、 插入排序: 直接插入排序(以排升序为例): 排序思想: 单趟:记录某个位置的值,一个一个和前面的值比较,碰到更大的就往后覆盖,碰到更小的或者相等的就结束,最后将记录的值插入到…

uniapp 微信小程序 功能入口

单行单独展示 效果图 html <view class"shopchoose flex jsb ac" click"routerTo(要跳转的页面)"><view class"flex ac"><image src"/static/dyd.png" mode"aspectFit" class"shopchooseimg"&g…

苍穹外卖-day05redis 缓存的学习

苍穹外卖-day05 课程内容 Redis入门Redis数据类型Redis常用命令在Java中操作Redis店铺营业状态设置 学习目标 了解Redis的作用和安装过程 掌握Redis常用的数据类型 掌握Redis常用命令的使用 能够使用Spring Data Redis相关API操作Redis 能够开发店铺营业状态功能代码 功能实…

Linux之系统管理

一、相关命令 筛选 grep&#xff0c;可以用来进行筛选&#xff0c;例如对目录筛选课写成 # 过滤出带serv的 ls /usr/sbin | grep serv2. 对服务的操作 2.1 centos6版本 service 服务名 start|stop|restart|status # start&#xff1a;开启 # stop&#xff1a;停止 # restart…

什么?Flutter 可能会被 SwiftUI/ArkUI 化?全新的 Flutter Roadmap

在刚刚过去的 FlutterInProduction 活动里&#xff0c;Flutter 官方除了介绍「历史进程」和「用户案例」之外&#xff0c;也着重提及了未来相关的 roadmap &#xff0c;其中就有 3.27 里的 Swift Package Manager 、 Widget 实时预览 和 Dart 与 native 平台原生语言直接互操作…

Unity录屏插件-使用Recorder录制视频

目录 1.Recorder的下载 2.Recorder面板 2.1常规录制属性 2.2录制器配置 2.2.1添加录制器 2.2.2配置Input属性 2.2.3配置 Output Format 属性 2.2.4配置 Output File 属性 3.Recorder的使用 3.1录制Game View视频 3.1.1Recorder配置与场景搭建 3.1.2开始录制 3.1.3…

Android Vendor Overlay机制

背景介绍&#xff1a; 看Android 15版本更新时&#xff0c;"Android 15 deprecates vendor overlay"。 猜想这个vendor overlay是之前用过的settings overlay&#xff0c; 不过具体是怎么回事呢&#xff1f; 目录 Vendor Overlay介绍 Vendor Overlay工作原理 Ven…