当前位置: 代码迷 >> 综合 >> PCIe学习笔记之Max payload size
  详细解决方案

PCIe学习笔记之Max payload size

热度:46   发布时间:2024-02-25 16:35:40.0

本文基于linux 5.7.0, 平台是arm64
在平时设备的使用过程中,有可能遇到过数据通信错误(malformed tlp), 或者网卡/磁盘在进行数据读写时性能没有达到预期,这些都可能和pcie 的max payload size配置有关系。

1. 概述

1.1 什么是max payload size

我们都知道,PCIe设备是以TLP的形式发送报文的,而max payload size(简称mps)决定了pcie设备实际使用的tlp能够传输的最大字节数。mps的大小是由PCIe链路两端的设备协商决定的,PCIe设备发送TLP时,其最大payload不能超过mps的值。
mps定义在Device control register中。
Device Control register:
在这里插入图片描述
同样定义在该寄存器中还有一个Max read request size(简称mrrs)配置,它表示每一个读请求所能够读到的最大字节数。当PCIe设备发送memory读请求TLP时,该TLP所请求的数据的大小不能超过mrrs的值。

1.2 max payload size的作用

pice协议规定有数据的TLP包的传递规则是:按照指定DW长度单位传递数据,发送端的数据承载量不得超过“Device Control Register”中的“Max_Payload_Size”数值,接收端中,所接收到的数据量也不能超过接收端“Device Control Register”中的“Max_Payload_Size”数值。
所以mps主要的作用是:
(1) mps的大小影响pcie设备的传输效率。 对于比较大的数据量,如果mps设置比较小,那么数据只能被分割成多个TLP进行发送,势必会影响pcie链路带宽的利用率。但是mps的值也不是越大越好,一方面mps设置的越大,硬件处理数据包所需的内存和逻辑量也随之增加; 另一方面,mps的值是一个自协商的结果,当前市场上支持较大mps值的ep设备不常见,所以没有必要设置本端的mps值太大。

(2) 不合理的mps设置会导致数据通信时上报"Malformed TLP"错误。 RC设备在与EP设备对接时,对端的EP设备的MPS可能各不相同,需要RC端去适配EP端的mps, 如果EP设备接收的TLP length字段超过了它的mps配置,该设备就会认为该TLP非法。

1.3 mrrs 对mps的影响

mrrs和mps并没有直接的联系,但是有时候mrrs会对mps有影响。
举个例子:一个典型的pcie拓扑结构, 中间的红色数字mps的值。
假设发起一个memory tlp读请求操作:
在这里插入图片描述
根据上文的分析,这种情况下,RC和EP的通信会失败,因为他们的mps的值不相等。
但是如果设置各个节点的mrrs的值为128B,那么虽然RC设备的mps为256B,但是实际能够传输的大小却受到mrrs的影响,变成128B,这种情况下,实际上传输的TLP报文最终是128字节,可以满足各节点mps一致的条件,可以传输成功。

总结: mrrs的值不能设置的太小,一方面,如果mrrs设置太小,会影响实际TLP的mps。另一方面,如果我们设定MRRS为128B,我们读64KB数据时,就需要发送512(64KB/128B=512)次读请求,这样的话,就增加了很多额外的开销,对数据传输速率也是一种伤害,所以,为了提高特别是大Block Size data的传输效率,尽量把mrrs设的大一点,用更少的次数传递更多的数据。但是mrrs也不能设置过大,在PCIe链路中,我们不能一直发送TLP,而是需要接受端有足够的接受空间之后,才能继续发送TLP,buffer空间太大的话对于EP的设备而言是比较苛刻的,所以在实际的设计中,也会对mrrs的大小进行限制。

2. 设备MPS的查询和配置

2.1 使用lspci 查询mps


[root@localhost linux]# lspci  -s 00:18.0 -vv
00:18.0 PCI bridge: VMware PCI Express Root Port (rev 01) (prog-if 00 [Normal decode])Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx-Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-Latency: 0, Cache Line Size: 32 bytesInterrupt: pin ? routed to IRQ 48Bus: primary=00, secondary=1b, subordinate=1b, sec-latency=0I/O behind bridge: 00007000-00007fffMemory behind bridge: fd100000-fd1fffffPrefetchable memory behind bridge: 00000000e7700000-00000000e77fffffSecondary status: 66MHz- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- <SERR- <PERR-BridgeCtl: Parity- SERR- NoISA+ VGA- MAbort- >Reset- FastB2B-PriDiscTmr- SecDiscTmr- DiscTmrStat- DiscTmrSERREn-Capabilities: [40] Subsystem: VMware PCI Express Root PortCapabilities: [48] Power Management version 3Flags: PMEClk- DSI- D1- D2- AuxCurrent=0mA PME(D0+,D1-,D2-,D3hot+,D3cold+)Status: D0 NoSoftRst- PME-Enable- DSel=0 DScale=0 PME-Capabilities: [50] Express (v2) Root Port (Slot+), MSI 00DevCap:	MaxPayload 128 bytes, PhantFunc 0ExtTag- RBE-DevCtl:	Report errors: Correctable- Non-Fatal- Fatal- Unsupported-RlxdOrd- ExtTag- PhantFunc- AuxPwr- NoSnoop-MaxPayload 128 bytes, MaxReadReq 128 bytes

  

查出来mps的大小为128 byte, mrrs的大小为 128byte。

2.2 使用setpci进行设置

在之前的博客 【PCIe学习笔记之pcie结构和配置空间】介绍过怎么用lspci和setpci得到配置空间的值。

Device Control寄存器位于PCI express capability结构里,对应的capability id 为0x10(#define PCI_CAP_ID_EXP 0x10 /* PCI Express */)。

[root@localhost linux]# lspci -s 00:18.0 -xxx
00:18.0 PCI bridge: VMware PCI Express Root Port (rev 01)
00: ad 15 a0 07 07 00 10 00 01 00 04 06 08 00 81 00
10: 00 00 00 00 00 00 00 00 00 1b 1b 00 70 70 00 00
20: 10 fd 10 fd 71 e7 71 e7 00 00 00 00 00 00 00 00

   
>

查看lspci结果, 可以看出偏移0x20的位置对应的capability id为0x10, 所以device control register的偏移是0x20 + 0x8。
查看device control register的值:

[root@localhost linux]# setpci -s 00:18.0 0x28.W
1b00

     

或者使用CAP_EXP(对应的是pcie express capability的偏移)

[root@localhost linux]# setpci -s 00:18.0 CAP_EXP+08.W
0000

       

可以看出device control register的值为0, 该寄存器【14:12】是mrrs, [7:5]是mps, 所以mps和mrrs都是0, 对应的正好是128B。

bit[2:0] max payload size or max read request size
000 128B
001 256B
010 512B
011 1024B
100 2048B
101 4096B

设置mps和mrrs的大小为256B,即【14:12】=0x1, [7:5]=0x1

setpci -s 00:18.0 CAP_EXP+08.W=0x2020

         
  • 1
[root@localhost linux]# lspci -s 00:18.0 -vvv
00:18.0 PCI bridge: VMware PCI Express Root Port (rev 01) (prog-if 00 [Normal decode])Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- Capabilities: [50] Express (v2) Root Port (Slot+), MSI 00DevCap:	MaxPayload 256 bytes, PhantFunc 0ExtTag- RBE-DevCtl:	Report errors: Correctable- Non-Fatal- Fatal- Unsupported-RlxdOrd- ExtTag- PhantFunc- AuxPwr- NoSnoop-MaxPayload 256 bytes, MaxReadReq 256 bytes

         

mps和mrrs的值成功设置为256byte. (有时候设置会不生效,这个也是和硬件支持的能力相关联)

3. linux内核实现

3.1 mps的配置类型

在linux内核中,当前有四种配置mps的类型,定义在include/linux/pci.h中

enum pcie_bus_config_types {
            PCIE_BUS_TUNE_OFF,      /* Don't touch MPS at all */PCIE_BUS_SAFE,          /* Use largest MPS boot-time devices support */PCIE_BUS_PERFORMANCE,   /* Use MPS and MRRS for best performance */PCIE_BUS_PEER2PEER,     /* Set MPS = 128 for all devices */
};

PCIE_BUS_TUNE_OFF: 不对PCIe MPS(Max Payload Size)进行调整,而是使用BIOS配置好的默认值。
PCIE_BUS_SAFE: 将每个设备的MPS都设为root complex下所有设备支持的MPS中的最大值。 指的是设置最小的那个设备的mps为所有设备的mps。
PCIE_BUS_PERFORMANCE: 将设备的MPS设为其上级总线允许的最大MPS,同时将MRRS(Max Read Request Size)设为能支持的最大值(但不能大于设备或总线所支持的MPS值)
PCIE_BUS_PEER2PEER: 所有的设备的MPS都设置为128B,以确保支持所有设备之间的点对点DMA,同时也能保证热插入(hot-added)设备能够正常工作,但代价是可能会造成性能损失。

3.2 os更改mps的配置类型

内核在启动的cmdline中可以配置pci_bus_config,如:

pci=pcie_bus_perf

3.3 代码实现

结合代码分析linux中mps的配置, 以及看mps是怎么生效的:

void pcie_bus_configure_settings(struct pci_bus *bus)
{
    u8 smpss = 0;if (!bus->self)return;if (!pci_is_pcie(bus->self))return;/** FIXME - Peer to peer DMA is possible, though the endpoint would need* to be aware of the MPS of the destination. To work around this,* simply force the MPS of the entire system to the smallest possible.*/if (pcie_bus_config == PCIE_BUS_PEER2PEER)           -------  (1)smpss = 0;if (pcie_bus_config == PCIE_BUS_SAFE) {
    smpss = bus->self->pcie_mpss;pcie_find_smpss(bus->self, &smpss);            ------- (2)pci_walk_bus(bus, pcie_find_smpss, &smpss);         }pcie_bus_configure_set(bus->self, &smpss);              -------- (3)pci_walk_bus(bus, pcie_bus_configure_set, &smpss);
}

(1) 当pcie_bus_config是peer2peer时,设置smpss的值为0, 最终通过pcie_write_mps(dev, 128 << smpss)写到配置空间里,所以peer2peer的配置下,mps被固定128B。

(2) 当pcie_bus_config是safe时, pcie_find_smpss()会被调用,该函数的作用是寻找一个最小的mps。

static int pcie_find_smpss(struct pci_dev *dev, void *data)
{
    u8 *smpss = data;if (*smpss > dev->pcie_mpss)*smpss = dev->pcie_mpss;return 0;
}

(3) pcie_bus_configure_set会将mps设置写进配置空间, 会对pci_bus_perf做特殊处理,
在pci_bus_perf配置下: 会对每一个非根节点取本身和父节点的mps的较小值作为自己的mps

static void pcie_write_mps(struct pci_dev *dev, int mps)
{int rc;if (pcie_bus_config == PCIE_BUS_PERFORMANCE) {mps = 128 << dev->pcie_mpss;if (pci_pcie_type(dev) != PCI_EXP_TYPE_ROOT_PORT &&dev->bus->self)/** For "Performance", the assumption is made that* downstream communication will never be larger than* the MRRS.  So, the MPS only needs to be configured* for the upstream communication.  This being the case,* walk from the top down and set the MPS of the child* to that of the parent bus.** Configure the device MPS with the smaller of the* device MPSS or the bridge MPS (which is assumed to be* properly configured at this point to the largest* allowable MPS based on its parent bus).*/mps = min(mps, pcie_get_mps(dev->bus->self)); }rc = pcie_set_mps(dev, mps);if (rc)pci_err(dev, "Failed attempting to set the MPS\n");
}

同时pci_bus_perf配置下也会对pcie_write_mrrs()进行调用, 将MRRS设为各个节点能支持的最大值(但不能大于设备或总线所支持的MPS值).

总结

结合Document和代码分析,最终我们可以得出mps的配置规则如下图所示:
在这里插入图片描述
绿色对应的是pci_bus_no_tune, 什么都没改。
橙色的对应的是pci_bus_safe, 所有的设备的mps改为最小的那个设备的mps值,即所有的设备的mps设置为256
黄色的对应的pci_bus_perf, 除了根节点以外,比较自己设备的mps和父节点的mps, 选择较小的那一个设为自己的mps, 所以除了rc的mps, 其他节点的mps都设置为256, 此时,还需要更新各个节点mrrs的大小,保证mrrs的值不大于自身的mps的值,否则会出现1.3章节介绍的问题。
蓝色的对应的是pci_bus_peer2peer, 保证DMA的通信,所有设备的mps设为128.

参考资料

PCIe 体系结构导读

  相关解决方案