From 7c0b82d087908765c6c7daeb06943e21b78d523e Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Wed, 26 Aug 2026 13:14:58 -0700 Subject: [PATCH 01/19] memory: brcmstb_dpfe: Fix out-of-bounds access due to DCPU offset On API v1/v2 boards, the DCPU coprocessor can steer kernel readl_relaxed() and writel_relaxed() to any address within 256 MB of the ioremapped DPFE dmem or regs base. The DCPU firmware provides a 28-bit offset which the driver adds to the ioremap base without any bounds checking in get_msg_ptr(). This allows a compromised DCPU firmware to trick the host kernel into reading or writing arbitrary memory-mapped I/O registers in vmalloc space. When combined with a root-writable sysfs file like dpfe_refresh, it provides an arbitrary MMIO write primitive. Similarly, world-readable sysfs files can be used to leak other devices' register contents. Fix this by recording the resource_size() of the dmem and regs ioremaps at probe time, and rejecting any offset that, along with the largest field accessed (DRAM_VENDOR_ERROR + sizeof(u32)), exceeds the recorded mapping size. Fixes: fee5f1ef6cf7 ("memory: brcmstb: dpfe: support new way of passing data from the DCPU") Cc: stable@vger.kernel.org Signed-off-by: Justin Chen Assisted-by: Gemini:gemini-3.1-pro-preview cursor Signed-off-by: Danesh Petigara Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260826201500.3000125-2-danesh.petigara@broadcom.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/brcmstb_dpfe.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/memory/brcmstb_dpfe.c b/drivers/memory/brcmstb_dpfe.c index 08d9e05b1b33..66343205f585 100644 --- a/drivers/memory/brcmstb_dpfe.c +++ b/drivers/memory/brcmstb_dpfe.c @@ -182,6 +182,8 @@ struct brcmstb_dpfe_priv { void __iomem *regs; void __iomem *dmem; void __iomem *imem; + resource_size_t regs_size; + resource_size_t dmem_size; struct device *dev; const struct dpfe_api *dpfe_api; struct mutex lock; @@ -401,9 +403,14 @@ static void __iomem *get_msg_ptr(struct brcmstb_dpfe_priv *priv, u32 response, */ switch (msg_type) { case 1: + if (DCPU_MSG_RAM_START + offset + DRAM_VENDOR_ERROR + + sizeof(u32) > priv->regs_size) + goto bad_offset; ptr = priv->regs + DCPU_MSG_RAM_START + offset; break; case 0: + if (offset + DRAM_VENDOR_ERROR + sizeof(u32) > priv->dmem_size) + goto bad_offset; ptr = priv->dmem + offset; break; default: @@ -415,6 +422,12 @@ static void __iomem *get_msg_ptr(struct brcmstb_dpfe_priv *priv, u32 response, } return ptr; + +bad_offset: + dev_err(priv->dev, "DCPU returned out-of-range offset %#x\n", offset); + if (buf && size) + *size = sprintf(buf, "ERROR: DCPU offset out of range\n"); + return NULL; } static void __finalize_command(struct brcmstb_dpfe_priv *priv) @@ -858,6 +871,7 @@ static int brcmstb_dpfe_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct brcmstb_dpfe_priv *priv; + struct resource *res; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -869,17 +883,21 @@ static int brcmstb_dpfe_probe(struct platform_device *pdev) mutex_init(&priv->lock); platform_set_drvdata(pdev, priv); - priv->regs = devm_platform_ioremap_resource_byname(pdev, "dpfe-cpu"); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dpfe-cpu"); + priv->regs = devm_ioremap_resource(dev, res); if (IS_ERR(priv->regs)) { dev_err(dev, "couldn't map DCPU registers\n"); return -ENODEV; } + priv->regs_size = resource_size(res); - priv->dmem = devm_platform_ioremap_resource_byname(pdev, "dpfe-dmem"); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dpfe-dmem"); + priv->dmem = devm_ioremap_resource(dev, res); if (IS_ERR(priv->dmem)) { dev_err(dev, "Couldn't map DCPU data memory\n"); return -ENOENT; } + priv->dmem_size = resource_size(res); priv->imem = devm_platform_ioremap_resource_byname(pdev, "dpfe-imem"); if (IS_ERR(priv->imem)) { From 6b07f8c06caca015a0db69e3f323b5186469bc66 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Wed, 26 Aug 2026 13:14:59 -0700 Subject: [PATCH 02/19] memory: brcmstb_dpfe: Bounds check DCPU-supplied MSG_ARG_COUNT A compromised or buggy DCPU coprocessor can make any user who reads world-readable /sys/devices/.../dpfe_info|dpfe_dram trigger a kernel stack out-of-bounds read of arbitrary extent. This bounds checks chksum_idx against MSG_FIELD_MAX after reading the response from DCPU message RAM. Fixes: 5d06f53d9509 ("memory: brcmstb: dpfe: Compute checksum at __send_command() time") Cc: stable@vger.kernel.org Signed-off-by: Justin Chen Assisted-by: Gemini:gemini-3.1-pro-preview cursor Signed-off-by: Danesh Petigara Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260826201500.3000125-3-danesh.petigara@broadcom.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/brcmstb_dpfe.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/memory/brcmstb_dpfe.c b/drivers/memory/brcmstb_dpfe.c index 66343205f585..7b02c36c7fca 100644 --- a/drivers/memory/brcmstb_dpfe.c +++ b/drivers/memory/brcmstb_dpfe.c @@ -503,6 +503,8 @@ static int __send_command(struct brcmstb_dpfe_priv *priv, unsigned int cmd, for (i = 0; i < MSG_FIELD_MAX; i++) result[i] = readl_relaxed(regs + DCPU_MSG_RAM(i)); chksum_idx = result[MSG_ARG_COUNT] + MSG_ARG_COUNT + 1; + if (chksum_idx >= MSG_FIELD_MAX) + ret = -EINVAL; } /* Tell DCPU we are done */ From 3db1fb5e1bb4c9eefc554f985f6f3316fa1292e5 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 27 Jul 2026 11:34:44 +0100 Subject: [PATCH 03/19] memory: renesas-rpc-if: Mark XSPI_CDD1BUF0 as volatile Add XSPI_CDD1BUF0 to the xSPI regmap volatile register table. XSPI_CDD1BUF0 is a data buffer register used during manual transfers and, like XSPI_CDD0BUF0, its contents are transient and should not be cached by regmap. Mark it as volatile so reads always access the hardware register. Fixes: 687cac9559d8 ("memory: renesas-rpc-if: Add RZ/G3E xSPI support") Cc: stable@vger.kernel.org Signed-off-by: Lad Prabhakar Link: https://patch.msgid.link/20260727103449.3188105-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/renesas-rpc-if.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/memory/renesas-rpc-if.c b/drivers/memory/renesas-rpc-if.c index 3755956ae906..104c28532c20 100644 --- a/drivers/memory/renesas-rpc-if.c +++ b/drivers/memory/renesas-rpc-if.c @@ -34,6 +34,7 @@ static const struct regmap_access_table rpcif_volatile_table = { static const struct regmap_range xspi_volatile_ranges[] = { regmap_reg_range(XSPI_CDD0BUF0, XSPI_CDD0BUF0), + regmap_reg_range(XSPI_CDD1BUF0, XSPI_CDD1BUF0), }; static const struct regmap_access_table xspi_volatile_table = { From 12e44bfc2a0e97f139c37b33925cd49d1621fa91 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 27 Jul 2026 11:34:45 +0100 Subject: [PATCH 04/19] memory: renesas-rpc-if: Move static bridge configurations out of dirmap hot paths Move the initialization of the xSPI Bridge Control Register 0 (XSPI_BMCTL0) and Bridge Configuration Register (XSPI_BMCFG) from the dirmap read and write implementations into the hardware initialization function. The bridge configuration values remain unchanged across sequential dirmap transfers. Keeping these regmap_update_bits() calls inside the hot paths (xspi_dirmap_read_impl and xspi_dirmap_write) introduces significant software overhead and redundant register locking cycles. Statically setting CS0ACC to 0x3 during xspi_hw_init_impl() permanently enables the memory area for both read and write operations. Signed-off-by: Lad Prabhakar Link: https://patch.msgid.link/20260727103449.3188105-3-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/renesas-rpc-if.c | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/drivers/memory/renesas-rpc-if.c b/drivers/memory/renesas-rpc-if.c index 104c28532c20..50294e5d2bf5 100644 --- a/drivers/memory/renesas-rpc-if.c +++ b/drivers/memory/renesas-rpc-if.c @@ -312,6 +312,14 @@ static int xspi_hw_init_impl(struct rpcif_priv *xspi, bool hyperflash) regmap_update_bits(xspi->regmap, XSPI_INTE, XSPI_INTE_CMDCMPE, XSPI_INTE_CMDCMPE); + regmap_update_bits(xspi->regmap, XSPI_BMCTL0, + XSPI_BMCTL0_CS0ACC(0xff), XSPI_BMCTL0_CS0ACC(0x03)); + + regmap_update_bits(xspi->regmap, XSPI_BMCFG, + XSPI_BMCFG_WRMD | XSPI_BMCFG_MWRCOMB | + XSPI_BMCFG_MWRSIZE(0xff) | XSPI_BMCFG_PREEN, + 0 | XSPI_BMCFG_MWRCOMB | XSPI_BMCFG_MWRSIZE(0x0f) | + XSPI_BMCFG_PREEN); return 0; } @@ -869,15 +877,6 @@ static size_t xspi_dirmap_read_impl(struct rpcif_priv *xspi, u64 offs, XSPI_CMCFG1_RDCMD_UPPER_BYTE(xspi->command) | XSPI_CMCFG1_RDLATE(xspi->dummy)); - regmap_update_bits(xspi->regmap, XSPI_BMCTL0, XSPI_BMCTL0_CS0ACC(0xff), - XSPI_BMCTL0_CS0ACC(0x01)); - - regmap_update_bits(xspi->regmap, XSPI_BMCFG, - XSPI_BMCFG_WRMD | XSPI_BMCFG_MWRCOMB | - XSPI_BMCFG_MWRSIZE(0xff) | XSPI_BMCFG_PREEN, - 0 | XSPI_BMCFG_MWRCOMB | XSPI_BMCFG_MWRSIZE(0x0f) | - XSPI_BMCFG_PREEN); - regmap_update_bits(xspi->regmap, XSPI_LIOCFGCS0, XSPI_LIOCFG_PRTMD(0x3ff), XSPI_LIOCFG_PRTMD(xspi->proto)); @@ -945,15 +944,6 @@ ssize_t xspi_dirmap_write(struct device *dev, u64 offs, size_t len, const void * XSPI_CMCFG2_WRCMD_UPPER(xspi->command) | XSPI_CMCFG2_WRLATE(xspi->dummy)); - regmap_update_bits(xspi->regmap, XSPI_BMCTL0, - XSPI_BMCTL0_CS0ACC(0xff), XSPI_BMCTL0_CS0ACC(0x03)); - - regmap_update_bits(xspi->regmap, XSPI_BMCFG, - XSPI_BMCFG_WRMD | XSPI_BMCFG_MWRCOMB | - XSPI_BMCFG_MWRSIZE(0xff) | XSPI_BMCFG_PREEN, - 0 | XSPI_BMCFG_MWRCOMB | XSPI_BMCFG_MWRSIZE(0x0f) | - XSPI_BMCFG_PREEN); - regmap_update_bits(xspi->regmap, XSPI_LIOCFGCS0, XSPI_LIOCFG_PRTMD(0x3ff), XSPI_LIOCFG_PRTMD(xspi->proto)); From 008a7b9631864b708ffc06d42264aeef09f09800 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 27 Jul 2026 11:34:46 +0100 Subject: [PATCH 05/19] memory: renesas-rpc-if: Consolidate XSPI_CDTBUF0 updates in write path Program the XSPI_CDTBUF0 TRTYPE, DATASIZE and ADDSIZE fields with a single regmap_update_bits() call in the RPCIF_DATA_OUT transfer path. The existing code updates these fields through three separate read-modify-write operations even though all values are known before the transfer request is issued. Combine the updates into a single register access to reduce overhead and simplify the configuration sequence. Signed-off-by: Lad Prabhakar Link: https://patch.msgid.link/20260727103449.3188105-4-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/renesas-rpc-if.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/memory/renesas-rpc-if.c b/drivers/memory/renesas-rpc-if.c index 50294e5d2bf5..e406afc42616 100644 --- a/drivers/memory/renesas-rpc-if.c +++ b/drivers/memory/renesas-rpc-if.c @@ -660,17 +660,12 @@ static int xspi_manual_xfer_impl(struct rpcif_priv *xspi) u32 bytes_left = xspi->xferlen - pos; u32 nbytes, data[2], *p = data; - regmap_update_bits(xspi->regmap, XSPI_CDTBUF0, - XSPI_CDTBUF_TRTYPE, XSPI_CDTBUF_TRTYPE); - nbytes = bytes_left >= max ? max : bytes_left; regmap_update_bits(xspi->regmap, XSPI_CDTBUF0, - XSPI_CDTBUF_DATASIZE(0xf), - XSPI_CDTBUF_DATASIZE(nbytes)); - - regmap_update_bits(xspi->regmap, XSPI_CDTBUF0, + XSPI_CDTBUF_TRTYPE | XSPI_CDTBUF_DATASIZE(0xf) | XSPI_CDTBUF_ADDSIZE(0x7), + XSPI_CDTBUF_TRTYPE | XSPI_CDTBUF_DATASIZE(nbytes) | XSPI_CDTBUF_ADDSIZE(xspi->addr_nbytes)); memcpy(data, xspi->buffer + pos, nbytes); From 26883f764dd66d0c82f258a12a2f0f769e6fc68e Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 27 Jul 2026 11:34:47 +0100 Subject: [PATCH 06/19] memory: renesas-rpc-if: Consolidate XSPI_CDTBUF0 updates in read path Program the XSPI_CDTBUF0 fields used by the RPCIF_DATA_IN transfer path through a single regmap_update_bits() call. The existing code updates TRTYPE, DATASIZE, ADDSIZE and LATE using multiple read-modify-write operations on the same register, even though all field values are known before the transfer starts. Build the register mask and value first, then perform a single register update. This reduces register accesses and simplifies the transfer setup without changing functionality. Signed-off-by: Lad Prabhakar Link: https://patch.msgid.link/20260727103449.3188105-5-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/renesas-rpc-if.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/drivers/memory/renesas-rpc-if.c b/drivers/memory/renesas-rpc-if.c index e406afc42616..c768e9c18556 100644 --- a/drivers/memory/renesas-rpc-if.c +++ b/drivers/memory/renesas-rpc-if.c @@ -697,30 +697,33 @@ static int xspi_manual_xfer_impl(struct rpcif_priv *xspi) while (pos < xspi->xferlen) { u32 bytes_left = xspi->xferlen - pos; u32 nbytes, data[2], *p = data; - - regmap_update_bits(xspi->regmap, XSPI_CDTBUF0, - XSPI_CDTBUF_TRTYPE, - ~(u32)XSPI_CDTBUF_TRTYPE); + u32 cdtbuf0_mask, cdtbuf0_val; /* nbytes can be up to 8 bytes */ nbytes = bytes_left >= max ? max : bytes_left; - regmap_update_bits(xspi->regmap, XSPI_CDTBUF0, - XSPI_CDTBUF_DATASIZE(0xf), - XSPI_CDTBUF_DATASIZE(nbytes)); + /* clear TRTYPE */ + cdtbuf0_mask = XSPI_CDTBUF_TRTYPE; + cdtbuf0_val = 0; - regmap_update_bits(xspi->regmap, XSPI_CDTBUF0, - XSPI_CDTBUF_ADDSIZE(0x7), - XSPI_CDTBUF_ADDSIZE(xspi->addr_nbytes)); + /* program DATASIZE */ + cdtbuf0_mask |= XSPI_CDTBUF_DATASIZE(0xf); + cdtbuf0_val |= XSPI_CDTBUF_DATASIZE(nbytes); + + /* program ADDSIZE */ + cdtbuf0_mask |= XSPI_CDTBUF_ADDSIZE(0x7); + cdtbuf0_val |= XSPI_CDTBUF_ADDSIZE(xspi->addr_nbytes); + + /* program LATE */ + cdtbuf0_mask |= XSPI_CDTBUF_LATE(0x1f); + cdtbuf0_val |= XSPI_CDTBUF_LATE(xspi->dummy); + + regmap_update_bits(xspi->regmap, XSPI_CDTBUF0, cdtbuf0_mask, cdtbuf0_val); if (xspi->addr_nbytes) regmap_write(xspi->regmap, XSPI_CDABUF0, xspi->smadr + pos); - regmap_update_bits(xspi->regmap, XSPI_CDTBUF0, - XSPI_CDTBUF_LATE(0x1f), - XSPI_CDTBUF_LATE(xspi->dummy)); - regmap_update_bits(xspi->regmap, XSPI_CDCTL0, XSPI_CDCTL0_TRREQ, XSPI_CDCTL0_TRREQ); From 0c49783b0ed5c3d27efb2d0d91a3272db154458d Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 27 Jul 2026 11:34:48 +0100 Subject: [PATCH 07/19] memory: renesas-rpc-if: Consolidate command setup register programming Simplify the command setup sequence in xspi_manual_xfer_impl() by combining updates to XSPI_CDCTL0 and XSPI_CDTBUF0. Clear the TRNUM and TRREQ fields in XSPI_CDCTL0 with a single regmap_update_bits() call. Likewise, program the command, command size and address size fields in XSPI_CDTBUF0 with a single regmap_write(), eliminating the subsequent ADDSIZE update. The intermediate write of zero to XSPI_CDABUF0 is also redundant, as the register is immediately programmed with the transfer address. Signed-off-by: Lad Prabhakar Link: https://patch.msgid.link/20260727103449.3188105-6-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/renesas-rpc-if.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/drivers/memory/renesas-rpc-if.c b/drivers/memory/renesas-rpc-if.c index c768e9c18556..d98991266887 100644 --- a/drivers/memory/renesas-rpc-if.c +++ b/drivers/memory/renesas-rpc-if.c @@ -636,18 +636,14 @@ static int xspi_manual_xfer_impl(struct rpcif_priv *xspi) u32 pos = 0, max = 8; int ret = 0; - regmap_update_bits(xspi->regmap, XSPI_CDCTL0, XSPI_CDCTL0_TRNUM(0x3), + /* Clear transaction number and request */ + regmap_update_bits(xspi->regmap, XSPI_CDCTL0, + XSPI_CDCTL0_TRNUM(0x3) | XSPI_CDCTL0_TRREQ, XSPI_CDCTL0_TRNUM(0)); - regmap_update_bits(xspi->regmap, XSPI_CDCTL0, XSPI_CDCTL0_TRREQ, 0); - regmap_write(xspi->regmap, XSPI_CDTBUF0, - XSPI_CDTBUF_CMDSIZE(0x1) | XSPI_CDTBUF_CMD_FIELD(xspi->command)); - - regmap_write(xspi->regmap, XSPI_CDABUF0, 0); - - regmap_update_bits(xspi->regmap, XSPI_CDTBUF0, XSPI_CDTBUF_ADDSIZE(0x7), - XSPI_CDTBUF_ADDSIZE(xspi->addr_nbytes)); + XSPI_CDTBUF_CMDSIZE(0x1) | XSPI_CDTBUF_CMD_FIELD(xspi->command) | + XSPI_CDTBUF_ADDSIZE(xspi->addr_nbytes)); regmap_write(xspi->regmap, XSPI_CDABUF0, xspi->smadr); From c8d0e87bc2413c6a3f92c7130a4cbad51f8703da Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 27 Jul 2026 11:34:49 +0100 Subject: [PATCH 08/19] memory: renesas-rpc-if: Use runtime PM autosuspend after transfers Replace pm_runtime_put() with pm_runtime_put_autosuspend() after manual transfers and direct memory-mapped read/write operations. Flash page programming may be performed as a sequence of closely spaced transfer operations. Calling pm_runtime_put() after each operation allows the runtime PM core to suspend the controller between successive transfers, introducing unnecessary suspend/resume cycles and potentially interrupting multi-step transfer sequences. Use pm_runtime_put_autosuspend() instead so the controller remains active while transfers continue, allowing it to suspend only after an idle period. Signed-off-by: Lad Prabhakar Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260727103449.3188105-7-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/renesas-rpc-if.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/memory/renesas-rpc-if.c b/drivers/memory/renesas-rpc-if.c index d98991266887..ec901a5a99aa 100644 --- a/drivers/memory/renesas-rpc-if.c +++ b/drivers/memory/renesas-rpc-if.c @@ -776,7 +776,7 @@ int rpcif_manual_xfer(struct device *dev) ret = rpc->info->impl->manual_xfer(rpc); - pm_runtime_put(dev); + pm_runtime_put_autosuspend(dev); return ret; } @@ -891,7 +891,7 @@ ssize_t rpcif_dirmap_read(struct device *dev, u64 offs, size_t len, void *buf) read = rpc->info->impl->dirmap_read(rpc, offs, len, buf); - pm_runtime_put(dev); + pm_runtime_put_autosuspend(dev); return read; } @@ -948,7 +948,7 @@ ssize_t xspi_dirmap_write(struct device *dev, u64 offs, size_t len, const void * regmap_update_bits(xspi->regmap, XSPI_BMCTL1, XSPI_BMCTL1_MWRPUSH, XSPI_BMCTL1_MWRPUSH); - pm_runtime_put(dev); + pm_runtime_put_autosuspend(dev); return writebytes; } From e61c1a1103e2617e8f86288368911a8591e35c1e Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 15 Aug 2026 21:45:59 +0800 Subject: [PATCH 09/19] memory: brcmstb_dpfe: validate firmware section sizes The firmware header is read before the image has been shown to contain a complete header. In addition, the final size check adds two firmware-provided u32 section lengths before comparing the result with fw->size, so the addition can wrap. Reject images shorter than the fixed header and checksum before reading the header. Then derive the available payload length with subtraction and require the two declared sections to fill it exactly. Fixes: 2f330caff577 ("memory: brcmstb: Add driver for DPFE") Assisted-by: Codex:gpt-5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260815134559.47888-1-pengpeng@iscas.ac.cn Signed-off-by: Krzysztof Kozlowski --- drivers/memory/brcmstb_dpfe.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/memory/brcmstb_dpfe.c b/drivers/memory/brcmstb_dpfe.c index 7b02c36c7fca..04ae3cfb449e 100644 --- a/drivers/memory/brcmstb_dpfe.c +++ b/drivers/memory/brcmstb_dpfe.c @@ -533,9 +533,15 @@ static int __verify_firmware(struct init_data *init, const struct firmware *fw) { const struct dpfe_firmware_header *header = (void *)fw->data; - unsigned int dmem_size, imem_size, total_size; + unsigned int dmem_size, imem_size; bool is_big_endian = false; const u32 *chksum_ptr; + size_t payload_size; + + if (fw->size < sizeof(*header) + sizeof(*chksum_ptr)) + return ERR_INVALID_SIZE; + + payload_size = fw->size - sizeof(*header) - sizeof(*chksum_ptr); if (header->magic == DPFE_BE_MAGIC) is_big_endian = true; @@ -554,13 +560,8 @@ static int __verify_firmware(struct init_data *init, if ((dmem_size % sizeof(u32)) != 0 || (imem_size % sizeof(u32)) != 0) return ERR_INVALID_SIZE; - /* - * The header + the data section + the instruction section + the - * checksum must be equal to the total firmware size. - */ - total_size = dmem_size + imem_size + sizeof(*header) + - sizeof(*chksum_ptr); - if (total_size != fw->size) + /* The data and instruction sections must fill the payload exactly. */ + if (dmem_size > payload_size || imem_size != payload_size - dmem_size) return ERR_INVALID_SIZE; /* The checksum comes at the very end. */ From 8676f8e6dc8da769d5192135992a78853f9ea048 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Fri, 21 Aug 2026 09:44:12 +0200 Subject: [PATCH 10/19] memory: emif: Replace __ASSEMBLY__ with __ASSEMBLER__ in non-uapi headers While the GCC and Clang compilers already define __ASSEMBLER__ auto- matically when compiling assembly code, __ASSEMBLY__ is a macro that only gets defined by the Makefiles in the kernel. This can be very confusing when switching between userspace and kernelspace coding, or when dealing with uapi headers that rather should use __ASSEMBLER__ instead. Let's standardize now on the __ASSEMBLER__ macro to avoid this confusion. Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260821074412.144248-1-thuth@redhat.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/emif.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/memory/emif.h b/drivers/memory/emif.h index 55aeb36a5bf2..5dfb09adb096 100644 --- a/drivers/memory/emif.h +++ b/drivers/memory/emif.h @@ -558,7 +558,7 @@ #define EMIF_SRAM_AM33_REG_LAYOUT 0x00000000 #define EMIF_SRAM_AM43_REG_LAYOUT 0x00000001 -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ /* * Structure containing shadow of important registers in EMIF * The calculation function fills in this structure to be later used for @@ -603,5 +603,5 @@ void ti_emif_enter_sr(void); void ti_emif_exit_sr(void); void ti_emif_abort_sr(void); -#endif /* __ASSEMBLY__ */ +#endif /* __ASSEMBLER__ */ #endif /* __EMIF_H */ From cce46902ea9fcd585cd200fffa3166f63bc571f1 Mon Sep 17 00:00:00 2001 From: Jiawen Liu <1298662399@qq.com> Date: Fri, 4 Sep 2026 14:01:35 +0400 Subject: [PATCH 11/19] memory: exynos5422-dmc: handle clock enable failures Check the return values of clk_prepare_enable() for fout_bpll and mout_bpll. If enabling fout_bpll fails, propagate the error. If enabling mout_bpll fails, disable fout_bpll before returning. This keeps the clock enable count balanced on probe error paths. Fixes: 6e7674c3c6df ("memory: Add DMC driver for Exynos5422") Signed-off-by: Jiawen Liu <1298662399@qq.com> Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/tencent_B0189529F550458590E3BA614EAB800FBF08@qq.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/samsung/exynos5422-dmc.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/memory/samsung/exynos5422-dmc.c b/drivers/memory/samsung/exynos5422-dmc.c index 788d49c688b1..98c319552583 100644 --- a/drivers/memory/samsung/exynos5422-dmc.c +++ b/drivers/memory/samsung/exynos5422-dmc.c @@ -1297,8 +1297,15 @@ static int exynos5_dmc_init_clks(struct exynos5_dmc *dmc) if (ret) return ret; - clk_prepare_enable(dmc->fout_bpll); - clk_prepare_enable(dmc->mout_bpll); + ret = clk_prepare_enable(dmc->fout_bpll); + if (ret) + return ret; + + ret = clk_prepare_enable(dmc->mout_bpll); + if (ret) { + clk_disable_unprepare(dmc->fout_bpll); + return ret; + } /* * Some bootloaders do not set clock routes correctly. From edda28d5317521c2f5172f869d3a433a2d37f7a2 Mon Sep 17 00:00:00 2001 From: Ketan Patil Date: Thu, 3 Sep 2026 18:25:18 +0000 Subject: [PATCH 12/19] memory: tegra: Use named defines for Tegra264 MC apertures The Tegra264 memory controller error interrupts are distributed across several HUB/HUBC apertures, each accessed like a broadcast channel using a fixed index. These indices were kept in a bare mc_hubc_aperture_number[] array, and handle_hub_irq() took a parameter of the same name that shadowed it. This driver is only applicable to Tegra264 devices and so this abstraction is not needed. Replace the array with named TEGRA264_MC_APERTURE_* defines to simplify the code. No functional change intended. Signed-off-by: Ketan Patil Link: https://patch.msgid.link/20260903182520.950555-2-ketanp@nvidia.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/tegra264.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/drivers/memory/tegra/tegra264.c b/drivers/memory/tegra/tegra264.c index 790349295dcc..56e55269eed7 100644 --- a/drivers/memory/tegra/tegra264.c +++ b/drivers/memory/tegra/tegra264.c @@ -717,11 +717,15 @@ static const char *const tegra264_rt_error_names[16] = { }; /* - * MC instance aperture mapping for hubc registers + * On Tegra264 the memory controller error interrupts are distributed across + * several HUB/HUBC apertures. Each aperture is accessed like a broadcast + * channel through mc_ch_readl()/mc_ch_writel() using its fixed index. */ -static const int mc_hubc_aperture_number[5] = { - 7, 8, 9, 10, 11 -}; +#define TEGRA264_MC_APERTURE_DISP 7 +#define TEGRA264_MC_APERTURE_SYSTEM 8 +#define TEGRA264_MC_APERTURE_VISION 9 +#define TEGRA264_MC_APERTURE_UPHY 10 +#define TEGRA264_MC_APERTURE_TOP 11 /* * tegra264_mc_icc_set() - Pass MC client info to the BPMP-FW @@ -1057,27 +1061,27 @@ static irqreturn_t handle_hub_irq(int irq, void *data, int mc_hubc_aperture_numb static irqreturn_t handle_disp_hub_irq(int irq, void *data) { - return handle_hub_irq(irq, data, mc_hubc_aperture_number[0]); + return handle_hub_irq(irq, data, TEGRA264_MC_APERTURE_DISP); } static irqreturn_t handle_system_hub_irq(int irq, void *data) { - return handle_hub_irq(irq, data, mc_hubc_aperture_number[1]); + return handle_hub_irq(irq, data, TEGRA264_MC_APERTURE_SYSTEM); } static irqreturn_t handle_vision_hub_irq(int irq, void *data) { - return handle_hub_irq(irq, data, mc_hubc_aperture_number[2]); + return handle_hub_irq(irq, data, TEGRA264_MC_APERTURE_VISION); } static irqreturn_t handle_uphy_hub_irq(int irq, void *data) { - return handle_hub_irq(irq, data, mc_hubc_aperture_number[3]); + return handle_hub_irq(irq, data, TEGRA264_MC_APERTURE_UPHY); } static irqreturn_t handle_top_hub_irq(int irq, void *data) { - return handle_hub_irq(irq, data, mc_hubc_aperture_number[4]); + return handle_hub_irq(irq, data, TEGRA264_MC_APERTURE_TOP); } static irqreturn_t handle_generic_irq(struct tegra_mc *mc, unsigned long intstat_reg) From fb8af372f899690646444cd0b38a88ec52568cbc Mon Sep 17 00:00:00 2001 From: Ketan Patil Date: Thu, 3 Sep 2026 18:25:19 +0000 Subject: [PATCH 13/19] memory: tegra: Parameterise Tegra264 MCF slice bound Replace the hard-coded slice bound check in handle_mcf_irq() with a TEGRA264_MC_NUM_SLICES define, so the number of MCF slices is named rather than being an unexplained literal. Note that although the existing logic is correct, the actual number of MCF slices for Tegra264 is 5 and not 4. So define TEGRA264_MC_NUM_SLICES as 5 and update the logic accordingly. No functional change intended. Signed-off-by: Ketan Patil Link: https://patch.msgid.link/20260903182520.950555-3-ketanp@nvidia.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/tegra264.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/memory/tegra/tegra264.c b/drivers/memory/tegra/tegra264.c index 56e55269eed7..b40275ae382e 100644 --- a/drivers/memory/tegra/tegra264.c +++ b/drivers/memory/tegra/tegra264.c @@ -727,6 +727,9 @@ static const char *const tegra264_rt_error_names[16] = { #define TEGRA264_MC_APERTURE_UPHY 10 #define TEGRA264_MC_APERTURE_TOP 11 +/* Tegra264 splits the MCF common interrupt status across five slices. */ +#define TEGRA264_MC_NUM_SLICES 5 + /* * tegra264_mc_icc_set() - Pass MC client info to the BPMP-FW * @src: ICC node for Memory Controller's (MC) Client @@ -941,7 +944,7 @@ static irqreturn_t handle_mcf_irq(int irq, void *data) for_each_set_bit(slice, &common_intstat, 32) { /* Find out the slice number on which interrupt occurred */ - if (slice > 4) { + if (slice >= TEGRA264_MC_NUM_SLICES) { dev_err(mc->dev, "Slice index out of bounds: %u\n", slice); return IRQ_NONE; } From f4e16143420f4021431cc652ebd71e176267ecbc Mon Sep 17 00:00:00 2001 From: Ketan Patil Date: Thu, 3 Sep 2026 18:25:20 +0000 Subject: [PATCH 14/19] memory: tegra: Refactor Tegra264 mc-err code The MC and HUB error-logging paths on Tegra264 used large switch statements that interleaved register offsets, bit positions and decoding parameters for each error type, which made them hard to read and review. Turn the error logging into a more structured implementation: - Describe each MCF error type with a tegra264_mc_fault descriptor filled in by a small per-type get_offsets() callback, and move the register access, client lookup and reporting into a common tegra264_mc_report_fault() helper. - Describe each HUB error type with a static tegra264_hub_error_handler table, since the HUB register offsets are fixed on Tegra264. No functional change intended. Signed-off-by: Ketan Patil Link: https://patch.msgid.link/20260903182520.950555-4-ketanp@nvidia.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/tegra264.c | 365 ++++++++++++++++++++------------ 1 file changed, 228 insertions(+), 137 deletions(-) diff --git a/drivers/memory/tegra/tegra264.c b/drivers/memory/tegra/tegra264.c index b40275ae382e..57e6395861ba 100644 --- a/drivers/memory/tegra/tegra264.c +++ b/drivers/memory/tegra/tegra264.c @@ -15,6 +15,47 @@ #include "mc.h" #include "tegra264-bwmgr.h" +/* + * Description of a single MCF error type: where to read the status and the + * faulting address, how to extract the high address bits, which bits encode + * the access direction and security state and the optional error-type sub-code. + */ +struct tegra264_mc_fault { + const char *const *type_names; + u32 addr_hi_mask; + u32 addr_hi_reg; + u32 addr_hi_shift; + u32 addr_reg; + u32 rw_bit; + u32 sec_bit; + u32 status1_reg; + u32 status_reg; + u32 type_mask; + u32 type_shift; +}; + +/* + * Maps an MCF interrupt to a callback that fills in the register offsets and + * decoding parameters for that error type. The register offsets live in the + * per-SoC tegra_mc_regs, so they are collected at runtime rather than being + * stored in a static table. + */ +struct tegra264_mc_error_handler { + u32 mask; + void (*get_offsets)(struct tegra_mc *mc, struct tegra264_mc_fault *fault); +}; + +/* + * Description of a single HUB error type. addr_reg is zero for error types + * that don't latch a fault address. + */ +struct tegra264_hub_error_handler { + u32 addr_hi_reg; + u32 addr_reg; + u32 mask; + u32 status_reg; +}; + /* * MC Client entries are sorted in the increasing order of the * override and security register offsets. @@ -814,115 +855,149 @@ static int tegra264_mc_icc_get_init_bw(struct icc_node *node, u32 *avg, u32 *pea return 0; } +static void tegra264_mc_fault_emem(struct tegra_mc *mc, struct tegra264_mc_fault *fault) +{ + fault->status_reg = mc->soc->regs->err_status; + fault->addr_reg = mc->soc->regs->err_add; + fault->addr_hi_reg = mc->soc->regs->err_add_hi; + fault->type_mask = mc->soc->mc_err_status_type_mask; + fault->type_shift = MC_ERR_STATUS_TYPE_SHIFT; + fault->type_names = tegra264_mc_error_names; +} + +static void tegra264_mc_fault_vpr(struct tegra_mc *mc, struct tegra264_mc_fault *fault) +{ + fault->status_reg = mc->soc->regs->err_vpr_status; + fault->addr_reg = mc->soc->regs->err_vpr_add; + fault->addr_hi_shift = MC_ERR_STATUS_ADR_HI_SHIFT; + fault->addr_hi_mask = mc->soc->mc_addr_hi_mask; +} + +static void tegra264_mc_fault_sec(struct tegra_mc *mc, struct tegra264_mc_fault *fault) +{ + fault->status_reg = mc->soc->regs->err_sec_status; + fault->addr_reg = mc->soc->regs->err_sec_add; + fault->addr_hi_shift = MC_ERR_STATUS_ADR_HI_SHIFT; + fault->addr_hi_mask = mc->soc->mc_addr_hi_mask; +} + +static void tegra264_mc_fault_mts(struct tegra_mc *mc, struct tegra264_mc_fault *fault) +{ + fault->status_reg = mc->soc->regs->err_mts_status; + fault->addr_reg = mc->soc->regs->err_mts_add; + fault->addr_hi_shift = MC_ERR_STATUS_ADR_HI_SHIFT; + fault->addr_hi_mask = mc->soc->mc_addr_hi_mask; +} + +static void tegra264_mc_fault_gsc(struct tegra_mc *mc, struct tegra264_mc_fault *fault) +{ + fault->status_reg = mc->soc->regs->err_gen_co_status; + fault->status1_reg = MC_ERR_GENERALIZED_CARVEOUT_STATUS_1_0; + fault->addr_reg = mc->soc->regs->err_gen_co_add; + fault->addr_hi_shift = MC_ERR_STATUS_GSC_ADR_HI_SHIFT; + fault->addr_hi_mask = MC_ERR_STATUS_GSC_ADR_HI_MASK; +} + +static void tegra264_mc_fault_route(struct tegra_mc *mc, struct tegra264_mc_fault *fault) +{ + fault->status_reg = mc->soc->regs->err_route_status; + fault->addr_reg = mc->soc->regs->err_route_add; + fault->addr_hi_shift = MC_ERR_STATUS_RT_ADR_HI_SHIFT; + fault->addr_hi_mask = mc->soc->mc_addr_hi_mask; + fault->rw_bit = MC_ERR_ROUTE_SANITY_RW; + fault->sec_bit = MC_ERR_ROUTE_SANITY_SEC; + fault->type_mask = MC_ERR_STATUS_RT_TYPE_MASK; + fault->type_shift = MC_ERR_STATUS_RT_TYPE_SHIFT; + fault->type_names = tegra264_rt_error_names; +} + +static const struct tegra264_mc_error_handler tegra264_mc_error_handlers[] = { + { MC_INT_DECERR_EMEM, tegra264_mc_fault_emem }, + { MC_INT_SECURITY_VIOLATION, tegra264_mc_fault_emem }, + { MC_INT_DECERR_VPR, tegra264_mc_fault_vpr }, + { MC_INT_SECERR_SEC, tegra264_mc_fault_sec }, + { MC_INT_DECERR_MTS, tegra264_mc_fault_mts }, + { MC_INT_DECERR_GENERALIZED_CARVEOUT, tegra264_mc_fault_gsc }, + { MC_INT_DECERR_ROUTE_SANITY, tegra264_mc_fault_route }, + { MC_INT_DECERR_ROUTE_SANITY_GIC_MSI, tegra264_mc_fault_route }, +}; + +static void tegra264_mc_report_fault(struct tegra_mc *mc, u32 channel, + unsigned int interrupt, + const struct tegra264_mc_fault *fault) +{ + const char *client = "unknown", *desc = "NA"; + u32 value, client_id, status1 = 0; + phys_addr_t addr = 0; + unsigned int i; + u8 type; + + value = mc_ch_readl(mc, channel, fault->status_reg); + + if (fault->addr_hi_reg) { + addr = mc_ch_readl(mc, channel, fault->addr_hi_reg); + } else if (fault->status1_reg) { + status1 = mc_ch_readl(mc, channel, fault->status1_reg); + addr = (status1 >> fault->addr_hi_shift) & fault->addr_hi_mask; + } else { + addr = (value >> fault->addr_hi_shift) & fault->addr_hi_mask; + } + + addr <<= 32; + addr |= mc_ch_readl(mc, channel, fault->addr_reg); + + client_id = value & mc->soc->client_id_mask; + for (i = 0; i < mc->soc->num_clients; i++) { + if (mc->soc->clients[i].id == client_id) { + client = mc->soc->clients[i].name; + break; + } + } + + if (fault->type_names) { + type = (value & fault->type_mask) >> fault->type_shift; + desc = fault->type_names[type]; + } + + dev_err_ratelimited(mc->dev, "%s: %s %s @%pa: %s (%s)\n", + client, value & fault->sec_bit ? "secure" : "non-secure", + value & fault->rw_bit ? "write" : "read", &addr, + tegra_mc_status_names[interrupt] ?: "unknown", desc); + + if (fault->status1_reg) + dev_err_ratelimited(mc->dev, "gsc_apr_id=%u gsc_co_apr_id=%u\n", + (status1 >> ERR_GENERALIZED_APERTURE_ID_SHIFT) & + ERR_GENERALIZED_APERTURE_ID_MASK, + (status1 >> ERR_GENERALIZED_CARVEOUT_APERTURE_ID_SHIFT) & + ERR_GENERALIZED_CARVEOUT_APERTURE_ID_MASK); +} + static void mcf_log_fault(struct tegra_mc *mc, u32 channel, unsigned long mcf_ch_intstatus) { - unsigned int bit; + unsigned int interrupt; - for_each_set_bit(bit, &mcf_ch_intstatus, 32) { - const char *client = "unknown", *desc = "NA"; - u32 status_reg, status1_reg = 0, addr_reg, addr_hi_reg = 0, err_type_mask = 0; - u32 value, client_id, i, addr_hi_shift = 0, addr_hi_mask = 0, status1; - u32 mc_rw_bit = MC_ERR_STATUS_RW, mc_sec_bit = MC_ERR_STATUS_SECURITY; - phys_addr_t addr = 0; - u8 type; + for_each_set_bit(interrupt, &mcf_ch_intstatus, 32) { + const struct tegra264_mc_error_handler *handler = NULL; + struct tegra264_mc_fault fault = { + .rw_bit = MC_ERR_STATUS_RW, + .sec_bit = MC_ERR_STATUS_SECURITY, + }; + unsigned int i; - switch (BIT(bit)) { - case MC_INT_DECERR_EMEM: - case MC_INT_SECURITY_VIOLATION: - status_reg = mc->soc->regs->err_status; - addr_reg = mc->soc->regs->err_add; - addr_hi_reg = mc->soc->regs->err_add_hi; - err_type_mask = mc->soc->mc_err_status_type_mask; - break; - - case MC_INT_DECERR_VPR: - status_reg = mc->soc->regs->err_vpr_status; - addr_reg = mc->soc->regs->err_vpr_add; - addr_hi_shift = MC_ERR_STATUS_ADR_HI_SHIFT; - addr_hi_mask = mc->soc->mc_addr_hi_mask; - break; - - case MC_INT_SECERR_SEC: - status_reg = mc->soc->regs->err_sec_status; - addr_reg = mc->soc->regs->err_sec_add; - addr_hi_shift = MC_ERR_STATUS_ADR_HI_SHIFT; - addr_hi_mask = mc->soc->mc_addr_hi_mask; - break; - - case MC_INT_DECERR_MTS: - status_reg = mc->soc->regs->err_mts_status; - addr_reg = mc->soc->regs->err_mts_add; - addr_hi_shift = MC_ERR_STATUS_ADR_HI_SHIFT; - addr_hi_mask = mc->soc->mc_addr_hi_mask; - break; - - case MC_INT_DECERR_GENERALIZED_CARVEOUT: - status_reg = mc->soc->regs->err_gen_co_status; - status1_reg = MC_ERR_GENERALIZED_CARVEOUT_STATUS_1_0; - addr_reg = mc->soc->regs->err_gen_co_add; - addr_hi_shift = MC_ERR_STATUS_GSC_ADR_HI_SHIFT; - addr_hi_mask = MC_ERR_STATUS_GSC_ADR_HI_MASK; - break; - - case MC_INT_DECERR_ROUTE_SANITY: - case MC_INT_DECERR_ROUTE_SANITY_GIC_MSI: - status_reg = mc->soc->regs->err_route_status; - addr_reg = mc->soc->regs->err_route_add; - addr_hi_shift = MC_ERR_STATUS_RT_ADR_HI_SHIFT; - addr_hi_mask = mc->soc->mc_addr_hi_mask; - mc_sec_bit = MC_ERR_ROUTE_SANITY_SEC; - mc_rw_bit = MC_ERR_ROUTE_SANITY_RW; - err_type_mask = MC_ERR_STATUS_RT_TYPE_MASK; - break; - - default: - dev_err_ratelimited(mc->dev, "Incorrect MC interrupt mask\n"); - return; - } - - value = mc_ch_readl(mc, channel, status_reg); - if (addr_hi_reg) { - addr = mc_ch_readl(mc, channel, addr_hi_reg); - } else { - if (!status1_reg) { - addr = ((value >> addr_hi_shift) & addr_hi_mask); - } else { - status1 = mc_ch_readl(mc, channel, status1_reg); - addr = ((status1 >> addr_hi_shift) & addr_hi_mask); - } - } - - addr <<= 32; - addr |= mc_ch_readl(mc, channel, addr_reg); - - client_id = value & mc->soc->client_id_mask; - for (i = 0; i < mc->soc->num_clients; i++) { - if (mc->soc->clients[i].id == client_id) { - client = mc->soc->clients[i].name; + for (i = 0; i < ARRAY_SIZE(tegra264_mc_error_handlers); i++) { + if (tegra264_mc_error_handlers[i].mask == BIT(interrupt)) { + handler = &tegra264_mc_error_handlers[i]; break; } } - if (err_type_mask == MC_ERR_STATUS_RT_TYPE_MASK) { - type = (value & err_type_mask) >> - MC_ERR_STATUS_RT_TYPE_SHIFT; - desc = tegra264_rt_error_names[type]; - } else if (err_type_mask) { - type = (value & err_type_mask) >> - MC_ERR_STATUS_TYPE_SHIFT; - desc = tegra264_mc_error_names[type]; + if (!handler) { + dev_err_ratelimited(mc->dev, "Incorrect MC interrupt mask\n"); + return; } - dev_err_ratelimited(mc->dev, "%s: %s %s @%pa: %s (%s)\n", - client, value & mc_sec_bit ? "secure" : "non-secure", - value & mc_rw_bit ? "write" : "read", &addr, - tegra_mc_status_names[bit] ?: "unknown", desc); - if (status1_reg) - dev_err_ratelimited(mc->dev, "gsc_apr_id=%u gsc_co_apr_id=%u\n", - ((status1 >> ERR_GENERALIZED_APERTURE_ID_SHIFT) - & ERR_GENERALIZED_APERTURE_ID_MASK), - ((status1 >> ERR_GENERALIZED_CARVEOUT_APERTURE_ID_SHIFT) - & ERR_GENERALIZED_CARVEOUT_APERTURE_ID_MASK)); + handler->get_offsets(mc, &fault); + tegra264_mc_report_fault(mc, channel, interrupt, &fault); } /* clear interrupts */ @@ -957,53 +1032,69 @@ static irqreturn_t handle_mcf_irq(int irq, void *data) return IRQ_HANDLED; } +static const struct tegra264_hub_error_handler tegra264_hub_error_handlers[] = { + { + .mask = MSS_HUB_COALESCER_ERR_INTMASK, + .status_reg = MSS_HUB_COALESCE_ERR_STATUS_0, + .addr_reg = MSS_HUB_COALESCE_ERR_ADR_0, + .addr_hi_reg = MSS_HUB_COALESCE_ERR_ADR_HI_0, + }, + { + .mask = MSS_HUB_SMMU_BYPASS_ALLOW_ERR_INTMASK, + .status_reg = MSS_HUB_SMMU_BYPASS_ALLOW_ERR_STATUS_0, + }, + { + .mask = MSS_HUB_ILLEGAL_TBUGRP_ID_INTMASK, + .status_reg = MSS_HUB_ILLEGAL_TBUGRP_ID_ERR_STATUS_0, + }, + { + .mask = MSS_HUB_MSI_ERR_INTMASK, + .status_reg = MSS_HUB_MSI_ERR_STATUS_0, + }, + { + .mask = MSS_HUB_POISON_RSP_INTMASK, + .status_reg = MSS_HUB_POISON_RSP_STATUS_0, + }, + { + .mask = MSS_HUB_RESTRICTED_ACCESS_ERR_INTMASK, + .status_reg = MSS_HUB_RESTRICTED_ACCESS_ERR_STATUS_0, + }, + { + .mask = MSS_HUB_RESERVED_PA_ERR_INTMASK, + .status_reg = MSS_HUB_RESERVED_PA_ERR_STATUS_0, + }, +}; + static void hub_log_fault(struct tegra_mc *mc, u32 hub, unsigned long hub_intstat) { - unsigned int bit; + unsigned int interrupt; - for_each_set_bit(bit, &hub_intstat, 32) { + for_each_set_bit(interrupt, &hub_intstat, 32) { + const struct tegra264_hub_error_handler *handler = NULL; const char *client = "unknown"; - u32 client_id, status_reg, value, i; + u32 client_id, value; phys_addr_t addr = 0; + unsigned int i; - switch (BIT(bit)) { - case MSS_HUB_COALESCER_ERR_INTMASK: - status_reg = MSS_HUB_COALESCE_ERR_STATUS_0; - addr = mc_ch_readl(mc, hub, MSS_HUB_COALESCE_ERR_ADR_HI_0); - addr <<= 32; - addr |= mc_ch_readl(mc, hub, MSS_HUB_COALESCE_ERR_ADR_0); - break; + for (i = 0; i < ARRAY_SIZE(tegra264_hub_error_handlers); i++) { + if (tegra264_hub_error_handlers[i].mask == BIT(interrupt)) { + handler = &tegra264_hub_error_handlers[i]; + break; + } + } - case MSS_HUB_SMMU_BYPASS_ALLOW_ERR_INTMASK: - status_reg = MSS_HUB_SMMU_BYPASS_ALLOW_ERR_STATUS_0; - break; - - case MSS_HUB_ILLEGAL_TBUGRP_ID_INTMASK: - status_reg = MSS_HUB_ILLEGAL_TBUGRP_ID_ERR_STATUS_0; - break; - - case MSS_HUB_MSI_ERR_INTMASK: - status_reg = MSS_HUB_MSI_ERR_STATUS_0; - break; - - case MSS_HUB_POISON_RSP_INTMASK: - status_reg = MSS_HUB_POISON_RSP_STATUS_0; - break; - - case MSS_HUB_RESTRICTED_ACCESS_ERR_INTMASK: - status_reg = MSS_HUB_RESTRICTED_ACCESS_ERR_STATUS_0; - break; - - case MSS_HUB_RESERVED_PA_ERR_INTMASK: - status_reg = MSS_HUB_RESERVED_PA_ERR_STATUS_0; - break; - - default: + if (!handler) { dev_err_ratelimited(mc->dev, "Incorrect HUB interrupt mask\n"); return; } - value = mc_ch_readl(mc, hub, status_reg); + if (handler->addr_reg) { + addr = mc_ch_readl(mc, hub, handler->addr_hi_reg); + addr <<= 32; + addr |= mc_ch_readl(mc, hub, handler->addr_reg); + } + + value = mc_ch_readl(mc, hub, handler->status_reg); client_id = value & mc->soc->client_id_mask; for (i = 0; i < mc->soc->num_clients; i++) { @@ -1014,8 +1105,8 @@ static void hub_log_fault(struct tegra_mc *mc, u32 hub, unsigned long hub_intsta } dev_err_ratelimited(mc->dev, "%s: @%pa: %s status: 0x%x\n", - client, &addr, tegra264_hub_error_names[bit] ?: "unknown", - value); + client, &addr, + tegra264_hub_error_names[interrupt] ?: "unknown", value); } /* clear interrupts */ From 314ef67d673284d4c070455bd62d332f6d0f8634 Mon Sep 17 00:00:00 2001 From: Miles Krause Date: Wed, 9 Sep 2026 19:26:26 -0400 Subject: [PATCH 15/19] memory: emif: fix device_node reference leak on np_ddr of_get_memory_device_details() takes a reference on the DDR device node via of_parse_phandle() and stores it in emif->np_ddr for later use, but drops that reference without calling of_node_put() on two error paths: the devm_kzalloc() allocation-failure check and the is_dev_data_valid() check. Both return NULL immediately, leaking the node reference each time either failure triggers. The same reference is also never released on the success path: it is kept for the life of the bound device (compared against a later instance's np_ddr in the duplicate-DDR-geometry check) but emif_remove() never calls of_node_put() on it, leaking it every time an EMIF device unbinds. of_node_put() on a NULL pointer is a no-op, so this is also safe for the non-DT get_device_details() path, where np_ddr is left NULL by devm_kzalloc(). Fixes: e6b42eb6a66c ("memory: emif: add device tree support to emif driver") Signed-off-by: Miles Krause Link: https://patch.msgid.link/20260909-master-v1-1-8800f5647ab2@gmail.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/emif.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/memory/emif.c b/drivers/memory/emif.c index 2fadad0666b1..44ccc2561835 100644 --- a/drivers/memory/emif.c +++ b/drivers/memory/emif.c @@ -922,6 +922,7 @@ static struct emif_data *of_get_memory_device_details( if (!emif || !pd || !dev_info) { dev_err(dev, "%s: Out of memory!!\n", __func__); + of_node_put(np_ddr); goto error; } @@ -946,6 +947,7 @@ static struct emif_data *of_get_memory_device_details( pd->device_info->io_width, pd->phy_type, pd->ip_rev, emif->dev)) { dev_err(dev, "%s: invalid device data!!\n", __func__); + of_node_put(np_ddr); goto error; } /* @@ -1140,6 +1142,7 @@ static void emif_remove(struct platform_device *pdev) struct emif_data *emif = platform_get_drvdata(pdev); emif_debugfs_exit(emif); + of_node_put(emif->np_ddr); } static void emif_shutdown(struct platform_device *pdev) From 605e6053de5c639abe4cba71644cd3c5fae46374 Mon Sep 17 00:00:00 2001 From: Zhengnan Chen Date: Mon, 31 Aug 2026 14:52:06 +0800 Subject: [PATCH 16/19] dt-bindings: memory-controllers: mtk-smi: Add support for mt8189 Add the SMI LARB, common and sub-common binding descriptions for MT8189. MT8189 adds an SMI sub-common block. Document this topology in mediatek,smi-common.yaml. Signed-off-by: Zhengnan Chen Signed-off-by: Congcong Yao Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260831-8189-smi-support-v4-1-8ae66dd3eeed@mediatek.com Signed-off-by: Krzysztof Kozlowski --- .../mediatek,smi-common.yaml | 21 ++++++++++++++++++- .../memory-controllers/mediatek,smi-larb.yaml | 3 +++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/memory-controllers/mediatek,smi-common.yaml b/Documentation/devicetree/bindings/memory-controllers/mediatek,smi-common.yaml index 0762e0ff66ef..33c989f7fc33 100644 --- a/Documentation/devicetree/bindings/memory-controllers/mediatek,smi-common.yaml +++ b/Documentation/devicetree/bindings/memory-controllers/mediatek,smi-common.yaml @@ -16,7 +16,7 @@ description: | MediaTek SMI have two generations of HW architecture, here is the list which generation the SoCs use: generation 1: mt2701 and mt7623. - generation 2: mt2712, mt6779, mt8167, mt8173, mt8183, mt8186, mt8188, mt8192 and mt8195. + generation 2: mt2712, mt6779, mt8167, mt8173, mt8183, mt8186, mt8188, mt8189, mt8192 and mt8195. There's slight differences between the two SMI, for generation 2, the register which control the iommu port is at each larb's register base. But @@ -25,6 +25,21 @@ description: | SMI generation 1 to transform the smi clock into emi clock domain, but that is not needed for SMI generation 2. + The smi-common connects with smi-larb and IOMMU. The maximum inputs number of + a smi-common is 8. In SMI generation 2, the engines number may be over 8. + In this case, we use a smi-sub-common to merge some larbs. + The block diagram something is like: + + IOMMU + | | + smi-common + --------------------------- + | | ... + larb0 sub-common ... <-max number is 8 + ---------------- + | | ... + larb1 larbX ... <-max number is 8 + properties: compatible: oneOf: @@ -40,6 +55,8 @@ properties: - mediatek,mt8186-smi-common - mediatek,mt8188-smi-common-vdo - mediatek,mt8188-smi-common-vpp + - mediatek,mt8189-smi-common + - mediatek,mt8189-smi-sub-common - mediatek,mt8192-smi-common - mediatek,mt8195-smi-common-vdo - mediatek,mt8195-smi-common-vpp @@ -108,6 +125,7 @@ allOf: compatible: contains: enum: + - mediatek,mt8189-smi-sub-common - mediatek,mt8195-smi-sub-common then: required: @@ -157,6 +175,7 @@ allOf: - mediatek,mt6795-smi-common - mediatek,mt8167-smi-common - mediatek,mt8173-smi-common + - mediatek,mt8189-smi-common then: properties: diff --git a/Documentation/devicetree/bindings/memory-controllers/mediatek,smi-larb.yaml b/Documentation/devicetree/bindings/memory-controllers/mediatek,smi-larb.yaml index 2e7fac4b5094..9a5dafd7c07e 100644 --- a/Documentation/devicetree/bindings/memory-controllers/mediatek,smi-larb.yaml +++ b/Documentation/devicetree/bindings/memory-controllers/mediatek,smi-larb.yaml @@ -27,6 +27,7 @@ properties: - mediatek,mt8183-smi-larb - mediatek,mt8186-smi-larb - mediatek,mt8188-smi-larb + - mediatek,mt8189-smi-larb - mediatek,mt8192-smi-larb - mediatek,mt8195-smi-larb @@ -85,6 +86,7 @@ allOf: - mediatek,mt8183-smi-larb - mediatek,mt8186-smi-larb - mediatek,mt8188-smi-larb + - mediatek,mt8189-smi-larb - mediatek,mt8195-smi-larb then: @@ -119,6 +121,7 @@ allOf: - mediatek,mt6779-smi-larb - mediatek,mt8186-smi-larb - mediatek,mt8188-smi-larb + - mediatek,mt8189-smi-larb - mediatek,mt8192-smi-larb - mediatek,mt8195-smi-larb From 771c036d72eefe36aa6c953ecf44bee4c000a368 Mon Sep 17 00:00:00 2001 From: Zhengnan Chen Date: Mon, 31 Aug 2026 14:52:07 +0800 Subject: [PATCH 17/19] memory: mtk-smi: Add MT8189 support Add the MT8189 SMI common, sub-common and LARB platform data. Enable the GALS clock for the MT8189 SMI sub-common. Signed-off-by: Zhengnan Chen Signed-off-by: Congcong Yao Reviewed-by: Yong Wu Link: https://patch.msgid.link/20260831-8189-smi-support-v4-2-8ae66dd3eeed@mediatek.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/mtk-smi.c | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/drivers/memory/mtk-smi.c b/drivers/memory/mtk-smi.c index aaeba8ab211e..8c4606cbd73d 100644 --- a/drivers/memory/mtk-smi.c +++ b/drivers/memory/mtk-smi.c @@ -401,6 +401,30 @@ static const u8 mtk_smi_larb_mt8188_ostd[][SMI_LARB_PORT_NR_MAX] = { [25] = {0x01}, }; +static const u8 mtk_smi_larb_mt8189_ostd[][SMI_LARB_PORT_NR_MAX] = { + [0] = {0x8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10,}, + [1] = {0x8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10,}, + [2] = {0x7, 0x7, 0x4, 0x4, 0x1, 0x1, 0x2, 0x2, 0x7, 0x7,}, + [4] = {0x2f, 0x1e, 0x9, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x5, 0x1, 0x17,}, + [7] = {0x20, 0x2, 0x1, 0x1, 0x1, 0x4, 0x2, 0x1, 0x1, 0x2, 0x3, 0x2, + 0xa, 0xf, 0x4, 0x6, 0x5, 0x1,}, + [9] = {0x6, 0x3, 0xc, 0x6, 0x1, 0x4, 0x3, 0x1, 0x2, 0x4, 0x5, 0x2, + 0x4, 0x2, 0x3, 0xb, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1,}, + [11] = {0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0xb, 0x1, 0x4, 0x6, 0x5, 0x6, 0x1, 0x5, 0x2, + 0x9, 0x5,}, + [13] = {0x2, 0x8, 0x8, 0x8, 0x4, 0x4, 0x4, 0x4, 0x4, 0xe, 0x4, 0x1, + 0x6, 0x6, 0x2,}, + [14] = {0x1, 0x1, 0x1, 0x20, 0xe, 0x4, 0x8, 0x8, 0x6, 0x4,}, + [16] = {0x1e, 0xc, 0x2, 0x8, 0xe, 0x2, 0x1e, 0x10, 0x4, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x4, 0x2, 0x4,}, + [17] = {0x1e, 0xc, 0x2, 0x8, 0xe, 0x2, 0x1e, 0x10, 0x4, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x4, 0x2, 0x4,}, + [19] = {0x2, 0x1, 0x3, 0x1,}, + [20] = {0x7, 0x7, 0x3, 0x3, 0x1, 0x1,}, +}; + static const u8 mtk_smi_larb_mt8192_ostd[][SMI_LARB_PORT_NR_MAX] = { [0] = {0x2, 0x2, 0x28, 0xa, 0xc, 0x28,}, [1] = {0x2, 0x2, 0x18, 0x18, 0x18, 0xa, 0xc, 0x28,}, @@ -533,6 +557,13 @@ static const struct mtk_smi_larb_gen mtk_smi_larb_mt8188 = { .ostd = mtk_smi_larb_mt8188_ostd, }; +static const struct mtk_smi_larb_gen mtk_smi_larb_mt8189 = { + .config_port = mtk_smi_larb_config_port_gen2_general, + .flags_general = MTK_SMI_FLAG_THRT_UPDATE | MTK_SMI_FLAG_SW_FLAG | + MTK_SMI_FLAG_SLEEP_CTL | MTK_SMI_FLAG_CFG_PORT_SEC_CTL, + .ostd = mtk_smi_larb_mt8189_ostd, +}; + static const struct mtk_smi_larb_gen mtk_smi_larb_mt8192 = { .config_port = mtk_smi_larb_config_port_gen2_general, .ostd = mtk_smi_larb_mt8192_ostd, @@ -556,6 +587,7 @@ static const struct of_device_id mtk_smi_larb_of_ids[] = { {.compatible = "mediatek,mt8183-smi-larb", .data = &mtk_smi_larb_mt8183}, {.compatible = "mediatek,mt8186-smi-larb", .data = &mtk_smi_larb_mt8186}, {.compatible = "mediatek,mt8188-smi-larb", .data = &mtk_smi_larb_mt8188}, + {.compatible = "mediatek,mt8189-smi-larb", .data = &mtk_smi_larb_mt8189}, {.compatible = "mediatek,mt8192-smi-larb", .data = &mtk_smi_larb_mt8192}, {.compatible = "mediatek,mt8195-smi-larb", .data = &mtk_smi_larb_mt8195}, {} @@ -808,6 +840,17 @@ static const struct mtk_smi_common_plat mtk_smi_common_mt8188_vpp = { .init = mtk_smi_common_mt8195_init, }; +static const struct mtk_smi_common_plat mtk_smi_common_mt8189 = { + .type = MTK_SMI_GEN2, + .bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(3) | F_MMU1_LARB(5) | + F_MMU1_LARB(7), +}; + +static const struct mtk_smi_common_plat mtk_smi_sub_common_mt8189 = { + .type = MTK_SMI_GEN2_SUB_COMM, + .has_gals = true, +}; + static const struct mtk_smi_common_plat mtk_smi_common_mt8192 = { .type = MTK_SMI_GEN2, .has_gals = true, @@ -852,6 +895,8 @@ static const struct of_device_id mtk_smi_common_of_ids[] = { {.compatible = "mediatek,mt8186-smi-common", .data = &mtk_smi_common_mt8186}, {.compatible = "mediatek,mt8188-smi-common-vdo", .data = &mtk_smi_common_mt8188_vdo}, {.compatible = "mediatek,mt8188-smi-common-vpp", .data = &mtk_smi_common_mt8188_vpp}, + {.compatible = "mediatek,mt8189-smi-common", .data = &mtk_smi_common_mt8189}, + {.compatible = "mediatek,mt8189-smi-sub-common", .data = &mtk_smi_sub_common_mt8189}, {.compatible = "mediatek,mt8192-smi-common", .data = &mtk_smi_common_mt8192}, {.compatible = "mediatek,mt8195-smi-common-vdo", .data = &mtk_smi_common_mt8195_vdo}, {.compatible = "mediatek,mt8195-smi-common-vpp", .data = &mtk_smi_common_mt8195_vpp}, From e3049c9edd4181e5ce4f83bbac4b611d921f45ff Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:12:54 -0700 Subject: [PATCH 18/19] memory: emif: Remove const from min_tck allocation type In preparation for making the devm_kmalloc family of allocators type aware, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (Before, the allocator would always return "void *", which can be implicitly cast to any pointer type.) The size was taken from "*pd->min_tck", so the converted allocation type would be "const struct lpddr2_min_tck *", as "pd->min_tck" points to const. The allocation is a writable copy assigned to a "void *" and then memcpy()ed into, so name the unqualified type directly, for a converted allocation type of "struct lpddr2_min_tck *". As there is no general way to remove const qualifiers, this keeps the allocation type matching how the memory is used. No change in allocation size results. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: drivers/memory/emif.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Link: https://patch.msgid.link/20260917211253.i.532-kees@kernel.org Signed-off-by: Krzysztof Kozlowski --- drivers/memory/emif.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/memory/emif.c b/drivers/memory/emif.c index 44ccc2561835..886c2ca0dc19 100644 --- a/drivers/memory/emif.c +++ b/drivers/memory/emif.c @@ -1064,7 +1064,8 @@ static struct emif_data *get_device_details( } if (pd->min_tck) { - temp = devm_kzalloc(dev, sizeof(*pd->min_tck), GFP_KERNEL); + temp = devm_kzalloc(dev, sizeof(struct lpddr2_min_tck), + GFP_KERNEL); if (temp) { memcpy(temp, pd->min_tck, sizeof(*pd->min_tck)); pd->min_tck = temp; From a22355280361d2a376a2020059a2bae11e6cea10 Mon Sep 17 00:00:00 2001 From: Diego Fernando Mancera Gomez Date: Thu, 17 Sep 2026 17:59:25 -0600 Subject: [PATCH 19/19] memory: stm32_omm: fix child clock leak on set_amcr() error path When the mux is enabled, stm32_omm_configure() enables the two OSPI child clocks via stm32_omm_toggle_child_clock(dev, true); they are meant to stay enabled for the device's lifetime and are disabled again in stm32_omm_remove() and on the devm_of_platform_populate() error path. However, if the subsequent stm32_omm_set_amcr() call fails, the function jumps to the "error:" label which only calls pm_runtime_put_sync_suspend() before returning, leaving the two child clocks enabled. As this happens during probe, the error is propagated and .remove() is never called, so the clock enable references are leaked. devm_clk_bulk_get() only releases the clock handles on unwind, it does not undo clk_prepare_enable(). Disable the child clocks on the set_amcr() error path, mirroring the cleanup already done on the devm_of_platform_populate() failure path. Fixes: 8181d061dcff ("memory: Add STM32 Octo Memory Manager driver") Cc: stable@vger.kernel.org Signed-off-by: Diego Fernando Mancera Gomez Link: https://patch.msgid.link/20260917235925.44185-1-diegomancera.dev@gmail.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/stm32_omm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/memory/stm32_omm.c b/drivers/memory/stm32_omm.c index 84eb23c824f5..3599683ab04a 100644 --- a/drivers/memory/stm32_omm.c +++ b/drivers/memory/stm32_omm.c @@ -274,6 +274,8 @@ static int stm32_omm_configure(struct device *dev) writel_relaxed(omm->cr, omm->io_base + OMM_CR); ret = stm32_omm_set_amcr(dev, true); + if (ret && (mux & CR_MUXEN)) + stm32_omm_toggle_child_clock(dev, false); error: pm_runtime_put_sync_suspend(dev);