硬盘是大家都很熟悉的设备,一路走来,从HDD到SSD,从SATA到NVMe,作为NVMe SSD的前端接口,PCIe再次进入我们的视野。作为x86体系关键的一环,PCIe标准历经PCI、PCI-X和PCIe,走过近30年时光。其中,Host发现与查找设备的方式却一脉沿袭。今天,我们就来聊聊PCIe设备在一个系统中是如何发现与访问的。

首先,我们来看一下在x86系统中,PCIe是什么样的一个体系架构。下图是一个PCIe的拓扑结构示例,PCIe协议支持256个Bus, 每条Bus最多支持32个Device,每个Device最多支持8个Function,所以由BDF(Bus,device,function)构成了每个PCIe设备节点的身份证号。

PCIe Bus

PCIe体系架构一般由root complex,switch,endpoint等类型的PCIe设备组成,在root complex和switch中通常会有一些embeded endpoint(这种设备对外不出PCIe接口)。这么多的设备,CPU启动后要怎么去找到并认出它们呢? Host对PCIe设备扫描是采用了深度优先算法,其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。我们一般称这个过程为PCIe设备枚举。枚举过程中host通过配置读事物包来获取下游设备的信息,通过配置写事物包对下游设备进行设置。

第一步,PCI Host主桥扫描Bus 0上的设备(在一个处理器系统中,一般将Root complex中与Host Bridge相连接的PCI总线命名为PCI Bus 0),系统首先会忽略Bus 0上的embedded EP等不会挂接PCI桥的设备,主桥发现Bridge 1后,将Bridge1 下面的PCI Bus定为 Bus 1,系统将初始化Bridge 1的配置空间,并将该桥的Primary Bus Number 和 Secondary Bus Number寄存器分别设置成0和1,以表明Bridge1 的上游总线是0,下游总线是1,由于还无法确定Bridge1下挂载设备的具体情况,系统先暂时将Subordinate Bus Number设为0xFF。

看下面这张图,大家已经知道今天的主角是UART,我们通常说的串口,UART包含TTL电平和RS-232电平两种,嵌入式系统里面,单片机的串口一般都是TTL电平。

UART

今天的内容关于UART的帧格式,比较简单,玩过单片机的小伙伴应该都知道。

UART的英文全称是:Universal Asynchronous Receiver/Transmitter,意为通用异步收发传输器。

UART因为有两根线数据线TX和RX,可以以全双工的形式进行发送和接收数据,同一时刻,两条链路的发送器和接收器可以同时传输数据。

UART

区别于全双工的,还有另一种,是半双工,因为只有一根数据线,所以数据传输是这样。

UART

或者是下面这样,同一时刻,只有一条链路在传输数据。

UART

除了双工形式,还有一种是半工,只有发送器到接收器这一个链路。

UART

说完了UART的工作模式,下面进入主题——UART帧格式,也可以称之为UART协议,单片机与PC之间的通信,为了保证数据通信的可靠性,双方都必须遵从UART协议。

UART

UART数据帧格式

其中各位的含义如下:

  • 起始位:发送1位逻辑0(低电平),开始传输数据。
  • 数据位:可以是5~8位的数据,先发低位,再发高位,一般常见的就是8位(1个字节),其他的如7位的ASCII码。
  • 校验位:奇偶校验,将数据位加上校验位,1的位数为偶数(偶校验),1的位数4为奇数(奇校验)。
  • 停止位:停止位是数据传输结束的标志,可以是1/1.5/2位的逻辑1(高电平)。
  • 空闲位:空闲时数据线为高电平状态,代表无数据传输。

如果我们传输数据0X33(00110011),那么对应的波形就是如下这样,因为是LSB在前,所以8位数据依次是11001100

UART

发送0X33数据帧格式

如果再发其他数据,再依次循环这个过程即可。

UART是异步传输,以1个字符为传输单位,传输2个字符之间的时间间隔,比如传输0X33后再传输0X35,这两者时间间隔是未知的。

但是同一字符内相邻位间的时间间隔是确定的,比如0X33低两位的1和1之间的时间间隔是确定的,这涉及到UART传输速率的概念——波特率

波特率的单位是bps,全称是bit per second,意为每秒钟传输的bit数量。

波特率9600bps,代表每秒钟传输bit的数量为9600,那么传输1bit数据的时间就是1/9600=104us,波特率115200bps,代表传输1bit数据的时间是8us。

两个串口之间是如何发送和接受数据呢?

首先,UART1以9600波特率发送0X33,先在数据线上放1个104us脉宽的低电平(起始位),然后是连续2个104us脉宽的高电平(2bit逻辑1),依次类推。

其次,UART2以9600波特率接收0X33,通过数这些数据的脉宽,来确认数据。

为了确保数据传输的正确性,减少误差,一般UART1和UART2之间的波特率差别小于10%,一次最多只能传输1个字节(8bit),也有效减小了累计误差。

PCIe(Peripheral Component Interconnect Express)是早期PCI总线的升级版。PCI 由英特尔开发并于 1992 年推出。它取代了早期 PC 上以特殊方式使用的旧的慢速总线。PCI 主要是 32 位总线,但它也支持 64 位。最重要的一点是它是一个并行总线。目前PCI只有历史意义,不再使用,这里不再赘述。

2004 年,一群英特尔工程师成立了 Arapaho 工作组并开始制定新标准。后来,其他公司也逐渐加入了这个小组。在最终确定为 PCI Express(PCIe) 之前,该标准曾多次重命名。PCIe 在某些方面是 PCI 的继承者,但在其他方面则是完全不同的类型。特别是,PCIe 是一种串行总线,与 PCI 较旧的并行接口(以及当时几乎所有其他总线)相比,它更像是一个板载网络。

早期标准PCIe 1.0a的数据速率为每通道250MB/秒,总速率为2.5GT/秒(千兆/秒)。与其他串行总线一样,性能通常以每秒传输数来衡量,因此不会将额外的位计为“数据”。PCIe 1.0a 使用了 8b / 10b 编码方案,因此只有 80% 的传输位是实际的“数据”。附加位有两个主要功能。首先,串行接口确保始终有足够的时钟转换来恢复时钟。它保证没有净直流电流。

之后,对标准进行了定期升级,传输速度得到了提高。由于 PCIe 主要用于基于 Intel 处理器的 PC 和服务器的,实际上新标准在 Intel 发布使用 PCIe 的处理器后生效。标准演进的总体思路是选择当时主流工艺节点所能达到的传输速率。然而,PCIe 应用非常普遍,以至于它们被用于大多数需要高性能外设总线的设计中,而不管底层架构如何。例如,Arm 服务器基础系统架构规范中指定了 PCIe规范。

2007 年推出的 PCIe 2.0 将传输速率提高了一倍,但使用相同的编码方法。

2010 年推出的 PCIe 3.0 已更改为更高效的 128b / 130b 编码方案,并添加了已知的二进制多项式加扰功能以平衡了0和1的时钟恢复,没有直流偏置。此外,传输速度也有明显提升。16 通道 PCIe 3.0 接口的最大传输速率为 15.7 GB/秒。但是,在实践中,如果您的设计需要这样的带宽,升级到 PCIe 4.0 会更容易。今天,PCIe 3.0 是移动设备中部署最广泛的 PCIe 版本。例如,Google TPU 版本 3 使用 PCIe 3.0,而当前的 USB4 标准是基于 PCIe 3.0。这听起来可能有点令人震惊,但 PCIe 被批准为标准后,花了将近十年的时间才成为主流。这有点像信用卡问题:商店不愿意接受繁琐的信用卡付款,直到许多人拥有信用卡,人们才不愿意接受信用卡付款。