aboutsummaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
Diffstat (limited to 'sys')
-rw-r--r--sys/dev/pci/pci.c293
-rw-r--r--sys/dev/pci/pci_private.h2
-rw-r--r--sys/dev/pci/pcivar.h4
-rw-r--r--sys/powerpc/ofw/ofw_pcibus.c1
4 files changed, 276 insertions, 24 deletions
diff --git a/sys/dev/pci/pci.c b/sys/dev/pci/pci.c
index 95b8e831e743..dd771c2e8fd3 100644
--- a/sys/dev/pci/pci.c
+++ b/sys/dev/pci/pci.c
@@ -4297,6 +4297,7 @@ pci_add_children(device_t dev, int domain, int busno)
for (f = first_func; f <= pcifunchigh; f++)
pci_identify_function(pcib, dev, domain, busno, s, f);
}
+ pcie_reconcile_link_mps(dev);
#undef REG
}
@@ -4426,16 +4427,154 @@ pci_create_iov_child_method(device_t bus, device_t pf, uint16_t rid,
}
#endif
+static int
+pcie_mps_bytes(uint16_t mps)
+{
+
+ return (128 << (mps >> 5));
+}
+
+/* Return the smallest configured MPS above dev, if the walk reaches a root. */
+static bool
+pcie_path_mps(device_t dev, uint16_t *mpsp)
+{
+ struct pci_devinfo *dinfo;
+ device_t bus, pcib, start;
+ uint16_t mps;
+ bool found;
+
+ start = dev;
+ found = false;
+ for (;;) {
+ bus = device_get_parent(dev);
+ if (bus == NULL)
+ break;
+ pcib = device_get_parent(bus);
+ if (pcib == NULL || !is_pci_device(pcib))
+ break;
+ dinfo = device_get_ivars(pcib);
+ if (dinfo->cfg.pcie.pcie_location != 0) {
+ mps = pcie_read_config(pcib, PCIER_DEVICE_CTL, 2) &
+ PCIEM_CTL_MAX_PAYLOAD;
+ if (!found || mps < *mpsp)
+ *mpsp = mps;
+ found = true;
+ if (dinfo->cfg.pcie.pcie_type == PCIEM_TYPE_ROOT_PORT)
+ return (true);
+ }
+ dev = pcib;
+ }
+ if (found && bootverbose)
+ device_printf(start,
+ "PCIe MPS path walk did not reach a Root Port\n");
+ return (false);
+}
+
+static bool
+pcie_mps_first_warning(device_t dev)
+{
+ struct pci_devinfo *dinfo;
+
+ dinfo = device_get_ivars(dev);
+ if ((dinfo->cfg.flags & PCICFG_MPS_WARNED) != 0)
+ return (false);
+ dinfo->cfg.flags |= PCICFG_MPS_WARNED;
+ return (true);
+}
+
+static void
+pcie_mps_conflict(device_t dev, uint16_t path_mps, uint16_t max_mps)
+{
+
+ if (!pcie_mps_first_warning(dev))
+ return;
+ device_printf(dev,
+ "maximum supported MPS %d is below configured path MPS %d; "
+ "cannot safely retune the shared ancestor hierarchy\n",
+ pcie_mps_bytes(max_mps), pcie_mps_bytes(path_mps));
+}
+
+static void
+pcie_mps_active_conflict(device_t dev, uint16_t path_mps,
+ uint16_t device_mps)
+{
+
+ if (!pcie_mps_first_warning(dev))
+ return;
+ device_printf(dev,
+ "configured MPS %d does not match path MPS %d while bus "
+ "mastering is enabled; leaving device unchanged\n",
+ pcie_mps_bytes(device_mps), pcie_mps_bytes(path_mps));
+}
+
+static void
+pcie_mps_mark_unreconciled(device_t dev)
+{
+ struct pci_devinfo *dinfo;
+
+ dinfo = device_get_ivars(dev);
+ dinfo->cfg.flags |= PCICFG_MPS_UNRECONCILED;
+}
+
+static void
+pcie_mps_unreconciled(device_t dev, uint16_t path_mps, uint16_t max_mps)
+{
+
+ pcie_mps_conflict(dev, path_mps, max_mps);
+ pcie_mps_mark_unreconciled(dev);
+}
+
+static void
+pcie_mps_active_unreconciled(device_t dev, uint16_t path_mps,
+ uint16_t device_mps)
+{
+
+ pcie_mps_active_conflict(dev, path_mps, device_mps);
+ pcie_mps_mark_unreconciled(dev);
+}
+
+static void
+pcie_mps_mark_link_unreconciled(device_t *devlist, int count,
+ uint16_t path_mps,
+ bool all)
+{
+ struct pci_devinfo *dinfo;
+ device_t child;
+ uint16_t mmps;
+ int i;
+
+ for (i = 0; i < count; i++) {
+ child = devlist[i];
+ dinfo = device_get_ivars(child);
+ if ((dinfo->cfg.flags & (PCICFG_VF |
+ PCICFG_MPS_UNRECONCILED)) != 0 ||
+ dinfo->cfg.pcie.pcie_location == 0)
+ continue;
+ if (all) {
+ pcie_mps_mark_unreconciled(child);
+ continue;
+ }
+ mmps = (pcie_read_config(child, PCIER_DEVICE_CAP, 2) &
+ PCIEM_CAP_MAX_PAYLOAD) << 5;
+ if (mmps < path_mps)
+ pcie_mps_unreconciled(child, path_mps, mmps);
+ }
+}
+
/*
- * For PCIe device set Max_Payload_Size to match PCIe root's.
+ * Tune a function discovered by rescan or hot-add against the established
+ * path. Never change a shared upstream port here: doing so requires
+ * quiescing every driver and draining all outstanding transactions in the
+ * hierarchy. Cold enumeration is reconciled by pcie_reconcile_link_mps().
*/
static void
pcie_setup_mps(device_t dev)
{
- struct pci_devinfo *dinfo = device_get_ivars(dev);
- device_t root;
- uint16_t rmps, mmps, mps;
+ struct pci_devinfo *dinfo;
+ device_t bus;
+ uint16_t mmps, mps, path_mps;
+ dinfo = device_get_ivars(dev);
/*
* PCIe r4.0, sec 9.3.5.4 defines the VF MPS and MRRS fields as
* Reserved and Preserved, with the PF settings applying to the VF.
@@ -4445,31 +4584,139 @@ pcie_setup_mps(device_t dev)
return;
if (dinfo->cfg.pcie.pcie_location == 0)
return;
- root = pci_find_pcie_root_port(dev);
- if (root == NULL)
+
+ /* Cold enumeration is reconciled one complete link at a time. */
+ bus = device_get_parent(dev);
+ if (!device_is_attached(bus))
+ return;
+ path_mps = 0;
+ if (!pcie_path_mps(dev, &path_mps))
return;
- /* Check whether the MPS is already configured. */
- rmps = pcie_read_config(root, PCIER_DEVICE_CTL, 2) &
- PCIEM_CTL_MAX_PAYLOAD;
+
+ mmps = (pcie_read_config(dev, PCIER_DEVICE_CAP, 2) &
+ PCIEM_CAP_MAX_PAYLOAD) << 5;
+ if (path_mps > mmps) {
+ pcie_mps_unreconciled(dev, path_mps, mmps);
+ return;
+ }
mps = pcie_read_config(dev, PCIER_DEVICE_CTL, 2) &
PCIEM_CTL_MAX_PAYLOAD;
- if (mps == rmps)
+ if (mps == path_mps)
return;
- /* Check whether the device is capable of the root's MPS. */
- mmps = (pcie_read_config(dev, PCIER_DEVICE_CAP, 2) &
+ if ((pci_read_config(dev, PCIR_COMMAND, 2) &
+ PCIM_CMD_BUSMASTEREN) != 0) {
+ pcie_mps_active_unreconciled(dev, path_mps, mps);
+ return;
+ }
+ pcie_adjust_config(dev, PCIER_DEVICE_CTL, PCIEM_CTL_MAX_PAYLOAD,
+ path_mps, 2);
+}
+
+/*
+ * Reconcile a newly enumerated link before attaching any child drivers. A
+ * Root Port may be lowered because its complete downstream hierarchy is still
+ * idle. A late reduction below a switch is not propagated through ancestors,
+ * since sibling subtrees may already be active.
+ */
+void
+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;
+ int count, error, i;
+
+ if (!pci_enable_mps_tune)
+ return;
+ /* Shared-path tuning is only safe before this bus attaches children. */
+ if (device_is_attached(bus))
+ return;
+ pcib = device_get_parent(bus);
+ if (!is_pci_device(pcib))
+ return;
+ upinfo = device_get_ivars(pcib);
+ if (upinfo->cfg.pcie.pcie_location == 0)
+ return;
+ error = device_get_children(bus, &devlist, &count);
+ if (error != 0)
+ return;
+
+ up_mps = pcie_read_config(pcib, PCIER_DEVICE_CTL, 2) &
+ PCIEM_CTL_MAX_PAYLOAD;
+ target = up_mps;
+ limiting = NULL;
+ up_mmps = (pcie_read_config(pcib, PCIER_DEVICE_CAP, 2) &
PCIEM_CAP_MAX_PAYLOAD) << 5;
- if (rmps > mmps) {
- /*
- * The device is unable to handle root's MPS. Limit root.
- * XXX: We should traverse through all the tree, applying
- * it to all the devices.
- */
- pcie_adjust_config(root, PCIER_DEVICE_CTL,
- PCIEM_CTL_MAX_PAYLOAD, mmps, 2);
- } else {
- pcie_adjust_config(dev, PCIER_DEVICE_CTL,
- PCIEM_CTL_MAX_PAYLOAD, rmps, 2);
+ if (target > up_mmps) {
+ target = up_mmps;
+ limiting = pcib;
+ }
+ /*
+ * Firmware may leave Bus Master Enable set after handoff. Since no
+ * child driver has attached during this cold pass, it is not a proxy
+ * for a live FreeBSD consumer.
+ */
+ for (i = 0; i < count; i++) {
+ child = devlist[i];
+ dinfo = device_get_ivars(child);
+ if ((dinfo->cfg.flags & (PCICFG_VF |
+ PCICFG_MPS_UNRECONCILED)) != 0 ||
+ dinfo->cfg.pcie.pcie_location == 0)
+ continue;
+ mmps = (pcie_read_config(child, PCIER_DEVICE_CAP, 2) &
+ PCIEM_CAP_MAX_PAYLOAD) << 5;
+ if (target > mmps) {
+ target = mmps;
+ limiting = child;
+ }
}
+
+ /*
+ * Do not lower one link below a switch without also reconciling every
+ * ancestor and sibling subtree. Recursive newbus attachment may already
+ * have made another subtree live, so leave the established path intact.
+ */
+ if (target < up_mps &&
+ upinfo->cfg.pcie.pcie_type != PCIEM_TYPE_ROOT_PORT) {
+ pcie_mps_conflict(limiting, up_mps, target);
+ pcie_mps_mark_link_unreconciled(devlist, count, up_mps,
+ up_mps > up_mmps);
+ /* Keep compatible functions at the established path MPS. */
+ target = up_mps;
+ }
+ /* Lower downstream producers before lowering the shared Root Port. */
+ for (i = 0; i < count; i++) {
+ child = devlist[i];
+ dinfo = device_get_ivars(child);
+ if ((dinfo->cfg.flags & (PCICFG_VF |
+ PCICFG_MPS_UNRECONCILED)) != 0 ||
+ dinfo->cfg.pcie.pcie_location == 0)
+ continue;
+ mps = pcie_read_config(child, PCIER_DEVICE_CTL, 2) &
+ PCIEM_CTL_MAX_PAYLOAD;
+ if (mps > target)
+ pcie_adjust_config(child, PCIER_DEVICE_CTL,
+ PCIEM_CTL_MAX_PAYLOAD, target, 2);
+ }
+ if (up_mps > target)
+ pcie_adjust_config(pcib, PCIER_DEVICE_CTL,
+ PCIEM_CTL_MAX_PAYLOAD, target, 2);
+
+ /* Raise idle children only after the upstream port is configured. */
+ for (i = 0; i < count; i++) {
+ child = devlist[i];
+ dinfo = device_get_ivars(child);
+ if ((dinfo->cfg.flags & (PCICFG_VF |
+ PCICFG_MPS_UNRECONCILED)) != 0 ||
+ dinfo->cfg.pcie.pcie_location == 0)
+ continue;
+ mps = pcie_read_config(child, PCIER_DEVICE_CTL, 2) &
+ PCIEM_CTL_MAX_PAYLOAD;
+ if (mps < target)
+ pcie_adjust_config(child, PCIER_DEVICE_CTL,
+ PCIEM_CTL_MAX_PAYLOAD, target, 2);
+ }
+ free(devlist, M_TEMP);
}
static void
diff --git a/sys/dev/pci/pci_private.h b/sys/dev/pci/pci_private.h
index da8f9c02088e..8a63af046aae 100644
--- a/sys/dev/pci/pci_private.h
+++ b/sys/dev/pci/pci_private.h
@@ -116,6 +116,8 @@ pci_create_iov_child_t pci_create_iov_child_method;
void pci_add_children(device_t dev, int domain, int busno);
void pci_add_child(device_t bus, struct pci_devinfo *dinfo);
+/* Call after cold enumeration and before attaching the bus's children. */
+void pcie_reconcile_link_mps(device_t bus);
device_t pci_add_iov_child(device_t bus, device_t pf, uint16_t rid,
uint16_t vid, uint16_t did);
void pci_add_resources(device_t bus, device_t dev, int force,
diff --git a/sys/dev/pci/pcivar.h b/sys/dev/pci/pcivar.h
index bcd4d2d35b54..27bac22c7896 100644
--- a/sys/dev/pci/pcivar.h
+++ b/sys/dev/pci/pcivar.h
@@ -171,7 +171,9 @@ struct pcicfg_ea {
STAILQ_HEAD(, pci_ea_entry) ea_entries; /* EA entries */
};
-#define PCICFG_VF 0x0001 /* Device is an SR-IOV Virtual Function */
+#define PCICFG_VF 0x0001 /* Device is an SR-IOV Virtual Function */
+#define PCICFG_MPS_WARNED 0x0002 /* MPS conflict already reported */
+#define PCICFG_MPS_UNRECONCILED 0x0004 /* MPS conflict left unchanged */
/* config header information common to all header types */
typedef struct pcicfg {
diff --git a/sys/powerpc/ofw/ofw_pcibus.c b/sys/powerpc/ofw/ofw_pcibus.c
index ff0fe39592e2..22a1634a0389 100644
--- a/sys/powerpc/ofw/ofw_pcibus.c
+++ b/sys/powerpc/ofw/ofw_pcibus.c
@@ -145,6 +145,7 @@ ofw_pcibus_attach(device_t dev)
if (!ofw_devices_only)
ofw_pcibus_enum_bus(dev, domain, busno);
+ pcie_reconcile_link_mps(dev);
bus_attach_children(dev);
return (0);
}