pcie 总线

cpu 对外设的访问方式

ram-like 接口设备访问

在嵌入式开发中,最容易访问的外设是什么?内存。
我们在写程序时,对变量的赋值、取值操作,实际上做了对内存进行读写,通过地址就可以对内存进行读写操作。

int* a=0x100000;
int* b=0x200000;
*a = 1000;
*b= *a;

实际上不仅是内存通过地址就可以简单访问,其它一些集成在soc 上的外设控制器也可以如此。(比如我们编写gpio 驱动、i2c控制器驱动等等,都是直接访问寄存器地址,设置寄存器的值来完成的)

CPU 与外设的连接结构图如下所示:
通过地址线传输想要访问的地址,数据线传输数据,读写信号线确定你的操作是读/写。
可以看到总线上所挂的设备(ram、flash、gpio控制器),它们对信号线都是共享的,那么怎么选中想要访问的设备呢?
它们每一个设备都有各自的地址段,当地址信号线上的地址属于某一个设备的地址范围时,cs(片选信号)就会选中芯片,然后进行访问。

关键在于:内存控制器,它会根据地址范围发出对应的片选信号,选中对应的设备。
类似于这样的接口 叫ram-like接口,所有的ram-like 接口设备,cpu 都可以直接通过地址读写。
在这里插入图片描述
下图是imx6ull 的片上外设控制器地址表(从0x0000_0000~ 0xFFFF_FFFF):
可以看到不同的外设控制器各自有自己的地址段,cpu 只要通过地址就可以访问到对应的外设控制器寄存器。(注意0x8000_0000 是DDR 控制器,并非上述的内存控制器)
imx6ull 控制器地址映射
imx6ull 控制器地址映射
在这里插入图片描述
(完整的控制器地址范围参考imx6ull 参考手册)

无法直接用地址访问的设备

上图中的外设控制器都可以通过地址直接访问,但是控制器下管理的设备是不能的。
比如i2c 从设备,我们无法通过地址来读写它的寄存器,必须要通过i2c控制器,编写出i2c 控制器驱动,才能间接的访问到i2c 从设备。
再比如挂载 emmc控制器下的emmc flash设备 ,要像访问emmc flash 就必须通过emmc 控制器,编写emmc控制器驱动间接访问。
这样的访问方式就会比较复杂,无法像内存一样轻松读写,因此pci 总线诞生。
请添加图片描述

pci 总线

PCI总线(Peripheral Component Interconnect,外部设备互联),挂在pci 总线下的设备,cpu可以通过地址直接访问,让访问pci 设备变得像内存一样简单。
那么它是怎么做到的呢?

pci 设备结构框图

如图是pci 设备与cpu相连的结构框图:
cpu——内存控制器——各种外设控制器(其中就包含pci 控制器)。
pci 设备分为两种:1.桥设备 2.端点设备(非桥设备)。
所谓的pci 控制器(root bridge) 就是属于 pci 桥设备,在pci 控制器所在的总线就是0 级总线,在bus0 下面挂有多个pci 设备(Audio、Motion Video、LAN、bridge等等)。
类似于Audio、LAN 这种有功能的芯片就是端点设备;
然而在bus0 上还可以挂有桥设备(可以挂任意个桥),桥下面又可以挂多个端点设备和桥设备,以此类推一层一层pci总线和pci 设备的框图就像树状图一样。
在这里插入图片描述
问题1:pci 控制器是如何让挂在它下面的pci 设备能像内存一样使用地址访问呢?
与前面访问内存控制器下面的设备类似,pci 总线上的每一个pci 设备都有属于自己的一段地址,如果需要访问的地址落在其中一个pci 设备的地址范围内,就么就是访问这个pci 设备。(如pci device0 拥有地址A-B、pci device1 拥有地址B-C…)

当cpu 想要访问某一个pci 设备时,会发出想要访问的设备地址(cpu_addr)、数据,这个地址会落在pci 控制器的地址范围内(pci 控制器挂在内存控制器下,这里描述的地址段任然是cpu_addr);
如图就是ls1046a(soc) 上pcie 控制器的地址范围。
在这里插入图片描述
cpu 地址经过pci 控制器转换为 pci 地址,然后在pci 总线上发出,寻找到cpu 想要访问的pci 设备。

注意: cpu地址空间与pci 地址空间是不一样的,它们是隔离的。(并不能直接使用cpu地址来查找pci 设备,需要经过pci 控制器转换)
pci_addr = cpu_addr + offset (地址转换其实就是加上一个偏移)
在这里插入图片描述

问题2: 前面说到每一个pci 设备都会拥有一段属于自己的地址范围,那么问题来了,如何确定pci 设备的地址范围呢?
在使用地址访问pci 设备之前,需要配置pci设备(给每一个pci 设备分配地址)。

pci 接口定义

在学习如何配置pci 设备前,先来了解下pci 接口定义(看它是怎么发出地址和数据的)
在这里插入图片描述
pci 接口定义如图所示:红色为必需信号线,蓝色为可选信号线。

必需的引脚中分为以下几个部分:

  • 地址与数据
    AD[31:0] : 地址线与数据线是复用的(怎么控制它的功能是传输地址还是数据,使用 interface control 中的FRAME# 信号线)。
    C/BE[3:0]# : 它也是一组复用信号线,当传输地址是它是命令 command;当传输数据时,它是字节使能(Byte enable),用于字节选择,可以进行单字节、字、双字访问;
    PAR : 奇偶校验信号,确保AD[31:00]和C/BE[3:0]#传递的正确性;

  • 控制接口
    FRAME# : 低电平有效,FRAME# 拉低的第一个时钟为AD[31:0] 地址周期,下一个时钟为数据周期。
    IRDY#: Initiator ready, 传输发起者就绪,一般由PCI主设备驱动此信号(准备好可以传输数据了)。
    TRDY#: Target ready,目标设备驱动,表示它就绪了。
    STOP# : 停止信号线,目标设备请求主设备停止当前总线事务。
    DEVSEL#: 设备选择。Device Select,PCI设备驱动此信号,表示说:我就是你想访问的设备。(应该是地址路由时使用的)
    IDSEL : ID并非身份ID,而是Initialization Device Select(初始化设备选择),在读写设备配置空间时用于选中某个设备。

  • 错误通知引脚
    PERR#: 奇偶校验错误。
    SERR#: 系统错误。

  • 仲裁引脚
    REQ#: 申请使用PCI总线。
    GNT#: 授予,表示你申请的PCI总线成功了,给你使用。

  • system
    CLK : 时钟信号线。
    RST#: 复位。

C/BE[3:0]# command 定义

在地址周期时,C/BE[3:0]# command 定义如下,它表明了你想要使用何种读写方式。
在这里插入图片描述
了解了硬件接口定义后,我们回到问题②,开始了解如何配置pci 设备。

pci 配置空间访问——非桥设备(type0)

每一条pci支总线最多可以拥有32个设备(实际上负载不了这么多),每一个设备都有8个功能,每一个功能有一块 256 Byte 的配置空间(所谓的配置空间即许多寄存器的集合)。

PCI设备可以简单地分为PCI Agent、PCI Bridge、Cardbus Bridge:

  • PCI Agent:真正的PCI设备(比如网卡),是PCI树的最末端
  • PCI Bridge:桥,用来扩展PCI设备,必定有一个Root Bridge,下面还可以有其他Bridge。
  • Cardbus Bridge:用得不多,不介绍。

根据pci 设备类型的不同,pci 配置空间也有不同。pci配置空间一共有256 字节,其中64 字节为头部空间,配置过程就是头部寄存器的读写。
非桥设备(type0)配置空间头部如下:

  • device id 与 vendor id: 各占2 字节,cpu读取它们就可以知道设备ID 和厂商ID。
  • header type: 设备类型,1字节。普通设备它的值为0x00,桥设备为0x01。
  • BAR: 普通设备需要在BAR 中声明自己需要多大的空间,配置该设备时cpu 会把分配的地址段首地址写回 BAR。
    在这里插入图片描述

pci 设备的简单配置过程:

  • 设备会在配置空间中声明自己需要多大的空间
  • cpu获取到这个值,就会给它分配指定长度的空间,并将首地址写回pci 设备配置空间。
  • 有了首地址后,该设备就有了自己的地址范围,首地址+长度= 地址范围;之后cpu就可以通过地址访问这个设备。
    注意: cpu 的地址和pci 的地址是不一样的,pci控制器会对地址做转换。

问题3: 配置pci 设备时,pci 总线上有那么多设备,如何选中某一个设备访问?

  • 通过IDSEL# 引脚选中设备,PCI 接口上有AD[31:0] 32条地址线,例如将AD31接到设备1的IDSEL、AD30——设备2 IDSEL、AD29——设备3 IDSEL,以此类推当选中某个设备时只要将对应的地址线拉低即可选中设备。
    (配置时地址线bit 31-11都是保留,可以与IDSEL相连)
    在这里插入图片描述
    问题4:如何选中设备上的某一个功能?配置空间中的某一个寄存器?
  • 在pci 接口定义中地址是由AD[31:0] 信号线来传输,那么地址就是在AD[31:0] 中去指定:
    在配置读写时AD[31:0] 有如下type0 定义,AD[10:8] 表示功能序号,AD[7:2]表示寄存器序号。
    type0:桥对当前级bus 上的设备配置时,会使用type0 类型地址定义。
    请添加图片描述
    pci 非桥设备配置过程:
  1. AD31——IDSEL# 拉低选中设备。
  2. FRAME# 拉低,第一个时钟周期 AD[31:0] 发送地址,AD[10:8] 选择功能,AD[7:2] 选择寄存器地址。
  3. C/BE# 发送命令 1010 读取配置空间。C/BE# 读写配置空间命令
  4. TRDY#、IRDY# 双方设备准备就绪后开始传输数据。
  5. 读取device id与vendor id 得知设备信息,header type 得知设备类型(桥或非桥)。
  6. 非桥设备读取BAR 获取需要分配的空间大小。
  7. 分配一段空间,将首地址写回BAR。配置完成
  8. 以此类推配置bus0 下的每一个设备。
    pci 设备读时序图

pci 配置空间访问——桥设备(type1)

桥设备(type1)配置空间头部如下:
type1 与type0 有少许的相同点:0x00~0x0c 这一段寄存器,除header type值不同外其它都相同,0x10开始之后的地址空间定义都不同。
BAR: 桥设备不需要多大的BAR空间,因为它只需要做转发工作。
Primary Bus: 上一级总线的总线号。
Secondary Bus: 自己的总线号。
Subordlinate Bus: 子总线中最大的总线号。
在这里插入图片描述

问题5:如何配置桥设备?
桥设备与非桥设备配置的区别:非桥设备需要分配一段pci 地址范围写入BAR 寄存器,而pci 桥设备则是需要配置总线号——需要将上一级的总线号、配置桥对应的总线号、该桥下游桥中最大的总线号分别写入 primary bus、secondary bus 和subordlinate bus 地址空间中。
(设备、功能、寄存器选中以及读写流程,参考前面的非桥设备配置)

pci 配置设备时遵循 “自左而右、深度优先” 的配置规则:
如图,从bus0 (最左边)开始遍历配置pci 设备,一旦发现桥设备(比如图中PCI 桥①),就会配置桥设备(此时只是分配了Primary Bus Number=0 和Secondary Bus Number=1,因为并不知道该桥下游桥中最大的总线号是多少,subordlinate bus 暂时配置为255),执行“深度优先”原则(优先配置PCI 桥①下的所有设备)。

同样,从bus1 的最左边开始遍历配置bus1 总线上的pci 设备,配置普通设备PCI11,然后又发现桥 (PCI桥片②),配置PCI桥片②(同样分配Primary Bus Number=1 和Secondary Bus Number=2,subordlinate bus 暂时配置为255),然后继续执行深度优先原则,配置PCI桥片②下面的所有设备,这样反复直到PCI桥片②下面的所有设备都被配置完毕,此时已经得到了PCI 桥片②下游的最大总线号,回到bus1 将它写入PCI 桥片② 的Subordlinate Bus地址空间中(PCI桥片② 配置完毕,继续往右遍历 bus1 上的其它设备,直到bus1 以及下游的所有设备被配置完毕)。

bus1 上的所有设备被配置完毕,得到了bus1 下游中最大的总线号,回到bus0 将下游最大总线号写入PCI桥片①的Subordlinate Bus地址空间(PCI桥片① 配置完毕),继续往右遍历 bus0 上的其它设备,直到bus0 以及下游的所有设备被配置完毕。
在这里插入图片描述

问题6:前面简单描述了遍历bus0 以及bus0下游所有pci 设备的流程,但是漏掉了一个细节。我们只讲述了对于当前桥设备下的pci设备配置访问(type0 类型的访问方式),比如root bridge想要访问bus0 上的pci 设备,可以使用type0 方式;但是如果root bridge 想要访问bus1 上的pci 设备又该如何到达它呢?在type0 中,C/BE[3:0]# 只指定了哪一个功能、哪一个寄存器,并没有指定哪一条总线、哪一个设备啊。PCI 桥片①如何将root bridge发出的地址和数据转发到bus1 上?
那么,接下来我们就来介绍type1 类型的访问方式,它适用于与当前桥并非直连,需要转发时的设备访问。

在越级配置下游设备时(比如上图中 root bridge 想要配置bus1、bus2等等,目标设备所在总线号 > 当前桥的总线号),PCI 桥需要发出 type1 类型的地址定义。
在type1 类型地址定义中规定了:
AD[23:16] ——总线号。(你想要配置设备所在的总线号是多少?)
AD[15:11] ——设备号。(你想要配置该总线上的第几个设备?)
AD[10:8] ——功能号。(你想要配置设备中的哪一个功能?)
AD[7:2]——寄存器号。(你想要配置该功能配置空间中的哪一个寄存器?)
请添加图片描述

  1. 主桥在bus0 上发出type 1类型的地址。
  2. AD[23:16]:bus number 确定要访问哪个桥设备(哪一级总线)(桥的配置空间中有三个bus号分别是primary bus上级、secondary bus自己、subordlinate bus子级中最大)。
    bus0 上的pci 桥设备都会收到 type1类型的地址,如果AD[23:16] 指定的总线号是某个桥或该桥的下游总线(Secondary Bus <= Bus Number <= Subordlinate Bus),那么这个桥就会将地址转发到自己的总线上 或是自己的下游总线。
  3. 如果Bus Number = Secondary Bus,需要访问的总线号与当前桥的总线号相等,那么需要访问的设备就挂在这一级的总线下,这个桥设备发出type 0 地址,使用这一级桥ADXX——IDSEL选中设备,根据Function Number选择哪个功能,Register Number 选择寄存器,就可以对某一级桥下的设备进行读写。
  4. secondary bus < bus number <= subordlinate bus 如果要访问的总线号大于当前桥设备的总线号,并且小于等于下游总线中最大的总线号时,那么要访问的设备就挂在这个桥下游总线,因此该桥会发出type1 命令找到下一级桥,以此类推直到发生步骤③。

这个过程类似于路由,需要到达的设备属于自己时,就转发给设备,不属于自己,就转发给下一级总线。

  • 桥:接收到Configuration Command type 1后,有2个选择
    1. 如果要访问的设备是跟它直连的PCI Agent
    • Configuration Command type 1转换为Configuration Command type 0 在AD[31:0] 上发出。
    • 使用IDSEL选择设备。
    1. 如果还需要通过下级的桥才能访问此设备
    • 转发Configuration Command type 1 在AD[31:0] 上发出。

请添加图片描述

PCIE 总线

PCIE 总线即PCI Express(快速的pci)。

在我们的印象中使用多条数据线并行传输的方式(例如pci 的AD[31:0])相对速率会比较高,但是当真正高速后,信号线之间会产生干扰,这种情况下反而使用串行传输速度会更快。
所以PCIE 的数据线改为串行的差分信号线(一个方向上有两根信号线tx+、tx-)。

PCIE 的接口引脚如图:
PCIE_TXP/N、PCIE_RXP/N,构成了一组发送与接收的信号线,被称为一个 lane ,P为+、N为-。PCIE_REFCLKP/N 时钟也是差分信号。
请添加图片描述
PCI接口的引脚时并行的,PCIe的是串行的,每个方向的数据使用2条差分信号线来传输,发送/接收两个方向就需要4条线,这被称为1个Lane:
请添加图片描述
PCIE 设备之间可以有多个lane:

  • 两个PCIe设备之间有一个Link。
  • 一个Link中有1对或多对"发送/接收"引脚,每对"发送/接收"引脚被称为"Lane"。
  • 一个Lane:有发送、接收两个方向,每个方向用2条差分信号线,所以1个Lane有4条线。
  • 一个Link最多可以有32 Lane。

PCIE 总线结构

PCIE总线 与PCI 总线的相同点: 虽然PCIE 信号线发生改变,但是对CPU 来讲它与PCI 总线是一样的,CPU仍让是使用地址即可以访问到PCIE设备,所以软件驱动上PCI 与PCIE 设备驱动是兼容的。
PCIE 仍然需要像PCI 一样配置设备,分配设备所需要的地址段。

对比PCI 与PCIE 结构图,PCI 总线 CPU 对接的是Host Bridge(主桥)、PCIE 总线CPU 对接的是Root Complex,实际上Root Complex已经将桥封装在内部,所以从CPU 的角度来说PCI 总线和PCIE总线是没有区别的,只是在桥后的部分不同。
PCIE 总线结构图
PCIe 与PCI的不同:

  • 连接方式不同:在PCI 系统中,可以有多个PCI设备都挂在一条总线上。但是在PCIe系统里,是点对点传输,即一条PCIE 总线只能连接一个PCIE设备(不包括Root Complex、switch内部结构),要接多个PCIe设备,必须使用Switch进行扩展。
  • 通信方式不同:PCI 使用并行通信,数据与地址在AD[31:0] 上并行传输;PCIe 使用串行通信,数据是有先后的一个Byte一个Byte发送接收,因此PCIe 采用数据包方式传输。

这是PCIE 总线详细的结构图,标注出了Root Complex、switch的内部结构。
Root Complex 由Host Bridge 和多个 PCI-to-PCI Bridge 组成。
Switch 由多个 PCI-to-PCI Bridge 组成,功能主要是扩展PCIE 能接更多的设备。
在这里插入图片描述
在这里插入图片描述

PCIE 设备之间数据包的传输

前面说到CPU 对于PCIE 设备仍然是使用PCI地址就可以访问到设备,那么它们需要像PCI 设备一样配置吗?
答案:是的
CPU 将如何配置它们?PCIE 的数据信号线变化,并且没有地址线,桥与PCIE 设备之间该以怎样的形式通信呢?
问题1:PCIE 设备之间如何通信(主要就是桥设备与普通设备),怎么传输地址、数据?

  • PCIE 总线数据线为串行信号线,以字节流的方式传输数据(一个一个Byte 发送),因此采用 数据包 方式传送。

PCIE 设备之间的数据通信,与网络协议栈有点类似,如图PCIE 协议栈一共有三层:
分别是Transaction层(事务层)、Data Link层(数据链路层)和 Physical层(物理层)。

事务层: 事务层传输的是Transaction Layer Packet(TLP),其中包含 header 头部、data payload(要传输的数据内容)和 ECRC (crc校验码)三部分。

header 头部中包含了命令类型(以什么方式读/写)、地址,保证数据能传输到目的地;ECRC 负责验证header 和data payload 传输正确性。
请添加图片描述
数据链路层:
(发送回路)数据链路层在拿到 TLP 包后在两端加上序列号和LCRC,在发生传输错误时,拥有重传机制。
(接收回路)接收到一个link 包,验证seq 与crc并将其剥离,然后传递给事务层。
请添加图片描述
物理层:
(发送回路)物理层 拿到link 包,在两端加上开始信号和结束信号。
(接收回路)接收到一个物理层的包,将两端的开始与结束信号拆分,传递给数据链路层。
请添加图片描述
请添加图片描述

Transaction Layer Packet(TLP) 格式

Transaction Layer Packet(TLP) 的通用格式如下:
请添加图片描述
PCIe TLP头部决定数据包的目标设备、寄存器地址、读写方式等等。
在 PCIE 的不同的读写情况下TLP 包的格式也有不同:

  • 命令类型:你是读内存还是写内存?读IO还是写IO?读配置还是写配置?
    在Header里面有定义。
  • 地址:对于内存读写、IO读写,地址保存在Header里。
  • Bus/Dev/Function/Regiser:对于配置读写,这些信息保存在Header里。
  • 数据:对于内存读、IO读、配置读,需要先发出读请求,再得到数据。
    分为2个阶段:读请求报文 和 完成报文。
    读请求报文,不含数据;完成报文,包含读到的数据内容。

对于PCIE 设备配置空间 读写,TLP header头部完整格式如下(表2-18):
Fmt 与type: 决定命令的类型,内存读/写、IO读/写、配置0读/写 或配置1读/写。(参照表格)
Bus Number: 要访问设备所在的总线号。
Device Number: 要访问的设备号。(总线下的第几个设备?)
Function Number: 要访问的功能号。(设备中的第几个功能?)
Register Number: 要访问的寄存器号。(配置空间中的哪一个寄存器? )

在这里插入图片描述
PCIE TLP 命令类型

PCIE 配置空间

PCIE总线 与PCI 一样,需要读取设备配置空间,获取到设备信息,分配地址并写入配置空间。
根据pcie设备类型不同,PCIE 配置空间分为两类:type0(非桥设备)和type1(桥设备)。
基本与PCI 设备配置空间相同,主要关注以下几个:

  • header type: 区分设备类型,非桥设备为0x00、桥设备为0x01。bit7 位决定PCIe 是多功能设备(一般endpoint才有多功能,桥设备一般为单功能),还是单功能设备。bit7=1 多功能,bit7=0 单功能。
    请添加图片描述

  • Base Address: 对于PCIE 非桥设备(type0) ,它会在BAR中声明自己需要多大的地址空间,配置过程中会将分配的地址段首地址写入BAR 寄存器。

对于PCIE桥设备(type1) 的配置,就是配置它的总线号:

  • Pirmary Bus Number: 上一级的总线号。
  • Secondary Bus Number: 自己的总线号。
  • Subordinate Bus Number: 下游总线号的最大数值。
    PCIE 配置空间

配置PCIe 设备

配置桥设备

配置PCIe 设备首先需要配置桥设备,因为需要通过桥的转发才能到达endpoint。
例如配置与主桥相连的A桥:

根据Fmt 与type选择有两种类型的TLP 配置包:分别为type0和type1。它们的区别是当需要到达的设备与桥直连时(比如host 与A、A与C),该桥会发出type0的包,直接到达设备;当需要到达的设备在它的下游,并且不与他直连时(比如A 与最下端设备),该桥会发出type1的包,type1经过一层层桥的转发,到达与该设备直连的桥时,该桥会发出type0 的包,到达设备。
在这里插入图片描述
① A与主桥相连,所以主桥发出type0 的包,并且标明Device 、Function 和Register Number。
② 根据设备号(Root Complex 为封装好的硬件,所以设备号已经在硬件上固定了,switch 也是一样)、功能号和寄存器号,就可以找到想要读写的寄存器。
③ 读取配置空间header type,发现A 设备为桥,将其的总线号分别配置为 Pirmary Bus Number=0、Secondary Bus Number=1、Subordinate Bus Number=255(暂定为255,待扫描完下游所有的桥设备后,修改为下游最大桥设备号)。
这样一个桥设备就配置完成了。

注意:

  • type0 需要到达的设备与桥直连,因此type0 的TLP 头部中应该不用注明Bus Number,因为这样没有意义。
  • 一个PCIe 设备中最多可以有8个功能,每个功能拥有一个配置空间。
    在这里插入图片描述

配置非桥设备

非桥设备如上图中的 Bus3 Dev0设备,这种设备一般被叫做endpoint

配置Bus3 Dev0:
① Host Bridge 发出type1 的TLP 数据包,Fmt=000、type=00101,Bus Number=3,Device Number=0,Function Number=0,Register Number=xxx。
② type1 TLP 包经过桥A->C->D桥,最终桥D发出type0 数据包,到达Bus3 Dev0。
③ 读取Bus3 Dev0 Function0 的配置寄存器,发现为普通设备
④ 读取BAR,获得设备需要的内存大小。
⑤ 分配指定大小内存,并将首地址写入设备0 BAR。配置完成。
在这里插入图片描述
在这里插入图片描述

PCIe 系统完整配置过程示例

5.2.1 硬件拓扑结构

以下图中的设备的配置过程为例,给大家做示范。
请添加图片描述

5.2.2 配置过程演示

下文中BDF表示Bus,Device,Function,用这三个数值来表示设备。

  1. 软件设置Host/PCI Bridge的Secondary Bus Number为0,Subordinate Bus Number为255(先设置为最大,后面再改)。
  2. 从Bus 0开始扫描:先尝试读到BDF(0,0,0)设备的Vendor ID,如果不成功表示没有这个设备,就尝试下一个设备BDF(0,1,0)。一个桥下最多可以直接连接32个设备,所以会尝试32次:Device号从0到31。注意:在Host/PCI Bridge中,这些设备的Device号是硬件写死的。
  3. 步骤2读取BDF(0,0,0)设备(即使图中的A)时,发现它的Header Type是01h,表示它是一个桥、单功能设备
  4. 发现了设备A是一个桥,配置它:
    • Primary Bus Number Register = 0:它的上游总线是Bus 0
    • Secondary Bus Number Register = 1:从它发出的总线是Bus 1
    • Subordinate Bus Number Register = 255:先设置为最大,后面再改
  5. 因为发现了桥A,执行"深度优先"的配置过程:先去枚举A下面的设备,再回来枚举跟A同级的B
  6. 软件读取BDF(1,0,0)设备(就是设备C)的Vendor ID,成功得到Vendor ID,表示这个设备存在。
  7. 它的Header Type是01h,表示这是一个桥、单功能设备。
  8. 配置桥C:
    • Primary Bus Number Register = 1:它的上游总线是Bus 1
    • Secondary Bus Number Register = 2:从它发出的总线是Bus 2
    • Subordinate Bus Number Register = 255:先设置为最大,后面再改
  9. 继续从桥C执行"深度优先"的配置过程,枚举Bus 2下的设备,从BDF(2,0,0)开始
  10. 读取BDF(2,0,0)设备(就是设备D)的Vendor ID,成功得到Vendor ID,表示这个设备存在。
  11. 它的Header Type是01h,表示这是一个桥、单功能设备。
  12. 配置桥D:
    • Primary Bus Number Register = 2:它的上游总线是Bus 2
    • Secondary Bus Number Register = 3:从它发出的总线是Bus 3
    • Subordinate Bus Number Register = 255:先设置为最大,后面再改
  13. 继续从桥D执行"深度优先"的配置过程,枚举Bus 2下的设备,从BDF(3,0,0)开始
  14. 读取BDF(3,0,0)设备的Vendor ID,成功得到Vendor ID,表示这个设备存在。
  15. 它的Header Type是80h,表示这是一个Endpoing、多功能设备。
  16. 软件枚举这个设备的所有8个功能,发现它有Function0、1
  17. 软件继续枚举Bus 3上其他设备(Device号1~31),没发现更多设备
  18. 现在已经扫描完桥D即Bus 3下的所有设备,它下面没有桥,所以桥D的Subordinate Bus Number等于3。扫描完Bus 3后,回退到上一级Bus 2,继续扫描其他设备,从BDF(2,1,0)开始,就是开始扫描设备E。
  19. 读取BDF(2,1,0)设备(就是设备E)的Vendor ID,成功得到Vendor ID,表示这个设备存在。
  20. 它的Header Type是01h,表示这是一个桥、单功能设备。
  21. 配置桥E:
    • Primary Bus Number Register = 2:它的上游总线是Bus 2
    • Secondary Bus Number Register = 4:从它发出的总线是Bus 4
    • Subordinate Bus Number Register = 255:先设置为最大,后面再改
  22. 继续从桥D执行"深度优先"的配置过程,枚举Bus 4下的设备,从BDF(4,0,0)开始
  23. 读取BDF(4,0,0)设备的Vendor ID,成功得到Vendor ID,表示这个设备存在。
  24. 它的Header Type是00h,表示这是一个Endpoing、单功能设备。
  25. 软件继续枚举Bus 4上其他设备(Device号1~31),没发现更多设备
  26. 已经枚举完设备E即Bus 4下的所有设备了,更新设备E的Subordinate Bus Number为4。然后继续扫描设备E的同级设备:Bus=2,Device从2到31,发现Bus 2上没有这些设备。
  27. 软件更新设备C即Bus 2的桥,把它的Subordinate Bus Number设置为4。然后继续扫描设备C的同级设备:Bus=1,Device从1到31,发现Bus 1上没有这些设备。
  28. 软件更新设备A即Bus 1的桥,把它的Subordinate Bus Number设置为4。然后继续扫描设备A的同级设备:Bus=0,Device从1到31,发现Bus 0上的设备B。
  29. 配置桥B:
    • Primary Bus Number Register = 0:它的上游总线是Bus 0
    • Secondary Bus Number Register = 5:从它发出的总线是Bus 5
    • Subordinate Bus Number Register = 255:先设置为最大,后面再改
  30. 再从桥B开始,执行"深度优先"的配置过程。

PCIe 路由方式

PCIe 的三种路由方式

数据传输时,最先要确定的是:怎么找到对方?
所谓"路由",就是怎么找到对方,PCIe协议中有三种路由方式:

  • 配置读、配置写:使用基于ID的路由,就是使用<Bus, Device, Function>来寻找对方。配置成功后,每个PCIe设备都有自己的PCIe地址空间了。
  • 内存读、内存写或者IO读、IO写:
    发出报文给对方:使用基于地址的路由
    对方返回数据或者返回状态时:使用基于ID的路由
  • 各类消息,比如中断、广播等:使用隐式路由

不管是什么路由方式,我们最终关心的是TLP的格式。

地址路由

内存读、内存写或者IO读、IO写属于地址路由。
下图是内存读写与IO读写的TLP 头部格式:内存读写数据格式有64bit和32bit两种,IO读写32位bit。
Address:代表要读写设备的地址(pci地址)。
requester ID:请求读写的设备id,其中 <Bus, Device, Function>。
tag:表示一次pcie的通信TLP 的序号。
pcie的每一次发送或读取信息都是通过TLP包,tag就表示TLP包的唯一序号。在设备发出包后会有一段缓存专门存放TLP包,如果对方回应这个TLP 那么设备会释放这段缓存,假如等待一段时间没有回应,设备将会重传这个TLP。就是通过tag判断发出TLP 包是否有回应。
请添加图片描述
在这里插入图片描述
想要了解PCIe 地址的路由方式首先要了解桥设备的配置空间(type1):
Memory Base 和Memory Limit:Base表示一段内存的首地址,Limit表示大小,合起来就描述了一段地址空间。描述下游设备所分配的所有地址段(一个桥下游设备所分配的地址应该是连续的,所以Base表示这些设备地址中最首的地址,Limit 表示它们的总长 )。
Prefetchable Memory Base、Limit:表示下游设备可预取的内存空间。
IO Base 和IO Limit:表示所有子设备所分配的IO 地址空间。
在这里插入图片描述

地址路由完成报文

下图里面的Requester ID、TAG,被称为"Transaction ID"。
在这里插入图片描述
主设备要给EndPoint的内存写数据,它发出"内存写报文",不需要对方回应。

主设备要读EndPoint的内存数据,它发出"内存读报文",需要对方回应。

主设备要给EndPoint的IO写数据,它发出"IO写报文",需要对方回应。

主设备要读EndPoint的IO数据,它发出"IO读报文",需要对方回应。

  • PCIe设备要回应时,回应谁?给"Requester ID",使用基于ID的路由
  • 发起PCIe传输的设备(主设备),对每次传输都分配一个独一的Tag,并且在硬件内部保存当前TLP。
  • 接收到回应报文后,才会根据Tag清除掉内存中保存数据。
  • 如果没接收到回应,或者失败了:会把硬件中保存的TLP重新发送出去。
    回应的完成报文,可以含有数据,也可以不含数据,TLP头部格式如下:
    请添加图片描述
地址路由寻址过程

场景①:最常见的场景。cpu 想要访问nedpoint1,cpu给他分配的空间是A-B,那么桥P-P2的Memory base和Limit描述的大小就是A-B(因为它的下游只有一个设备。如桥p-p1他的下游有多个设备分配了空间,分别是A-B和C-D,那么它的base和Limit 描述的这段空间就是A-D )。

  1. cpu 发出的指令首先到达RC,RC将cpu_addr 转换为 pci_addr,并发出TLP包。
  2. TLP 到达p-p1,对比自己base 和Limit 知道,这条消息是要访问自己的下游设备,于是往下一级总线转发。
  3. 同样p-p2 发现要访问的是自己的下游设备,往下转发;p-p3 发现不是便不转发。
  4. 最终到达EP1。当EP1 回应cpu 消息时使用的是ID路由,RC 发给EP1的TLP 中包含了requester ID,通过它就将回应的消息返回给RC,cpu最终从RC读取。

场景②:EP1 与EP2之间的通信

  1. EP2 发出含有EP1地址的TLP包,P-P3 桥发现自己不能处理,于是便往上一级总线转发。
  2. P-P2 发现自己可以处理,于是往自己的下一级转发,最终到达EP1。

场景③:EP2 通过DMA 直接访问内存(主存储器)

  1. EP2 发出含有主存储器 地址的TLP包,P-P3 桥发现自己不能处理,于是便往上一级总线转发。
  2. 同样P-P1 也往上一级转发,最终通过RC的地址转换(pci_addr to cpu_addr),到达主存储器。

在这里插入图片描述

隐式路由

消息报文的头部格式如下:
请添加图片描述
消息报文中头部的Type字段里低3位表示隐式路由方式:

  • 000:路由到RC
  • 001:使用地址路由(使用地址路由的消息不常见)
  • 010:使用ID路由
  • 011:来自RC的广播报文,底下的PCIe桥会向下游转发此消息
  • 100:本地消息,消息到达目的设备后结束,不会再次转发
  • 101:路由到RC,仅用于电源管理

在这里插入图片描述

PCIe 控制器地址转换解析(RK3399)

在前面的章节中我们描述了,PCIE 总线如何读写配置空间;如何进行PCIE 设备的内存读写、IO 读写等等。
对于它们,不管是何种读写方式最终都是发出TLP 包,里面包含有读写类型、目标设备地址等等。
这一系列的操作相当复杂,然而对于cpu 来说,这一切都是无感的,不管是配置读写、内存读写或IO 读写,cpu 始终都是发出一个cpu_addr。
在这里插入图片描述

对于配置读写来说,PCIE总线需要Bus Number/Device Number/Func Number/Register Number 来确定目标设备的地址,那么cpu_addr 是如何转换成我们需要的这些元素呢?
在这里插入图片描述

对于内存读写和IO读写来说,PCIE总线需要指定内存地址(PCI 地址空间)或 IO地址(PCI 地址空间) 来确定目标设备,cpu_addr 又是如何转换成我们所需要的内存地址 和IO地址呢?
在这里插入图片描述
另外还有一点,cpu 只发出一个简单的cpu_addr,又是怎么得到它想要用哪种读写方式呢?(Fmt 与Type的值)
在这里插入图片描述
其实,这些一系列的从cpu_addr 到B/D/F/R 和pci_addr 的转换过程,以及得到 Fmt/Type 的值都是由PCIE 控制器来完成。

下面,参考RK3399 来仔细研究一下PCIE 控制器到底是如何转换的。

对于RK3399 的PCIe控制器,它的 CPU地址空间可以分为:
在这里插入图片描述
RK3399 PCIE 控制器拥有Region0-Region32 的32段寄存器地址范围:
其中Region0 就是用来访问配置空间的,Region1-Region32 是用来访问PCIE 内存空间和IO空间的。

当CPU 想要访问PCIE 配置空间寄存器时,它会发出“0xF8xxxxxx” 的cpu地址,这个地址刚好落在Region0 的范围内,Region0 就会将它转换成配置读写的TLP 包在PCIE 总线上发出。
同样的,当CPU 想要访问PCIE 内存空间或是IO 空间时,它会发出属于Region1-Region32 的CPU 地址范围,由地址对应的Region 转换成内存/IO 读写的TLP 包在PCIE 总线上发出。

那么Region0 以及Region1-Region32,这些Region 的转换工作又是如何完成的呢?这就要具体去细看Region 中的寄存器地址了。

对于每一个Region,它们都拥有下列6 个寄存器,通过cpu 发出的cpu_addr 结合这6 个寄存器,就可以顺利的转换出TLP 包了(准确的来说是TLP header)。
在这里插入图片描述
观察TLP 的头部,我们可以把其中的信息分成2类:其它信息+地址信息。
对于配置读写的TLP,B/D/F/R 就是它的地址信息,除地址信息之外的都是其它信息。
对于内存读写、IO读写的TLP,要访问PCI地址就是它的地址信息,除地址信息之外的都是其它信息。

(上述6个寄存器也可分为两类,地址信息寄存器(ob_addr0-ob_addr1) 和其它信息寄存器(ob_desc0-ob_desc3);TLP header中的地址信息使用ob_addr0-ob_addr1 描述,其它信息使用ob_desc0-ob_desc3 描述)
在这里插入图片描述
在这里插入图片描述

配置空间读写cpu_addr-TLP 转换解析

对于配置空间读写,cpu 发出cpu_addr 落入region0 地址范围,需要通过region0 来帮助完成 cpu_addr->TLP 转换。
在这里插入图片描述

根据图2-18可知:
配置读写时,地址信息 = Bus Number + Device Number + Func Number + Register Number = 28 位。
其它信息 = 4Btye + 4Btye = 64位。
在这里插入图片描述

表17-7 是region0 详细的ob_addr0-ob_addr1 和ob_desc0-ob_desc1 寄存器描述:

addr0[5:0]: 表示你需要指定从cpu_addr 中获取几位 (指定位数= 寄存器值 +1),来组成地址信息。(至少需要8位(Register Numer))

比如addr0[5:0] = 27时,表示需要从cpu_addr 获取28位(低28位) 来组成<B,D,F,R> 地址,一般为了简单都是使用28位——即所有的信息都来自cpu_addr,此时寄存器 addr0中的Bus Number、Device Number、Func Number、ExtReg Number位就可以忽略。

addr0[11:8]: 4 位的 ExtReg Number。当addr0[5:0] 设置的位数少于12 位时,用这4位来作为ExtReg Number 组成地址信息。addr0[14:12]: 3 位的 Func Number。当addr0[5:0] 设置的位数少于15 位时,用这3位来作为Func Number 组成地址信息。addr0[19:15]: 5 位的 Device Number。当addr0[5:0] 设置的位数少于20 位时,用这5位来作为Device Number 组成地址信息。
addr0[14:12]: 8 位的 Bus Number。当addr0[5:0] 设置的位数少于28 位时,用这8位来作为Bus Number 组成地址信息。

desc0desc1 负责提供64位其它信息,其中desc1 负责高32 位,desc0 负责低32位。
在这里插入图片描述
表17-8 是desc0 的详细描述:
desc[3:0]: 当desc[3:0] = 1010时,它表示type0类型的配置访问;当desc[3:0] = 1011时,它表示type1类型的配置访问。(参考前面TLP header 中Fmt 和Type 组成的配置访问类型)
在这里插入图片描述
cpu访问配置空间示例
Region 0的地址范围是:0xF8000000~0xF9FFFFFF。
CPU想访问这个设备:Bus=bus,Dev=dev,Fun=fun,Reg=reg,那么CPU读写这个地址即可:
0xF8000000 + (bus<<20) | (dev<<15) | (fun<<12) | (reg)
所以,当cpu 访问PCIE 设备配置空间时,cpu只需要发出地址,就会自动被转换成设备地址信息。

当addr0[5:0] =27时表示,使用cpu_addr 的低28位来提供地址信息,但是0xF8000000 把第28位占用了,所以不能使用28位!
RK官方的代码中addr0[5:0]= 24,也就是使用cpu_addr 的低25位来提供地址信息——Bus Number的高3位由addr0[27:20] 提供,低5位由cpu_addr 提供。

内存、IO读写 cpu_addr-TLP 转换解析

PCIe 控制器中Region1-Region32 是用来访问PCIe 设备内存空间或IO空间的。
下面以Region1 作为示例,访问PCIe 设备内存空间(64位地址) (其它的Region 也是类似的)。
Region 1:0xFA000000~0xFA0FFFFF
cpu 发出cpu_addr,刚好落入region1 范围内,会被Region1 转换成访问内存类型 或是IO 类型的TLP 包。
在这里插入图片描述
同样的对于Region1-32 也有ob_addr0-ob_addr1、ob_desc0-ob_desc3 这6个寄存器,来帮助完成TLP 的转换。

表17-6 是寄存器ob_desc0 的详细描述:
ob_desc[3:0]: 表示传输类型,内存空间访问类型 或是IO空间访问类型。
在这里插入图片描述
下表是ob_addr0 和ob_addr1 的详细描述:
addr0[5:0]: 表示你需要由cpu_addr 中的多少位来提供地址信息(指定位数= 寄存器值 +1),cpu提供的字段会用来作为低位使用。(最低不少于8位)
addr0[31:8]: 如果addr[5:0] 设置的位数太少的话,就由该字段来补充。
**addr1[31:0]: ** 提供PCIe 内存访问 64位地址中的高32位。
在这里插入图片描述
内存、IO 地址转换示例:
Region 1的CPU地址范围是:0xFA000000~0xFA0FFFFF,是1M空间。

我们一般会让PCI地址等于CPU地址,所以这样设置:

  • addr0:
    • [5:0]等于19,表示CPU_ADDR[19:0]共20位地址传入TLP
    • [31:8]等于0xFA0000 (这里是为了简单,将PCI_addr 设置得与CPU_addr 相等,你也可以设置其它偏移值)
  • addr1:设置为0

如上设置后,CPU读写地址时0xFA0?????,就会转换为PCI地址:0xFA0?????,转换过程如下:
pci_addr = cpu_addr[19:0] | (addr0[31:20] << 20) | (addr1<<32)
= 0x????? + (0xFA0 << 20) | (0 << 32)
= 0xFA0?????

Linux PCIe驱动

PCIe 驱动分为PCIe 控制器驱动 和 PCIe 设备驱动。

Linux PCIE Host 驱动解析(rk3399)

rk3399_PCIE_Host 驱动——地址解析与映射

在前面的章节中,我们讲到rk3399 PCIe 控制器中有如下的地址范围。在驱动中,我们需要去描述这些地址范围,并使用它们。
在这里插入图片描述
Linux 驱动都遵循硬件信息驱动代码 分离的原则,所以PCIe 控制器驱动可分为两部分:

1.用来描述PCIe 控制器硬件信息的设备树节点;2. PCIe 控制器的驱动代码

设备树解析:

arch/arm64/boot/dts/rk3399.dtsi 文件中可以找到描述PCIe 控制器的设备树节点:

bus-range: 描述支持的PCIe 总线个数。<0x0 0x1f> 表示最多支持32条总线。

reg: 描述的是地址资源。一共描述了2段地址:①region0 地址范围位,该地址段命名为axi-base。 ②pcie 控制器内存寄存器地址范围,该地址段命名为apb-base。

ranges: 这个属性是用来描述pcie 设备的内存地址空间 和IO 地址空间的。这里将region1-region30 一共30M 作为pcie 内存空间,将region31 作为IO空间。(region32 没有在设备树中记录)

内存空间与IO 空间的区别:
内存空间,它的作用只是用来存放数据,cpu写入的数据不会改变;
IO 空间,可以把它想象成外设寄存器,寄存器内的值,反映了各种设备状态,写入读出的值都有独特的意义。

在这里插入图片描述

驱动代码解析:
在阅读驱动代码前,我们先根据前面章节中pcie 控制器的原理结合Linux的特性来想象一下驱动中需要去做哪些事情:

检索设备,配置设备:
首先,在使用地址路由 访问PCIe 设备前,需要遍历配置 PCIe 控制器下的所有设备,对于普通设备需要分配内存地址或io地址,对于桥设备需要分配总线号。

① 配置空间访问的准备
配置设备(设置pcie 设备的配置空间)时,使用的是id 路由方式。
对于配置空间访问,是通过region0 来完成,cpu 发出属于region0 范围的cpu_addr,region0 根据ob_addr 与ob_desc 寄存器来生成访问配置空间的TLP 包(id 路由的TLP)。
要知道,在Linux 中cpu 使用的是虚拟地址,所以cpu 发出的region0 地址段需要经过映射。

a. 解析dtb 中的region0 地址资源,映射region0 地址段。
b. 初始化region0 的ob_addr 和 ob_desc 寄存器。

② 总线资源准备
配置桥设备时,需要给桥分配总线号,对于总线号它也是一种资源。(一个pcie 控制器,它允许的子总线数量是有限的)

a. 解析dtb 中的总线资源,记录总线资源。

② 内存、IO 地址资源准备
对于地址路由,需要先给pcie设备分配地址。
使用地址访问设备时,通过region1-31 来完成,cpu 发出属于region1-31 范围的cpu_addr,根据region的ob_addr 与ob_desc 寄存器来生成地址路由的TLP 包。
同样的cpu,需要对region1-31 的地址段进行映射。

a. 解析dtb 描述的内存、IO地址资源(region1-31),对它们进行地址映射;
这些地址是要分配给PCIe 设备使用的,我们需要把这些地址资源记录起来,以便分配的时候使用(cpu 使用的是cpu_addr,pcie 设备使用的是pci_addr,所以在记录地址资源的时候,同时要记录cpu_addr 与pci_addr 的偏移值)。

b. 配置region1-31 对应的ob_addr、ob_desc 寄存器。

下面开始阅读代码:
PCIe 控制器驱动使用platform 总线模型管理,在内核启动过程中会将dtb 中的PCIe 控制器节点转换成一个platform_device,并注册到platform 总线;
在驱动代码中构建platform_driver,并注册到platform 总线,注册过程中如果双方匹配,就会调用platform_driver->probe 执行驱动代码。
在这里插入图片描述
rockchip_pcie_probe 函数:

res 定义一个资源链表(对于要分配的总线资源、内存空间资源、io空间资源,在后面的代码中都会挂入到这个链表中)。
rockchip_pcie 通常在写驱动时,都会自定义一个结构体,方便描述驱动中要用到的各个成员。
rockchip_pcie_parse_dt 解析设备树节点所描述的各种硬件信息。
在这里插入图片描述
rockchip_pcie_parse_dt
解析 reg 中描述的地址资源:
第一段为region0 的地址范围,映射的虚拟地址记录到rockchip->reg_base;第二段是pcie 控制器内部寄存器的地址范围,映射后的虚拟地址记录到rockchip->apb_base。
在这里插入图片描述
其它的各种属性解析(详细参考源码)。
在这里插入图片描述
rockchip_pcie_probe
err = rockchip_pcie_init_port(rockchip);
rockchip_pcie_init_port 函数中对ob_addr0、ob_addr1 和ob_desc0、ob_desc1 设置。(还有其它的硬件代码,参考源码)
在这里插入图片描述
在这里插入图片描述
rockchip_pcie_probe
err = of_pci_get_host_bridge_resources(dev->of_node, 0, 0xff,&res, &io_base);
of_pci_get_host_bridge_resources 解析设备树中的"bus-range"、“ranges” 描述的资源,并把它们添加到res 资源链表中。
在这里插入图片描述
解析“bus-range” 为一个struct resource,调用pci_add_resource将它挂入res 链表。(挂入链表的是struct resource_entry,里面包含有resource)
在这里插入图片描述
在这里插入图片描述
of_pci_range_parser_init 读取“ranges” 属性的值到parser(struct of_pci_range_parser)。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
for_each_of_pci_range for循环遍历parser 中的每一个range。
在上面的ranges 属性中一共描述了2个range(一个range用7个值来描述),第一个range 是分配给pcie 设备使用的内存空间 地址段;第二个range 是分配给pcie 设备使用的IO空间 地址段。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
for_each_of_pci_range for循环遍历parser 中的每一个range,将每一个range 都转化成struct resouce,然后调用pci_add_resource_offset将resource 添加到res 链表中。
在这里插入图片描述
在这里插入图片描述
rockchip_pcie_probe
err = devm_request_pci_bus_resources(dev, &res);
devm_request_pci_bus_resources 向内核申请res 链表中的资源。(包括总线资源、内存地址资源和io地址资源) 在这里插入图片描述
在这里插入图片描述
遍历res 链表中的所有资源,记录到rockchip中。

rockchip_pcie_probe
err = rockchip_cfg_atu(rockchip);
rockchip_cfg_atu 配置地址转换单元,还记得前面说过生成地址路由的TLP 包需要配置region 的ob_addr 和ob_desc 寄存器吗,这一步就实在这个函数中设置的(不包括region0)。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 27
    点赞
  • 145
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
PCIe总线是一种高速串行接口协议,用于连接计算机内部的各种设备,如显卡、声卡、存储设备等。学习PCIe总线需要掌握以下几个方面的知识。 首先,应该了解PCIe总线的基本概念和工作原理。PCIe总线的传输速度非常快,具有高带宽和低延迟的特点。它采用点对点(Point-to-Point)连接方式,每个设备都有独立的通信通道,能够实现高效的数据传输。 其次,需要掌握PCIe总线的电气特性和信号传输方式。PCIe总线使用差分信号进行数据传输,可以有效抵抗噪声和干扰,提高传输质量。此外,还需要学习常用的PCIe接口类型和插槽规格,以便选择合适的设备和扩展卡进行扩展。 另外,了解PCIe总线的配置空间和寄存器编程也是很重要的。PCIe设备内部有一片配置空间,包含了设备的基本信息和寄存器,可以通过编程来访问和配置设备。掌握寄存器的编程方法可以实现对设备的控制和管理。 最后,需要了解PCIe总线在操作系统中的驱动程序开发。学习如何编写PCIe设备的驱动程序,可以实现与设备的交互和数据传输。对于开发人员而言,掌握PCIe总线的驱动开发技术,对于实现硬件设备的功能和性能优化至关重要。 总之,学习PCIe总线需要掌握其基本概念、工作原理、电气特性和信号传输方式、配置空间和寄存器编程,以及在操作系统中的驱动程序开发。这些知识将帮助我们了解和应用PCIe总线,提高计算机系统的性能和扩展能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值