* [PATCH v3 0/2] pci: AMD: Add Versal2 CPM6 PCIe host controller support
@ 2026-08-03 14:44 Sai Krishna Musham
2026-08-03 14:44 ` [PATCH v3 1/2] dt-bindings: PCI: amd-mdb: Add CPM6 support Sai Krishna Musham
2026-08-03 14:44 ` [PATCH v3 2/2] PCI: amd-mdb: Add CPM6 host controller support Sai Krishna Musham
0 siblings, 2 replies; 5+ messages in thread
From: Sai Krishna Musham @ 2026-08-03 14:44 UTC (permalink / raw)
To: bhelgaas, lpieralisi, kw, mani, robh, krzk+dt, conor+dt, cassel
Cc: linux-pci, devicetree, linux-kernel, michal.simek,
bharat.kumar.gogada, thippeswamy.havalige, sai.krishna.musham,
pranav.sanwal
Add support for the AMD Versal2 CPM6 PCIe host controller to the
AMD MDB PCIe driver.
Sai Krishna Musham (2):
dt-bindings: PCI: amd-mdb: Add CPM6 support
PCI: amd-mdb: Add CPM6 host controller support
.../bindings/pci/amd,versal2-mdb-host.yaml | 45 +-
.../devicetree/bindings/pci/snps,dw-pcie.yaml | 2 +
drivers/pci/controller/dwc/pcie-amd-mdb.c | 413 +++++++++++++++---
3 files changed, 395 insertions(+), 65 deletions(-)
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
--
2.44.4
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 1/2] dt-bindings: PCI: amd-mdb: Add CPM6 support
2026-08-03 14:44 [PATCH v3 0/2] pci: AMD: Add Versal2 CPM6 PCIe host controller support Sai Krishna Musham
@ 2026-08-03 14:44 ` Sai Krishna Musham
2026-08-03 14:50 ` sashiko-bot
2026-08-03 14:44 ` [PATCH v3 2/2] PCI: amd-mdb: Add CPM6 host controller support Sai Krishna Musham
1 sibling, 1 reply; 5+ messages in thread
From: Sai Krishna Musham @ 2026-08-03 14:44 UTC (permalink / raw)
To: bhelgaas, lpieralisi, kw, mani, robh, krzk+dt, conor+dt, cassel
Cc: linux-pci, devicetree, linux-kernel, michal.simek,
bharat.kumar.gogada, thippeswamy.havalige, sai.krishna.musham,
pranav.sanwal
The AMD CPM6 PCIe controller is based on the Synopsys DesignWare PCIe IP.
Add "intr" to snps,dw-pcie.yaml vendor-specific reg-names for the
per-controller interrupt register region used by CPM6.
Update amd,versal2-mdb-host.yaml with separate register definitions:
- MDB5: 4 regions (slcr, config, dbi, atu)
- CPM6: 5 regions (slcr, config, dbi, atu, intr)
Signed-off-by: Sai Krishna Musham <sai.krishna.musham@amd.com>
---
Changes in v3:
- Update subject to match history.
- Move allOf to the end, after required block.
- Drop the CPM6 example.
Changes in v2:
- Update the CPM6 device tree binding and example.
v1 https://lore.kernel.org/all/20260402180006.486229-2-sai.krishna.musham@amd.com/
v2 https://lore.kernel.org/all/20260728202044.1785986-2-sai.krishna.musham@amd.com/
---
.../bindings/pci/amd,versal2-mdb-host.yaml | 45 ++++++++++++++++---
.../devicetree/bindings/pci/snps,dw-pcie.yaml | 2 +
2 files changed, 42 insertions(+), 5 deletions(-)
diff --git a/Documentation/devicetree/bindings/pci/amd,versal2-mdb-host.yaml b/Documentation/devicetree/bindings/pci/amd,versal2-mdb-host.yaml
index 406c15e1dee1..672589066854 100644
--- a/Documentation/devicetree/bindings/pci/amd,versal2-mdb-host.yaml
+++ b/Documentation/devicetree/bindings/pci/amd,versal2-mdb-host.yaml
@@ -9,27 +9,30 @@ title: AMD Versal2 MDB(Multimedia DMA Bridge) Host Controller
maintainers:
- Thippeswamy Havalige <thippeswamy.havalige@amd.com>
-allOf:
- - $ref: /schemas/pci/pci-host-bridge.yaml#
- - $ref: /schemas/pci/snps,dw-pcie.yaml#
-
properties:
compatible:
- const: amd,versal2-mdb-host
+ enum:
+ - amd,versal2-mdb-host
+ - amd,versal2-cpm6-host
+ - amd,versal2-cpm6-host1
reg:
+ minItems: 4
items:
- description: MDB System Level Control and Status Register (SLCR) Base
- description: configuration region
- description: data bus interface
- description: address translation unit register
+ - description: CPM6 PCIe error and event interrupt registers
reg-names:
+ minItems: 4
items:
- const: slcr
- const: config
- const: dbi
- const: atu
+ - const: intr
ranges:
maxItems: 2
@@ -92,6 +95,38 @@ required:
- "#interrupt-cells"
- interrupt-controller
+allOf:
+ - $ref: /schemas/pci/pci-host-bridge.yaml#
+ - $ref: /schemas/pci/snps,dw-pcie.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: amd,versal2-mdb-host
+ then:
+ properties:
+ reg:
+ minItems: 4
+ maxItems: 4
+ reg-names:
+ minItems: 4
+ maxItems: 4
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - amd,versal2-cpm6-host
+ - amd,versal2-cpm6-host1
+ then:
+ properties:
+ reg:
+ minItems: 5
+ maxItems: 5
+ reg-names:
+ minItems: 5
+ maxItems: 5
+
unevaluatedProperties: false
examples:
diff --git a/Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml b/Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml
index b3216141881c..21f86609ddb6 100644
--- a/Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml
+++ b/Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml
@@ -117,6 +117,8 @@ properties:
enum: [ ecam ]
- description: AMD MDB PCIe SLCR region
const: slcr
+ - description: AMD CPM6 PCIe error and event interrupt registers
+ const: intr
allOf:
- contains:
enum: [ dbi, ctrl ]
--
2.44.4
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 2/2] PCI: amd-mdb: Add CPM6 host controller support
2026-08-03 14:44 [PATCH v3 0/2] pci: AMD: Add Versal2 CPM6 PCIe host controller support Sai Krishna Musham
2026-08-03 14:44 ` [PATCH v3 1/2] dt-bindings: PCI: amd-mdb: Add CPM6 support Sai Krishna Musham
@ 2026-08-03 14:44 ` Sai Krishna Musham
2026-08-03 14:58 ` sashiko-bot
1 sibling, 1 reply; 5+ messages in thread
From: Sai Krishna Musham @ 2026-08-03 14:44 UTC (permalink / raw)
To: bhelgaas, lpieralisi, kw, mani, robh, krzk+dt, conor+dt, cassel
Cc: linux-pci, devicetree, linux-kernel, michal.simek,
bharat.kumar.gogada, thippeswamy.havalige, sai.krishna.musham,
pranav.sanwal
Extend the AMD MDB PCIe driver to support AMD Versal2 CPM6 variants
(amd,versal2-cpm6-host and amd,versal2-cpm6-host1). Like MDB5, CPM6 is
based on the Synopsys DesignWare PCIe controller and reuses the existing
DesignWare host support; only the interrupt architecture differs:
- The per-controller interrupt registers (MISC_EVENT, PCIE_ERR) are in a
dedicated "intr" region instead of the shared SLCR
- The aggregation layer (MERGED, PS_MISC) is shared and sticky; it
requires an explicit W1C-clear after servicing to prevent IRQ storms
- The completion timeout is reported via PCIE_ERR instead of MISC_EVENT
Add variant-specific register layout and implement aggregator clearing
that, after every interrupt pass, clears this host's contribution from
the MERGED register and the corresponding bit in the PS_MISC severity
register.
Signed-off-by: Sai Krishna Musham <sai.krishna.musham@amd.com>
---
Changes in v3:
- Add missing completion-timeout handling via PCIE_ERR.
- Add CPM6 INTx shared-enable refcount handling (mask/unmask via intx_refmask).
- Fix probe-failure cleanup to avoid double-free/use-after-free risk.
- Fix CPM6 slcr mapping to use non-exclusive ioremap for shared SLCR.
- Clear unhandled asserted bits in AMD_CPM6_PCIE_ERR_STATUS.
Changes in v2:
- Add CPM6 host controller interrupt support.
v1 https://lore.kernel.org/all/20260402180006.486229-3-sai.krishna.musham@amd.com/
v2 https://lore.kernel.org/all/20260728202044.1785986-3-sai.krishna.musham@amd.com/
---
drivers/pci/controller/dwc/pcie-amd-mdb.c | 413 ++++++++++++++++++----
1 file changed, 353 insertions(+), 60 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-amd-mdb.c b/drivers/pci/controller/dwc/pcie-amd-mdb.c
index dee295f281a3..d88fb365c310 100644
--- a/drivers/pci/controller/dwc/pcie-amd-mdb.c
+++ b/drivers/pci/controller/dwc/pcie-amd-mdb.c
@@ -21,6 +21,23 @@
#include "../../pci.h"
#include "pcie-designware.h"
+/*
+ * On CPM6 the per-controller PCIe interrupt registers (MISC_EVENT and
+ * PCIE_ERR) live in a dedicated region ("intr"), separate from the CPM SLCR
+ * region ("slcr") that holds the MERGED and PS severity registers they feed
+ * into. Each has a sticky W1C STATUS, a read-only MASK, and write-1
+ * ENABLE/DISABLE register.
+ */
+#define AMD_CPM6_PCIE_ERR_STATUS 0x500
+#define AMD_CPM6_PCIE_ERR_MASK 0x504
+#define AMD_CPM6_PCIE_ERR_ENABLE 0x508
+#define AMD_CPM6_PCIE_ERR_DISABLE 0x50C
+
+#define AMD_CPM6_MISC_EVENT_STATUS 0x514
+#define AMD_CPM6_MISC_EVENT_MASK 0x518
+#define AMD_CPM6_MISC_EVENT_ENABLE 0x51C
+#define AMD_CPM6_MISC_EVENT_DISABLE 0x520
+
#define AMD_MDB_TLP_IR_STATUS_MISC 0x4C0
#define AMD_MDB_TLP_IR_MASK_MISC 0x4C4
#define AMD_MDB_TLP_IR_ENABLE_MISC 0x4C8
@@ -30,7 +47,23 @@
#define AMD_MDB_PCIE_INTR_INTX_ASSERT(x) BIT((x) * 2)
-/* Interrupt registers definitions. */
+#define AMD_CPM6_MERGED_STATUS 0x648
+#define AMD_CPM6_MERGED_ENABLE 0x650
+
+/* MERGED input bits for the MISC_EVENT/PCIE_ERR sources this driver handles. */
+#define AMD_CPM6_MERGED_PCIE_ERR_HOST0 13
+#define AMD_CPM6_MERGED_MISC_EVENT_HOST0 14
+#define AMD_CPM6_MERGED_PCIE_ERR_HOST1 16
+#define AMD_CPM6_MERGED_MISC_EVENT_HOST1 17
+
+/*
+ * The PS_MISC severity register feeds the misc/OR GIC line. The MERGED
+ * aggregator appears as bit 21 within it.
+ */
+#define AMD_CPM6_PS_MISC_IR_STATUS 0x340
+#define AMD_CPM6_PS_IR_MERGED BIT(21)
+
+/* MDB5 interrupt register definitions. */
#define AMD_MDB_PCIE_INTR_CMPL_TIMEOUT 15
#define AMD_MDB_PCIE_INTR_INTX 16
#define AMD_MDB_PCIE_INTR_PM_PME_RCVD 24
@@ -39,6 +72,9 @@
#define AMD_MDB_PCIE_INTR_NONFATAL 27
#define AMD_MDB_PCIE_INTR_FATAL 28
+/* Completion timeout lives in PCIE_ERR_STATUS; give it a dedicated hwirq. */
+#define AMD_CPM6_PCIE_ERR_CMPL_RADM 20
+
#define IMR(x) BIT(AMD_MDB_PCIE_INTR_ ##x)
#define AMD_MDB_PCIE_IMR_ALL_MASK \
( \
@@ -51,24 +87,133 @@
AMD_MDB_TLP_PCIE_INTX_MASK \
)
+/* CPM6 hwirq mapping (hwirq == MISC_EVENT status bit). */
+#define AMD_CPM6_PCIE_INTR_FATAL 17
+#define AMD_CPM6_PCIE_INTR_NONFATAL 18
+#define AMD_CPM6_PCIE_INTR_MISC_CORRECTABLE 19
+#define AMD_CPM6_PCIE_INTR_PME_TO_ACK_RCVD 20
+#define AMD_CPM6_PCIE_INTR_PM_PME_RCVD 21
+#define AMD_CPM6_PCIE_INTR_INTX 22
+
+/* Map the PCIE_ERR completion timeout onto an event-domain hwirq. */
+#define AMD_CPM6_PCIE_INTR_CMPL_TIMEOUT 15
+
+#define AMD_CPM6_MISC_EVENT_MASK_ALL \
+ ( \
+ BIT(AMD_CPM6_PCIE_INTR_FATAL) | \
+ BIT(AMD_CPM6_PCIE_INTR_NONFATAL) | \
+ BIT(AMD_CPM6_PCIE_INTR_MISC_CORRECTABLE) | \
+ BIT(AMD_CPM6_PCIE_INTR_PME_TO_ACK_RCVD) | \
+ BIT(AMD_CPM6_PCIE_INTR_PM_PME_RCVD) | \
+ BIT(AMD_CPM6_PCIE_INTR_INTX) \
+ )
+
+/* Sources handled in the PCIE_ERR register. */
+#define AMD_CPM6_PCIE_ERR_MASK_ALL BIT(AMD_CPM6_PCIE_ERR_CMPL_RADM)
+
+enum amd_mdb_pcie_version {
+ MDB5,
+ CPM6,
+ CPM6_HOST1,
+};
+
+struct amd_mdb_intr_cause {
+ const char *sym;
+ const char *str;
+};
+
+struct amd_mdb_pcie_variant {
+ enum amd_mdb_pcie_version version;
+ u32 misc_status_reg;
+ u32 misc_mask_reg;
+ u32 misc_enable_reg;
+ u32 misc_disable_reg;
+ u32 misc_mask_all;
+ u32 intx_hwirq;
+ u32 intx_mask;
+};
+
/**
* struct amd_mdb_pcie - PCIe port information
* @pci: DesignWare PCIe controller structure
* @slcr: MDB System Level Control and Status Register (SLCR) base
+ * @intr_base: Per-controller interrupt register base. On CPM6 this maps the
+ * "intr" region holding the MISC_EVENT/PCIE_ERR registers; on MDB5
+ * the interrupt registers live in the SLCR block, so it aliases
+ * @slcr.
+ * @variant: Interrupt layout data for the matched platform compatible
* @intx_domain: INTx IRQ domain pointer
* @mdb_domain: MDB IRQ domain pointer
* @perst_gpio: GPIO descriptor for PERST# signal handling
* @intx_irq: INTx IRQ interrupt number
+ * @intx_refmask: CPM6 mask of unmasked INTx lines; gates the shared aggregate
*/
struct amd_mdb_pcie {
struct dw_pcie pci;
void __iomem *slcr;
+ void __iomem *intr_base;
+ const struct amd_mdb_pcie_variant *variant;
struct irq_domain *intx_domain;
struct irq_domain *mdb_domain;
struct gpio_desc *perst_gpio;
int intx_irq;
+ u32 intx_refmask;
+};
+
+#define _IC(x, s)[AMD_MDB_PCIE_INTR_ ## x] = { __stringify(x), s }
+
+static const struct amd_mdb_intr_cause mdb5_intr_cause[32] = {
+ _IC(CMPL_TIMEOUT, "Completion timeout"),
+ _IC(PM_PME_RCVD, "PM_PME message received"),
+ _IC(PME_TO_ACK_RCVD, "PME_TO_ACK message received"),
+ _IC(MISC_CORRECTABLE, "Correctable error message"),
+ _IC(NONFATAL, "Non fatal error message"),
+ _IC(FATAL, "Fatal error message"),
};
+#define _IC6(x, s)[AMD_CPM6_PCIE_INTR_ ## x] = { __stringify(x), s }
+
+static const struct amd_mdb_intr_cause cpm6_intr_cause[32] = {
+ _IC6(CMPL_TIMEOUT, "Completion timeout"),
+ _IC6(PM_PME_RCVD, "PM_PME message received"),
+ _IC6(PME_TO_ACK_RCVD, "PME_TO_ACK message received"),
+ _IC6(MISC_CORRECTABLE, "Correctable error message"),
+ _IC6(NONFATAL, "Non fatal error message"),
+ _IC6(FATAL, "Fatal error message"),
+};
+
+/* Both cause tables are indexed by hwirq, so they must be the same size. */
+static_assert(ARRAY_SIZE(mdb5_intr_cause) == ARRAY_SIZE(cpm6_intr_cause));
+
+static u32 amd_mdb_pcie_merged_host_mask(struct amd_mdb_pcie *pcie)
+{
+ return pcie->variant->version == CPM6 ?
+ BIT(AMD_CPM6_MERGED_MISC_EVENT_HOST0) |
+ BIT(AMD_CPM6_MERGED_PCIE_ERR_HOST0) :
+ BIT(AMD_CPM6_MERGED_MISC_EVENT_HOST1) |
+ BIT(AMD_CPM6_MERGED_PCIE_ERR_HOST1);
+}
+
+static void amd_mdb_pcie_clear_aggregators(struct amd_mdb_pcie *pcie)
+{
+ if (pcie->variant->version == MDB5)
+ return;
+
+ /*
+ * Clear this host's serviced contributions (MISC_EVENT and PCIE_ERR)
+ * from MERGED.
+ */
+ writel_relaxed(amd_mdb_pcie_merged_host_mask(pcie),
+ pcie->slcr + AMD_CPM6_MERGED_STATUS);
+
+ /*
+ * Clear MERGED in the PS_MISC severity register so the misc GIC line
+ * de-asserts.
+ */
+ writel_relaxed(AMD_CPM6_PS_IR_MERGED,
+ pcie->slcr + AMD_CPM6_PS_MISC_IR_STATUS);
+}
+
static const struct dw_pcie_host_ops amd_mdb_pcie_host_ops = {
};
@@ -81,14 +226,21 @@ static void amd_mdb_intx_irq_mask(struct irq_data *data)
u32 val;
raw_spin_lock_irqsave(&port->lock, flags);
- val = FIELD_PREP(AMD_MDB_TLP_PCIE_INTX_MASK,
- AMD_MDB_PCIE_INTR_INTX_ASSERT(data->hwirq));
-
- /*
- * Writing '1' to a bit in AMD_MDB_TLP_IR_DISABLE_MISC disables that
- * interrupt, writing '0' has no effect.
- */
- writel_relaxed(val, pcie->slcr + AMD_MDB_TLP_IR_DISABLE_MISC);
+ if (pcie->variant->version == MDB5) {
+ val = FIELD_PREP(AMD_MDB_TLP_PCIE_INTX_MASK,
+ AMD_MDB_PCIE_INTR_INTX_ASSERT(data->hwirq));
+ /*
+ * Writing '1' to a bit in AMD_MDB_TLP_IR_DISABLE_MISC disables
+ * that interrupt, writing '0' has no effect.
+ */
+ writel_relaxed(val, pcie->intr_base + pcie->variant->misc_disable_reg);
+ } else {
+ /* CPM6 shares one INTx enable; drop it on the last mask. */
+ pcie->intx_refmask &= ~BIT(data->hwirq);
+ if (!pcie->intx_refmask)
+ writel_relaxed(pcie->variant->intx_mask,
+ pcie->intr_base + pcie->variant->misc_disable_reg);
+ }
raw_spin_unlock_irqrestore(&port->lock, flags);
}
@@ -101,14 +253,21 @@ static void amd_mdb_intx_irq_unmask(struct irq_data *data)
u32 val;
raw_spin_lock_irqsave(&port->lock, flags);
- val = FIELD_PREP(AMD_MDB_TLP_PCIE_INTX_MASK,
- AMD_MDB_PCIE_INTR_INTX_ASSERT(data->hwirq));
-
- /*
- * Writing '1' to a bit in AMD_MDB_TLP_IR_ENABLE_MISC enables that
- * interrupt, writing '0' has no effect.
- */
- writel_relaxed(val, pcie->slcr + AMD_MDB_TLP_IR_ENABLE_MISC);
+ if (pcie->variant->version == MDB5) {
+ val = FIELD_PREP(AMD_MDB_TLP_PCIE_INTX_MASK,
+ AMD_MDB_PCIE_INTR_INTX_ASSERT(data->hwirq));
+ /*
+ * Writing '1' to a bit in AMD_MDB_TLP_IR_ENABLE_MISC enables
+ * that interrupt, writing '0' has no effect.
+ */
+ writel_relaxed(val, pcie->intr_base + pcie->variant->misc_enable_reg);
+ } else {
+ /* CPM6 shares one INTx enable; raise it on the first unmask. */
+ if (!pcie->intx_refmask)
+ writel_relaxed(pcie->variant->intx_mask,
+ pcie->intr_base + pcie->variant->misc_enable_reg);
+ pcie->intx_refmask |= BIT(data->hwirq);
+ }
raw_spin_unlock_irqrestore(&port->lock, flags);
}
@@ -148,42 +307,40 @@ static irqreturn_t dw_pcie_rp_intx(int irq, void *args)
unsigned long val;
int i, int_status;
- val = readl_relaxed(pcie->slcr + AMD_MDB_TLP_IR_STATUS_MISC);
- int_status = FIELD_GET(AMD_MDB_TLP_PCIE_INTX_MASK, val);
+ val = readl_relaxed(pcie->intr_base + pcie->variant->misc_status_reg);
- for (i = 0; i < PCI_NUM_INTX; i++) {
- if (int_status & AMD_MDB_PCIE_INTR_INTX_ASSERT(i))
+ if (pcie->variant->version == MDB5) {
+ int_status = FIELD_GET(AMD_MDB_TLP_PCIE_INTX_MASK, val);
+ for (i = 0; i < PCI_NUM_INTX; i++) {
+ if (int_status & AMD_MDB_PCIE_INTR_INTX_ASSERT(i))
+ generic_handle_domain_irq(pcie->intx_domain, i);
+ }
+ } else {
+ /* CPM6 exposes only an aggregate INTx indication */
+ if (!(val & pcie->variant->intx_mask))
+ return IRQ_NONE;
+ for (i = 0; i < PCI_NUM_INTX; i++)
generic_handle_domain_irq(pcie->intx_domain, i);
}
return IRQ_HANDLED;
}
-#define _IC(x, s)[AMD_MDB_PCIE_INTR_ ## x] = { __stringify(x), s }
-
-static const struct {
- const char *sym;
- const char *str;
-} intr_cause[32] = {
- _IC(CMPL_TIMEOUT, "Completion timeout"),
- _IC(PM_PME_RCVD, "PM_PME message received"),
- _IC(PME_TO_ACK_RCVD, "PME_TO_ACK message received"),
- _IC(MISC_CORRECTABLE, "Correctable error message"),
- _IC(NONFATAL, "Non fatal error message"),
- _IC(FATAL, "Fatal error message"),
-};
-
static void amd_mdb_event_irq_mask(struct irq_data *d)
{
struct amd_mdb_pcie *pcie = irq_data_get_irq_chip_data(d);
struct dw_pcie *pci = &pcie->pci;
struct dw_pcie_rp *port = &pci->pp;
unsigned long flags;
- u32 val;
raw_spin_lock_irqsave(&port->lock, flags);
- val = BIT(d->hwirq);
- writel_relaxed(val, pcie->slcr + AMD_MDB_TLP_IR_DISABLE_MISC);
+ if (pcie->variant->version != MDB5 &&
+ d->hwirq == AMD_CPM6_PCIE_INTR_CMPL_TIMEOUT)
+ writel_relaxed(AMD_CPM6_PCIE_ERR_MASK_ALL,
+ pcie->intr_base + AMD_CPM6_PCIE_ERR_DISABLE);
+ else
+ writel_relaxed(BIT(d->hwirq),
+ pcie->intr_base + pcie->variant->misc_disable_reg);
raw_spin_unlock_irqrestore(&port->lock, flags);
}
@@ -193,11 +350,15 @@ static void amd_mdb_event_irq_unmask(struct irq_data *d)
struct dw_pcie *pci = &pcie->pci;
struct dw_pcie_rp *port = &pci->pp;
unsigned long flags;
- u32 val;
raw_spin_lock_irqsave(&port->lock, flags);
- val = BIT(d->hwirq);
- writel_relaxed(val, pcie->slcr + AMD_MDB_TLP_IR_ENABLE_MISC);
+ if (pcie->variant->version != MDB5 &&
+ d->hwirq == AMD_CPM6_PCIE_INTR_CMPL_TIMEOUT)
+ writel_relaxed(AMD_CPM6_PCIE_ERR_MASK_ALL,
+ pcie->intr_base + AMD_CPM6_PCIE_ERR_ENABLE);
+ else
+ writel_relaxed(BIT(d->hwirq),
+ pcie->intr_base + pcie->variant->misc_enable_reg);
raw_spin_unlock_irqrestore(&port->lock, flags);
}
@@ -226,13 +387,43 @@ static irqreturn_t amd_mdb_pcie_event(int irq, void *args)
{
struct amd_mdb_pcie *pcie = args;
unsigned long val;
+ u32 ev_raw, err;
int i;
- val = readl_relaxed(pcie->slcr + AMD_MDB_TLP_IR_STATUS_MISC);
- val &= ~readl_relaxed(pcie->slcr + AMD_MDB_TLP_IR_MASK_MISC);
+ ev_raw = readl_relaxed(pcie->intr_base + pcie->variant->misc_status_reg);
+ val = ev_raw;
+ val &= ~readl_relaxed(pcie->intr_base + pcie->variant->misc_mask_reg);
+
+ if (pcie->variant->version == MDB5) {
+ for_each_set_bit(i, &val, 32)
+ generic_handle_domain_irq(pcie->mdb_domain, i);
+ writel_relaxed(val, pcie->intr_base + pcie->variant->misc_status_reg);
+ return IRQ_HANDLED;
+ }
+
+ val &= pcie->variant->misc_mask_all;
+
for_each_set_bit(i, &val, 32)
generic_handle_domain_irq(pcie->mdb_domain, i);
- writel_relaxed(val, pcie->slcr + AMD_MDB_TLP_IR_STATUS_MISC);
+
+ /* Clear handled + any unhandled sticky bits to avoid IRQ storms. */
+ writel_relaxed(ev_raw, pcie->intr_base + pcie->variant->misc_status_reg);
+
+ /* On CPM6 completion timeout is reported via PCIE_ERR. */
+ err = readl_relaxed(pcie->intr_base + AMD_CPM6_PCIE_ERR_STATUS);
+ if (err) {
+ u32 pending = err & ~readl_relaxed(pcie->intr_base +
+ AMD_CPM6_PCIE_ERR_MASK);
+
+ if (pending & AMD_CPM6_PCIE_ERR_MASK_ALL)
+ generic_handle_domain_irq(pcie->mdb_domain,
+ AMD_CPM6_PCIE_INTR_CMPL_TIMEOUT);
+ /* Clear every asserted bit so the leaf and MERGED de-assert. */
+ writel_relaxed(err, pcie->intr_base + AMD_CPM6_PCIE_ERR_STATUS);
+ }
+
+ /* Sticky aggregation bits; clear each pass or the IRQ re-fires */
+ amd_mdb_pcie_clear_aggregators(pcie);
return IRQ_HANDLED;
}
@@ -250,24 +441,47 @@ static void amd_mdb_pcie_free_irq_domains(struct amd_mdb_pcie *pcie)
}
}
-static int amd_mdb_pcie_init_port(struct amd_mdb_pcie *pcie)
+static void amd_mdb_pcie_init_port(struct amd_mdb_pcie *pcie)
{
- unsigned long val;
+ u32 misc_mask_all;
+ u32 val;
+
+ misc_mask_all = pcie->variant->misc_mask_all;
/* Disable all TLP interrupts. */
- writel_relaxed(AMD_MDB_PCIE_IMR_ALL_MASK,
- pcie->slcr + AMD_MDB_TLP_IR_DISABLE_MISC);
+ writel_relaxed(misc_mask_all,
+ pcie->intr_base + pcie->variant->misc_disable_reg);
+
+ if (pcie->variant->version != MDB5)
+ writel_relaxed(AMD_CPM6_PCIE_ERR_MASK_ALL,
+ pcie->intr_base + AMD_CPM6_PCIE_ERR_DISABLE);
/* Clear pending TLP interrupts. */
- val = readl_relaxed(pcie->slcr + AMD_MDB_TLP_IR_STATUS_MISC);
- val &= AMD_MDB_PCIE_IMR_ALL_MASK;
- writel_relaxed(val, pcie->slcr + AMD_MDB_TLP_IR_STATUS_MISC);
+ val = readl_relaxed(pcie->intr_base + pcie->variant->misc_status_reg) &
+ misc_mask_all;
+ writel_relaxed(val, pcie->intr_base + pcie->variant->misc_status_reg);
+
+ if (pcie->variant->version != MDB5) {
+ val = readl_relaxed(pcie->intr_base + AMD_CPM6_PCIE_ERR_STATUS) &
+ AMD_CPM6_PCIE_ERR_MASK_ALL;
+ writel_relaxed(val, pcie->intr_base + AMD_CPM6_PCIE_ERR_STATUS);
+ }
/* Enable all TLP interrupts. */
- writel_relaxed(AMD_MDB_PCIE_IMR_ALL_MASK,
- pcie->slcr + AMD_MDB_TLP_IR_ENABLE_MISC);
-
- return 0;
+ writel_relaxed(misc_mask_all,
+ pcie->intr_base + pcie->variant->misc_enable_reg);
+
+ if (pcie->variant->version != MDB5) {
+ writel_relaxed(AMD_CPM6_PCIE_ERR_MASK_ALL,
+ pcie->intr_base + AMD_CPM6_PCIE_ERR_ENABLE);
+
+ /*
+ * Unmask this host's MISC_EVENT and PCIE_ERR inputs in the
+ * shared MERGED aggregator so they reach the GIC.
+ */
+ writel_relaxed(amd_mdb_pcie_merged_host_mask(pcie),
+ pcie->slcr + AMD_CPM6_MERGED_ENABLE);
+ }
}
/**
@@ -327,10 +541,13 @@ static int amd_mdb_pcie_init_irq_domains(struct amd_mdb_pcie *pcie,
static irqreturn_t amd_mdb_pcie_intr_handler(int irq, void *args)
{
struct amd_mdb_pcie *pcie = args;
+ const struct amd_mdb_intr_cause *intr_cause;
struct device *dev;
struct irq_data *d;
dev = pcie->pci.dev;
+ intr_cause = pcie->variant->version == MDB5 ?
+ mdb5_intr_cause : cpm6_intr_cause;
/*
* In the future, error reporting will be hooked to the AER subsystem.
@@ -351,15 +568,19 @@ static int amd_mdb_setup_irq(struct amd_mdb_pcie *pcie,
struct dw_pcie *pci = &pcie->pci;
struct dw_pcie_rp *pp = &pci->pp;
struct device *dev = &pdev->dev;
+ const struct amd_mdb_intr_cause *intr_cause;
int i, irq, err;
+ intr_cause = pcie->variant->version == MDB5 ?
+ mdb5_intr_cause : cpm6_intr_cause;
+
amd_mdb_pcie_init_port(pcie);
pp->irq = platform_get_irq(pdev, 0);
if (pp->irq < 0)
return pp->irq;
- for (i = 0; i < ARRAY_SIZE(intr_cause); i++) {
+ for (i = 0; i < ARRAY_SIZE(mdb5_intr_cause); i++) {
if (!intr_cause[i].str)
continue;
@@ -379,7 +600,7 @@ static int amd_mdb_setup_irq(struct amd_mdb_pcie *pcie,
}
pcie->intx_irq = irq_create_mapping(pcie->mdb_domain,
- AMD_MDB_PCIE_INTR_INTX);
+ pcie->variant->intx_hwirq);
if (!pcie->intx_irq) {
dev_err(dev, "Failed to map INTx interrupt\n");
return -ENXIO;
@@ -435,9 +656,36 @@ static int amd_mdb_add_pcie_port(struct amd_mdb_pcie *pcie,
struct device *dev = &pdev->dev;
int err;
- pcie->slcr = devm_platform_ioremap_resource_byname(pdev, "slcr");
- if (IS_ERR(pcie->slcr))
- return PTR_ERR(pcie->slcr);
+ if (pcie->variant->version == MDB5) {
+ /*
+ * On MDB5 all interrupt registers live in the SLCR block, so
+ * the interrupt-register base simply aliases @slcr.
+ */
+ pcie->slcr = devm_platform_ioremap_resource_byname(pdev, "slcr");
+ if (IS_ERR(pcie->slcr))
+ return PTR_ERR(pcie->slcr);
+ pcie->intr_base = pcie->slcr;
+ } else {
+ struct resource *res;
+
+ /*
+ * CPM6 moves the per-controller MISC_EVENT/PCIE_ERR registers
+ * into a separate "intr" region. The SLCR block, which holds
+ * the shared MERGED/PS_MISC aggregators, is shared by both CPM6
+ * host controllers, so map it without requesting exclusive
+ * ownership; otherwise the second controller fails to probe.
+ */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "slcr");
+ if (!res)
+ return -EINVAL;
+ pcie->slcr = devm_ioremap(dev, res->start, resource_size(res));
+ if (!pcie->slcr)
+ return -ENOMEM;
+
+ pcie->intr_base = devm_platform_ioremap_resource_byname(pdev, "intr");
+ if (IS_ERR(pcie->intr_base))
+ return PTR_ERR(pcie->intr_base);
+ }
err = amd_mdb_pcie_init_irq_domains(pcie, pdev);
if (err)
@@ -483,6 +731,9 @@ static int amd_mdb_pcie_probe(struct platform_device *pdev)
pci = &pcie->pci;
pci->dev = dev;
+ pcie->variant = of_device_get_match_data(dev);
+ if (!pcie->variant)
+ return -EINVAL;
platform_set_drvdata(pdev, pcie);
@@ -514,9 +765,51 @@ static void amd_mdb_pcie_shutdown(struct platform_device *pdev)
gpiod_set_value_cansleep(pcie->perst_gpio, 1);
}
+static const struct amd_mdb_pcie_variant cpm6_host = {
+ .version = CPM6,
+ .misc_status_reg = AMD_CPM6_MISC_EVENT_STATUS,
+ .misc_mask_reg = AMD_CPM6_MISC_EVENT_MASK,
+ .misc_enable_reg = AMD_CPM6_MISC_EVENT_ENABLE,
+ .misc_disable_reg = AMD_CPM6_MISC_EVENT_DISABLE,
+ .misc_mask_all = AMD_CPM6_MISC_EVENT_MASK_ALL,
+ .intx_hwirq = AMD_CPM6_PCIE_INTR_INTX,
+ .intx_mask = BIT(AMD_CPM6_PCIE_INTR_INTX),
+};
+
+static const struct amd_mdb_pcie_variant cpm6_host1 = {
+ .version = CPM6_HOST1,
+ .misc_status_reg = AMD_CPM6_MISC_EVENT_STATUS,
+ .misc_mask_reg = AMD_CPM6_MISC_EVENT_MASK,
+ .misc_enable_reg = AMD_CPM6_MISC_EVENT_ENABLE,
+ .misc_disable_reg = AMD_CPM6_MISC_EVENT_DISABLE,
+ .misc_mask_all = AMD_CPM6_MISC_EVENT_MASK_ALL,
+ .intx_hwirq = AMD_CPM6_PCIE_INTR_INTX,
+ .intx_mask = BIT(AMD_CPM6_PCIE_INTR_INTX),
+};
+
+static const struct amd_mdb_pcie_variant mdb5_host = {
+ .version = MDB5,
+ .misc_status_reg = AMD_MDB_TLP_IR_STATUS_MISC,
+ .misc_mask_reg = AMD_MDB_TLP_IR_MASK_MISC,
+ .misc_enable_reg = AMD_MDB_TLP_IR_ENABLE_MISC,
+ .misc_disable_reg = AMD_MDB_TLP_IR_DISABLE_MISC,
+ .misc_mask_all = AMD_MDB_PCIE_IMR_ALL_MASK,
+ .intx_hwirq = AMD_MDB_PCIE_INTR_INTX,
+ .intx_mask = AMD_MDB_TLP_PCIE_INTX_MASK,
+};
+
static const struct of_device_id amd_mdb_pcie_of_match[] = {
{
.compatible = "amd,versal2-mdb-host",
+ .data = &mdb5_host,
+ },
+ {
+ .compatible = "amd,versal2-cpm6-host",
+ .data = &cpm6_host,
+ },
+ {
+ .compatible = "amd,versal2-cpm6-host1",
+ .data = &cpm6_host1,
},
{},
};
--
2.44.4
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: PCI: amd-mdb: Add CPM6 support
2026-08-03 14:44 ` [PATCH v3 1/2] dt-bindings: PCI: amd-mdb: Add CPM6 support Sai Krishna Musham
@ 2026-08-03 14:50 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-08-03 14:50 UTC (permalink / raw)
To: Sai Krishna Musham; +Cc: linux-pci, conor+dt, robh, devicetree
> The AMD CPM6 PCIe controller is based on the Synopsys DesignWare PCIe IP.
> Add "intr" to snps,dw-pcie.yaml vendor-specific reg-names for the
> per-controller interrupt register region used by CPM6.
>
> Update amd,versal2-mdb-host.yaml with separate register definitions:
> - MDB5: 4 regions (slcr, config, dbi, atu)
> - CPM6: 5 regions (slcr, config, dbi, atu, intr)
>
> Signed-off-by: Sai Krishna Musham <sai.krishna.musham@amd.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260803144412.713639-1-sai.krishna.musham@amd.com?part=1
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3 2/2] PCI: amd-mdb: Add CPM6 host controller support
2026-08-03 14:44 ` [PATCH v3 2/2] PCI: amd-mdb: Add CPM6 host controller support Sai Krishna Musham
@ 2026-08-03 14:58 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-08-03 14:58 UTC (permalink / raw)
To: Sai Krishna Musham; +Cc: devicetree, conor+dt, linux-pci, robh
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
New issues:
- [High] Shared PCIe interrupt line lacks IRQF_SHARED and fails to return IRQ_NONE, breaking the second CPM6 host controller.
- [High] Uncoordinated W1C clearing of the shared sticky `PS_MISC` aggregator bit causes lost interrupts for the other host controller.
Pre-existing issues:
- [High] Probe error paths manually destroy IRQ domains before devres frees the requested IRQs and fail to dispose of mappings, leading to UAF and descriptor leaks.
- [High] Unconditionally unmasking the shared INTx hardware interrupt during initialization causes 100% CPU lockup IRQ storms if an unhandled INTx asserts.
- [High] Only INTA is mapped and handled for MDB5; assertions of INTB, INTC, or INTD lead to unhandled IRQ storms.
- [High] The driver fails to disable and clear all unhandled interrupt sources during initialization, risking IRQ storms.
--
--- Patch [2]: [PATCH v3 2/2] PCI: amd-mdb: Add CPM6 host controller support ---
commit 143e844605c2e7ba1267fd4895c79f0d1c955a17
Author: Sai Krishna Musham <sai.krishna.musham@amd.com>
PCI: amd-mdb: Add CPM6 host controller support
Extend the AMD MDB PCIe driver to support AMD Versal2 CPM6 variants
(amd,versal2-cpm6-host and amd,versal2-cpm6-host1). Like MDB5, CPM6 is
based on the Synopsys DesignWare PCIe controller and reuses the existing
DesignWare host support; only the interrupt architecture differs.
[ ... ]
> @@ -51,24 +87,133 @@
> AMD_MDB_TLP_PCIE_INTX_MASK \
> )
[ ... ]
> +static void amd_mdb_pcie_clear_aggregators(struct amd_mdb_pcie *pcie)
> +{
> + if (pcie->variant->version == MDB5)
> + return;
> +
> + /*
> + * Clear this host's serviced contributions (MISC_EVENT and PCIE_ERR)
> + * from MERGED.
> + */
> + writel_relaxed(amd_mdb_pcie_merged_host_mask(pcie),
> + pcie->slcr + AMD_CPM6_MERGED_STATUS);
> +
> + /*
> + * Clear MERGED in the PS_MISC severity register so the misc GIC line
> + * de-asserts.
> + */
> + writel_relaxed(AMD_CPM6_PS_IR_MERGED,
> + pcie->slcr + AMD_CPM6_PS_MISC_IR_STATUS);
> +}
[Severity: High]
Does this unconditional lockless write clear the shared sticky aggregator bit
safely?
This register clears bit 21, which is a single sticky aggregator representing
the OR of events from both CPM6 host controllers. An unconditional W1C write
by HOST0 will clear the latched state even if HOST1 currently has pending
unhandled events.
Could this lack of synchronization across hosts result in dropped edge
transitions and permanently lost interrupts for the other controller?
[ ... ]
> @@ -226,13 +387,43 @@ static irqreturn_t amd_mdb_pcie_event(int irq, void *args)
> {
> struct amd_mdb_pcie *pcie = args;
> unsigned long val;
> + u32 ev_raw, err;
> int i;
>
> - val = readl_relaxed(pcie->slcr + AMD_MDB_TLP_IR_STATUS_MISC);
> - val &= ~readl_relaxed(pcie->slcr + AMD_MDB_TLP_IR_MASK_MISC);
> + ev_raw = readl_relaxed(pcie->intr_base + pcie->variant->misc_status_reg);
> + val = ev_raw;
> + val &= ~readl_relaxed(pcie->intr_base + pcie->variant->misc_mask_reg);
> +
> + if (pcie->variant->version == MDB5) {
> + for_each_set_bit(i, &val, 32)
> + generic_handle_domain_irq(pcie->mdb_domain, i);
> + writel_relaxed(val, pcie->intr_base + pcie->variant->misc_status_reg);
> + return IRQ_HANDLED;
> + }
[ ... ]
> + /* Sticky aggregation bits; clear each pass or the IRQ re-fires */
> + amd_mdb_pcie_clear_aggregators(pcie);
>
> return IRQ_HANDLED;
> }
[Severity: High]
Since both CPM6 host controllers share the PS_MISC severity register which
feeds a single shared GIC interrupt line, shouldn't this physical IRQ be
requested with the IRQF_SHARED flag in amd_mdb_setup_irq()?
Currently, devm_request_irq() is called without IRQF_SHARED, which causes the
second controller's probe to fail with -EBUSY.
Furthermore, the shared handler amd_mdb_pcie_event() unconditionally returns
IRQ_HANDLED even when it performs no work (e.g. when ev_raw == 0). Does this
violate shared IRQ semantics and defeat the spurious interrupt detection
mechanism?
[ ... ]
> @@ -250,24 +441,47 @@ static void amd_mdb_pcie_free_irq_domains(struct amd_mdb_pcie *pcie)
> }
> }
[Severity: High]
This is a pre-existing issue, but is it safe to manually destroy the IRQ
domains in the probe error path before devres frees the requested IRQs?
When dw_pcie_host_init() fails, the error path calls
amd_mdb_pcie_free_irq_domains(), which immediately removes the domains here.
However, irq_dispose_mapping() is never called, leaving the irq_desc mappings
allocated and triggering a warning during irq_domain_remove.
Additionally, the physical IRQ handler is managed by devres and remains
registered until the probe function fully unwinds. If an interrupt fires in
this window, the handler will use the destroyed domain, leading to kernel
crashes. Could this domain teardown be deferred via devm_add_action_or_reset()
to ensure LIFO cleanup order?
> -static int amd_mdb_pcie_init_port(struct amd_mdb_pcie *pcie)
> +static void amd_mdb_pcie_init_port(struct amd_mdb_pcie *pcie)
> {
> - unsigned long val;
> + u32 misc_mask_all;
> + u32 val;
> +
> + misc_mask_all = pcie->variant->misc_mask_all;
>
> /* Disable all TLP interrupts. */
> - writel_relaxed(AMD_MDB_PCIE_IMR_ALL_MASK,
> - pcie->slcr + AMD_MDB_TLP_IR_DISABLE_MISC);
> + writel_relaxed(misc_mask_all,
> + pcie->intr_base + pcie->variant->misc_disable_reg);
[Severity: High]
This is a pre-existing issue, but should the initialization sequence disable
all interrupt sources rather than just the known subset?
The driver only writes the known subset mask (misc_mask_all) to the disable
register. If the bootloader or firmware left any other unhandled interrupt
sources enabled and asserted, they remain unmasked.
When the main event handler runs, it masks out the known bits for processing
but still clears the unhandled status bits and returns IRQ_HANDLED without
actually handling the source or disabling it. For level-sensitive interrupts,
this immediately re-asserts the bit, leading to an IRQ storm. Could this use
~0U to disable all sources?
[ ... ]
> /* Enable all TLP interrupts. */
> - writel_relaxed(AMD_MDB_PCIE_IMR_ALL_MASK,
> - pcie->slcr + AMD_MDB_TLP_IR_ENABLE_MISC);
> -
> - return 0;
> + writel_relaxed(misc_mask_all,
> + pcie->intr_base + pcie->variant->misc_enable_reg);
[Severity: High]
This is a pre-existing issue, but does unconditionally unmasking the shared
INTx hardware interrupt during initialization risk an IRQ storm?
Here, the driver writes misc_mask_all to the enable register, which unmasks
INTx in hardware before any child driver has requested it (intx_refmask = 0).
If INTx is already asserted by a device (e.g., from BIOS), the hardware
interrupt fires immediately.
Because no child driver is registered, no handler clears the interrupt at the
PCI device level. Later in dw_pcie_rp_intx(), the handler unconditionally
returns IRQ_HANDLED, preventing the kernel's spurious interrupt detector from
masking the IRQ and resulting in a permanent CPU lockup.
[ ... ]
> @@ -379,7 +600,7 @@ static int amd_mdb_setup_irq(struct amd_mdb_pcie *pcie,
> }
>
> pcie->intx_irq = irq_create_mapping(pcie->mdb_domain,
> - AMD_MDB_PCIE_INTR_INTX);
> + pcie->variant->intx_hwirq);
[Severity: High]
This isn't a bug introduced by this patch, but are the INTB, INTC, and INTD
interrupts handled correctly for MDB5?
On MDB5, STATUS_MISC exposes separate bits for INTA, INTB, INTC, and INTD.
However, this setup only creates a single IRQ mapping for hwirq 16 (INTA).
If INTB, INTC, or INTD asserts, amd_mdb_pcie_event() iterates over the bits
and calls generic_handle_domain_irq() for the unmapped hwirqs, which will fail.
The handler then clears the status bit and returns IRQ_HANDLED. Since the
underlying device's interrupt was never serviced, it re-asserts immediately.
Does this create a permanent IRQ storm?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260803144412.713639-1-sai.krishna.musham@amd.com?part=2
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-03 14:58 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-03 14:44 [PATCH v3 0/2] pci: AMD: Add Versal2 CPM6 PCIe host controller support Sai Krishna Musham
2026-08-03 14:44 ` [PATCH v3 1/2] dt-bindings: PCI: amd-mdb: Add CPM6 support Sai Krishna Musham
2026-08-03 14:50 ` sashiko-bot
2026-08-03 14:44 ` [PATCH v3 2/2] PCI: amd-mdb: Add CPM6 host controller support Sai Krishna Musham
2026-08-03 14:58 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox