git: 673cb5265a2d - main - pci: Add a hierarchy-wide MPS limit
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Fri, 07 Aug 2026 05:50:12 UTC
The branch main has been updated by kbowling:
URL: https://cgit.FreeBSD.org/src/commit/?id=673cb5265a2df2228982fc220f4e7ea62ab765b2
commit 673cb5265a2df2228982fc220f4e7ea62ab765b2
Author: Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-06 06:41:41 +0000
Commit: Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-08-07 05:49:09 +0000
pci: Add a hierarchy-wide MPS limit
Add a boot-time ceiling for MPS reconciliation. Apply it only while an
entire cold-enumerated link can be configured consistently, and leave
an established active path unchanged.
MFC after: 2 weeks
---
share/man/man4/pci.4 | 15 +++++++++++++++
sys/dev/pci/pci.c | 51 ++++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 59 insertions(+), 7 deletions(-)
diff --git a/share/man/man4/pci.4 b/share/man/man4/pci.4
index 4ff03740b2c5..dc4bf4387057 100644
--- a/share/man/man4/pci.4
+++ b/share/man/man4/pci.4
@@ -617,6 +617,21 @@ The maximum amount of memory permitted for the configuration parameters
used when creating Virtual Functions via SR-IOV.
This tunable can also be changed at runtime via
.Xr sysctl 8 .
+.It Va hw.pci.mps_limit Pq Defaults to 0
+Limit the MPS selected while reconciling a PCI-express hierarchy during
+cold enumeration.
+Valid non-zero values are powers of two from 128 through 4096 bytes.
+A value of zero imposes no additional limit: reconciliation starts with the
+MPS established by firmware and lowers it only when required by the
+enumerated hierarchy.
+The tunable is a ceiling and never raises MPS above the firmware-established
+value.
+Other values are ignored with a warning.
+The limit has no effect when
+.Va hw.pci.enable_mps_tune
+is disabled.
+The limit does not override the established path MPS when a device is added
+to an active bus.
.It Va hw.pci.realloc_bars Pq Defaults to 0
Attempt to allocate a new resource range during the initial device scan
for any memory or I/O port resources with firmware-assigned ranges that
diff --git a/sys/dev/pci/pci.c b/sys/dev/pci/pci.c
index dd771c2e8fd3..02642ad40239 100644
--- a/sys/dev/pci/pci.c
+++ b/sys/dev/pci/pci.c
@@ -437,6 +437,11 @@ SYSCTL_BOOL(_hw_pci, OID_AUTO, enable_mps_tune, CTLFLAG_RWTUN,
&pci_enable_mps_tune, 1,
"Enable tuning of MPS(maximum payload size)." );
+static int pci_mps_limit;
+SYSCTL_INT(_hw_pci, OID_AUTO, mps_limit, CTLFLAG_RDTUN, &pci_mps_limit, 0,
+ "Limit PCIe MPS to this many bytes (power of two from 128 to 4096)");
+static bool pci_mps_limit_warned;
+
static bool pci_intx_reroute = true;
SYSCTL_BOOL(_hw_pci, OID_AUTO, intx_reroute, CTLFLAG_RWTUN,
&pci_intx_reroute, 0, "Re-route INTx interrupts when scanning devices");
@@ -4434,6 +4439,25 @@ pcie_mps_bytes(uint16_t mps)
return (128 << (mps >> 5));
}
+static bool
+pcie_mps_limit_value(uint16_t *mps)
+{
+
+ if (pci_mps_limit == 0)
+ return (false);
+ if (pci_mps_limit < 128 || pci_mps_limit > 4096 ||
+ !powerof2(pci_mps_limit)) {
+ if (!pci_mps_limit_warned) {
+ printf("pci: invalid hw.pci.mps_limit=%d; ignoring\n",
+ pci_mps_limit);
+ pci_mps_limit_warned = true;
+ }
+ return (false);
+ }
+ *mps = (fls(pci_mps_limit) - 8) << 5;
+ return (true);
+}
+
/* Return the smallest configured MPS above dev, if the walk reaches a root. */
static bool
pcie_path_mps(device_t dev, uint16_t *mpsp)
@@ -4623,8 +4647,9 @@ pcie_reconcile_link_mps(device_t bus)
{
struct pci_devinfo *dinfo, *upinfo;
device_t child, limiting, pcib, *devlist;
- uint16_t mmps, mps, target, up_mmps, up_mps;
+ uint16_t cap_target, lmps, mmps, mps, target, up_mmps, up_mps;
int count, error, i;
+ bool limit_requested;
if (!pci_enable_mps_tune)
return;
@@ -4643,12 +4668,12 @@ pcie_reconcile_link_mps(device_t bus)
up_mps = pcie_read_config(pcib, PCIER_DEVICE_CTL, 2) &
PCIEM_CTL_MAX_PAYLOAD;
- target = up_mps;
+ cap_target = up_mps;
limiting = NULL;
up_mmps = (pcie_read_config(pcib, PCIER_DEVICE_CAP, 2) &
PCIEM_CAP_MAX_PAYLOAD) << 5;
- if (target > up_mmps) {
- target = up_mmps;
+ if (cap_target > up_mmps) {
+ cap_target = up_mmps;
limiting = pcib;
}
/*
@@ -4665,11 +4690,15 @@ pcie_reconcile_link_mps(device_t bus)
continue;
mmps = (pcie_read_config(child, PCIER_DEVICE_CAP, 2) &
PCIEM_CAP_MAX_PAYLOAD) << 5;
- if (target > mmps) {
- target = mmps;
+ if (cap_target > mmps) {
+ cap_target = mmps;
limiting = child;
}
}
+ target = cap_target;
+ limit_requested = pcie_mps_limit_value(&lmps) && up_mps > lmps;
+ if (limit_requested && target > lmps)
+ target = lmps;
/*
* Do not lower one link below a switch without also reconciling every
@@ -4678,7 +4707,15 @@ pcie_reconcile_link_mps(device_t bus)
*/
if (target < up_mps &&
upinfo->cfg.pcie.pcie_type != PCIEM_TYPE_ROOT_PORT) {
- pcie_mps_conflict(limiting, up_mps, target);
+ if (cap_target < up_mps)
+ pcie_mps_conflict(limiting, up_mps, cap_target);
+ if (limit_requested) {
+ device_printf(pcib,
+ "cannot apply hw.pci.mps_limit=%d below a switch "
+ "without retuning the shared ancestor hierarchy; "
+ "leaving path MPS %d unchanged\n",
+ pci_mps_limit, pcie_mps_bytes(up_mps));
+ }
pcie_mps_mark_link_unreconciled(devlist, count, up_mps,
up_mps > up_mmps);
/* Keep compatible functions at the established path MPS. */