[RFC PATCH v2] PCI: Work around Pericom PI7C9X3G606GPC Port 4 BAR erratum

From: Nirmoy Das

Date: Thu Sep 17 2026 - 06:43:48 EST


When the Cross-Domain End-Point (CDEP) function is disabled on the
Pericom PI7C9X3G606GPC, Port 4 exposes neither a BAR resource nor an
MSI-X capability. It still checks memory requests against
BAR 0 + 0x7f000 and BAR 0 + 0x7f080, the MSI-X Table and PBA addresses.
With the internal BAR value at zero, reads reaching Port 4 at these
addresses receive Unsupported Request (UR) Completions.

The affected system has this topology:

0002:a1:00.0 PI7C9X3G606GPC upstream port [12d8:c008]
+-0002:a2:04.0 PI7C9X3G606GPC Port 4 [12d8:c008]
+-0002:a3:00.0 Micron 7450 PRO NVMe SSD [1344:51c3]

Diodes' workaround is to copy the upstream port's BAR 0 into Port 4.
This places the Table and PBA addresses within MMIO space already
allocated to the switch. Port 4 accepts the write but still returns
zero on config-space reads, so readback cannot verify the mirror.

Firmware can apply the workaround during boot, but Linux BAR sizing
reads zero from Port 4 and writes that zero back after probing.
Resource assignment can also move the upstream BAR 0, leaving the
firmware mirror stale.

Reapply the mirror after resource assignment and again after PCI
configuration state is restored during early resume. If the upstream
BAR is 64-bit, also copy BAR 1 and disable memory decoding while
updating both halves, unless mmio_always_on is set.

Assisted-by: Codex:GPT-5
Signed-off-by: Nirmoy Das <nirmoyd@xxxxxxxxxx>
---
Changes in v2:
- Explain the CDEP-disabled address check and how BAR sizing can undo
the firmware workaround.
- Clarify that Port 4 accepts BAR writes but still reads back as zero.
- Include the affected PCI topology in the commit message.

The unpatched boot log shows Linux moving the upstream BAR 0:

[ 6.440107] pci 0002:a1:00.0: BAR 0 [mem 0x10300000-0x1037ffff]
[ 6.445353] pci 0002:a1:00.0: BAR 0 [mem 0x10c00000-0x10c7ffff]: assigned

v1: https://lore.kernel.org/r/20260723161010.2638956-1-nirmoyd@xxxxxxxxxx

drivers/pci/quirks.c | 89 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 89 insertions(+)

diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index de9bbccda21fd1b..88ad85ecd7eb004 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -6272,6 +6272,95 @@ DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_PERICOM, 0x2608,
DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_PERICOM, 0x2608,
pci_fixup_pericom_acs_store_forward);

+#define PCI_DEVICE_ID_PERICOM_PI7C9X3G606GPC 0xc008
+
+/*
+ * With CDEP disabled, Port 4 exposes no BAR or MSI-X capability, but its
+ * internal MSI-X Table/PBA address check remains active. Mirror the
+ * upstream BAR 0 to keep these addresses in MMIO space assigned to the
+ * switch.
+ *
+ * Port 4 is device 4, function 0 below the upstream port. Its BAR 0
+ * accepts writes but reads as zero, so BAR sizing clears the mirror.
+ * Reapply it after resource assignment and during early resume, after
+ * PCI state restoration.
+ */
+static void pci_fixup_pericom_pi7c9x3g606gpc_bar0_mirror(struct pci_dev *pdev)
+{
+ struct pci_dev *upstream;
+ bool bar0_64, disable_mem;
+ u16 cmd = 0;
+ u32 bar = 0, bar1 = 0, upstream_bar = 0, upstream_bar1 = 0;
+
+ if (pci_pcie_type(pdev) != PCI_EXP_TYPE_DOWNSTREAM)
+ return;
+
+ if (PCI_SLOT(pdev->devfn) != 4 || PCI_FUNC(pdev->devfn))
+ return;
+
+ upstream = pci_upstream_bridge(pdev);
+ if (!upstream || upstream->vendor != PCI_VENDOR_ID_PERICOM ||
+ upstream->device != PCI_DEVICE_ID_PERICOM_PI7C9X3G606GPC ||
+ pci_pcie_type(upstream) != PCI_EXP_TYPE_UPSTREAM)
+ return;
+
+ pci_read_config_dword(upstream, PCI_BASE_ADDRESS_0, &upstream_bar);
+ if (upstream_bar & PCI_BASE_ADDRESS_SPACE_IO)
+ return;
+
+ bar0_64 = (upstream_bar & PCI_BASE_ADDRESS_MEM_TYPE_MASK) ==
+ PCI_BASE_ADDRESS_MEM_TYPE_64;
+ if (bar0_64)
+ pci_read_config_dword(upstream, PCI_BASE_ADDRESS_1,
+ &upstream_bar1);
+
+ if (!(upstream_bar & PCI_BASE_ADDRESS_MEM_MASK) &&
+ (!bar0_64 || !upstream_bar1)) {
+ pci_warn(pdev, "skipping PI7C9X3G606GPC BAR 0 mirror workaround because upstream BAR 0 is unassigned\n");
+ return;
+ }
+
+ pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &bar);
+ if (bar0_64) {
+ pci_read_config_dword(pdev, PCI_BASE_ADDRESS_1, &bar1);
+ if (bar == upstream_bar && bar1 == upstream_bar1)
+ return;
+ } else {
+ if (bar == upstream_bar)
+ return;
+ }
+
+ /*
+ * Disable memory decoding while updating a 64-bit BAR, unless
+ * mmio_always_on is set, as pci_std_update_resource() does.
+ */
+ disable_mem = bar0_64 && !pdev->mmio_always_on;
+ if (disable_mem) {
+ pci_read_config_word(pdev, PCI_COMMAND, &cmd);
+ pci_write_config_word(pdev, PCI_COMMAND,
+ cmd & ~PCI_COMMAND_MEMORY);
+ }
+
+ pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, upstream_bar);
+ if (bar0_64)
+ pci_write_config_dword(pdev, PCI_BASE_ADDRESS_1, upstream_bar1);
+ if (disable_mem)
+ pci_write_config_word(pdev, PCI_COMMAND, cmd);
+
+ if (bar0_64)
+ pci_info(pdev, "wrote upstream BAR 0/1 %#x/%#x to Port 4 BAR 0/1 for PI7C9X3G606GPC BAR 0 mirror workaround\n",
+ upstream_bar, upstream_bar1);
+ else
+ pci_info(pdev, "wrote upstream BAR 0 %#x to Port 4 BAR 0 for PI7C9X3G606GPC BAR 0 mirror workaround\n",
+ upstream_bar);
+}
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_PERICOM,
+ PCI_DEVICE_ID_PERICOM_PI7C9X3G606GPC,
+ pci_fixup_pericom_pi7c9x3g606gpc_bar0_mirror);
+DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_PERICOM,
+ PCI_DEVICE_ID_PERICOM_PI7C9X3G606GPC,
+ pci_fixup_pericom_pi7c9x3g606gpc_bar0_mirror);
+
static void nvidia_ion_ahci_fixup(struct pci_dev *pdev)
{
pdev->dev_flags |= PCI_DEV_FLAGS_HAS_MSI_MASKING;

base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
--
2.43.0