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

Kevin Bowling <[email protected]>
Newsgroups gmane.os.freebsd.devel.cvs.src
Message-ID <6a757214.37566.47a2ab44__49448.9038059086$1786081836$gmane$org@gitrepo.freebsd.org>
The branch main has been updated by kbowling:

URL: https://cgit.FreeBSD.org/src/commit/?id=673cb5265a2df2228982fc220f4e7ea62ab765b2

commit 673cb5265a2df2228982fc220f4e7ea62ab765b2
Author:     Kevin Bowling <[email protected]>
AuthorDate: 2026-08-06 06:41:41 +0000
Commit:     Kevin Bowling <[email protected]>
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. */
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.