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. */

Reply via email to