PCIe相关

[转载]老男孩读PCIe之五:TLP结构

来源: http://www.ssdfans.com/?p=3683

无论Request TLP,还是作为回应的Completion TLP,它们模样都差不多:

图5.1

TLP主要由三部分组成:Header,Data和CRC。TLP都是生于发送端的事务层(Transaction Layer),终于接收端的事务层。

每个TLP都有一个Header,跟动物一样,没有头就活不了,所以TLP可以没手没脚,但不能没有头。事务层根据上层请求内容,生成TLP Header。Header内容包括发送者的相关信息、目标地址(该TLP要发给谁)、TLP类型(前面提到的诸如Memory read,Memory Write之类的)、数据长度(如果有的话)等等。

Data Payload域,用以放有效载荷数据。该域不是必须的,因为并不是每个TLP都必须携带数据的,比如Memory Read TLP,它只是一个请求,数据是由目标设备通过Completion TLP返回的。后面我们会整理哪些TLP需要携带数据,哪些TLP不带数据的。前面也提到,一个TLP最大载重是4KB,数据长度大于4KB的话,就需要分几个TLP传输。

ECRC(End to End CRC)域,它对之前的Header和Data(如果有的话)生成一个CRC,在接收端然后根据收到的TLP,重新生成Header和Data(如果有的话)的CRC,和收到的CRC比较,一样则说明数据在传输过程中没有出错,否则就有错。它也是可选的,可以设置不加CRC。

图5.2

Data域和CRC域没有什么好说的,有花头的是Header域,我们要深入其中看看。

一个Header大小可以是3DW,也可以是4DW。以4DW的Header为例,TLP的Header长下面样子:

图5.3

红色区域为所有TLP Header公共部分,所有Header都有这些;其它则是跟具体的TLP相关。

稍微解释一下:

Fmt:Format, 表明该TLP是否带有数据,Header是3DW还是4DW;

Type:TLP类型,上一节提到的,Memory Read, Memory Write, Configuration Read, Configuration Write, Message和Completion,等等;

R: Reserved,为0;

TC: Traffic Class,TLP也分三六九等,优先级高的先得到服务。这里是3比特,说明可以分为8个等级,0-7,TC默认是0,数字越大,优先级越高;

Attr: Attrbiute, 属性,前后共三个bit,先不说;

TH: TLP Processing Hints,先不说;

TD: TLP Digest,之前说ECRC可选,如果这个这个bit置起来,说明该TLP包含ECRC,接收端应该做CRC校验;

EP: Poisoned data, 有毒的数据,远离,哈哈;

AT: Address Type,地址种类,先不说;

Length: Payload数据长度,10个bit,最大1024,单位DW,所以TLP最大数据长度是4KB; 该长度总是DW的整数倍,如果TLP的数据不是DW的整数倍(不是4Byte的整数倍),则需要用到下面两个域:

Last DW BE 和 1st DW BE。

我觉得,到目前为止,对于Header,我们只需知道它大概有什么内容,没有必要记住每个域是什么。

这里重点讲讲Fmt和Type,看看不同的TLP(精简版的,Native PCIe设备所有)其Fmt和Type应该怎样编码

Table 5.1

从上可以看出,Configuration和Completion 的TLP(以C打头的TLP), 其Header大小总是3字节; Message TLP的Header总是4字节;而Memory相关的TLP取决于地址空间的大小,地址空间小于4GB的,Header大小为3DW,大于4GB的,Header大小则为4DW。

上面介绍了几个TLP Header的通用部分,下面分别介绍具体TLP的Header。

Memory TLP

有两个重要的东西在前面没有提到,那就是TLP的源和目标,即该TLP是哪里产生的,它要到哪里去,它们都包含在Header里面的。因为不同的TLP类型,寻址方式不同,因此要具体TLP具体来看这两个东西。

图5.4

对一个PCIe设备来说,它开放给Host访问的设备空间首先会映射到Host的内存空间,Host如果想访问设备的某个空间,TLP Header当中的地址应该设置为该访问空间在Host内存的映射地址。如果Host内存空间小于4GB,则Memory读写TLP的Header大小为3DW,大于4GB,则为4DW。那是因为,对4GB内存空间,32bit的地址用1DW就可以表示,该地址位于Byte8-11;而4GB以上的内存空间,需要2DW表示地址,该地址位于Byte8-15。

该TLP经过Switch的时候,Switch会根据地址信息,把该TLP转发到目标设备。之所以能唯一的找到目标设备,那是因为不同的Endpoint设备空间会映射到Host内存空间的不同位置。

关于TLP路由,后面还会专门讲。

Memory TLP的目标是通过内存地址告知的,而源是通过"Requester ID"告知。每个设备在PCIe系统中都有唯一的ID,该ID由总线(Bus)、设备(Device)、功能(Function)三者唯一确定。这个后面也会专门讲,这里只需知道一个PCIe组成有唯一的ID,不管是RC, Switch还是Endpoint。

Configuration TLP

Endpoint和Switch的配置(Configuration)格式不一样,分别为Type 0和 Type 1来表示。配置可以认为是一个Endpoint或者Switch的一个标准空间,这段空间在初始化时也需要映射到Host的内存空间。与设备的其他空间不同,该空间是标准化的,即不管哪个厂家生产的设备,都需要有这么段空间,而且哪个地方放什么东西,都是协议规定好的,Host按协议访问这部分空间。由于每个设备ID唯一,而其Configuration又是固定好的,因此,Host访问PCIe设备的配置空间,只需指定目标设备的ID就可以了,不需要内存地址。

下面是访问Endpoint的配置空间的TLP Header (Type 0):

图5.5

Bus Number + Device + Function就唯一决定了目标设备; Ext Reg Number + Register Number相当于配置空间的偏移。找到了设备,然后指定了配置空间的偏移,就能找到具体想访问的配置空间的某个位置。

Message TLP

Message TLP用以传输中断、错误、电源管理等信息,取代PCI时代的边带信号传输。Message TLP的Header 大小总是4DW。

图5.6

Message Code来指定该Message的类型,具体如下:

图5.7

不同的Message Code,最后两个DW的意义也不同,这里不展开。

Completion TLP

有non-posted request TLP,才有Completion TLP。有因才有果。前面看到,Requester 的TLP当中都有Requester ID和Tag,来告诉接收者发起者是谁。那么响应者的目标地址就很简单,照抄发起者的源地址就可以了。因此,Completion TLP的Header如下:

图5.8

Completion TLP,一方面,可以返回请求者的数据,比如作为Memory或者Configuration Read的响应;另一方面,还可以返回该事务(Transaction)的状态,因此,在Completion TLP的Header里面有一个Completion Status,用以返回事务状态:

图5.9


09/21/2017 Thu

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

首先PCIe switch和PCIe bridge都是baiPCIe相关芯片的类别总称。
PCIe switch中文翻译为PCIe开关或PCIe交换机,主要作用将PCIe设备互联,PCIe switch芯片与其设备的通信协议都是PCIe;
PCIe bridge中文翻译为PCIe桥接器,主要作用是互联PCIe设备与其他总线协议设备(例如PCI,USB等),PCIe bridge芯片实现了PCIe总线协议设备与其他总线协议(PCI,USB等)设备的通信。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

2  P C I E设备配置空间读取 

读取PCI-E设备配置空间的命令是lspci

NAME

lspci – list all PCI devices

   
 

SYNOPSIS

lspci [options]

   
 

详细命令参数,可以使用man lspci来查看,这里我们只介绍常用参数。

命令默认输出结果是,当前系统的所有PCI/PCI-E设备。

[root@localhost ~]# lspci

00:00.0 Host bridge: Intel Corporation 5500 I/O Hub to ESI Port (rev 13)

00:01.0 PCI bridge: Intel Corporation 5520/5500/X58 I/O Hub PCI Express Root Port 1 (rev 13)

00:03.0 PCI bridge: Intel Corporation 5520/5500/X58 I/O Hub PCI Express Root Port 3 (rev 13)

00:07.0 PCI bridge: Intel Corporation 5520/5500/X58 I/O Hub PCI Express Root Port 7 (rev 13)

00:09.0 PCI bridge: Intel Corporation 5520/5500/X58 I/O Hub PCI Express Root Port 9 (rev 13)

00:10.0 PIC: Intel Corporation 5520/5500/X58 Physical and Link Layer Registers Port 0 (rev 13)

00:10.1 PIC: Intel Corporation 5520/5500/X58 Routing and Protocol Layer Registers Port 0 (rev 13)

00:11.0 PIC: Intel Corporation 5520/5500 Physical and Link Layer Registers Port 1 (rev 13)

00:11.1 PIC: Intel Corporation 5520/5500 Routing & Protocol Layer Register Port 1 (rev 13)

00:13.0 PIC: Intel Corporation 5520/5500/X58 I/O Hub I/OxAPIC Interrupt Controller (rev 13)

00:14.0 PIC: Intel Corporation 5520/5500/X58 I/O Hub System Management Registers (rev 13)

… …

01:00.0 Ethernet controller: Broadcom Corporation NetXtreme II BCM5709 Gigabit Ethernet (rev 20)

01:00.1 Ethernet controller: Broadcom Corporation NetXtreme II BCM5709 Gigabit Ethernet (rev 20)

04:00.0 SCSI storage controller: LSI Logic / Symbios Logic SAS1068E PCI-Express Fusion-MPT SAS (rev 08)

05:00.0 VGA compatible controller: XGI Technology Inc. (eXtreme Graphics Innovation) Z9s/Z9m (XG21 core)

[root@localhost ~]#

   
 

常用参数:

-v 显示设备的详细信息。

-vv 显示设备更详细的信息。

-vvv 显示设备所有可解析的信息。

-x 以16进制显示配置空间的前64字节,或者CardBus桥的前128字节。

-xxx 以16进制显示整个PCI配置空间(256字节)。

-xxxx 以16进制显示整个PCI-E配置空间(4096字节)。

-s [[[[<domain>]:]<bus>]:][<slot>][.[<func>]]:显示指定设备。

示例:

[root@localhost ~]# lspci -vvvxxxx -s 00:14.0

00:14.0 PIC: Intel Corporation 5520/5500/X58 I/O Hub System Management Registers (rev 13) (prog-if 00 [8259])

    Subsystem: Unknown device 00e5:0008

    Control: I/O- Mem- BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B-

    Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR-

    Capabilities: [40] Express Unknown type IRQ 0

        Device: Supported: MaxPayload 128 bytes, PhantFunc 0, ExtTag-

        Device: Latency L0s <64ns, L1 <1us

        Device: Errors: Correctable- Non-Fatal- Fatal- Unsupported-

        Device: RlxdOrd- ExtTag- PhantFunc- AuxPwr- NoSnoop-

        Device: MaxPayload 128 bytes, MaxReadReq 128 bytes

        Link: Supported Speed unknown, Width x0, ASPM L0s, Port 0

        Link: Latency L0s unlimited, L1 unlimited

        Link: ASPM Disabled CommClk- ExtSynch-

        Link: Speed unknown, Width x0

00: 86 80 2e 34 00 00 10 00 13 00 00 08 10 00 80 00

10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

20: 00 00 00 00 00 00 00 00 00 00 00 00 e5 00 08 00

… …

fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

   
 

当我们用老版本的Linux系统在新平台上运行时,会发现lspci命令结果很多值为unknown。lspci显示的设备名称如”Host bridge: Intel Corporation 5500 I/O Hub to ESI Port (rev 13)“,实际上从文件/usr/share/hwdata/pci.ids进行匹配的,PCI-E配置空间并没有类似Intel这样的字符串。出现Unknown设备时,我们可以更新pci.ids文件。

pci.ids文件下载地址为:

http://pciids.sourceforge.net/

下载后,直接覆盖/usr/share/hwdata/pci.ids文件即可。

3  PCI-E设备配置空间修改 

修改PCIE配置空间的命令为:setpci

NAME

setpci – configure PCI devices

  

SYNOPSIS

setpci [options] devices

  

对于setpci命令来说,主要的参数如下:

-s [[[[<domain>]:]<bus>]:][<slot>][.[<func>]]

   
 

就是我们要指定设备,然后修改其配置空间。常用命令格式和参数如下:

setpci -s BUSID:DEVID.FUNCID REGISTEROFFSET.B=NEWVALUE

setpci -s BUSID:DEVID.FUNCID REGISTEROFFSET.W=NEWVALUE

setpci -s BUSID:DEVID.FUNCID REGISTEROFFSET.L=NEWVALUE

   
 

如:

setpci -s 0:14.0 60.B=6

是将设备0:14.0设备,PCI配置空间便宜量为0x60,写入新的字节值为6。查看PCI配置空间修改是否生效,可以通过lspci命令来查看,如设置0:14.0后,读取命令为lspci –s 0:14.0 –xxx。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

PCIE 调试过程记录

遇到的问题

  • PCIE link不稳定
  • 配置空间读写正常,Memory mapping空间读写异常

缘由

之前对PCIE的认识一直停留在概念的阶段,只知道是一个高速通讯协议,主要用于板内、板间的高速BUS。正好公司最近在调试一个PowerPC平台的PCIE BUS的BSP。需要一些PCIE的硬件、软件知识。下面通过解决实际问题过程的方法来进一步理解PCIE总线协议。但也仅仅限于工程应用(实际产品中调试、应用)层面。

具体解决过程

PCIE 物理层link 不稳定

启动u-boot后,可以看到PCIE link status信息log
u-boot下的pcie link status
从log上分析:系统启动了3个PCIE controller,PCIE 号为:0.0.0 2.0.0 4.0.0。
三个数字的具体含义为:0.0.0 :Bus Number Device Number Function Number。
这三个Host Bridge 下面分别挂了一个EP设备,PCIE 号为:1.0.0 、3.0.0 、5.0.0。
正常看到这些PCIE bridge 和EP说明PCIE 读PCIE的配置空间是正确的,PCIE在physical 的link状态是OK的。
可以使用pci header + PCIE号的方式查看Bridge和EP的Config 信息。
pci config space
若出现读取配置信息不稳定的情况,则说明PCIE link 不稳定。需要进一步排查硬件,软件辅助查看link status的方法是查看PCIE Host Bridge的link status的寄存器值。PCIE 规范里有一个LTSSM(Link Training and Status State Machine),各种status的code规范里都有定义。
这个LTSSM在PCIE Extend Config Space里面。在P3041中的offset为0x404。
可以使用命令:pci display.b 0.0.0 404查看LTSSM状态。
正常情况下的输出:
LTSSM
PCIE 初始化完成后会进入L0状态。异常状态见PCIE link 异常log。
物理层link 不稳定,怀疑以下原因:
- 高速串行信号质量问题
- Serdes电源问题
- 时钟问题

Serdes电源经过测量后,基本排除。
高速信号完整性问题经过示波器测量后,也基本排除。
时钟问题,最开始只是简单测试了下时钟的频率和幅值。没有实质性去分析时钟的jitter等时钟质量问题。后分析原理图发现,pcie的Host和EP端使用了不同的pcie时钟IC,也就是说使用了非同源时钟。在没有详细查看pcie specification关于pcie时钟的情况下,将host和EP端的时钟改为同源时钟(当然是选择飞线喽),pcie物理层link问题解决。
项目没有那么紧张的时候,反思此事,仔细查看pcie specification中关于时钟的要求,并没有要求Host和EP端必须使用同源时钟。只是对同源、非同源时钟的精度做了要求。
同源情况下要求100M. +-600ppm
非同源时钟要求100M. +-300ppm
哦,原来是这样,接着去看原理图中选用的PCIE时钟IC,发现EP端的pcie时钟IC为25M输入然后倍频出来100M时钟的,且使用了一个带有SSC功能的IC,原理配配置上打开了该功能。根据pcie规范中介绍的,该功能
为pcie一项技术,该功能为了减少板级EMI对时钟做了特殊能量扩散处理。有SSC功能的时钟超出300ppm的抖动,但在600ppm内。
这样得出一个结论,我们使用了非同源的设计,EP端使用的pcie时钟IC的jitter超出了300ppm。看来不能稳定的link也是情理之中。这样看来就有另一种解决方法,把EP端PCIE侧的SSC功能关闭,去实践发现将SSC功能关闭后,可以正常稳定link。
我们使用的pcie时钟芯片支持SSC且配置打开,时钟ppm在300到600之间。又非同源,因此link不稳定。将此功能关闭,时钟在300ppm,因此可以稳定link。

解决方法:
- 飞线统一成同一时钟源。
- 将EP端的时钟IC的SSC功能关闭。

PCIE memap空间读写异常

问题:

  • pcie可以正常读写配置空间,但无法正常读写memap 空间

最后定位问题:
一个address是36bit的,但软件定义为一个32bit的变量,从而导致软件读写pcie memap空间失败(读错了位置)。
解决方法:fixed the software bug。☺

这个问题定位主要需要一些关于PICE的一些地址的知识,要理解他们之间的关系。

  • PCIE空间的BAR
  • CPU访问PCIE设备的高速外设(IO)的地址
  • 应用层访问PCIE memap的地址

    来张图吧,
    PCIE addresses

    上图中,有几个关键的Address,Virtual Address这个大家比较好理解,但凡有一点在linux下开发经验的程序员都知道linux下的应用程序只能访问Virtual Address,那如果我们需要访问一些指定的Physical Address怎么办,这在driver开发当中十分常见。linux提供了mamap系统调用,用于Virtual Address和Physical Address的转换。另外,PICE设备在读写时使用PCIE的BAR地址,BAR地址是PCIE控制器以memap 方式读写PCIE EP设备时使用的地址,是PCIE协议使用的地址。

    我们以写PCIE EP的memap空间为例子说明一下地址之间的转换是怎么做的。PCIE的memap空间是OS分配的,一般是一个固定的地址和大小,然后OS启动后,PCIE的应用程序将PCIE的memap的Physical Address通过mamap系统调用映射为Virtual Address,然后应用程序就会通过此地址读写EP设备的memap空间,当CPU发出写EP设备的memap空间时,PCIE控制器会根据设置的BAR地址转换成相应的PICE协议要求的地址。BAR是在PCIE枚举的过程中设置的,一般通过枚举方式设置成合理的地址值,EP这一侧会根据配置的值将自身的memap地址空间映射到设置的地址上去。

举一个具体的例子:
CPU分配的PCIE Physical Address为0xc20000000,设置的BAR地址为0xe0000000。应用层APP实际使用的Virtual Address和OS有关系,不同的进程调用mamap映射为同一个Physical Address可能会产生不同的Virtual Address值。

总结

应用层调试方法,config space配置、memap空间的理解
Pcie控制器的配置、使用
从实际调试PCIE设备的过程看,设备调试和使用主要涉及到两个方面:config space和MMIO space。配置空间主要是用于配置PCIE设备。很多PCI设备仅仅支持64字节的配置空间。PCI和PCIe配置空间的区别如下:
PCI/PCI-X和PCIe设备还扩展了0x40和0xFF这段配置空间,这段空间主要存放一些与MSI或者MSI-X 中断机制相关的Capability结构。其中所有能够提交中断请求的PCIe设备,必须支持MSI或者MSI-X 中断机制相关的Capability结构。
PCIe设备还支持0x100 -0xFFF这段扩展配置空间。PCIe设备的扩展配置空间最大为4KB,在PCIe总线的扩展配置空间中,存放PCIe所独有的一些Capability结构,而PCI设备不能使用这段空间。
在x86处理器中,使用CONFIG_ADDRESS寄存器与CONFIG_DATA寄存器访问0x00-0xFF,而使用ECAM方式访问0x000-0xFFF这段空间;而在PowerPC处理器中,可以使用CFG_DATA和CFG_ADDR寄存器访问0x000-0xFFF。
MMIO(Memory mapping I/O)即内存映射I/O,它是PCI规范的一部分,I/O设备被放置在内存空间而不是I/O空间。从处理器的角度看,内存映射I/O后系统设备访问起来和内存一样。这样访问AGP/PCI-E显卡上的帧缓存,BIOS,PCI设备就可以使用读写内存一样的汇编指令完成,简化了程序设计的难度和接口的复杂性。I/O作为CPU和外设交流的一个渠道,主要分为两种,一种是Port I/O,一种是MMIO(Memory mapping I/O)。
底层使用pcie接口的软件移植需要考虑以下方面:

  1. 确定address bus的宽度,是32bit还是64bit或者其他。
  2. 确定CPU的大小端,以及PCIE的大小端问题。
  3. check下PICE max payload问题。

附录


$$$develop from single control storeage based on P3041
$$$Hit any key to stop autoboot:  0 
=> pci display.b 0.0.0 400
00000400: 00 00 00 00 33 00 00 00 e2 04 00 00 00 00 00 00
00000410: 01 00 00 00 19 00 00 00 00 00 00 00 40 40 96 96
00000420: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000430: 00 00 00 00 00 00 00 00 5c 41 c2 00 00 00 00 00
=> pci display.b 0.0.0 400
00000400: 00 00 00 00 02 00 00 00 e2 04 00 00 00 00 00 00
00000410: 01 00 00 00 19 00 00 00 00 00 00 00 40 40 96 96
00000420: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000430: 00 00 00 00 00 00 00 00 5c 41 c2 00 00 00 00 00

Linux平台PCIe驱动编写

https://blog.csdn.net/qq_21792169/article/details/88708428?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v28-5-88708428.nonecase&utm_term=bar%20linux%20%E6%9F%A5%E7%9C%8Bpcie&spm=1000.2123.3001.4430

以前文章分析了PCIe整个系统知识,包括如何扫描PCIE树,这篇文章讲解一下当拿到一个PCIe设备时如何编写驱动程序。编写驱动程序在应用程序中编写,同样可以在内核层编写驱动。

从应用层编写驱动主要是使用pcilib库和/dev/mem接口,下面开始分析代码。根据pcie设备的厂家ID和设备ID初始化设备,并且返回访问设备指针描述符,pci_dev指针指向我们需要访问的设备。

                                                                                                        图 1

清理PCIe设备函数:pci_cleanup(myaccess);

读写配置空间函数如下:

                                                                                                    图 2

到目前为止程序能够顺利访问PCIe 4k配置空间,写配置空间时必须在root用户下,接下来实现访问bar空间,bar空间访问分为两部分,得到大小和映射。获取bar空间大小先给bar0寄存器全写1,查看位的变化情况得到大小。

获取bar空间大小函数:

                                                                                             图 3

映射bar空间,程序中获取到的地址已经是cpu存储域的地址 ,而不是PCIe域地址。/dev/mem接口是物理地址的全部镜像,mmap可以通过该接口将指定的物理地址映射到虚拟地址空间上,这样进程就可以访问实际存在的物理地址空间。映射函数如下,phy_addr参数待映射的物理地址,size是待映射的物理地址大小。

                                                                                            图 4

程序退出时应该释放映射的内存和关闭/dev/mem设备节点。

munmap(vir_addr,size);

close(pcieVirAddr_fd);

驱动程序中编写PCIe驱动程序,所有的思路与应用程序一致,只是用到的函数接口和类型不一样。但是我建议将PCIe驱动写入到内核中。

驱动程序中这里只列举出核心代码,详细代码与Makefile请下载源码阅读。

                                                                                           图 5 得到设备指针

                                                                                    图 6 映射BAR空间

如果仅仅是调试为了方便,那么可以使用系统的lspci指令访问配置空间。

源码下载地址:https://download.csdn.net/download/qq_21792169/11044860 (密码:linux)

猜你喜欢

转载自blog.csdn.net/u014426028/article/details/109544492