1.概述
MPS(Max Payload Size)和MRRS(Max Read Request Size)共同影响PCIe总线的传输效率。如果MPS和MRRS设置的过小,传输相同长度的数据,需要更多的TLP报文,导致PCIe总线传输效率降低,如果MPS和MRRS设置的过大,超出系统中某些设备的允许值,导致这些设备无法处理TLP报文,然后上报Malformed TLP错误。因此合理的设置MPS和MRRS非常重要,内核提供了5种策略,可以根据不同的场景进行选择。MPS和MRRS的意义参考PCIe总线-MPS MRRS RCB参数介绍(四)。
2.策略
2.1.策略定义
内核定义的5种配置MPS和MRRS的策略如下所示。
[include/linux/pci.h]
enum pcie_bus_config_types {
PCIE_BUS_TUNE_OFF, /* Don't touch MPS at all */
PCIE_BUS_DEFAULT, /* Ensure MPS matches upstream bridge */
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和MRRS,内核不会修改。 - PCIE_BUS_DEFAULT
内核会修改设备的MPS,使设备和设备上游桥设置的MPS保持一致(如果上游桥为Root Port,且设置的MPS大于设备支持的最大MPS,则将设备支持的最大MPS设置到Root Port中)。MRRS使用设备硬件的默认值,内核不会修改。 - PCIE_BUS_SAFE
使用总线上所有设备支持的最大MPS中最小的MPS。MRRS使用设备硬件的默认值,内核不会修改。 - PCIE_BUS_PERFORMANCE
比较设备上游桥设置的MPS和设备支持的最大MPS,使用较小的MPS(如果上游桥为Root Port,则将MPS设置为桥支持的最大MPS)。MRRS和MPS设置相同。 - PCIE_BUS_PEER2PEER
所有设备的MPS都设置为128。MRRS使用设备硬件的默认值,内核不会修改。
2.2.策略配置
内核中定义了pcie_bus_config变量,用于保存MPS和MRRS的设置策略。如下所示,pcie_bus_config的值由配置选项决定,配置选项有两种方式决定,一个是内核menuconfig,另一种是通过command-line参数传入。
[drivers/pci/pci.c]
/* PCIe MPS/MRRS strategy; can be overridden by kernel command-line param */
#ifdef CONFIG_PCIE_BUS_TUNE_OFF
enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_TUNE_OFF;
#elif defined CONFIG_PCIE_BUS_SAFE
enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_SAFE;
#elif defined CONFIG_PCIE_BUS_PERFORMANCE
enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_PERFORMANCE;
#elif defined CONFIG_PCIE_BUS_PEER2PEER
enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_PEER2PEER;
#else
enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_DEFAULT;
#endif
如下图所示,MPS和MRRS的设置策略,内核menuconfig配置选项如下图所示。
如下图所示,内核MPS和MRRS的menuconfig选项需要先使能CONFIG_EXPERT
选项,否则不会出现在menuconfig选项中。
内核MPS和MRRS策略的command-line参数如下所示:
pci=pcie_bus_tune_off
pci=pcie_bus_safe
pci=pcie_bus_perf
pci=pcie_bus_peer2peer
3.设置流程
如下图所示,内核中设置MPS/MRRS分为两步,第一步是在扫描单个设备的时候,先读取设备的配置空间,获取设备支持的最大MPS,然后调用pci_configure_mps
函数设置设备的MPS,第二步是在内核扫描完总线上所有PCIe设备之后,根据策略,遍历总线,调用pcie_bus_configure_settings
设置设备的MPS和MRRS。
3.1.第一步
设备支持的最大MPS通过配置空间的Device Capabilities Register获取,并保存到pci_dev
的pcie_mpss
成员中。pci_configure_mps
函数的主要作用是实现PCIE_BUS_TUNE_OFF
策略,将设备和设备上游桥的MPS设置相同,具体的流程如下:
- 当设备是RC下面接的内部EP(
PCI_EXP_TYPE_RC_END
)时。MPS/MRRS的配置策略为PCIE_BUS_PEER2PEER
,则MPS固定设置为128,如果是其他策略,则MPS设置为设备支持的最大值。 - 获取当前设备和设备上游桥设置的MPS,当两者的MPS相等时,则直接返回,不设置。
- 当MPS/MRRS的配置策略为
PCIE_BUS_TUNE_OFF
、PCIE_BUS_PEER2PEER
、PCIE_BUS_SAFE
和PCIE_BUS_PERFORMANCE
时直接返回,等待整个PCIe总线枚举完成后再进行设置(PCIE_BUS_TUNE_OFF
除外)。 - 当MPS/MRRS的配置策略为
PCIE_BUS_DEFAULT
,如果设备支持的最大MPS小于设备上游桥设置的MPS,且设备上游桥为Root Port(PCI_EXP_TYPE_ROOT_PORT
),则将设备上游桥的MPS设置为设备支持的最大MPS。 - 将设备上游桥的MPS设置的设备当中。
[drivers/pci/probe.c]
static void pci_configure_mps(struct pci_dev *dev)
{
......
/* 1. PCI_EXP_TYPE_RC_END类型的设备,PCIE_BUS_PEER2PEER策略固定设置为128,
* 其他策略固定设置为支持的最大值
*/
if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_END) {
if (pcie_bus_config == PCIE_BUS_PEER2PEER)
mps = 128;
else
mps = 128 << dev->pcie_mpss;
rc = pcie_set_mps(dev, mps);
......
}
/* 2. 当设备和设备上游桥设置的MPS相等时,则直接返回 */
mps = pcie_get_mps(dev);
p_mps = pcie_get_mps(bridge);
if (mps == p_mps)
return;
/* 3. 非PCIE_BUS_DEFAULT策略直接返回,不设置 */
if (pcie_bus_config == PCIE_BUS_TUNE_OFF) {
......
return;
}
if (pcie_bus_config != PCIE_BUS_DEFAULT)
return;
/* 4. 修正Root Port的MPS */
mpss = 128 << dev->pcie_mpss;
if (mpss < p_mps && pci_pcie_type(bridge) == PCI_EXP_TYPE_ROOT_PORT) {
pcie_set_mps(bridge, mpss);
......
p_mps = pcie_get_mps(bridge);
}
/* 5. 将设备上游桥的MPS设置的设备当中 */
rc = pcie_set_mps(dev, p_mps);
......
}
3.2.第二步
当整个PCIe总线枚举完成之后,内核会遍历Host bridge子总线,调用pcie_bus_configure_settings
函数设置MPS和MRRS。对于PCIE_BUS_TUNE_OFF
和PCIE_BUS_DEFAULT
策略,则直接跳过,不设置。
pcie_bus_configure_settings
的执行流程如下所示:
- 当MPS/MRRS的配置策略为
PCIE_BUS_PEER2PEER
,则MPS固定设置为128。 - 当MPS/MRRS的配置策略为
PCIE_BUS_SAFE
,如果设备是支持热插拔的桥,则MPS为128,否则设置为这条子总线上设备支持的最大MPS中最小的一个(子总线从该设备上游桥开始)。 - 设置当前设备和其下游设备的MPS、MRRS。
[drivers/pci/probe.c]
void pcie_bus_configure_settings(struct pci_bus *bus)
{
u8 smpss = 0;
......
/* 1. MPS固定为128 */
if (pcie_bus_config == PCIE_BUS_PEER2PEER)
smpss = 0;
/* 2. 如果设备是支持热插拔的桥,则MPS为128,否则设置为这条子总线上
* 设备支持的最大MPS中最小的一个
*/
if (pcie_bus_config == PCIE_BUS_SAFE) {
smpss = bus->self->pcie_mpss;
pcie_find_smpss(bus->self, &smpss);
pci_walk_bus(bus, pcie_find_smpss, &smpss);
}
/* 3. 设置当前设备和其下游设备的MPS、MRRS */
pcie_bus_configure_set(bus->self, &smpss);
pci_walk_bus(bus, pcie_bus_configure_set, &smpss);
}
pcie_bus_configure_set
函数如下所示,对于MPS/MRRS的配置策略为PCIE_BUS_TUNE_OFF
和PCIE_BUS_DEFAULT
,直接返回,不设置MPS和MRRS,而对于其他策略,调用pcie_write_mps
设置MPS,调用pcie_write_mrrs
设置MRRS。
[drivers/pci/probe.c]
static int pcie_bus_configure_set(struct pci_dev *dev, void *data)
{
......
if (pcie_bus_config == PCIE_BUS_TUNE_OFF ||
pcie_bus_config == PCIE_BUS_DEFAULT)
return 0;
mps = 128 << *(u8 *)data;
orig_mps = pcie_get_mps(dev);
pcie_write_mps(dev, mps); // 设置MPS
pcie_write_mrrs(dev); // 设置MRRS
......
}
pcie_write_mps
函数如下所示,对于MPS/MRRS的配置策略为PCIE_BUS_PERFORMANCE
,如果设备是Root Port(PCI_EXP_TYPE_ROOT_PORT
)则MPS为其支持的最大MPS,如果是其他设备,则取设备和设备上游桥支持的最大MPS中最小的一个。最后调用pcie_set_mps
函数将MPS设置到配置空间的Device Control Register中。
[drivers/pci/probe.c]
static void pcie_write_mps(struct pci_dev *dev, int mps)
{
......
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)
mps = min(mps, pcie_get_mps(dev->bus->self));
}
rc = pcie_set_mps(dev, mps);
......
}
pcie_write_mrrs
函数如下所示,只有MPS/MRRS的设置策略为PCIE_BUS_PERFORMANCE
才会设置MRRS,其他策略使用设备默认的MRRS。在性能模式下,MRRS应该设置为设备支持的最大值,但不能超过设备设置的MPS,因此先读取设备的MPS,然后将读取的值作为MRRS值设置到配置空间的Device Control Register中,如果写入的MRRS值超过了设备支持的最大值,则减半继续设置。
[drivers/pci/probe.c]
static void pcie_write_mrrs(struct pci_dev *dev)
{
// 只有PCIE_BUS_PERFORMANCE策略才会配置MRRS
if (pcie_bus_config != PCIE_BUS_PERFORMANCE)
return;
mrrs = pcie_get_mps(dev); // 获取设备配置的MPS
// 设置MRRS,和设备的MPS相等
while (mrrs != pcie_get_readrq(dev) && mrrs >= 128) {
rc = pcie_set_readrq(dev, mrrs);
if (!rc)
break;
mrrs /= 2;
}
}
4.设置接口
除了内核会根据选择的策略设置MPS和MRRS之外,驱动也可以调用内核提供的接口主动设置MPS和MRRS。内核定义的接口如下所示:
[include/linux/pci.h]
int pcie_get_readrq(struct pci_dev *dev); // 获取设置的MRRS
int pcie_set_readrq(struct pci_dev *dev, int rq); // 设置MRRS
int pcie_get_mps(struct pci_dev *dev); // 获取设置的MPS
int pcie_set_mps(struct pci_dev *dev, int mps); // 设置MPS
参考资料
- PCI Express® Base Specification Revision 5.0 Version 1.0
- Linux Kernel 5.10