2026/4/18 11:25:40
网站建设
项目流程
自适应网站模板下载,wordpress自豪地采用修改,.wordpress,互联网营销主要学什么1. 简介
我们可以把 Linux PCI 子系统理解成两件事#xff1a;
把设备找出来#xff1a;把整条 PCI/PCIe 链路上有什么设备、挂在哪一层桥下面都摸清楚#xff0c;形成一棵“总线树”。把地址分好#xff1a;给每个设备分配它需要的 I/O 地址和内存地址#xff08;也就是…1. 简介我们可以把 Linux PCI 子系统理解成两件事把设备找出来把整条 PCI/PCIe 链路上有什么设备、挂在哪一层桥下面都摸清楚形成一棵“总线树”。把地址分好给每个设备分配它需要的 I/O 地址和内存地址也就是我们常说的 BAR并把这些地址放到内核的“资源树”里统一管理避免冲突。这篇文章的目标是把主链路理清系统在启动时做了哪些步骤资源window / BAR / bus number是怎么从设备树一路流转到最终分配结果的遇到资源冲突/设备不工作时应该先看哪里文中会在描述关键动作时顺便点出对应的函数名方便需要时再回源码定位。2. PCI 总线初始化2.1 平台驱动入口把 PCI 主桥“准备好”这一步可以理解成把“PCI 主桥host bridge”这块硬件的基础信息准备好让后续“扫设备、分资源”能顺利进行。这里抓住 3 件关键事情即可准备一份“可用资源清单”这份清单来自设备树ranges/bus-range内容是这条 PCI 总线对外能提供哪些 I/O window / 内存 window这条总线允许使用哪些 bus number 范围这份清单之后会挂到 host bridge 上后续会“搬运”到 root bus。准备“配置空间访问方式”扫设备之前内核必须能读写 PCI 配置空间例如 vendor/device ID、BAR 寄存器都在配置空间里。在很多 ARM/SoC 平台上这一层就是 ECAM把一段 MMIO 区域映射成“配置空间窗口”后续用内存读写访问配置寄存器。确定后续资源策略只沿用固件还是允许重分配如果固件已经把 BAR 都配好且希望完全沿用就进入“只认领、不重排”的模式。如果固件没配好/有冲突/希望内核重新做一遍分配就允许重分配。这些准备动作主要发生在平台驱动的pci_host_common_probe()中它会分配主桥对象devm_pci_alloc_host_bridge()、解析设备树窗口devm_of_pci_bridge_init()、建立 ECAM 配置空间访问gen_pci_init()/pci_ecam_create()并根据设备树选择是否进入“只探测不重分配PCI_PROBE_ONLY”读取由of_pci_check_probe_only()控制。2.2 解析设备树把 ranges 变成“可用窗口清单”这一步是把设备树里描述的“地址窗口”翻译成内核能理解的数据结构并且先做一次“占位/防冲突”。可以把设备树ranges看成一张“地址翻译表”设备侧PCI 总线侧看到的地址是什么PCI bus addressCPU 侧真实的物理地址是什么CPU physical address这段窗口多大、属于 I/O 还是内存内核解析后会得到两类结果一组 windowI/O window / MEM window后面给 BAR 分配地址时的“候选池”。一段 bus-range告诉内核“这条总线最多可以编号到多少”。同时内核会把这些 window先放进全局资源树里占住位置避免多个 host bridge 把同一段物理地址当成自己的窗口。这一步对应的实现通常是devm_of_pci_bridge_init()触发解析流程窗口与 bus-range 的细节解析由devm_of_pci_get_host_bridge_resources()完成而把窗口“先占位”到全局资源树的动作由devm_request_pci_bus_resources()完成。2.3 PCI 核心对象数据结构关系资源resource一段“地址范围”。可以是内存、I/O 端口也可以是 bus number 范围。窗口windowhost bridge 或桥设备提供的一段“可分配池”后面所有 BAR 都要从窗口里分配。总线bus设备的“容器”一条 bus 上挂着多个设备也有自己的可用窗口。设备dev最终使用资源的人它的 BAR/ROM/桥窗口都会以“资源”的形式记录下来。一句话串起来先有窗口能分配的池再有需求设备要多少最后把需求放进窗口里分配/认领。2.4 关键结构体字段速查名称意义常见字段资源resource一段地址范围内存/I/O/bus numberstart/end/flags资源列表节点resource_entry“窗口清单”里的一个条目res资源本体、offset地址翻译差值主桥host bridge这条 PCI 根总线的“入口”和“总窗口提供者”windows、ops/sysdata总线bus一层拓扑节点挂设备也挂窗口resources、busn_res设备dev最终需要 BAR 的对象resource[]BAR/窗口结果对应结构体struct resource、struct resource_entry、struct pci_host_bridge、struct pci_bus、struct pci_dev。2.5 设备树 demo用ranges/bus-range理解 window 与 offset下面这个设备树 demo 只用来“建立直觉”ranges 描述的是“PCI 侧地址”和“CPU 物理地址”之间的对应关系。不需要记住每个 cell 的细节只要理解它最终会生成若干段I/O window / 内存 window这些 window 会变成“后续给 BAR 分配地址的候选池”pcie10000000 { compatible pci-host-ecam-generic; device_type pci; /* ECAM 配置空间用于访问 PCI 配置空间 */ reg 0x0 0x10000000 0x0 0x01000000; /* 16MB */ /* 根总线 bus-range决定本主桥可用的总线号范围 */ bus-range 0x00 0xff; #address-cells 3; #size-cells 2; /* * ranges描述 PCI bus address - CPU 物理地址 的 window * Linux 解析后生成 struct resource (CPU 地址区间) 并计算 resource_entry-offset */ ranges /* IO space: PCI 0x00000000..0x000fffff - CPU 0x3f000000..0x3f0fffff */ 0x01000000 0x0 0x00000000 0x0 0x3f000000 0x0 0x00100000 /* MEM space: PCI 0x00000000..0x0fffffff - CPU 0x40000000..0x4fffffff */ 0x02000000 0x0 0x00000000 0x0 0x40000000 0x0 0x10000000; };offset如果某个 window 的 CPU 物理起始地址是0x40000000而 PCI 侧起始地址是0x0那 offset 就是0x40000000。日志打印内核打印 root bus resource 时常会同时打印 CPU 区间和 bus address 区间二者差值就是 offset。计算方式offset 计算offset cpu_addr - pci_addr把 CPU 侧资源反推出 PCI 侧地址pci_start res.start - offsetpci_end res.end - offsetoffset 的计算发生在解析窗口时devm_of_pci_get_host_bridge_resources()会把 ranges 翻译成资源条目并通过pci_add_resource_offset()记录 CPU 地址与 PCI 地址之间的差值。3. PCI 设备扫描3.1 扫描入口先找设备、再处理资源、最后交给驱动这一阶段发生的事情非常直观先把设备都找出来再决定资源是“沿用”还是“重新分配”最后把设备交给驱动框架。可以把它拆成 3 步理解建“总线树”从 root bus 往下扫描把所有设备和桥都挂到内核的拓扑结构上。处理资源如果系统处于“只沿用固件”的模式把固件已经配好的 BAR/窗口登记到资源树里不改配置空间。如果系统允许重分配计算桥需要的窗口大小然后为每个设备分配 BAR并把结果写回配置空间。让驱动开始工作把pci_dev注册到 driver model触发驱动匹配与probe()。这三步的入口在pci_host_probe()它先调用pci_scan_root_bus_bridge()完成 root bus 注册与扫描如果处于“只探测不重分配PCI_PROBE_ONLY”就通过pci_bus_claim_resources()认领资源如果允许内核重分配则依次执行pci_bus_size_bridges()与pci_bus_assign_resources()最后用pci_bus_add_devices()把设备加入驱动框架。3.2 root bus 注册把“窗口清单”挂到根总线上这一阶段的目的可以一句话概括把“设备树给的窗口清单”搬到 root bus 名下让后续扫描/分配都以 root bus 为起点。完成后会得到 3 个非常关键的“结果”root bus 被创建并注册可以在 sysfs 中看到对应的 bus。root bus 拥有自己的 window 列表后续 BAR 分配时会从这个列表里找可用地址。root bus 的 bus number 范围被登记避免同一个 domain 内 bus number 冲突。还有一点为了减少碎片内核会尽量把相邻且属性一致的窗口合并成更大的窗口这对后续分配成功率有帮助。这里对应的实现路径是pci_register_host_bridge()创建并注册 root bus并通过pci_bus_add_resource()把 IO/MEM 窗口挂到bus-resources通过pci_bus_insert_busn_res()登记 bus number 范围。3.3 扫描从 bus - device - function扫描阶段最重要的产出是两件事一棵完整的拓扑树谁挂在谁下面、哪些是桥、哪些是普通设备。每个设备“想要什么资源”比如某个 BAR 需要多大的内存空间、是不是 64 位、是不是 prefetch。扫描方法可以粗略理解为在每条 bus 上按 slot/function 去探测设备是否存在如果探测到的是桥就会创建子 bus 并递归向下。实现上扫描的递归入口是pci_scan_child_bus()当发现桥设备时会通过pci_alloc_child_bus()创建子总线对象并继续向下。3.4 配置空间访问读写设备信息的“唯一入口”扫描与资源读取都离不开一件事能访问 PCI 配置空间。在 ECAM 模式下配置空间其实就是一段 MMIO内核先把它映射出来后续所有“读 vendor/device、读写 BAR”都通过这段映射完成。为了把“硬件访问方法”说清楚可以把 ECAM 访问理解为一次确定性的地址计算以每个 function 4KB 配置空间为例输入cfg_baseECAM 基址bus, dev, fn, reg 配置空间访问地址 cfg_addr cfg_base (bus 20) (dev 15) (fn 12) reg 含义 - 每个 bus 占 1MB - 每个 device 占 32KB - 每个 function 占 4KB不同平台可能不是 ECAM而是其他pci_ops实现例如 x86 的 legacy I/O port 机制但对上层来说都抽象成pci_read_config_*()/pci_write_config_*()。这里需要记住配置空间访问方式由 host bridge 决定不同平台只是实现不同但上层扫描/分配流程基本一致。驱动或 PCI core 调用pci_read_config_*()/pci_write_config_*()时最终都会走到 host bridge 提供的配置空间访问实现例如 ECAM。3.5 驱动什么时候会 probe扫描结束之后当扫描与资源处理结束后内核会把这些“已经准备好的 PCI 设备”交给统一的驱动框架设备会出现在 sysfs 中内核会去匹配对应的驱动匹配成功后驱动的probe()才会被调用把设备正式交给驱动框架的动作发生在pci_bus_add_devices()。4. 资源管理4.1 根窗口window从设备树一路传到“可分配池”资源管理里最重要的概念是“窗口window”窗口是“池子”后续所有设备 BAR 都要从这个池子里分配地址。窗口来自 host bridge以及中间的桥根窗口一般来自设备树ranges。资源从上到下的流转设备树给出 root windowI/O、内存、bus-range。内核先把这些 window 记录在 host bridge 上。root bus 创建时把 window “搬运”到 root bus 的资源列表里作为后续分配入口。offset 是读日志时非常实用的“线索”它表示“同一段窗口在 CPU 侧和 PCI 侧看起来各是什么地址”。很多平台出问题时只要 offset 不合理后续 BAR 就必然出问题。窗口解析来自devm_of_pci_get_host_bridge_resources()而窗口在 root bus 上的落点由pci_register_host_bridge()完成并通过pci_bus_add_resource()进入bus-resources。4.1.1 谁保存“窗口”谁保存“结果”对象关系--------------------------------------------------- | 主桥对象保存“根窗口清单”和“配置空间访问方式” | | (struct pci_host_bridge) | |---------------------------------------------------| | windows: 根窗口清单IO/MEM/bus-range | | dma_ranges: inbound 窗口清单 | | ops/sysdata: 配置空间访问上下文ECAM 等 | | busnr: 根总线号 | | bus: 指向 root bus | -------------------------------------------------- | | 注册 root bus会搬运窗口 | (pci_register_host_bridge) v --------------------------------------------------- | 根总线对象后续“扫描/分配”的起点 | | (struct pci_bus, root) | |---------------------------------------------------| | resources: 可用窗口列表从 windows 搬运而来 | | busn_res: 可用 bus number 范围 | | devices: 本层发现的设备列表 | -------------------------------------------------- | | 扫描会把设备挂上来 v --------------------------------------------------- | 设备对象记录“我需要什么/我被分到了什么” | | (struct pci_dev) | |---------------------------------------------------| | resource[]: BAR/ROM/桥窗口最终结果都在这里 | | subordinate: 如果是桥指向下游 child bus | -------------------------------------------------- | | 桥连接出的下游总线 v --------------------------------------------------- | 子总线对象拥有自己的窗口与设备列表 | | (struct pci_bus, child) | |---------------------------------------------------| | resources/busn_res/devices | --------------------------------------------------- 补充说明 1) host_bridge.windows 里每个节点是 resource_entry - resource 2) pci_register_host_bridge() 会把 windows 中的 IO/MEM 挂到 bus-resources 3) 设备扫描阶段填充 pci_dev.resource[]BAR/桥窗口分配阶段把它们“落到” bus 的 window 中4.1.2 窗口地址翻译offset 的意义术语对照bridge-windows、struct resource_entry、resource_entry-offset。设备树 ranges 的一条记录概念 PCI bus address: [ pci_addr ................ pci_addr size - 1 ] CPU phys addr : [ cpu_addr ................ cpu_addr size - 1 ] 内核会把它翻译成一条“窗口记录” - 这段窗口在 CPU 侧的范围 [ cpu_addr ........ cpu_addr size - 1 ] - 这段窗口在 PCI 侧的范围 [ pci_addr ........ pci_addr size - 1 ] - 两者的差值offset cpu_addr - pci_addr CPU 地址 PCI bus address offset PCI bus address CPU 地址 - offset 这也是为什么内核打印 root bus resource 时经常会带一段 bus address root bus resource [CPU-start..CPU-end] (bus address [CPU-start-offset .. CPU-end-offset])4.1.3 资源树长什么样谁挂在谁下面Linux 全局资源树抽象重点看“谁在谁下面” 1) 内存资源树iomem_resource iomem_resource -- [RAM/Reserved/...] (省略) -- [PCI Host Window #1] --- devm_request_pci_bus_resources() 申请 window | -- [PCI Bus 00 Window] --- claim/assign 阶段把下游资源插入到 window 下 | -- [Dev 00:01.0 BAR0] | -- [Dev 00:02.0 BAR2] | -- [Bridge 00:1c.0 MEM Window] | -- [Dev 01:00.0 BAR0] | -- ... -- [PCI Host Window #2] 2) I/O 端口资源树ioport_resource ioport_resource -- [PCI Host IO Window] -- [Dev xx:yy.z I/O BAR] 3) bus number 资源IORESOURCE_BUS per-domain busn resource (get_pci_domain_busn_res()) -- [root bus busn_res: 00-ff] -- [bridge child busn_res: 01-1f] -- ... 要点 1) 设备树解析阶段先把“根窗口”占住避免窗口之间互相踩。 2) 资源处理阶段再把“设备 BAR/桥窗口”作为子节点挂进去形成最终的资源树。4.2 bus number 资源为什么需要单独管理很多时候只关注“内存/I/O 地址”但实际上bus number 也是资源而且冲突会导致非常诡异的问题比如扫描不到设备、桥下面的设备编号不稳定。可以把它理解为root bus 会先拿到一个“可用编号范围”例如00-ff后续每过一个桥就会从父范围里切一段给子总线这样每条 bus 都知道自己的编号边界避免互相覆盖实现上bus number 范围会通过pci_bus_insert_busn_res()插入资源树如果固件没给足范围扫描结束后会用pci_bus_update_busn_res_end()更新边界。4.3 设备 BAR 解析把“我需要多少空间”变成资源需求BAR 解析的目标是弄清楚“这个设备想要多大空间、是什么类型”并把它变成一个“资源需求”。可以用 5 个动作理解它不需要读每个寄存器细节先读一次 BAR看看现在的地址长什么样。用硬件机制反推大小把 BAR 临时写成全 1再读回来就能知道哪些位是“地址位”从而算出这个 BAR 的 decode size。判断类型它是 I/O 端口还是内存是否 64 位是否 prefetch把“需求”记录下来用struct resource保存 size、类型等信息。做一次地址转换校验如果平台的“PCI 地址 - CPU 物理地址”翻译对不上就把这个 BAR 标记为“需要重新分配”。这些动作主要由__pci_read_base()完成。这里补齐一个在实际系统里非常常见、也最容易影响分配结果的组合MEM64 Prefetchable下面简称MEM64 PREF。在配置空间里怎么识别 MEM64 PREFPCI 规范里内存 BAR 的低位既包含地址也包含类型位内存 BARMemory BAR低位含义概念表达 bit0: 0 表示 Memory BAR bit2:1: 内存类型0032-bit, 1064-bit bit3: 1 表示 Prefetchable 因此 - MEM64BAR 类型位 64-bit - PREFprefetchable 位 1 - MEM64 PREF两者同时成立在内核里怎么表示 MEM64 PREF内核最终会把它落到资源标志上dev-resource[i].flagsIORESOURCE_MEM | IORESOURCE_MEM_64 | IORESOURCE_PREFETCH这三个标志会直接影响后续“选窗口 找洞”的策略IORESOURCE_PREFETCH决定它应该尽量放进prefetchable windowPREF_MEM。IORESOURCE_MEM_64决定它具备超过 4GB 的地址可达性允许分到高地址窗口同时 BAR 寄存器本身会占用两个 dword低 32 位 高 32 位。把第 2 步“反推大小”写成可计算的形式典型硬件机制适用于大多数 BAR输入BAR 寄存器当前值 old 步骤 1) 保存 old 2) 向 BAR 写入全 10xFFFFFFFF 或对 64-bit BAR 写两个 dword 3) 读回 mask 4) 恢复 old 大小计算概念表达 - 对内存 BAR addr_mask mask 0xFFFFFFF0 size (~addr_mask) 1 - 对 I/O BAR addr_mask mask 0xFFFFFFFC size (~addr_mask) 1 备注 - BAR 低位包含类型位/属性位先用掩码去掉剩下的才是“可变地址位”。 - 64-bit BAR 需要把高 32 位一起纳入地址/掩码计算。4.4 桥窗口bridge window的资源表示桥设备PCI-to-PCI bridge / PCIe port本身也有资源窗口I/O、MEM、prefetch MEM用于下游设备地址转发。可以把桥窗口理解成“桥给下游开的转发表范围”下游设备的 BAR 必须落在桥窗口里否则地址无法被桥转发。桥的窗口同样会以struct resource形式记录并最终参与后续的资源树管理。在某些特殊桥subtractive/transparent场景下下游 bus 会“继承”父 bus 的窗口这会让资源可用范围看起来更大。补充一个“硬件原理”的直觉桥的窗口寄存器本质上就是一组比较器。当一条 PCIe TLP/PCI 事务携带地址 addr - 如果 addr 落在桥的 MEM/IO/PREF window 范围内桥就把事务转发到下游 - 否则就不转发可能上抛/完成错误/走默认路径取决于类型与平台桥窗口的读取与整理发生在pci_read_bridge_bases()/pci_read_bridge_windows()。4.5 资源策略沿用固件还是内核重分配沿用固件只认领适合固件已经把 BAR 配好且系统希望完全沿用固件的地址规划结果内核把这些地址“登记进资源树”用于冲突检测与可视化但不会改动配置空间内核接管重分配适合固件没配好、存在冲突、或希望内核统一重新分配结果内核会计算桥窗口需求、在窗口池里为每个 BAR 找位置并写回设备配置空间对应到实现就是沿用固件时走pci_bus_claim_resources()重分配时先pci_bus_size_bridges()再pci_bus_assign_resources()。其中“计算桥窗口需求sizing”可以描述为输入一棵以 bridge 为根的子树子树中每个设备都有若干资源请求 request_i 输出该 bridge 对下游需要提供的窗口集合 window_type_jMEM/IO/PREF 等及其大小 过程抽象表达 for each window_type in {IO, MEM, PREF_MEM}: size 0 for each request in subtree where request.type window_type: size align_up(size, request.align) size size request.size bridge_window[window_type].size align_up(size, bridge_granularity) 说明 - align_up 规则与 BAR 分配一致按对齐要求累加 - 现实实现里还会考虑最小窗口粒度、prefetch/non-pref 分组、32/64-bit 约束等4.6 BAR 分配在“窗口池子”里给设备找一块合适的空位分配 BAR 这件事可以把它理解成“在一堆可用窗口里找车位”先选对停车场I/O BAR 必须进 I/O 窗口内存 BAR 必须进内存窗口。再考虑车辆限制有的 BAR 需要 64 位地址可达性MEM64、有的需要更大对齐、有的要求可预取prefetch。最后找空位从窗口里找一段连续的空闲区间放进去。关键结论是分配一定会遵守类型与属性约束I/O/内存、可预取/不可预取、32 位/64 位否则设备可能“看起来有地址但实际上不可用”。把约束说得更明确一些尤其是 MEM64 PREFMEM32非 64-bit地址上限通常受限于 32-bit可抽象为max 0xFFFFFFFF。MEM64允许分配到 4GB 以上的窗口上限由对应 window 决定。PREFprefetchable应当优先/必须放进 prefetchable 的窗口池桥/主桥的 PREF_MEM window把 PREF BAR 塞进 non-pref window 会让桥的转发属性不匹配轻则性能问题重则直接不可用。真正“在窗口里找空位”的过程由pci_bus_alloc_resource()负责。其中“找空位”可以描述为输入 - request: (size, align, min, max, flags) - windows: 一组可分配窗口来自 bus-resources每个窗口可视为区间 [start, end] - occupied: 已被占用的子区间集合资源树中的子节点 输出 - assigned: 为 request 选出的 [addr, addr size - 1]或失败 补充max 的设置通常直接来自地址可达性约束 - 如果 request 不是 64-bit 可达没有 MEM64则 max 至少要收紧到 32-bit 上限 - 如果 request 是 MEM64尤其是 MEM64 PREF则 max 可以跟随候选窗口上限 算法区间分配 / first-fit 的直觉表达 for each window in windows: if window.flags 与 request.flags 不兼容continue candidate align_up(max(window.start, request.min), request.align) while candidate size - 1 min(window.end, request.max): if [candidate, candidate size - 1] 与 occupied 无冲突return assigned candidate align_up(next_gap_start(candidate), request.align) return FAIL其中align_up(x, a)的计算是align_up(x, a) (x a - 1) ~(a - 1)4.7 提高分配成功率先排序、再多轮尝试内核为了提高“分配成功率”和“减少碎片”通常不会简单地按设备枚举顺序分配而是会做两件事按“最难放进去的”优先分配通常是对齐要求更大的资源BAR 对齐越大越容易造成碎片先分配能提高成功率多轮尝试尽量更紧凑直觉上就是不要一次就放死必要时会分多轮把布局“挤一挤”减少碎片“分阶段满足约束”第一阶段只满足必需的资源请求required第二阶段在不破坏第一阶段结果的前提下尝试满足可选的扩展目标optional例如更好的对齐/更大的窗口可以把它写成一个更贴近实现的步骤化过程输入devices按拓扑展开requests每个设备的 BAR/桥窗口请求 输出每个 request 的分配结果写回 dev-resource[] 并最终写回配置空间 1) 收集所有 requests 2) 按 key 排序典型 key对齐从大到小、size 从大到小、类型/属性分组 3) 第一轮对每个 request 调用“区间分配”在对应窗口中找位置 4) 第二轮可选对允许扩展的 request 进行尝试失败则回退到第一轮已满足的最小可用结果 5) 将分配结果写回 - 设备 BAR写 BAR 寄存器 - 桥窗口写 bridge window 寄存器这里的分配排序与多轮尝试最终都收敛到pci_bus_assign_resources()这条主路径上。4.8 驱动侧怎么“用”这些资源申请、映射、释放内核把 BAR 分配好并写回配置空间之后驱动要做的事情其实就三句话确认地址和长度这个 BAR 最终被分到了哪里、多大。申请占用告诉内核“本驱动要使用这段地址”避免多个驱动重复占用。映射后访问把 BAR 映射成 CPU 可直接读写的地址再去访问寄存器。补充一个基本原理I/O BAR通常对应端口地址空间访问可能走特殊的 I/O 指令路径不同架构实现不同。MEM BAR对应 MMIO 地址空间需要映射后用普通的 load/store 访问寄存器常见接口是pci_iomap()/pcim_iomap_regions()。为了减少“申请了但忘了释放”的 bug更推荐优先使用托管接口pcim/devm卸载时会自动清理。常用接口在驱动里会这样出现读取地址长度用pci_resource_start()/pci_resource_len()申请占用用pci_request_region()映射访问用pci_iomap()如果想省心直接用托管的pcim_iomap_regions()。5. PCI 扫描与资源管理流程示意下面描述主链路与两个分支沿用固件 vs 内核重分配平台驱动入口 | v --------------------------------------------- | 第一步准备主桥入口 | | - 解析设备树/固件得到根窗口清单 | | - 建立配置空间访问方式ECAM 等 | --------------------------------------------- | v --------------------------------------------- | 第二步创建 root bus 并扫描拓扑 | | - 把窗口清单挂到 root bus | | - 扫描所有设备与桥得到“资源需求” | --------------------------------------------- v ---------------------------------------------------------- | | | 选择资源策略沿用还是重分配 | | | ---------------------------------------------------------- | yes | no v v --------------------------------------- --------------------------------------- | 沿用固件只登记不改配置空间 | | 内核接管计算 分配 写回 | -------------------------------------- | - 资源排序、多轮尝试、按规则找洞 | | -------------------------------------- | | ---------------------------------------------- v ------------------------------------------- | 设备加入驱动框架触发驱动 probe | ------------------------------------------- | v 驱动申请/映射 BAR通常优先用 pcim/devm 图中“资源相关关键产物”对照 1) 根窗口清单bridge-windows来自设备树/固件 2) 可分配窗口bus-resources分配 BAR 的“池子” 3) 设备资源结果dev-resource[]排障时最常看的最终结果 4) 全局资源树iomem_resource/ioport_resource冲突检测与可视化依据