git: 673cb5265a2d - main - pci: Add a hierarchy-wide MPS limit

From: Kevin Bowling <kbowling_at_FreeBSD.org>
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. */