一、PCI 非桥设备的访问方法

1.1 硬件结构

在这里插入图片描述
  如何访问到某个PCI设备?上图中有PCI0 和 PCI1两个PCI总线,这两个总线通过PCI-to-PCI Bridge桥设备进行连接,属于同一个PCI总线树。

1)CPU地址空间与PCI设备访问

  • 地址转换机制:CPU发出的地址(CPU address)通过桥芯片转换为PCI总线地址(PCI address),不同地址范围访问不同外设
  • 访问对象判断:地址范围决定访问对象,可能是内存、GPIO模块或PCI设备
  • 桥接功能:根桥(root bridge)负责CPU地址与PCI地址的转换

2)PCI设备地址分配与响应

  • 设备响应机制:多个PCI设备同时看到总线地址,通过配置确定响应者
  • 配置必要性:必须预先配置设备地址空间,否则设备无法识别是否应该响应
  • 地址空间申请:各设备声明所需地址空间大小(如1K/1M/2K/2M等)

3)配置PCI设备的过程

  • 读取配置:通过扫描PCI设备,读取其配置寄存器,确定设备类型(网卡/串口等、获取设备申请的资源空间大小
  • 地址分配:驱动程序分配PCI地址空间给设备 如分配1K空间给某设备,将分配结果写入设备配置寄存器
  • 地址映射原理:分配地址范围(如a到a+1K)后,访问该范围即访问对应设备
  • 响应流程:当PCI地址落在分配范围内时,设备响应读写请求

1.2 PCI本地总线的信号

  疑问一:一开始的时候PCI总线连接了多个设备,设备都还没有地址空间。设备如何知道cpu在总线上传输的地址是用来访问他的,还是访问其他设备的?
  疑问二:那么一开始我们想去配置它的时候,我们怎样才能够选中这个设备?
  疑问三:在PCI总线上有那么多设备,我要去配置它的时候,我怎么知道我要去配置谁?
  疑问四:我怎么去选中?
在这里插入图片描述
在这里插入图片描述

上图来源于《PCI_SPEV_V3_0.pdf》,引脚主要分为6类:

类别 信号 描述
系统引脚 CLK:给PCI设备提供时钟
RST#:用于复位PCI设备
地址/数据引脚 AD[31:00]:地址/数据分时复用
C/BE[3:0]:命令或者字节使能
PAR:校验引脚
接口控制 FRAME#:PCI主设备驱动此信号,表示一个传输开始了、进行中
IRDY#:Initiator ready, 传输发起者就绪,一般由PCI主设备驱动此信号
TRDY#:Target ready,目标设备驱动,表示它就绪了
STOP#:目标设备驱动,表示它想停止当前传输
LOCK#:锁定总线,独占总线,有PCI桥驱动此信号
IDSEL:Initialization Device Select,配置设备时,用来选中某个PCI设备
DEVSEL#:Device Select,PCI设备驱动此信号,表示说:我就是你想访问的设备
仲裁引脚 REQ#:申请使用PCI总线
GNT#:授予,表示你申请的PCI总线成功了,给你使用
错误通知引脚 PERR#:奇偶校验错误
SERR#:系统错误
中断引脚(可选) INTA#、INTB#、INTC#、INTD#

设备选择机制

  • IDSEL实现:不同设备使用不同信号线作为IDSEL(如AD31/AD30等连接各个设备的IDSEL)
  • 桥接设计:由桥芯片决定各设备的IDSEL连接方式
  • 配置访问:通过特定IDSEL信号选中设备后访问其配置空间

PCI 设备的配置空间结构

  • 空间特性:256字节的特殊访问空间,包含预定义头和设备相关区域
  • 必需字段:所有PCI设备必须支持Vendor ID/Device ID/Command/Status等基本字段
  • 功能划分:
    • 设备识别区(0x00-0x0C)
    • 设备控制区(0x04-0x08)
    • 基地址寄存器(0x10-0x24)
    • 中断配置区(0x3C)

配置空间
在这里插入图片描述
1)Type 0配置空间头部结构:每个PCI设备都有256字节的配置寄存器空间,Type 0头部占据前64字节,关键寄存器:

  • 设备标识:包含Device ID和Vendor ID(00h位置)
  • 控制状态:Command和Status寄存器(04h位置)
  • 分类信息:Class Code和Revision ID(08h位置)
  • 地址分配:Base Address Registers(10h-24h位置)用于分配设备所需的内存/IO空间
  • 中断配置:Interrupt Line和Interrupt Pin(3Ch位置)

2)设备功能与寄存器访问

  • 功能选择:
    • 物理设备选择:通过IDSEL信号线选中特定物理设备
    • 功能选择:单个物理设备最多支持8种功能(Function),每种功能有独立的256字节配置空间
  • 寄存器访问机制:
    • 空间分配:系统通过写入Base Address Register为设备分配地址空间
    • 地址识别:设备监测总线地址,当访问落在分配空间内时响应请求
    • 层次选择:需要三级选择机制(设备→功能→寄存器)才能访问具体配置寄存器

3)配置空间深入解析

  • 地址映射:
    • 空间申请:设备通过Base Address Register声明所需空间大小
    • 地址写入:系统分配空间后将基地址写入对应寄存器
  • 功能扩展:
    • Type 1头部:用于PCI-to-PCI桥接设备,与Type 0结构不同
    • 能力列表:通过Capabilities Pointer实现新功能的向后兼容扩展
  • 预取问题:
    • 数据一致性:桥接器预取可能导致读取陈旧数据
    • 解决方案:应在事务结束时标记未使用的预取数据为无效
  • Type 0与Type 1区别:
    • Type 0配置事务(AD[1:0]=“00”)用于选择当前总线上的设备
    • Type 1配置事务(AD[1:0]=“01”)用于将配置请求传递到另一个总线段
      字段含义:
      Register Number:选择目标配置空间中的DWORD
      Function Number:选择多功能设备中的8个可能功能之一
      Device Number:选择给定总线上的32个设备之一(实际支持数量有限制)

在这里插入图片描述
FRAME#:由当前主设备驱动,指示访问开始和持续时间
IRDY#:发起方准备信号,表示主设备完成当前数据阶段的能力
TRDY#:目标设备准备信号,表示从设备完成当前数据阶段的能力
C/BE[3:0]#:地址阶段定义总线命令,数据阶段用作字节使能

  • 访问流程:

    • 通过IDSEL信号选中特定设备
    • 在AD[31:0]总线上指定功能号和寄存器号
    • 通过C/BE#引脚确定读/写操作(1010表示配置读,1011表示配置写)
    • 完成数据传输(读时返回数据,写时写入配置信息)信号
  • 配置过程:

    • 读取配置寄存器获取设备信息(设备ID、厂商ID等)和空间需求
    • 驱动程序分配地址空间并将首地址写入基地址寄存器
    • 设备被配置好后,CPU地址会转换为PCI地址在总线上传输
    • 设备识别到自己的地址范围后响应读写请求
  • 地址映射建议:

    • 强烈建议设备通过基地址寄存器将其内部寄存器映射到内存空间而非I/O空间
    • I/O空间在PC系统中有限且高度碎片化,未来更难分配
    • 内存空间映射允许设备在不支持I/O空间的系统中使用

您指出了一个关键问题!虽然内存空间和I/O空间在物理上都是CPU地址空间的一部分,但它们在硬件实现、访问机制和系统架构层面存在本质区别。以下是深度解析:


1.3 内存空间 vs I/O空间

特性 内存空间 (MMIO) I/O空间 (PIO)
访问指令 普通内存读写指令 (如ldr/str) 专用I/O指令 (x86: in/out)
地址解码 通过内存控制器统一管理 独立I/O控制器处理
ARM支持 全系原生支持 需模拟或硬件转换层
地址范围 64位大地址空间 (理论16EB) 16位地址空间 (仅64KB)
硬件成本 无需额外电路 需单独I/O解码电路
执行效率 可参与缓存/流水线优化 强制串行化(内存屏障)
地址复用 与物理内存统一编址 独立地址空间

硬件访问层级的根本分离

Load/Store指令
Load/Store指令
IN/OUT指令
CPU
Memory_Controller
RAM
PCIe设备寄存器
IO_Controller
I/O端口
  • MMIO:被CPU视为普通内存单元,访问路径与物理内存一致
  • PIO:需要经过独立的I/O控制器(如x86的Southbridge)

ARM架构的特殊性

  • 原生无I/O空间:ARM指令集没有专用I/O指令
  • 模拟代价
    // ARM上inb()的实际实现 (arch/arm/include/asm/io.h)
    #define inb(port)  ({ u8 __v = __raw_readb(0xf0000000 + (port)); __v; })
    
    实质是通过MMIO映射到保留的内存区域(如0xf0000000),性能损失约30%

系统资源限制

  • I/O空间瓶颈
    • 仅64KB全局共享空间(0x0000-0xFFFF)
    • 典型分配:
      # lspci -v 查看I/O分配
      00:1f.7 SATA controller: I/O ports at 5088 [size=8]  # 仅8字节!
      
  • MMIO优势
    • 可分配GB级连续空间(如GPU显存映射)
    • 支持64位地址(PCIe 3.0+)

性能关键差异

操作 MMIO PIO
单次寄存器读取 1-3 CPU周期 10+周期
缓存支持 可配置Uncacheable 永不缓存
乱序执行 允许 禁止
DMA协同 直接参与 需复制

为什么嵌入式ARM强烈推荐MMIO?

  1. 架构兼容性
    ARMv7/v8无原生PIO支持,使用PIO需额外硬件转换层(如PCIe桥片的PIO模拟)

  2. 资源扩展性

    // 设备树中MMIO区域分配示例 (1GB空间)
    reg = <0x0 0x30000000 0x0 0x40000000>;
    

    而PIO在设备增多时会出现端口冲突(尤其多网卡/多串口场景)

  3. 性能需求
    现代高速设备(如10G网卡)寄存器访问延迟要求:

    • MMIO:~100ns
    • PIO:>500ns(包含模拟开销)
  4. 安全隔离
    MMIO可通过SMMU(IOMMU) 实现内存保护,PIO缺乏硬件级隔离


实战案例:PCIe网卡驱动选择

  • MMIO方案
    void read_reg(struct adapter *adap) {
        return readl(adap->regs + REG_OFFSET); // 单指令完成
    }
    
  • PIO方案
    void read_reg(struct adapter *adap) {
        outw(REG_OFFSET, adap->port_base);     // 触发IO控制器
        return inw(adap->port_base + 4);        // 二次访问
    }
    
    性能对比:MMIO吞吐量可达PIO的5倍(实测Intel X550网卡)

结论:MMIO是嵌入式ARM的必然选择

  1. 规避硬件缺陷:绕过ARM无原生PIO支持的短板
  2. 释放性能潜力:利用内存总线带宽(PCIe 4.0 x16=64GB/s)
  3. 简化系统设计:统一使用内存映射架构
  4. 面向未来:PCIe 6.0已弃用I/O空间(保留仅用于传统兼容)

当您在ARM设备树中看到:

pcie {
    /* 明确禁用I/O空间 */
    device_type = "pci";
    #address-cells = <3>;
    #size-cells = <2>;
    ranges = <0x82000000 0x0 0x00000000   // 仅MEM空间
              0x0 0x40000000 0x0 0x80000000>;
};

这正体现了现代嵌入式设计对MMIO的绝对依赖。

二、PCI 桥设备的访问方法

在这里插入图片描述
  PCI结构图中,CPU信号进入root bridge后引出PCI总线0,该总线上可挂载普通PCI设备或桥设备。桥设备可扩展出新的PCI总线1,总线零与总线一的信号(AD0-31)互不连通。

PCI Agent与桥设备

  • PCI Agent:挂载于总线上的普通设备。
  • 桥设备:可扩展新总线(如PCI总线1),其信号与原总线独立。
  • 设备访问原理:通过特定AD引脚(如AD 31、AD 30)选中设备,引脚连接至设备的ID SEL引脚。

设备访问步骤:
在这里插入图片描述
PCI桥的作用:

  • 核心功能:转发信号,连接下级总线与设备。
  • 空间需求:桥设备通常无需额外PCI地址空间。

PCI桥连接的PCI设备访问方法

  • 配置桥设备:分配总线号并写入其配置寄存器。
  • 访问桥后设备:通过桥设备转发type 1配置命令,转换为type 0命令后访问目标设备。

桥设备配置示例
在这里插入图片描述

  • 配置PCI桥设备
    • 选中桥设备:通过AD 29引脚选中总线零上的桥设备。 通过C/BE[3:0]引脚确定读/写配置空间(配置读或配置写);
    • 识别与配置:读取head type(01h)确认桥设备后,分配总线号(如1)并写入其配置寄存器。
  • 配置PCI桥后面的设备:物理设备最多支持8个功能,每个功能含256个寄存器,通过功能号与寄存器号访问
    • type1配置命令:包含目标总线号(如1)、设备号(如31)、功能号及寄存器号。
    • 信号转换:桥设备将type 1命令转换为type 0命令,通过AD 1撇引脚选中目标设备并传输功能号与寄存器号。
    • 多级桥访问:通过桥设备的primarily/secondly/subordinate bus number字段确定转发路径,最终由末端桥完成type 1至type 0的转换。

内容总结

  • PCI设备可分为普通agent和桥设备,区分依据是配置寄存器中的head type值:
    • 普通设备的head type值为00
    • 桥设备的head type值为01
  • 配置命令类型及功能:
    • Type 0配置命令:用于访问与root bridge直接连接的PCI设备(包括agent和桥),通过AD引脚(如AD31或AD30)选中目标设备后,可在AD总线上传输其他信号以选择具体功能或寄存器
    • Type 1配置命令:用于访问主桥下属子桥后的设备,既可访问子桥本身,也可访问其后的设备
  • Type 0配置命令中AD引脚的使用限制:
    • AD0不可用于设备选择,最低需从AD11开始
    • 实际硬件实现中常采用AD16作为起始引脚(如AD16选中第0个设备,AD17选中第1个设备),原因包括:PCI总线理论支持32个设备,但受功耗等因素限制,实际挂载数量较少 采用高位AD引脚(如AD16~AD31)可简化硬件设计

关键结论: Type 0命令通过AD31或AD16等高位引脚选择设备,Type 1命令用于层级式访问桥接设备及其下属设备。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐