0%

Linux PCI 驱动框架分析(1)

背景

  • Read the fucking source code! –By 鲁迅
  • A picture is worth a thousand words. –By 高尔基

说明:

  1. Kernel 版本:4.14
  2. ARM64 处理器
  3. 使用工具:Source Insight 3.5, Visio

1. 概述

从本文开始,将会针对 PCIe 专题来展开,涉及的内容包括:

  1. PCI/PCIe 总线硬件;
  2. Linux PCI 驱动核心框架;
  3. Linux PCI Host 控制器驱动;

不排除会包含 PCIe 外设驱动模块,一切随缘。

作为专题的第一篇,当然会先从硬件总线入手。
进入主题前,先讲点背景知识。
在 PC 时代,随着处理器的发展,经历了几代 I/O 总线的发展,解决的问题都是 CPU 主频提升与外部设备访问速度的问题:

  1. 第一代总线包含ISAEISAVESAMicro Channel等;
  2. 第二代总线包含PCIAGPPCI-X等;
  3. 第三代总线包含PCIemPCIem.2等;

PCIe(PCI Express)是目前 PC 和嵌入式系统中最常用的高速总线,PCIe 在 PCI 的基础上发展而来,在软件上 PCIe 与 PCI 是后向兼容的,PCI 的系统软件可以用在 PCIe 系统中。

本文会分两部分展开,先介绍 PCI 总线,然后再介绍 PCIe 总线,方便在理解上的过渡,开始旅程吧。

2. PCI Local Bus

2.1 PCI 总线组成

  • PCI总线(Peripheral Component Interconnect,外部设备互联),由 Intel 公司提出,其主要功能是连接外部设备;
  • PCI Local Bus,PCI 局部总线,局部总线技术是 PC 体系结构发展的一次变革,是在ISA总线CPU总线之间增加的一级总线或管理层,可将一些高速外设,如图形卡、硬盘控制器等从ISA总线上卸下,而通过局部总线直接挂接在 CPU 总线上,使之与高速CPU总线相匹配。PCI 总线,指的就是PCI Local Bus

先来看一下 PCI Local Bus 的系统架构图:

image-20240416110614230

从图中看,与 PCI 总线相关的模块包括:

  1. Host Bridge,比如 PC 中常见的North Bridge(北桥)
    图中处理器、Cache、内存子系统通过 Host Bridge 连接到 PCI 上,Host Bridge 管理 PCI 总线域,是联系处理器和 PCI 设备的桥梁,完成处理器与 PCI 设备间的数据交换。其中数据交换,包含处理器访问PCI设备的地址空间PCI设备使用DMA机制访问主存储器,在 PCI 设备用 DMA 访问存储器时,会存在 Cache 一致性问题,这个也是 Host Bridge 设计时需要考虑的;
    此外,Host Bridge 还可选的支持仲裁机制,热插拔等;

  2. PCI Local Bus
    PCI 总线,由 Host Bridge 或者 PCI-to-PCI Bridge 管理,用来连接各类设备,比如声卡、网卡、IDE 接口等。可以通过 PCI-to-PCI Bridge 来扩展 PCI 总线,并构成多级总线的总线树,比如图中的PCI Local Bus #0PCI Local Bus #1两条 PCI 总线就构成一颗总线树,同属一个总线域;

  3. PCI-To-PCI Bridge
    PCI桥,用于扩展 PCI 总线,使采用 PCI 总线进行大规模系统互联成为可能,管理下游总线,并转发上下游总线之间的事务;

  4. PCI Device
    PCI 总线中有三类设备:PCI 从设备,PCI 主设备,桥设备。
    PCI 从设备:被动接收来自 Host Bridge 或者其他 PCI 设备的读写请求;
    PCI 主设备:可以通过总线仲裁获得 PCI 总线的使用权,主动向其他 PCI 设备或主存储器发起读写请求;
    桥设备:管理下游的 PCI 总线,并转发上下游总线之间的总线事务,包括PCI桥PCI-to-ISA桥PCI-to-Cardbus桥等。

2.2 PCI 总线信号定义

PCI 总线是一条共享总线,可以挂接多个 PCI 设备,PCI 设备通过一系列信号与 PCI 总线相连,包括:地址 / 数据信号、接口控制信号、仲裁信号、中断信号等。如下图:

image-20240416110619128

  • 左侧红色框里表示的是 PCI 总线必需的信号,而右侧蓝色框里表示的是可选的信号;
  • AD[31:00]:地址与数据信号复用,在传送时第一个时钟周期传送地址,下一个时钟周期传送数据;
  • C/BE[3:0]#:PCI 总线命令与字节使能信号复用,在地址周期中表示的是 PCI 总线命令,在数据周期中用于字节选择,可以进行单字节、字、双字访问;
  • PAR:奇偶校验信号,确保AD[31:00]C/BE[3:0]#传递的正确性;
  • Interface Control:接口控制信号,主要作用是保证数据的正常传递,并根据 PCI 主从设备的状态,暂停、终止或者正常完成总线事务:
    • FRAME#:表示 PCI 总线事务的开始与结束;
    • IRDY#:信号由 PCI 主设备驱动,信号有效时表示 PCI 主设备数据已经 ready;
    • TRDY#:信号由目标设备驱动,信号有效时表示目标设备数据已经 ready;
    • STOP#:目标设备请求主设备停止当前总线事务;
    • DEVSEL#:PCI 总线的目标设备已经准备好;
    • IDSEL:PCI 总线在配置读写总线事务时,使用该信号选择 PCI 目标设备;
  • Arbitration:仲裁信号,由REQ#GNT#组成,与 PCI 总线的仲裁器直接相连,只有 PCI 主设备需要使用该组信号,每条 PCI 总线上都有一个总线仲裁器;
  • Error Reporting:错误信号,包括PERR#奇偶校验错误和SERR系统错误;
  • System:系统信号,包括时钟信号和复位信号;

看一下C/BE[3:0]都有哪些命令吧:

image-20240416110624548

2.3 PCI 事务模型

PCI 使用三种模型用于数据的传输:

image-20240416110646652

  1. Programmed I/O:通过 IO 读写访问 PCI 设备空间;
  2. DMA:PIO 的方式比较低效,DMA 的方式可以直接去访问主存储器而无需 CPU 干预,效率更高;
  3. Peer-to-peer:两台 PCI 设备之间直接传送数据;

2.4 PCI 总线地址空间映射

PCI 体系架构支持三种地址空间:

image-20240416110651082

  1. memory空间
    针对 32bit 寻址,支持 4G 的地址空间,针对 64bit 寻址,支持 16EB 的地址空间;

  2. I/O空间
    PCI 最大支持 4G 的 IO 空间,但受限于 x86 处理器的 IO 空间(16bits 带宽),很多平台将 PCI 的 IO 地址空间限定在 64KB;

  3. 配置空间
    x86 CPU 可以直接访问memory空间I/O空间,而配置空间则不能直接访问;
    每个 PCI 功能最多可以有 256 字节的配置空间;
    PCI 总线在进行配置的时候,采用 ID 译码方式,使用设备的 ID 号,包括Bus NumberDevice NumberFunction NumberRegister Number,每个系统支持 256 条总线,每条总线支持 32 个设备,每个设备支持 8 个功能,由于每个功能最多有 256 字节的配置空间,因此总的配置空间大小为:256B * 8 * 32 * 256 = 16M;

    有必要再进一步介绍一下配置空间:
    x86 CPU 无法直接访问配置空间,通过 IO 映射的数据端口和地址端口间接访问 PCI 的配置空间,其中地址端口映射到0CF8h - 0CFBh,数据端口映射到0CFCh - 0CFFh

    image-20240416110655409

    • 图为配置地址寄存器构成,PCI 的配置过程分为两步:
      1. CPU 写 CF8h 端口,其中写的内容如图所示,BUS,Device,Function 能标识出特定的设备功能,Doubleword 来指定配置空间的具体某个寄存器;
      2. CPU 可以 IO 读写 CFCh 端口,用于读取步骤 1 中的指定寄存器内容,或者写入指定寄存器内容。这个过程有点类似于通过 I2C 去配置外接芯片;

    那具体的配置空间寄存器都是什么样的呢?每个功能 256Byte,前边 64Byte 是 Header,剩余的 192Byte 支持可选功能。有种类型的 PCI 功能:Bridge 和 Device,两者的 Header 都不一样。

    • Bridge

      image-20240416110659912

    • Device

      image-20240416110703859

配置空间中有个寄存器字段需要说明一下:Base Address Register,也就是BAR空间,当 PCI 设备的配置空间被初始化后,该设备在 PCI 总线上就会拥有一个独立的 PCI 总线地址空间,这个空间就是BAR空间BAR空间可以存放 IO 地址空间,也可以存放存储器地址空间。

  • PCI 总线取得了很大的成功,但随着 CPU 的主频不断提高,PCI 总线的带宽也捉襟见肘。此外,它本身存在一些架构上的缺陷,面临一系列挑战,包括带宽、流量控制、数据传送质量等;
  • PCIe 应运而生,能有效解决这些问题,所以 PCIe 才是我们的主角;

3. PCI Express

3.1 PCIe 体系结构

先看一下 PCIe 架构的组成图:

image-20240416110708127

  • Root Complex:CPU 和 PCIe 总线之间的接口可能会包含几个模块(处理器接口、DRAM 接口等),甚至可能还会包含芯片,这个集合就称为Root Complex,它作为 PCIe 架构的根,代表 CPU 与系统其它部分进行交互。广义来说,Root Complex可以认为是 CPU 和 PCIe 拓扑之间的接口,Root Complex会将 CPU 的 request 转换成 PCIe 的 4 种不同的请求(Configuration、Memory、I/O、Message);
  • Switch:从图中可以看出,Swtich提供扇出能力,让更多的 PCIe 设备连接在 PCIe 端口上;
  • Bridge:桥接设备,用于去连接其他的总线,比如 PCI 总线或 PCI-X 总线,甚至另外的 PCIe 总线;
  • PCIe Endpoint:PCIe 设备;
  • 图中白色的小方块代表Downstream端口,灰色的小方块代表Upstream端口;

前文提到过,PCIe 在软件上保持了后向兼容性,那么在 PCIe 的设计上,需要考虑在 PCI 总线上的软件视角,比如Root Complex的实现可能就如下图所示,从而看起来与 PCI 总线相差无异:

image-20240416110712560

  • Root Complex 通常会实现一个内部总线结构和多个桥,从而扇出到多个端口上;
  • Root Complex 的内部实现不需要遵循标准,因此都是厂家 specific 的;

Switch的实现可能如下图所示:

image-20240416110715854

  • Switch 就是一个扩展设备,所以看起来像是各种桥的连接路由;

3.2 PCIe 数据传输

image-20240416110719784

  • 与 PCI 总线不同(PCI 设备共享总线),PCIe 总线使用端到端的连接方式,互为接收端和发送端,全双工,基于数据包的传输;
  • 物理底层采用差分信号(PCI 链路采用并行总线,而 PCIe 链路采用串行总线),一条 Lane 中有两组差分信号,共四根信号线,而 PCIe Link 可以由多条 Lane 组成,可以支持 1、2、4、8、12、16、32 条;

PCIe 规范定义了分层的架构设计,包含三层:

image-20240416110723864

  1. Transaction 层

    • 负责 TLP 包(Transaction Layer Packet)的封装与解封装,此外还负责 QoS,流控、排序等功能;
  2. Data Link 层

    • 负责 DLLP 包(Data Link Layer Packet)的封装与解封装,此外还负责链接错误检测和校正,使用 Ack/Nak 协议来确保传输可靠;
  3. Physical 层

    • 负责Ordered-Set包的封装与解封装,物理层处理 TLPs、DLLPs、Ordered-Set 三种类型的包传输;

数据包的封装与解封装,与网络包的创建与解析很类似,如下图:

image-20240416110727997

  • 封装的时候,在 Payload 数据前添加各种包头,解析时是一个逆向的过程;

来一个更详细的 PCIe 分层图:

image-20240416110731440

3.3 PCIe 设备的配置空间

为了兼容 PCI 软件,PCIe 保留了 256Byte 的配置空间,如下图:

image-20240416110735220

此外,在这个基础上将配置空间扩展到了 4KB,还进行了功能的扩展,比如 Capability、Power Management、MSI 中断等:

image-20240416110749946

  • 扩展后的区域将使用 MMIO 的方式进行访问;

草草收场吧,对 PCI 和 PCIe 有一些轮廓上的认知了,可以开始 Source Code 的软件分析了,欲知详情、下回分解!