* [PATCH 0/2] ARM: l2c: honour status = "disabled" on the L2 cache node
@ 2026-11-11 13:39 Agustin Santiago Isasmendi
2026-11-11 13:39 ` [PATCH 1/2] ARM: l2c: factor out the number of ways Agustin Santiago Isasmendi
2026-11-11 13:39 ` [PATCH 2/2] ARM: l2c: honour status = "disabled" on the L2 cache node Agustin Santiago Isasmendi
0 siblings, 2 replies; 5+ messages in thread
From: Agustin Santiago Isasmendi @ 2026-11-11 13:39 UTC (permalink / raw)
To: Russell King
Cc: linux-arm-kernel, linux-kernel, Arnd Bergmann,
Stefan Dösinger, Navid Ghahremani, Krzysztof Kozlowski
Hi Russell,
l2x0_of_init() finds the L2 cache node by compatible and never looks at
its status property, so a device tree cannot keep the L2 cache off.
Patch 2 makes it honour status = "disabled", as the newer cache
controller drivers in drivers/cache/ already do. If the firmware left
the cache enabled it cannot just be ignored, since nothing would
maintain it, so it is cleaned, invalidated and disabled instead. Patch 1 moves
the number-of-ways computation out of the per-part init code so that
the disable path can reuse it.
The user is the ZTE ZX279128S (dual Cortex-A9, L2C-310 r3p3), where a
PCIe read on one CPU deadlocks against an L2C-310 line operation by
physical address or a Cache Sync on the other CPU, and the board hangs
within seconds of WiFi traffic. Running with the L2 cache off avoids
it, and that was suggested as the fallback for the platform when a
lock-based workaround was found too invasive for generic code:
https://lore.kernel.org/r/20261110153522.1620088-1-isasmendi.agus@gmail.com
Whether the ZX279128S device tree disables the cache is a decision for
that thread; this series only makes the property work.
No in-tree L2 cache node is disabled, so existing device trees are not
affected. Tested on the ZTE ZXHN H3600 (not in mainline yet, on top of
Navid Ghahremani's v2 platform series): with the node disabled the cache
stays off (control register 0) and the system runs normally; with it
enabled the cache comes up as before; the firmware-enabled path was
exercised with a test-only change that enables the cache before the
check. Builds without warnings with W=1 (board config and
multi_v7_defconfig); checkpatch --strict is clean.
Agustin Santiago Isasmendi (2):
ARM: l2c: factor out the number of ways
ARM: l2c: honour status = "disabled" on the L2 cache node
arch/arm/mm/cache-l2x0.c | 85 ++++++++++++++++++++++++++++------------
1 file changed, 59 insertions(+), 26 deletions(-)
base-commit: c03c1a14507dfaf8b856c2608f9ac216021fc222
--
2.30.2
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 1/2] ARM: l2c: factor out the number of ways
2026-11-11 13:39 [PATCH 0/2] ARM: l2c: honour status = "disabled" on the L2 cache node Agustin Santiago Isasmendi
@ 2026-11-11 13:39 ` Agustin Santiago Isasmendi
2026-11-11 14:24 ` Arnd Bergmann
2026-11-11 13:39 ` [PATCH 2/2] ARM: l2c: honour status = "disabled" on the L2 cache node Agustin Santiago Isasmendi
1 sibling, 1 reply; 5+ messages in thread
From: Agustin Santiago Isasmendi @ 2026-11-11 13:39 UTC (permalink / raw)
To: Russell King
Cc: linux-arm-kernel, linux-kernel, Arnd Bergmann,
Stefan Dösinger, Navid Ghahremani, Krzysztof Kozlowski
__l2c_init() derives the number of ways from the cache ID and the
auxiliary control register. Move that into l2c_num_ways() so that the
next patch can use it before the cache is initialised. No functional
change.
Assisted-by: LLM
Signed-off-by: Agustin Santiago Isasmendi <isasmendi.agus@gmail.com>
---
arch/arm/mm/cache-l2x0.c | 50 +++++++++++++++++++---------------------
1 file changed, 24 insertions(+), 26 deletions(-)
diff --git a/arch/arm/mm/cache-l2x0.c b/arch/arm/mm/cache-l2x0.c
index 470867160076..eaa1fb44886e 100644
--- a/arch/arm/mm/cache-l2x0.c
+++ b/arch/arm/mm/cache-l2x0.c
@@ -778,6 +778,26 @@ static const struct l2c_init_data l2c310_init_fns __initconst = {
},
};
+/* Number of ways, from the cache ID and the auxiliary control register */
+static unsigned int __init l2c_num_ways(u32 cache_id, u32 aux)
+{
+ switch (cache_id & L2X0_CACHE_ID_PART_MASK) {
+ case L2X0_CACHE_ID_PART_L310:
+ return aux & L310_AUX_CTRL_ASSOCIATIVITY_16 ? 16 : 8;
+
+ case L2X0_CACHE_ID_PART_L210:
+ case L2X0_CACHE_ID_PART_L220:
+ return (aux >> 13) & 0xf;
+
+ case AURORA_CACHE_ID:
+ return 2 << ((((aux >> 13) & 0xf) + 1) >> 2);
+
+ default:
+ /* Assume unknown chips have 8 ways */
+ return 8;
+ }
+}
+
static int __init __l2c_init(const struct l2c_init_data *data,
u32 aux_val, u32 aux_mask, u32 cache_id, bool nosync)
{
@@ -809,33 +829,11 @@ static int __init __l2c_init(const struct l2c_init_data *data,
pr_warn("L2C: DT/platform modifies aux control register: 0x%08x -> 0x%08x\n",
old_aux, aux);
- /* Determine the number of ways */
- switch (cache_id & L2X0_CACHE_ID_PART_MASK) {
- case L2X0_CACHE_ID_PART_L310:
- if ((aux_val | ~aux_mask) & (L2C_AUX_CTRL_WAY_SIZE_MASK | L310_AUX_CTRL_ASSOCIATIVITY_16))
- pr_warn("L2C: DT/platform tries to modify or specify cache size\n");
- if (aux & (1 << 16))
- ways = 16;
- else
- ways = 8;
- break;
-
- case L2X0_CACHE_ID_PART_L210:
- case L2X0_CACHE_ID_PART_L220:
- ways = (aux >> 13) & 0xf;
- break;
-
- case AURORA_CACHE_ID:
- ways = (aux >> 13) & 0xf;
- ways = 2 << ((ways + 1) >> 2);
- break;
-
- default:
- /* Assume unknown chips have 8 ways */
- ways = 8;
- break;
- }
+ if ((cache_id & L2X0_CACHE_ID_PART_MASK) == L2X0_CACHE_ID_PART_L310 &&
+ (aux_val | ~aux_mask) & (L2C_AUX_CTRL_WAY_SIZE_MASK | L310_AUX_CTRL_ASSOCIATIVITY_16))
+ pr_warn("L2C: DT/platform tries to modify or specify cache size\n");
+ ways = l2c_num_ways(cache_id, aux);
l2x0_way_mask = (1 << ways) - 1;
/*
--
2.30.2
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 2/2] ARM: l2c: honour status = "disabled" on the L2 cache node
2026-11-11 13:39 [PATCH 0/2] ARM: l2c: honour status = "disabled" on the L2 cache node Agustin Santiago Isasmendi
2026-11-11 13:39 ` [PATCH 1/2] ARM: l2c: factor out the number of ways Agustin Santiago Isasmendi
@ 2026-11-11 13:39 ` Agustin Santiago Isasmendi
2026-11-11 13:56 ` Arnd Bergmann
1 sibling, 1 reply; 5+ messages in thread
From: Agustin Santiago Isasmendi @ 2026-11-11 13:39 UTC (permalink / raw)
To: Russell King
Cc: linux-arm-kernel, linux-kernel, Arnd Bergmann,
Stefan Dösinger, Navid Ghahremani, Krzysztof Kozlowski
l2x0_of_init() looks the L2 cache node up by compatible and enables the
cache whatever its status property says, so a device tree cannot keep
the L2 cache off.
On the ZTE ZX279128S (dual Cortex-A9, L2C-310 r3p3) a PCIe read on one
CPU deadlocks against an L2C-310 line operation by physical address or a
Cache Sync on the other CPU, and the board hangs within seconds of WiFi
traffic. Running with the L2 cache off avoids the deadlock, so a board
needs a way to say so.
Honour the status property, as the newer cache controller drivers
already do (drivers/cache/ax45mp_cache.c, starfive_starlink_cache.c).
If the firmware left the cache enabled, it cannot simply be ignored,
since nothing would maintain it: clean, invalidate and disable it
instead. No in-tree L2 cache node is disabled, so nothing changes for
existing device trees.
Tested on the ZTE ZXHN H3600: with the node disabled the cache stays
off and the system runs normally; with it enabled the cache comes up as
before. The firmware-enabled path was exercised with a test-only
change that enables the cache before the check.
Assisted-by: LLM
Signed-off-by: Agustin Santiago Isasmendi <isasmendi.agus@gmail.com>
---
arch/arm/mm/cache-l2x0.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/arch/arm/mm/cache-l2x0.c b/arch/arm/mm/cache-l2x0.c
index eaa1fb44886e..0687e5a6ad10 100644
--- a/arch/arm/mm/cache-l2x0.c
+++ b/arch/arm/mm/cache-l2x0.c
@@ -1757,6 +1757,34 @@ static const struct of_device_id l2x0_ids[] __initconst = {
{}
};
+/*
+ * The device tree marks the cache as disabled. Leave it off; if the
+ * firmware enabled it, clean, invalidate and disable it rather than leave
+ * it running without any cache maintenance.
+ */
+static void __init l2c_of_disabled(void __iomem *base)
+{
+ u32 cache_id, aux;
+ unsigned int sync;
+
+ if (!(readl_relaxed(base + L2X0_CTRL) & L2X0_CTRL_EN)) {
+ pr_info("L2C: disabled by the device tree\n");
+ return;
+ }
+
+ pr_warn("L2C: disabled by the device tree but enabled by the firmware, disabling it\n");
+ cache_id = readl_relaxed(base + L2X0_CACHE_ID);
+ aux = readl_relaxed(base + L2X0_AUX_CTRL);
+ sync = (cache_id & L2X0_CACHE_ID_PART_MASK) == AURORA_CACHE_ID ?
+ AURORA_SYNC_REG : L2X0_CACHE_SYNC;
+ l2x0_way_mask = (1 << l2c_num_ways(cache_id, aux)) - 1;
+ __l2c_op_way(base + L2X0_CLEAN_INV_WAY);
+ writel_relaxed(0, base + sync);
+ l2c_wait_mask(base + sync, 1);
+ l2c_write_sec(0, base, L2X0_CTRL);
+ dsb(st);
+}
+
int __init l2x0_of_init(u32 aux_val, u32 aux_mask)
{
const struct l2c_init_data *data;
@@ -1779,6 +1807,13 @@ int __init l2x0_of_init(u32 aux_val, u32 aux_mask)
l2x0_saved_regs.phy_base = res.start;
+ if (!of_device_is_available(np)) {
+ l2c_of_disabled(l2x0_base);
+ iounmap(l2x0_base);
+ l2x0_base = NULL;
+ return -ENODEV;
+ }
+
data = of_match_node(l2x0_ids, np)->data;
if (of_device_is_compatible(np, "arm,pl310-cache") &&
--
2.30.2
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 2/2] ARM: l2c: honour status = "disabled" on the L2 cache node
2026-11-11 13:39 ` [PATCH 2/2] ARM: l2c: honour status = "disabled" on the L2 cache node Agustin Santiago Isasmendi
@ 2026-11-11 13:56 ` Arnd Bergmann
0 siblings, 0 replies; 5+ messages in thread
From: Arnd Bergmann @ 2026-11-11 13:56 UTC (permalink / raw)
To: Agustin Santiago Isasmendi, Russell King
Cc: linux-arm-kernel, linux-kernel, Stefan Dösinger,
Navid Ghahremani, Krzysztof Kozlowski
On Wed, Nov 11, 2026, at 14:39, Agustin Santiago Isasmendi wrote:
> l2x0_of_init() looks the L2 cache node up by compatible and enables the
> cache whatever its status property says, so a device tree cannot keep
> the L2 cache off.
>
> On the ZTE ZX279128S (dual Cortex-A9, L2C-310 r3p3) a PCIe read on one
> CPU deadlocks against an L2C-310 line operation by physical address or a
> Cache Sync on the other CPU, and the board hangs within seconds of WiFi
> traffic. Running with the L2 cache off avoids the deadlock, so a board
> needs a way to say so.
>
> Honour the status property, as the newer cache controller drivers
> already do (drivers/cache/ax45mp_cache.c, starfive_starlink_cache.c).
> If the firmware left the cache enabled, it cannot simply be ignored,
> since nothing would maintain it: clean, invalidate and disable it
> instead. No in-tree L2 cache node is disabled, so nothing changes for
> existing device trees.
>
> Tested on the ZTE ZXHN H3600: with the node disabled the cache stays
> off and the system runs normally; with it enabled the cache comes up as
> before. The firmware-enabled path was exercised with a test-only
> change that enables the cache before the check.
>
> Assisted-by: LLM
> Signed-off-by: Agustin Santiago Isasmendi <isasmendi.agus@gmail.com>
Hi Augustin,
It is a little strange to use the MMIO registers of a status="disabled"
device to turn it off, and it would be nice if we could require
the firmware to leave it in a consistent state instead, but I assume
you can't do that for the one device you are interested in because
you don't have the firmware sources, right?
In this case
Reviewed-by: Arnd Bergmann <arnd@arndb.de>
but maybe add a code comment that explains this to the reader.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 1/2] ARM: l2c: factor out the number of ways
2026-11-11 13:39 ` [PATCH 1/2] ARM: l2c: factor out the number of ways Agustin Santiago Isasmendi
@ 2026-11-11 14:24 ` Arnd Bergmann
0 siblings, 0 replies; 5+ messages in thread
From: Arnd Bergmann @ 2026-11-11 14:24 UTC (permalink / raw)
To: Agustin Santiago Isasmendi, Russell King
Cc: linux-arm-kernel, linux-kernel, Stefan Dösinger,
Navid Ghahremani, Krzysztof Kozlowski
On Wed, Nov 11, 2026, at 14:39, Agustin Santiago Isasmendi wrote:
> __l2c_init() derives the number of ways from the cache ID and the
> auxiliary control register. Move that into l2c_num_ways() so that the
> next patch can use it before the cache is initialised. No functional
> change.
>
> Assisted-by: LLM
> Signed-off-by: Agustin Santiago Isasmendi <isasmendi.agus@gmail.com>
Reviewed-by: Arnd Bergmann <arnd@arndb.de>
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-11 14:24 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-11 13:39 [PATCH 0/2] ARM: l2c: honour status = "disabled" on the L2 cache node Agustin Santiago Isasmendi
2026-11-11 13:39 ` [PATCH 1/2] ARM: l2c: factor out the number of ways Agustin Santiago Isasmendi
2026-11-11 14:24 ` Arnd Bergmann
2026-11-11 13:39 ` [PATCH 2/2] ARM: l2c: honour status = "disabled" on the L2 cache node Agustin Santiago Isasmendi
2026-11-11 13:56 ` Arnd Bergmann
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®