type
status
date
slug
summary
tags
category
icon
password

PCIe基础

参考:

PCIe硬件协议

PCIe是一个多层协议,由事务层数据交换层物理层构成。物理层又可进一步分为逻辑子层电气子层。逻辑子层又可分为物理代码子层(PCS)和介质接入控制子层(MAC)。
下面按照自底向上的顺序介绍PCIe的硬件协议

物理层

  • 功能:物理层负责 PCIe 链路的物理传输,包括电气信号、连接器和电缆等。它将数据包转换为电信号,通过 PCIe 通道进行传输。
  • 主要任务
    • 链路初始化和维护
    • 电气信号传输
    • 数据编码和解码
    • 电源管理
💡
这里的主要任务跟计网中的链路层挺像的
  • 特性
    • 连接与通道:PCIe设备之间的链接将使用两设备中较少通道数的作为标准。
      • 💡
        例如使用x4接口的卡与x1接口的卡进行连接,在物理层面上由于二者插槽数量不同,所以无法正常工作(插不进),但x4接口的卡能在x4的插槽上只建立1个传输通道以连接x1插槽接口。
    • 传输
      • 单通道传输:在进行单通道传输时,PCIe卡能在同一数据传输通道内传输包括中断在内的全部控制信息。这也方便与PCI的兼容。
      • 多通道传输:多通道上的数据传输采取交叉访问,这意味着连续字节交叉访问在不同的通道上。这一特性被称之为“数据条纹”,需要非常复杂的硬件支持连续数据的同步访问,也对链接的数据吞吐量要求极高。

数据链路层

  • 数据校验:数据链路层采用按序的交换层信息包(Transaction Layer Packets,TLPs),是由交换层生成,按32位循环冗余校验码(CRC,本文中用LCRC)进行数据保护。
  • ACK与NCK:数据链路层采用著名的协议(Ack and Nak signaling)的信息包。TLPs能通过LCRC校验和连续性校验的称为Ack(命令正确应答);没有通过校验的称为Nak(没有应答)。
    • 💡
      实际上就是通过这个ACK和NAK检查数据包在传输过程中的损坏,而数据包的丢失则通过延时机制进行处理。
      这里对ACK和NAK的处理跟计网的TCP协议中的ACK与NAK差不多。也是收发双方维护一个Replay Buffer(也就是计网中对应的窗口)。对发送方而言,窗口中维护的是已发送但是未应答的数据包,以便出现ACK超时的时候能够进行重传;对接收方而言,就是通过Replay Buffer中的数据包的序列号进行ACK和NAK信号的发送。
      ACK信息、NAK信息、数据包序列号以及CRC校验位也会包括在TLPs中,这与计网中的协议规定也是一致的。
      💡
      到这也能发现这里的数据链路层也是为上层提供了一个可靠的数据传输

事务层

  • 数据传输效率
    • PCI Express采用分离交换(数据提交和应答在时间上分离),可保证传输通道在目标端设备等待发送回应信息时传送其它数据信息。
      这么看的话分离交换主要就是在通道空闲的时候(就是接收方还没有开始发送应答信息时),尽可能利用通道进行数据包传输。
      我的理解是“分离交换”与流水线传输差不多,只不过计网中流水线传输的速度应该会更快一点,因为数据包的发送和应答在时间上是可以并行的(因为网络链路有多条,具体数据包和应答走哪条链路取决于路由算法),而这里说的分离交换是针对于一个传输通道而言的,因此数据提交和应答一定要在时间上分离。
  • 流量控制
    • PCI Express采用可信性流控制。这一模式下,一个设备广播它可接收缓存的初始可信信号量。链接另一方的设备会在发送数据时统计每一发送的TLP所占用的可信信号量,直至达到接收端初始可信信号最高值。接收端在处理完毕缓存中的TLP后,它会回送发送端一个比初始值更大的可信信号量。可信信号统计是定制的标准计数器,相比于其他方法,如基于握手的传输协议,这一模式的优势在于可信信号的回传反应时间不会影响系统性能,因为如果双方设备的缓存足够大,是不会出现达到可信信号最高值的情况,这样发送数据不会停顿。
      💡
      大概就是收发双方互通缓冲区的大小,防止收方的缓冲区被数据包淹没

PCIe的配置空间

PCIe设备的总线结构

PCIe设备的总线拓扑结构为:
notion image
下面介绍一些图中比较特殊的设备
  • Root Complex(PCIe根桥设备)
    • root complex主要负责PCIe报文的解析和生成。RC接受来自CPU的IO指令,生成对应的PCIe报文,或者接受来自设备的PCIe TLP报文,解析数据传输给CPU或者内存。
  • Switch(交换机)
    • PCIe的转接器设备,目的是扩展PCIe总线。和PCI并行总线不同,采用端到端的连接方式,因此在每一条PCIe链路中两端只能各连接一个设备, 如果需要挂载更多的PCIe设备,那就需要用到switch转接器。
      💡
      从图中也可以发现,在总线拓扑中,交换机一般只有一个入边,但是可以有多个出边,所以当需要连接多个PCIe设备的时候,就需要使用交换机扩展PCIe总线,以便连接更多设备。由于交换机和PCIe总线的特性,所以PCIe采用的是树形拓扑结构。
  • PCIe Endpoint(PCIe端点)
    • 指PCIe终端设备,是PCIe树形结构中的叶子结点(如NVMe磁盘等)

BDF

BDF是指:Bus,Device,Function,即PCIe设备的总线号、设备号、功能号。
  • 总线号:总线号是指PCIe设备的总线ID。总线号占用8位,因此PCIe总线最多支持256个子总线
  • 设备号:在指定的总线上,PCI设备的ID。设备号占用5位,因此一条PCIe子总线最多挂载32个PCIe设备。
  • 功能号:在指定的PCIe物理设备上,PCIe设备的功能ID。PCIe物理设备可以实现多个功能设备,且逻辑功能相互独立。功能号占用3位,所以每个物理设备最多支持8个功能。
通过BDF就可以唯一标识一个PCIe设备上的某个特定的功能。因此通过指定BDF,就可以实现访问特定PCIe设备的配置空间
💡
在后面使用lspci指令时,可以查看PCIe设备的BDF。
另外需要注意的是,这里由于一个物理设备上的每个功能都是逻辑上独立的,所以在实际应用场景中,会认为一个BDF就对应了一个PCIe设备(虽然是逻辑上的对应),而不会认为一个BDF对应了一个物理PCIe设备上的一个Function

PCIe的配置空间

下面进入正题
  • PCIe空间:PCI有三个相互独立的物理地址空间
    • 内存地址空间
    • I/O地址空间
    • 配置空间
    • 💡
      这三个地址空间都是采用唯一的地址进行寻址,比如我们使用地址0x100时需要指定这个地址在哪个地址空间。内存地址空间、I/O地址空间、配置空间中的0x100偏移,对应的是不同的物理地址
  • PCIe配置空间的作用
    • 可以通过读取配置空间可以获得设备的信息
    • 可以通过配置空间来配置设备:通过pci设备的id和配置空间的偏移地址, 软件可以来访问具体的寄存器。
    • 💡
      这里说到软件可以通过配置空间访问寄存器。需要注意的是这里是只PCIe设备的配置寄存器。在BAR空间中还有一些寄存器被称为操作寄存器,那些寄存器适用于进行IO操作的。
  • PCIe配置空间大小/布局概述
    • 功能配置空间大小:PCIe设备的每一个功能(function)都对应一个独立的配置空间,其大小通常为256B。
    • PCIe设备配置空间:如下图,PCIe设备的总配置空间的大小为4KB。
      • notion image
        💡
        上图中的单位为DW双字,即为四个字节
  • PCIe配置空间详解
    • 💡
      这一部分主要是根据上面的配置空间概述中的图分part进行阐述
    • PCI头配置(注意是PCI而不是PCIe)
      • 从配置图中可以发现,在PCIe设备配置空间的头64字节,名称为PCI配置头。其又被称为PCI标准配置空间头。
        PCI配置分为两种:type0、type1。type0主要是针对PCI的endpoint设备(如磁盘,网卡等),type1主要是针对PCI bridge,switch。两种PCI配置的格式如下:
        notion image
        下面对各个字段进行详解
      • Device ID:设备ID, 表示该PCI设备的设备号,只读。
      • Vendor ID:厂商ID, 表示生产该设备的厂商的编号,只读。
      • Status:pci设备状态寄存器,用于保存pci设备的状态,如中断状态或运行产生错误时的状态。
        • 💡
          PCI设备寄存器的位描述如下:
          notion image
          我对这里的目标异常中断和主控器异常中断的理解是,由于PCI设备可能是段节点,也可能是PCI桥、PCI交换机。端节点可以接收主控器中断(即交换机或者桥发出的中断),而PCI桥、PCI交换机可以向目标设备(也就是端设备)发出中断,也可以接收目标设备的中断(或者理解为接收中断返回的信息),所以在图中只有:发送目标中断、接收目标中断、接收主控器异常中断。
      • Command: PCI设备命令寄存器,在pci设备使能pci_enable_device时会配置该寄存器。主要负责使能或关闭pci设备的I/O 访问,memory访问和INTx中断等。
        • 💡
          Command字段的位描述如下:
          notion image
      • Class Code:设备分类信息, 表示pci设备属于哪一种类别,如网卡,存储卡,显卡等(每一个类别都对应一个class code)。需要重点关注的一个寄存器
      • Revision ID:设备版本ID, 表示PCI设备的版本号。该寄存器可以被认为是Device ID寄存器的扩展。 只读。
      • BIST:可选,用于内部自检。
      • Header type:PCI设备头类型寄存器,表示该设备时PCI端设备还是PCI 桥设备。PCI配置空间是type0还是type1就是由该寄存器决定。
        • 💡
          所以按道理来说,如果要解析一个PCI配置空间,就需要先查看Header type。
      • Lantency Timer:在PCI总线中,多个设备共享同一条总线带宽,该寄存器用来控制PCI设备占用PCI总线的时间。PCIe设备不需要使用该寄存器,该寄存器的值必须为0。因为PCIe总线的仲裁方法与PCI总线不同,使用的连接方法也与PCI总线不同。
      • Cache line size:cache缓存大小。对于PCIe设备,该寄存器的值无意义。
      • Expansion Rom Base Address:扩展ROM映射基地址寄存器。分配给ROM使用,用于PCI设备在处理器还没有运行操作系统之前,完成基本的初始化设置。
      • Base Address register:BAR地址寄存器负责PCI设备内部空间的映射(这里说的BAR就是上面PCI头中的Base Address 0/1/2/…)
        • 💡
          BAR空间通常情况下是用于映射IO空间以及操作寄存器的。
          从上图中也可以看出,TYPE0的PCI头有6个32bit的BAR寄存器,TYPE1有2个32bit的BAR寄存器。每一个BAR地址对应一个地址空间。在BAR寄存器中有些Bit是只读的,是PCI设备在出厂前就固定好的Bit。
          💡
          通常情况下,是BAR寄存器的低位会被固化为0,以便映射时进行内存空间对齐。而被固化为0的位数又与PCI设备的地址空间大小有关。而没有固化的位,就取决于PCI设备映射到的内存地址。
      • Capbility Pointer:PCI capbility的地址偏移(就是一个相对地址), capbility用于表示PCI设备支持的能力。该寄存器存放Capabilities 结构链表的头指针。在一个PCIe 设备中,可能含有多个Capability 结构,这些寄存器组成一个链表。
      • Interrupt Pin:PCI设备中断引脚,支持INTX A/B/C/D四个中断引脚。
      • Interrupt line:表示当前PCI设备使用的中断号。
      • type0 独有的寄存器
        • CardBus CISpointer:只读,可选,用于表明访问CIS(card info structure)的地址空间, 通常不会涉及。
        • Subsystem ID/ subsystem Vendor ID:子系统和子厂商ID,可以结合Device ID和Vendor ID来组成完成的PCI设备标识。
        • Max_lat:设备期望的最大延时,只读。
        • Min_Gbt:设备期望的最小延时,只读。
      • type1 独有的寄存器
        • Primary Bus Number:表示PCI设备挂在的PCI总线号
        • Subordinate Bus Number:PCI桥可以管理其下的PCI总线子树。其中Subordinate Bus Number寄存器存放当前PCI子树中,编号最大的PCI总线号。
        • Secondary Bus Number:存放当前PCI桥Secondary Bus使用的总线号,这个PCI总线号也是该PCI桥管理的PCI子树中编号最小的PCI总线号
        • Secondary Latency Timer:PCI桥下游的延时寄存器,和Latency Timer寄存器的含义相近
        • I/O base:表示IO寻址的基地址
        • I/O limit:IO寻址的上限
        • Secondary status:保存PCI下游总线和设备的状态。
        • Memory Base:表示memory寻址的基地址,和IO Base类似
        • Memory Limit:表示memroy寻址的上限,和IO limit类似
        • Prefetchable Memory Base:可预期内存的基地址
        • Prefetchable Memory Limit:可预期内存寻址的上限。
        • Bridge Control Register:管理PCI桥的Secondary Bus, 可以控制 Secondary Bus的 Reset。
          • 💡
            所以从上面TYPE1各个寄存器来看,Secondary应该表示的是“下游”的意思
    • PCI capbility结构:这一段配置空间主要存放一些与MSI/MSI-X中断机制和电源管理相关的capbility。(注意是PCI而不是)
      • 💡
        说白了就是这段空间指示了一个PCI设备具有的(或者说能提供的)扩展功能(基本功能应该通过上面的class code就可以标识)
    • PCIe扩展配置:这一段配置空间主要就是PICe设备的扩展配置,包含PCIe的扩展能力
      • 💡
        在PCI设备的配置空间中只有前256字节。PCIe设备的配置空间为了兼容PCI,PCIe设备的配置空间前256字节与PCI保持一致
  • PCIe配置空间的访问
    • ARM使用ECAM的方式访问PCIe配置空间。
      ECAM是一个将配置空间映射到MEMORY空间的规则。硬件根据ECAM的方式将某个Memory空间映射给PCI配置空间,CPU访问对应的memory空间即可以操作PCIe配置空间:
      notion image
      💡
      一个PCIe总线的配置空间映射最多需要使用256 buses × 32 devices × 8 functions × 4 KiB = 256 MiB的内存空间。另外需要注意的是,上面在讲BDF的时候提到,我们认为的PCIe设备都是物理设备上逻辑独立的一个功能,BDF与这样的PCIe设备是一一对应的关系。所以这里就可以看出来,一个BDF对应的PCIe设备的配置空间的大小就是4K。
      在完成这样的映射之后,CPU发出的地址如果落在ECAM的范围内, 根据对应的BDF就可以访问到对应PCIe端设备的配置空间了。
      💡
      当然在读取的过程中可能还涉及一些指令或者字节序的转换(也就是某种IO协议),这里我觉得不是重点,就不再赘述了。

PCIe的BAR空间

在上面介绍的PCIe配置空间中,无论是TYPE0还是TYPE1的PCIe配置空间中,都有:Base Address,即BAR。一个BAR提供了一个基地址,也就对应了一个地址空间

BAR格式

  • Memory BAR格式
    • notion image
    • Bit0:在Memory Bar中为0,用于表示当前的Bar寄存器为Memory Bar
    • Bit1:保留为0
    • Bit2:表示地址空间的位数。为0则表示Memory Bar映射到的地址空间为32位;为1则表示Memory Bar映射到的地址空间为64位
      • 💡
        需要注意的是这里的32位、64位表示的是系统能直接寻址的地址空间,换句话说就是地址线的宽度。
    • Bit3:在Memory Bar中表示是否允许预取。为1表示可以预取,为0表示不可预取
  • IO BAR格式
    • notion image
    • Bit0:在IO Bar中为1,用于表示当前的Bar寄存器为IO Bar
    • Bit1:保留为0
  • Memory Bar和IO Bar的区别
    • 首先需要知道,内存地址空间与IO地址空间在系统中是分离的。
    • 内存地址空间与IO地址空间
      • 内存地址空间:即物理意义上的内存空间
      • IO地址空间: I/O 地址空间是计算机系统中专门用于与外部设备(如硬盘、网络适配器、键盘、鼠标等)进行通信的地址空间。I/O 地址空间与内存地址空间是分开的,专门用于访问 I/O 设备的寄存器和数据端口。
      💡
      可能是因为我现在用的ARM架构好像都是统一编址,就对IO地址空间没什么概念了。。。
    • Memory BAR与I/O BAR
      • Memory BAR
        • 定义:Memory BAR指定的是设备的内存映射地址,设备的寄存器和内存通过内存 BAR 映射到系统的物理内存地址空间。
        • 访问Memory BAR空间方式:内存 BAR 映射的地址空间可以使用标准的内存读写指令(如 mov 指令)进行访问
        • 映射方式:通常采用MMIO,即内存映射IO
      • I/O BAR
        • 定义:I/O BAR 用于指定设备的 I/O 映射地址,设备的寄存器通过 I/O BAR 映射到系统的 I/O 地址空间
        • 访问IO BAR空间方式:I/O BAR 映射的地址空间需要使用专用的IO指令(如 in 和 out 指令)进行访问
        • 映射方式:通常采用PIO,直接将IO BAR的地址空间映射到系统的IO地址空间中。
        💡
        需要注意的是,通常的操作是,现根据BAR的类型,然后选择对应的映射方式(也就是对Memory BAR而言,会使用MMIO,然后向BAR中存入一个内存地址;对IO BAR而言,会使用PIO,然后向BAR中存入一个IO地址。这是因为最低位已经决定了这个BAR空间访问时的操作类型,也就是应该通过什么指令来访问该BAR的地址空间)。这样理解就不会有我的那个问题了:如果一个IO BAR的地址空间再使用MMIO映射到内存空间中,那么IO BAR不是就变成了Memory BAR吗?
  • BAR空间的计算与初始化
    • 就像上面在PCIe配置空间中介绍的一样,BAR寄存器中有些低位是被固化为0的,所以BAR对应的地址空间的大小在PCIe设备出厂的时候就已经被确定了
      💡
      这也是非常合理的,因为BAR空间的大小就应该是PCIe设备的地址空间(操作寄存器、内存)的大小,这些信息在PCIe设备出厂的时候就可以确定了。
    • BAR空间的计算与初始化过程
      • 向BAR寄存器写全1
      • 读BAR寄存器,根据低位为0的个数确定BAR空间的大小,例如当前可操作的最低位为12,因此当前BAR对应的地址空间大小为4KB(也就是BAR空间的粒度为4K)
      • 从系统的物理地址空间(需要根据BAR寄存器的类型来决定应该在Memory地址空间中分配还是应该在IO地址空间中分配)中分配⼀段地址空间,并把相应的基地址写回到BAR寄存器。
      • 💡
        需要注意的是,在分配空间的时候,需要与BAR空间的大小进行对齐(因为如果不对齐的话,BAR的低位是没办法写入的)。
        另外还有一个注意点是,如果一个BAR没有被使用,那么该BAR应该被硬件置零,以表示该BAR是不可使用的。

系统枚举PCI设备的过程

系统如何检测PCIe设备是否存在

在了解系统枚举PCIe设备的过程之前,还需要有一个前置知识:系统如何判断PCIE设备是否在线。
对于PCI总线,如果一个设备不存在,那么当系统软件尝试去读这个设备的配置空间寄存器时,会出现读请求超时并产生 Master Abort 错误。与此同时,由于没有连接设备,对应的总线信号(包括数据信号)处于上拉(pulled up)状态。因此,在设备不存在的情况下,读 Vendor ID 寄存器的操作会得到特定值:0xFFFF。当系统软件读到某个设备的 Vendor ID 是 0xFFFF 时,就会知道该设备不存在。
对于 PCIe 总线,当尝试读取一个不存在的设备的配置空间寄存器时,会得到一个不带数据的 PCIe 完成消息(Completion without data)。比较特殊的是,这个完成消息会携带一个 UR (Unsupported Request) 状态标识。为了与 PCI 总线兼容,PCIe Root Complex 在枚举阶段收到这种特殊的完成消息时,会向处理器返回特定值:0xFFFF
💡
所以经过兼容之后,无论是PCI总线还是PCIe总线,当设备不存在的时候,读取VID都会返回0xFFFF。

枚举扫描算法

PCIe设备的枚举是一套算法,但是我没办法简洁凝练地表述这个算法,因此这里我选择的学习方式为:结合一个具体的例子来理解系统枚举PCIe设备的过程。假设有一个PCIe设备树结构如下:
notion image
下面就分步介绍系统枚举扫描该PCIe设备树的过程
  • 对CPU来说,最开始仅仅知道Bus0的存在。
    • 💡
      由于CPU最开始仅仅知道Bus0的存在,所以采取的策略就是枚举可能的BDF,然后根据VID探测设备是否存在。
      因此首先从Bus0,Dev0开始,先去尝试读取Dev0中Fun0的DID&VID,若返回0xFFFF,则Dev0中没有Fun0,因此该设备不存在,继续探查Bus0,Dev1,Fun0
  • 如果Bus0,Dev0,Fun0存在,那么就将读取其Header寄存器的值。经过检查将发现Header的值为1,即表示这是一个PCI To PCI Bridge
    • 💡
      在讲配置空间中也有提到这个Header字段,该字段的作用就在枚举的时候体现:判断一个PCIe设备是一个PCIe端设备还是一个PCIe桥
      而每一个bridge对应一个上游Bus(在这里是Bus0)和下游Bus(在这里是Bus1)。
      💡
      这是因为PCIe桥的作用就是连接两条总线(可以结合上面的图进行理解,如BDF000为一个PCIe桥,他连接了Bus0和Bus1)
      那么接下来就开始填写这个Dev0,Fun0的配置空间,将其总线号寄存器设置如下:
      💡
      在上面配置空间的介绍中,Secondary Bus Number还有一种描述:该字段也表示了当前PCIe桥管理的PCIe子树中编号最小的总线号。这个与上面注释中的描述是一致的:由于枚举扫描算法采用深度优先搜索,所以进行枚举搜索的时候,当前PCIe设备紧接着的下游总线一定被第一个扫描到,所以该编号应该当前PCIe设备下游设备树中总线的最小编号。
      Subordinate Bus Number能表示下游最远的总线编号也是同理,这里就不再赘述了。
      至此,BDF000知道了他管理的下游的一条Bus,编号为1。该总线与当前PCIe桥直接相连,同时也是当前探测到的最远的总线。
  • 更新HostBridge的SuBN = 1
    • 💡
      这里就表明,每次搜寻到一根总线之后,都需要向上更新SuBN,以记录某一个PCI桥设备所管理的最远的总线
  • 接下来,由于PCIE设备扫描遵循深度优先法则
    • 💡
      需要注意的是,这里的深度优先法则应该就是对物理拓扑结构进行深度优先搜索,而不是对BDF组合进行深度优先搜索(如果对BDF深度优先搜索的话,搜索顺序应该是000 001 002…)
      因此在进行Bus0其他Dev扫描之前,必须首先扫描Bus1。
  • 软件读取BDF100的DID&VID,若返回不为0xFFFF,则表示该设备存在。然后查看Header寄存器值为1,表示BDF100依旧是一个PCI to PCI Bridge。接下来就需要填写BDF100配置空间中的总线寄存器
    • 然后再更新BDF000和HostBridge的SuBN = 2
    • 继续延续深度优先法则,开始扫描桥C下管理的Bus2总线,首先读取BDF200的DID&VID,会发现返回0xFFFF,表明BDF200设备不存在。
    • 按照深度优先搜索的原则,接下来应该搜索BDF210。尝试读取BDF210的DID&VID,返回的值不为0xFFFF,表示该设备存在。然后查看该设备的Head,发现值仍为1,表示当前PCIe设备仍为一个PCIe桥。
    • 接下来需要设置该PCIe设备的配置空间
      • 然后需要更新BDF000、BDF100、BDF210的SuBN = 3
      • 继续深度优先搜索,搜索BDF300,尝试读取该设备的DID&VID,发现返回值不为0xFFFF,表示该设备存在。接下来查看该PCIe设备配置空间中的Header字段,该字段的值为0,表明BDF300是一个PCIe端设备
      • 按照深度优先搜索的原则,接下来应该查看BDF301-307,查找之后会发现在Bus3、Dev0上,仅有Fun0存在
      至此基于深度扫描法则的一条支路扫描完毕。接下来的部分就是重复以上过程、继续深度优先搜索。
      💡
      需要注意的是,每次更新SuBN时,都仅仅会向上回溯更新一条支路的SuBN
      到最后递归回到Host Bridge的时候,就探测出了一个完整的PCIe设备树。

      lspci指令

      参考:

      lspci指令的参数

      参数
      作用
      -v
      使得 lspci 以冗余模式显示所有设备的详细信息。
      -vv
      使得 lspci 以过冗余模式显示更详细的信息
      -n
      以数字形式显示 PCI 生产厂商和设备号(也就是配置空间中的VID和DID)
      -x
      以十六进制显示 PCI 配置空间 (configuration space) 的前64个字节映像 (PCI标准头部信息)。此参数对调试驱动和 lspci 本身很有用。
      -xxx
      以十六进制显示所有 PCI 配置空间的映像。此选项只有 root 可用,并且很多 PCI 设备在你试图读取配置空间的未定义部分时会崩溃。
      -b
      以总线为中心进行查看。显示所有 IRQ 号和记忆体地址,就像 PCI 总线上的卡看到的一样,而不是核心看到的内容。
      -t
      以树形方式显示包含所有总线、桥、设备和它们的连接的图表。
      -s [[<bus>]:][<dev>][.[<func>]]
      仅显示指定总线、插槽上的设备或设备上的功能块信息。设备地址的任何部分都可以忽略,或以通配符“*”代替 。所有数字都是十六进制。例如:「0:」指的是在0号总线上的所有设备;「0」指的是在任意总线上0号设备的所有功能块;「0.3」选择 了所有总线上0号设备的第三个功能块;「.4」则是只列出每一设备上的第四个功能块。
      -d [<vendor>]:[<device>]
      只显示指定生产厂商和设备 ID 的设备。 这两个 ID 都以十六进制表示,可以忽略或者以通配符“*”代替 (意味著所有值)。
      --version
      显示 lspci 的版本。这个选项应当单独使用。

      lspci指令的使用

      由于很多lspci指令有很多参数,而且可以随意组合,所以这里就只实操其中我感觉比较有用的几个参数
      • 指令1
        • 指令执行结果
          • notion image
        • 结果分析
          • 首先需要知道,在这里还有一个额外的字段:PCI域
            💡
            PCI域ID存在的目的是为了突破pcie256条总线的限制。
            这里以0000:00:00.0为例,0000、00、00、0依次为PCI域、PCI总线号、PCI设备号、PCI功能号
            那么从指令执行的结果来看,所有的PCI设备都在PCI域0中,BDF000为Host Bridge,BDF010为PCI桥,其后还挂载了Bus1,但是Bus1上没有挂载其他的PCI设备
            此外,Bus0上还挂载了BDF0.7.0、BDF0.7.1、BDF0.7.3。。。BDF0.11.0。BDF0.11.0为PCI桥设备,其下游还有Bus2,而Bus2上挂载了设备BDF2.0.0、BDF2.1.0、BDF2.2.0、BDF2.3.0、BDF2.4.0。该PCI设备树的剩余部分就不再赘述了。
      • 指令2
        • 由于这里展示出的PCI设备标准头信息比较多,所以这里就仅看两个比较有代表性的PCI设备:BDF0.11.0(为PCI桥设备)与BDF2.4.0(为PCI端设备)。
        • BDF0.11.0
          • -v执行结果:
            notion image
            -x执行结果:
            notion image
            通过头部的Header字段(01,第15个字节)可以发现,当前设备为桥设备;其BAR0为:0x00 00 00 00,BAR1为:0x00 00 00 00
        • BDF2.4.0
          • -v执行结果:
            notion image
            -x执行结果:
            notion image
            通过头部中的Header字段(00)可以发现,该设备为端设备;BAR0-4为:0x00 00 00 00,BAR5为:0x00 e0 5e fd

      DMA

      DMA的知识由于在本科课程中已经学习过,所以这里的参考资料就是计组的PPT了:

      DMA基本概念

      DMA的定义

      DMA(Direct Memory Access):直接存储器访问,依靠硬件直接在主存与外围设备之间传送数据,在数据传送过程中不需要CPU的干预

      DMA的实质和特点

      • 实质:总线权切换
      • 特点:随机性;并行性;处理简单批量高速数据传送。

      DMA的响应流程

      notion image

      DMA控制器与接口

      这里主要是介绍一下DMA控制器和外设接口的硬件组成、硬件作用以及控制器和接口的作用
      • DMA控制器
        • 控制/状态逻辑:控制数据传输的方向(从存储器到外设接口,还是从外设接口到存储器)
        • 地址计数器:提供主存地址
        • 交换量计数器:控制传送次数
      • 外设接口
        • DMA请求/中断请求:传递请求(DMA或中断请求)
        • 数据缓冲器:暂存数据
        • 外设寻址逻辑:提供外设地址(实现读取外设的内存或者特定的内部寄存器)
      • DMA控制器的作用
        • 接收初始化信息(传送方向、主存首址、交换量)——DMA初始化
        • 接收外设DMA请求,判优,向CPU申请总线控制权——传送前
        • 接管总线权,发地址、读/写命令——传送期间
      • 外设接口的作用
        • 接收初始化信息(外设寻址信息)——初始化
        • 向DMA控制器发请求——传送前,外设准备好
        • 传送数据——传送期间

      磁盘的DMA传输过程

      notion image
      • CPU向适配器送出驱动器号、圆柱面号、磁头号、起始扇区号、扇区数等外设寻址信息
        • 💡
          这一段相当于是初始化磁盘,就是将磁盘的磁头移动到特定的位置
          向DMA控制器送出传送方向、主存首址、交换量等信息。
          💡
          这一步是初始化DMA控制器
      • 适配器启动寻道,并通过中断方式判段寻道是否正确(不正确,重新寻道;正确,启动磁盘读/写)
      • 适配器准备好(读盘:缓冲区满一扇区;写盘:缓冲区空一扇区),提出DMA请求。
        • 💡
          这里的DMA请求是传递给DMA控制器的,然后由DMA控制器负责向CPU申请总线权。
      • CPU响应,放弃总线权,DMA控制器接管总线,实现M与磁盘直传。
      • 批量传送完毕,适配器申请中断。
      • CPU响应,作善后处理。

      IOMMU

      参考:

      IOMMU概述

      IOMMU(Input/Output Memory Management Unit)是一个内存管理单元(Memory Management Unit),它的作用是连接DMA-capable I/O总线(Direct Memory Access-capable I/O Bus)和主存(main memory),用于管理和保护设备对系统内存的访问
      传统的内存管理单元会把CPU访问的虚拟地址转化成实际的物理地址。而IOMMU则是把设备(device)访问的虚拟地址转化成物理地址。

      IOMMU功能

      💡
      关于IOMMU的功能实际上就跟MMU差不多。也是通过页表的方式实现设备访问内存时,虚拟地址与物理地址的转换。通过页表机制,不同设备访问的虚拟地址空间就是相互隔离的了
      • 地址转换:
        • IOMMU 将设备访问内存时提供的虚拟地址转换为物理地址,使得设备可以使用虚拟地址进行内存访问。这种地址转换类似于 CPU 的虚拟内存管理,提供了灵活的内存分配和管理。
      • 内存保护与设备隔离:
        • IOMMU 通过虚拟地址转换的机制,提供内存保护,防止设备访问未经授权的内存区域。
        • 而在内存保护的基础上,IOMMU 就可以提供设备隔离的功能,防止一个设备的错误或恶意行为影响其他设备或系统内存。
      • DMA 重映射:
        • IOMMU 支持 DMA(Direct Memory Access,直接内存访问)重映射,使得设备可以高效地进行 DMA 操作。
          • 💡
            对这里的DMA重映射做出一些解释:在使用DMA时,设备通过直接访问内存来进行数据传输。在传统DMA中,DMA控制器指定的是物理地址,设备读写也是直接根据该物理地址进行内存寻址;而在IOMMU的支持下,DMA控制器可能会直接指定虚拟地址,该虚拟地址将通过IOMMU转换为物理地址,然后再读取内存。
        • 通过 DMA 重映射,可以避免 DMA 操作中的地址冲突和内存保护问题,扩大DMA的数据传输量
          • 💡
            这里提到的可以避免地址冲突和内存保护问题,这是因为有一些设备的DMA寻址是有限制的,比如一台机器某一条总线上的DMA控制器仅支持32位寻址,如果在传统DMA的模式下,这一条总线上所有设备将共用这32位的DMA空间(因为这32位DMA空间直接对应了物理内存),这样就可能导致不同设备可能会使用同一个DMA地址(对应同一个内存地址),这样就会导致地址冲突和内存保护的问题;而在IOMMU的支持下,可以实现DMA的映射。那么这个时候32位DMA地址空间就是每个设备独享的(因为不同设备进行DMA的时候,虽然指定的可能是同一个虚拟地址,但是对应的物理地址是不同的),并且各个设备之间的DMA空间是相互隔离的,因此就解决了设备DMA地址冲突和内存保护的问题,同时扩大了DMA数据的传输量(从原来的设备均摊到设备独享)。

      杂记

      • AGP:AGP(Accelerated Graphics Port,加速图形端口)是一种专门用于显卡和主板之间高速数据传输的接口标准。
      • PCI的快速背靠背:在 PCI总线规范中,快速背靠背(Fast Back-to-Back)是一种优化技术,用于提高总线的利用率和数据传输效率。它允许在不插入空闲周期的情况下连续发起多个总线事务,从而减少总线的空闲时间,提高数据传输速率。

      疑问

      notion image
      为什么这里说一个Function对应的配置空间是256B?不是应该是一个BDF(一个BDF应该就意味着是一个Function吧)就对应了一个逻辑上的PCIe设备,也就对应了一个4K的配置空间吗?
       
      Paper2PX4
      Loading...
      Noah
      Noah
      永远年轻,永远热泪盈眶
      公告
      ❗❗复习笔记问题❗❗
      由于兼容性问题
      导入md文件可能导致了一些格式错误
      🌹如发现格式错误,请联系我~🌹
      🌹如博客内容有误也欢迎指出~🌹