PCIe学习笔记之Max payload size

    科技2024-05-29  119

    本文基于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 bytes Interrupt: pin ? routed to IRQ 48 Bus: primary=00, secondary=1b, subordinate=1b, sec-latency=0 I/O behind bridge: 00007000-00007fff Memory behind bridge: fd100000-fd1fffff Prefetchable memory behind bridge: 00000000e7700000-00000000e77fffff Secondary 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 Port Capabilities: [48] Power Management version 3 Flags: 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 00 DevCap: MaxPayload 128 bytes, PhantFunc 0 ExtTag- 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 size000128B001256B010512B0111024B1002048B1014096B

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

    setpci -s 00:18.0 CAP_EXP+08.W=0x2020 [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 00 DevCap: MaxPayload 256 bytes, PhantFunc 0 ExtTag- 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 体系结构导读

    Processed: 0.011, SQL: 8