本文基于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
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 体系结构导读