* [PATCH v5 1/7] firmware: samsung: acpm: Fix cross-thread RX length corruption
2026-05-05 13:12 [PATCH v5 0/7] firmware: samsung: acpm: Various fixes for sashiko bug reports Tudor Ambarus
@ 2026-05-05 13:12 ` Tudor Ambarus
2026-05-06 15:17 ` Alexey Klimov
2026-05-05 13:12 ` [PATCH v5 2/7] firmware: samsung: acpm: Fix mailbox channel leak on probe error Tudor Ambarus
` (6 subsequent siblings)
7 siblings, 1 reply; 10+ messages in thread
From: Tudor Ambarus @ 2026-05-05 13:12 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar
Cc: linux-kernel, linux-samsung-soc, linux-arm-kernel, peter.griffin,
andre.draszik, jyescas, kernel-team, Tudor Ambarus, stable,
Titouan Ameline
Sashiko identified a cross-thread RX length corruption bug when
reviewing the thermal addition to ACPM [1].
When multiple threads concurrently send IPC requests, the ACPM polling
mechanism can encounter responses belonging to other threads. To drain
the queue, the driver saves these concurrent responses into an internal
cache (`rx_data->cmd`) to be retrieved later by the owning thread.
Previously, the driver incorrectly used `xfer->rxcnt` (the expected
receive length of the *current* polling thread) when copying data for
*other* threads into this cache. If the threads expected responses of
different lengths, this resulted in buffer underflows (leading to reads
of uninitialized memory) or potential buffer overflows.
Fix this by replacing the boolean `response` flag in
`struct acpm_rx_data` with `rxcnt`, caching the exact expected receive
length for each specific transaction during transfer preparation. Use
this cached length when saving concurrent responses.
Consequently, ensure that `xfer->rxcnt` is explicitly zeroed in driver
helpers (e.g., `acpm_dvfs_set_xfer`) for fire-and-forget messages to
prevent uninitialized stack garbage from being interpreted as a massive
expected receive length.
Cc: stable@vger.kernel.org
Fixes: a88927b534ba ("firmware: add Exynos ACPM protocol driver")
Reported-by: Titouan Ameline <titouan.ameline@gmail.com>
Closes: https://sashiko.dev/#/patchset/20260420-acpm-tmu-v3-0-3dc8e93f0b26%40linaro.org [1]
Closes: https://lore.kernel.org/r/20260426210255.73674-1-titouan.ameline@gmail.com/
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
drivers/firmware/samsung/exynos-acpm-dvfs.c | 3 +++
drivers/firmware/samsung/exynos-acpm.c | 15 ++++++++-------
2 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/drivers/firmware/samsung/exynos-acpm-dvfs.c b/drivers/firmware/samsung/exynos-acpm-dvfs.c
index 06bdf62dea1f..fdea7aa24ca0 100644
--- a/drivers/firmware/samsung/exynos-acpm-dvfs.c
+++ b/drivers/firmware/samsung/exynos-acpm-dvfs.c
@@ -31,6 +31,9 @@ static void acpm_dvfs_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdlen,
if (response) {
xfer->rxcnt = cmdlen;
xfer->rxd = cmd;
+ } else {
+ xfer->rxcnt = 0;
+ xfer->rxd = NULL;
}
}
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index 16c46ed60837..e95edc350efa 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -104,12 +104,12 @@ struct acpm_queue {
*
* @cmd: pointer to where the data shall be saved.
* @n_cmd: number of 32-bit commands.
- * @response: true if the client expects the RX data.
+ * @rxcnt: expected length of the response in 32-bit words.
*/
struct acpm_rx_data {
u32 *cmd;
size_t n_cmd;
- bool response;
+ size_t rxcnt;
};
#define ACPM_SEQNUM_MAX 64
@@ -199,7 +199,7 @@ static void acpm_get_saved_rx(struct acpm_chan *achan,
const struct acpm_rx_data *rx_data = &achan->rx_data[tx_seqnum - 1];
u32 rx_seqnum;
- if (!rx_data->response)
+ if (!rx_data->rxcnt)
return;
rx_seqnum = FIELD_GET(ACPM_PROTOCOL_SEQNUM, rx_data->cmd[0]);
@@ -256,7 +256,7 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer)
seqnum = rx_seqnum - 1;
rx_data = &achan->rx_data[seqnum];
- if (rx_data->response) {
+ if (rx_data->rxcnt) {
if (rx_seqnum == tx_seqnum) {
__ioread32_copy(xfer->rxd, addr, xfer->rxcnt);
rx_set = true;
@@ -268,7 +268,8 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer)
* clear yet the bitmap. It will be cleared
* after the response is copied to the request.
*/
- __ioread32_copy(rx_data->cmd, addr, xfer->rxcnt);
+ __ioread32_copy(rx_data->cmd, addr,
+ rx_data->rxcnt);
}
} else {
clear_bit(seqnum, achan->bitmap_seqnum);
@@ -380,8 +381,8 @@ static void acpm_prepare_xfer(struct acpm_chan *achan,
/* Clear data for upcoming responses */
rx_data = &achan->rx_data[achan->seqnum - 1];
memset(rx_data->cmd, 0, sizeof(*rx_data->cmd) * rx_data->n_cmd);
- if (xfer->rxd)
- rx_data->response = true;
+ /* zero means no response expected */
+ rx_data->rxcnt = xfer->rxcnt;
/* Flag the index based on seqnum. (seqnum: 1~63, bitmap: 0~62) */
set_bit(achan->seqnum - 1, achan->bitmap_seqnum);
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH v5 1/7] firmware: samsung: acpm: Fix cross-thread RX length corruption
2026-05-05 13:12 ` [PATCH v5 1/7] firmware: samsung: acpm: Fix cross-thread RX length corruption Tudor Ambarus
@ 2026-05-06 15:17 ` Alexey Klimov
0 siblings, 0 replies; 10+ messages in thread
From: Alexey Klimov @ 2026-05-06 15:17 UTC (permalink / raw)
To: Tudor Ambarus
Cc: Krzysztof Kozlowski, Alim Akhtar, linux-kernel, linux-samsung-soc,
linux-arm-kernel, peter.griffin, andre.draszik, jyescas,
kernel-team, stable, Titouan Ameline
On Tue May 5, 2026 at 2:12 PM BST, Tudor Ambarus wrote:
> Sashiko identified a cross-thread RX length corruption bug when
> reviewing the thermal addition to ACPM [1].
>
> When multiple threads concurrently send IPC requests, the ACPM polling
> mechanism can encounter responses belonging to other threads. To drain
> the queue, the driver saves these concurrent responses into an internal
> cache (`rx_data->cmd`) to be retrieved later by the owning thread.
>
> Previously, the driver incorrectly used `xfer->rxcnt` (the expected
> receive length of the *current* polling thread) when copying data for
> *other* threads into this cache. If the threads expected responses of
> different lengths, this resulted in buffer underflows (leading to reads
> of uninitialized memory) or potential buffer overflows.
>
> Fix this by replacing the boolean `response` flag in
> `struct acpm_rx_data` with `rxcnt`, caching the exact expected receive
> length for each specific transaction during transfer preparation. Use
> this cached length when saving concurrent responses.
>
> Consequently, ensure that `xfer->rxcnt` is explicitly zeroed in driver
> helpers (e.g., `acpm_dvfs_set_xfer`) for fire-and-forget messages to
> prevent uninitialized stack garbage from being interpreted as a massive
> expected receive length.
>
> Cc: stable@vger.kernel.org
> Fixes: a88927b534ba ("firmware: add Exynos ACPM protocol driver")
> Reported-by: Titouan Ameline <titouan.ameline@gmail.com>
As far as I can see, the name in this tag should be
Titouan Ameline de Cadeville.
> Closes: https://sashiko.dev/#/patchset/20260420-acpm-tmu-v3-0-3dc8e93f0b26%40linaro.org [1]
> Closes: https://lore.kernel.org/r/20260426210255.73674-1-titouan.ameline@gmail.com/
> Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
> ---
> drivers/firmware/samsung/exynos-acpm-dvfs.c | 3 +++
> drivers/firmware/samsung/exynos-acpm.c | 15 ++++++++-------
> 2 files changed, 11 insertions(+), 7 deletions(-)
[..]
Best regards,
Alexey
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v5 2/7] firmware: samsung: acpm: Fix mailbox channel leak on probe error
2026-05-05 13:12 [PATCH v5 0/7] firmware: samsung: acpm: Various fixes for sashiko bug reports Tudor Ambarus
2026-05-05 13:12 ` [PATCH v5 1/7] firmware: samsung: acpm: Fix cross-thread RX length corruption Tudor Ambarus
@ 2026-05-05 13:12 ` Tudor Ambarus
2026-05-05 13:13 ` [PATCH v5 3/7] firmware: samsung: acpm: Fix dummy stubs to return ERR_PTR Tudor Ambarus
` (5 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Tudor Ambarus @ 2026-05-05 13:12 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar
Cc: linux-kernel, linux-samsung-soc, linux-arm-kernel, peter.griffin,
andre.draszik, jyescas, kernel-team, Tudor Ambarus, stable
Sashiko identified the leak at [1].
The ACPM driver allocates hardware mailbox channels using
`mbox_request_channel()` during `acpm_channels_init()`. However, the
driver lacked a `.remove` callback and did not free these channels on
subsequent error paths inside `acpm_probe()`.
Additionally, if `acpm_achan_alloc_cmds()` failed during the channel
initialization loop, the function returned immediately, bypassing the
manual cleanup and permanently leaking any channels successfully
requested in previous loop iterations.
Fix this by modifying `acpm_free_mbox_chans()` to match the `devres`
action signature and registering it via `devm_add_action_or_reset()`.
Cc: stable@vger.kernel.org
Fixes: a88927b534ba ("firmware: add Exynos ACPM protocol driver")
Closes: https://sashiko.dev/#/patchset/20260420-acpm-tmu-v3-0-3dc8e93f0b26%40linaro.org [1]
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
drivers/firmware/samsung/exynos-acpm.c | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index e95edc350efa..9766425a44ab 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -527,10 +527,11 @@ static int acpm_achan_alloc_cmds(struct acpm_chan *achan)
/**
* acpm_free_mbox_chans() - free mailbox channels.
- * @acpm: pointer to driver data.
+ * @data: pointer to driver data.
*/
-static void acpm_free_mbox_chans(struct acpm_info *acpm)
+static void acpm_free_mbox_chans(void *data)
{
+ struct acpm_info *acpm = data;
int i;
for (i = 0; i < acpm->num_chans; i++)
@@ -558,6 +559,10 @@ static int acpm_channels_init(struct acpm_info *acpm)
if (!acpm->chans)
return -ENOMEM;
+ ret = devm_add_action_or_reset(dev, acpm_free_mbox_chans, acpm);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add mbox free action.\n");
+
chans_shmem = acpm->sram_base + readl(&shmem->chans);
for (i = 0; i < acpm->num_chans; i++) {
@@ -579,10 +584,8 @@ static int acpm_channels_init(struct acpm_info *acpm)
cl->dev = dev;
achan->chan = mbox_request_channel(cl, 0);
- if (IS_ERR(achan->chan)) {
- acpm_free_mbox_chans(acpm);
+ if (IS_ERR(achan->chan))
return PTR_ERR(achan->chan);
- }
}
return 0;
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v5 3/7] firmware: samsung: acpm: Fix dummy stubs to return ERR_PTR
2026-05-05 13:12 [PATCH v5 0/7] firmware: samsung: acpm: Various fixes for sashiko bug reports Tudor Ambarus
2026-05-05 13:12 ` [PATCH v5 1/7] firmware: samsung: acpm: Fix cross-thread RX length corruption Tudor Ambarus
2026-05-05 13:12 ` [PATCH v5 2/7] firmware: samsung: acpm: Fix mailbox channel leak on probe error Tudor Ambarus
@ 2026-05-05 13:13 ` Tudor Ambarus
2026-05-05 13:13 ` [PATCH v5 4/7] firmware: samsung: acpm: Add memory barrier before advancing RX pointer Tudor Ambarus
` (4 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Tudor Ambarus @ 2026-05-05 13:13 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar
Cc: linux-kernel, linux-samsung-soc, linux-arm-kernel, peter.griffin,
andre.draszik, jyescas, kernel-team, Tudor Ambarus, stable
Sashiko identified a potential NULL pointer dereference [1].
The dummy stub implementation for devm_acpm_get_by_node() returns NULL
when CONFIG_EXYNOS_ACPM_PROTOCOL is disabled.
However, the active implementation of this function returns an ERR_PTR
on failure, and the consumer driver checks the return value using
IS_ERR(). Because IS_ERR(NULL) evaluates to false, returning NULL from
the stub tricks consumer drivers into treating the NULL return as a
valid handle. Subsequent attempts to access handle->ops result in a
fatal NULL pointer dereference.
Fix this by returning ERR_PTR(-ENODEV) in the disabled configuration
to correctly propagate the disabled state and match the API contract.
Cc: stable@vger.kernel.org
Fixes: 6837c006d4e7 ("firmware: exynos-acpm: add empty method to allow compile test")
Closes: https://sashiko.dev/#/patchset/20260420-acpm-tmu-v3-0-3dc8e93f0b26%40linaro.org [1]
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
include/linux/firmware/samsung/exynos-acpm-protocol.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h
index 13f17dc4443b..d4db2796a6fb 100644
--- a/include/linux/firmware/samsung/exynos-acpm-protocol.h
+++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h
@@ -8,6 +8,7 @@
#ifndef __EXYNOS_ACPM_PROTOCOL_H
#define __EXYNOS_ACPM_PROTOCOL_H
+#include <linux/err.h>
#include <linux/types.h>
struct acpm_handle;
@@ -57,7 +58,7 @@ struct acpm_handle *devm_acpm_get_by_node(struct device *dev,
static inline struct acpm_handle *devm_acpm_get_by_node(struct device *dev,
struct device_node *np)
{
- return NULL;
+ return ERR_PTR(-ENODEV);
}
#endif
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v5 4/7] firmware: samsung: acpm: Add memory barrier before advancing RX pointer
2026-05-05 13:12 [PATCH v5 0/7] firmware: samsung: acpm: Various fixes for sashiko bug reports Tudor Ambarus
` (2 preceding siblings ...)
2026-05-05 13:13 ` [PATCH v5 3/7] firmware: samsung: acpm: Fix dummy stubs to return ERR_PTR Tudor Ambarus
@ 2026-05-05 13:13 ` Tudor Ambarus
2026-05-05 13:13 ` [PATCH v5 5/7] firmware: samsung: acpm: Fix false timeouts and Use-After-Free in polling Tudor Ambarus
` (3 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Tudor Ambarus @ 2026-05-05 13:13 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar
Cc: linux-kernel, linux-samsung-soc, linux-arm-kernel, peter.griffin,
andre.draszik, jyescas, kernel-team, Tudor Ambarus, stable
Sashiko identified a silent data corruption in [1].
In acpm_get_rx(), the driver reads the response payload from SRAM using
__ioread32_copy() and subsequently updates the hardware RX rear pointer
via writel().
On weakly ordered architectures like ARM64, writel() provides a write
memory barrier (wmb()), which strictly orders prior writes against
subsequent writes. However, it does not order prior reads against
subsequent writes. Consequently, the CPU is permitted to reorder the
writel() store to become globally visible before the payload reads
have completed.
If this reordering occurs, the firmware may observe the updated rear
pointer, assume the queue slot is available, and overwrite the SRAM
payload while the kernel is still actively reading from it, leading
to silent data corruption.
Fix this by inserting a full memory barrier (mb()) before the writel()
to guarantee that all payload reads have completed before the hardware
queue pointer is advanced.
Cc: stable@vger.kernel.org
Fixes: a88927b534ba ("firmware: add Exynos ACPM protocol driver")
Closes: https://sashiko.dev/#/patchset/20260429-acpm-fixes-sashiko-reports-v3-0-47cf74ab09ad%40linaro.org
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
drivers/firmware/samsung/exynos-acpm.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index 9766425a44ab..a9449bc33bd0 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -5,6 +5,7 @@
* Copyright 2024 Linaro Ltd.
*/
+#include <asm/barrier.h>
#include <linux/bitfield.h>
#include <linux/bitmap.h>
#include <linux/bits.h>
@@ -278,6 +279,9 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer)
i = (i + 1) % achan->qlen;
} while (i != rx_front);
+ /* Ensure all payload reads complete before advancing the rear pointer */
+ mb();
+
/* We saved all responses, mark RX empty. */
writel(rx_front, achan->rx.rear);
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v5 5/7] firmware: samsung: acpm: Fix false timeouts and Use-After-Free in polling
2026-05-05 13:12 [PATCH v5 0/7] firmware: samsung: acpm: Various fixes for sashiko bug reports Tudor Ambarus
` (3 preceding siblings ...)
2026-05-05 13:13 ` [PATCH v5 4/7] firmware: samsung: acpm: Add memory barrier before advancing RX pointer Tudor Ambarus
@ 2026-05-05 13:13 ` Tudor Ambarus
2026-05-05 13:13 ` [PATCH v5 6/7] firmware: samsung: acpm: Fix missing LKMM barriers in sequence allocator Tudor Ambarus
` (2 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Tudor Ambarus @ 2026-05-05 13:13 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar
Cc: linux-kernel, linux-samsung-soc, linux-arm-kernel, peter.griffin,
andre.draszik, jyescas, kernel-team, Tudor Ambarus, stable
Sashiko identified severe races in the polling state machine [1].
In the ACPM driver's polling mode, threads waited for responses by
monitoring the globally shared 'bitmap_seqnum'. This caused false
timeouts because if a thread processed its response and freed the
sequence number, a concurrent TX thread could immediately reallocate
it before the polling thread woke up.
Additionally, the driver suffered from a cross-thread Use-After-Free
(UAF) preemption race. Previously, acpm_get_rx() cleared the sequence
number of whichever RX message it drained from the hardware queue. This
meant Thread A could globally free Thread B's sequence slot while
Thread B was asleep. A new Thread C could then steal the slot,
overwrite the buffer, and leave Thread B to wake up to corrupted state
or a timeout.
Fix this by rewriting the polling state machine:
1. Decouple polling from the global allocator by introducing a per-slot
'completed' flag, synchronized via smp_store_release() and
smp_load_acquire().
2. Strip acpm_get_saved_rx() out of acpm_get_rx() to make it a pure
queue-draining function. Introduce a 'native_match' boolean argument
which evaluates to true only if the thread natively processed its
own sequence number during the call. This explicitly informs the
polling loop whether it must retrieve its payload from the
cross-thread cache.
3. Centralize the cache fallback and sequence number free (clear_bit)
inside the polling loop. Crucially, the free operation now strictly
targets the thread's own TX sequence number (xfer->txd[0]), rather
than the drained RX sequence number. This enforces strict ownership:
a thread only ever frees its own allocated sequence slot, and only
at the exact moment it completes its poll, eliminating the UAF
window.
Furthermore, explicitly guard the 'native_match' assignment with an
if (rx_seqnum == tx_seqnum) check, even for zero-length (no payload)
responses. While an unguarded assignment wouldn't crash (because the
cache fallback acpm_get_saved_rx() safely returns early on zero-length
transfers) doing so would "lie" to the state machine. If a thread
drained the queue and found another thread's zero-length message,
setting native_match = true would falsely convince the polling loop
that it natively handled its own response. Maintaining a rigorous state
machine requires that native_match is only set when a thread explicitly
processes its own sequence number.
Cc: stable@vger.kernel.org
Fixes: a88927b534ba ("firmware: add Exynos ACPM protocol driver")
Closes: https://sashiko.dev/#/patchset/20260429-acpm-fixes-sashiko-reports-v3-0-47cf74ab09ad%40linaro.org [1]
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
drivers/firmware/samsung/exynos-acpm.c | 68 ++++++++++++++++++++++++----------
1 file changed, 48 insertions(+), 20 deletions(-)
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index a9449bc33bd0..2dea9b7bfe91 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -106,11 +106,14 @@ struct acpm_queue {
* @cmd: pointer to where the data shall be saved.
* @n_cmd: number of 32-bit commands.
* @rxcnt: expected length of the response in 32-bit words.
+ * @completed: flag indicating if the firmware response has been fully
+ * processed.
*/
struct acpm_rx_data {
u32 *cmd;
size_t n_cmd;
size_t rxcnt;
+ bool completed;
};
#define ACPM_SEQNUM_MAX 64
@@ -205,26 +208,28 @@ static void acpm_get_saved_rx(struct acpm_chan *achan,
rx_seqnum = FIELD_GET(ACPM_PROTOCOL_SEQNUM, rx_data->cmd[0]);
- if (rx_seqnum == tx_seqnum) {
+ if (rx_seqnum == tx_seqnum)
memcpy(xfer->rxd, rx_data->cmd, xfer->rxcnt * sizeof(*xfer->rxd));
- clear_bit(rx_seqnum - 1, achan->bitmap_seqnum);
- }
}
/**
* acpm_get_rx() - get response from RX queue.
* @achan: ACPM channel info.
* @xfer: reference to the transfer to get response for.
+ * @native_match: pointer to a boolean set to true if the thread natively
+ * processed its own sequence number during this call.
*
* Return: 0 on success, -errno otherwise.
*/
-static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer)
+static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer,
+ bool *native_match)
{
u32 rx_front, rx_seqnum, tx_seqnum, seqnum;
const void __iomem *base, *addr;
struct acpm_rx_data *rx_data;
u32 i, val, mlen;
- bool rx_set = false;
+
+ *native_match = false;
guard(mutex)(&achan->rx_lock);
@@ -233,10 +238,8 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer)
tx_seqnum = FIELD_GET(ACPM_PROTOCOL_SEQNUM, xfer->txd[0]);
- if (i == rx_front) {
- acpm_get_saved_rx(achan, xfer, tx_seqnum);
+ if (i == rx_front)
return 0;
- }
base = achan->rx.base;
mlen = achan->mlen;
@@ -260,8 +263,13 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer)
if (rx_data->rxcnt) {
if (rx_seqnum == tx_seqnum) {
__ioread32_copy(xfer->rxd, addr, xfer->rxcnt);
- rx_set = true;
- clear_bit(seqnum, achan->bitmap_seqnum);
+ /*
+ * Signal completion to the polling thread.
+ * Pairs with smp_load_acquire() in polling
+ * loop.
+ */
+ smp_store_release(&rx_data->completed, true);
+ *native_match = true;
} else {
/*
* The RX data corresponds to another request.
@@ -271,9 +279,21 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer)
*/
__ioread32_copy(rx_data->cmd, addr,
rx_data->rxcnt);
+ /*
+ * Signal completion to the polling thread.
+ * Pairs with smp_load_acquire() in polling
+ * loop.
+ */
+ smp_store_release(&rx_data->completed, true);
}
} else {
- clear_bit(seqnum, achan->bitmap_seqnum);
+ /*
+ * Signal completion to the polling thread.
+ * Pairs with smp_load_acquire() in polling loop.
+ */
+ smp_store_release(&rx_data->completed, true);
+ if (rx_seqnum == tx_seqnum)
+ *native_match = true;
}
i = (i + 1) % achan->qlen;
@@ -285,13 +305,6 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer)
/* We saved all responses, mark RX empty. */
writel(rx_front, achan->rx.rear);
- /*
- * If the response was not in this iteration of the queue, check if the
- * RX data was previously saved.
- */
- if (!rx_set)
- acpm_get_saved_rx(achan, xfer, tx_seqnum);
-
return 0;
}
@@ -306,6 +319,7 @@ static int acpm_dequeue_by_polling(struct acpm_chan *achan,
const struct acpm_xfer *xfer)
{
struct device *dev = achan->acpm->dev;
+ bool native_match;
ktime_t timeout;
u32 seqnum;
int ret;
@@ -314,12 +328,25 @@ static int acpm_dequeue_by_polling(struct acpm_chan *achan,
timeout = ktime_add_us(ktime_get(), ACPM_POLL_TIMEOUT_US);
do {
- ret = acpm_get_rx(achan, xfer);
+ ret = acpm_get_rx(achan, xfer, &native_match);
if (ret)
return ret;
- if (!test_bit(seqnum - 1, achan->bitmap_seqnum))
+ /*
+ * Safely check if our specific transaction has been processed.
+ * smp_load_acquire prevents the CPU from speculatively
+ * executing subsequent instructions before the transaction is
+ * synchronized.
+ */
+ if (smp_load_acquire(&achan->rx_data[seqnum - 1].completed)) {
+ /* Retrieve payload if another thread cached it for us */
+ if (!native_match)
+ acpm_get_saved_rx(achan, xfer, seqnum);
+
+ /* Relinquish ownership of the sequence slot */
+ clear_bit(seqnum - 1, achan->bitmap_seqnum);
return 0;
+ }
/* Determined experimentally. */
udelay(20);
@@ -384,6 +411,7 @@ static void acpm_prepare_xfer(struct acpm_chan *achan,
/* Clear data for upcoming responses */
rx_data = &achan->rx_data[achan->seqnum - 1];
+ rx_data->completed = false;
memset(rx_data->cmd, 0, sizeof(*rx_data->cmd) * rx_data->n_cmd);
/* zero means no response expected */
rx_data->rxcnt = xfer->rxcnt;
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v5 6/7] firmware: samsung: acpm: Fix missing LKMM barriers in sequence allocator
2026-05-05 13:12 [PATCH v5 0/7] firmware: samsung: acpm: Various fixes for sashiko bug reports Tudor Ambarus
` (4 preceding siblings ...)
2026-05-05 13:13 ` [PATCH v5 5/7] firmware: samsung: acpm: Fix false timeouts and Use-After-Free in polling Tudor Ambarus
@ 2026-05-05 13:13 ` Tudor Ambarus
2026-05-05 13:13 ` [PATCH v5 7/7] firmware: samsung: acpm: Fix infinite loop on sequence number exhaustion Tudor Ambarus
2026-05-06 8:29 ` [PATCH v5 0/7] firmware: samsung: acpm: Various fixes for sashiko bug reports Tudor Ambarus
7 siblings, 0 replies; 10+ messages in thread
From: Tudor Ambarus @ 2026-05-05 13:13 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar
Cc: linux-kernel, linux-samsung-soc, linux-arm-kernel, peter.griffin,
andre.draszik, jyescas, kernel-team, Tudor Ambarus, stable
Sashiko identified memory ordering races in [1].
The ACPM driver uses a globally shared 'bitmap_seqnum' to track
available sequence numbers. Even though threads now strictly free their
own sequence numbers, the allocation and freeing of these bits across
concurrent threads are effectively lockless operations and require
explicit LKMM memory barriers.
Previously, the driver used plain bitwise operators (test_bit, set_bit,
clear_bit), which lack ordering guarantees. This creates two race
conditions on weakly ordered architectures like ARM64:
1. Polling Release Violation: The polling thread copies its payload and
calls clear_bit(). Without a release barrier, the CPU can reorder
the memory operations, making the cleared bit globally visible
before the payload reads have fully completed.
2. TX Acquire Violation: The TX thread loops on test_bit(), calls
set_bit(), and then wipes the payload buffer via memset(). Without
an acquire barrier, the CPU can speculatively execute the memset()
before the bit is safely and formally claimed.
If these reorderings overlap, a new TX thread can claim the sequence
number and overwrite the buffer while the original polling thread is
still actively reading from it.
Fix this by upgrading the bitwise operators. Wrap the TX allocation in
test_and_set_bit_lock() to establish formal LKMM Acquire semantics, and
pair it with clear_bit_unlock() in the polling path to enforce Release
semantics.
Cc: stable@vger.kernel.org
Fixes: a88927b534ba ("firmware: add Exynos ACPM protocol driver")
Closes: https://sashiko.dev/#/patchset/20260423-acpm-fixes-sashiko-reports-v1-0-2217b790925e%40linaro.org [1]
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
drivers/firmware/samsung/exynos-acpm.c | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index 2dea9b7bfe91..fd2e46e9f7e9 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -8,7 +8,7 @@
#include <asm/barrier.h>
#include <linux/bitfield.h>
#include <linux/bitmap.h>
-#include <linux/bits.h>
+#include <linux/bitops.h>
#include <linux/cleanup.h>
#include <linux/container_of.h>
#include <linux/delay.h>
@@ -344,7 +344,7 @@ static int acpm_dequeue_by_polling(struct acpm_chan *achan,
acpm_get_saved_rx(achan, xfer, seqnum);
/* Relinquish ownership of the sequence slot */
- clear_bit(seqnum - 1, achan->bitmap_seqnum);
+ clear_bit_unlock(seqnum - 1, achan->bitmap_seqnum);
return 0;
}
@@ -401,11 +401,18 @@ static void acpm_prepare_xfer(struct acpm_chan *achan,
struct acpm_rx_data *rx_data;
u32 *txd = (u32 *)xfer->txd;
- /* Prevent chan->seqnum from being re-used */
+ /*
+ * Prevent chan->seqnum from being re-used.
+ * test_and_set_bit_lock() provides formal LKMM Acquire semantics.
+ * It pairs with the RX thread's clear_bit_unlock() to ensure the CPU
+ * does not speculatively execute the rx_data buffer wipe (memset)
+ * before the sequence number is safely claimed.
+ */
do {
if (++achan->seqnum == ACPM_SEQNUM_MAX)
achan->seqnum = 1;
- } while (test_bit(achan->seqnum - 1, achan->bitmap_seqnum));
+ /* Flag the index based on seqnum. (seqnum: 1~63, bitmap: 0~62) */
+ } while (test_and_set_bit_lock(achan->seqnum - 1, achan->bitmap_seqnum));
txd[0] |= FIELD_PREP(ACPM_PROTOCOL_SEQNUM, achan->seqnum);
@@ -415,9 +422,6 @@ static void acpm_prepare_xfer(struct acpm_chan *achan,
memset(rx_data->cmd, 0, sizeof(*rx_data->cmd) * rx_data->n_cmd);
/* zero means no response expected */
rx_data->rxcnt = xfer->rxcnt;
-
- /* Flag the index based on seqnum. (seqnum: 1~63, bitmap: 0~62) */
- set_bit(achan->seqnum - 1, achan->bitmap_seqnum);
}
/**
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v5 7/7] firmware: samsung: acpm: Fix infinite loop on sequence number exhaustion
2026-05-05 13:12 [PATCH v5 0/7] firmware: samsung: acpm: Various fixes for sashiko bug reports Tudor Ambarus
` (5 preceding siblings ...)
2026-05-05 13:13 ` [PATCH v5 6/7] firmware: samsung: acpm: Fix missing LKMM barriers in sequence allocator Tudor Ambarus
@ 2026-05-05 13:13 ` Tudor Ambarus
2026-05-06 8:29 ` [PATCH v5 0/7] firmware: samsung: acpm: Various fixes for sashiko bug reports Tudor Ambarus
7 siblings, 0 replies; 10+ messages in thread
From: Tudor Ambarus @ 2026-05-05 13:13 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar
Cc: linux-kernel, linux-samsung-soc, linux-arm-kernel, peter.griffin,
andre.draszik, jyescas, kernel-team, Tudor Ambarus, stable
Sashiko identified a possible infinite loop [1].
ACPM IPC sequence numbers are tracked via a 64-bit bitmap. Previously,
acpm_prepare_xfer() used a do...while loop to search for a free
sequence number.
If all 63 available sequence numbers are leaked due to transient
hardware timeouts or mailbox failures, the bitmap becomes full.
The next call to acpm_prepare_xfer() would enter an infinite loop.
Fix this by utilizing the kernel's optimized bitmap search functions
(find_next_zero_bit / find_first_zero_bit). If the pool is completely
exhausted, log the failure and return -EBUSY to allow the kernel to
fail gracefully instead of hanging.
Furthermore, drop the allocation loop entirely. Because
acpm_prepare_xfer() is strictly called under the 'tx_lock' mutex,
sequence number allocations are perfectly serialized. If
find_next_zero_bit() locates a free bit, a single
test_and_set_bit_lock() is mathematically guaranteed to succeed.
To enforce this locking invariant, wrap the allocation in a
WARN_ON_ONCE. If the atomic set fails, it indicates the driver's
mutex serialization is fundamentally broken. The warning generates a
stack trace for debugging, while returning -EIO immediately aborts the
transfer to prevent silent payload corruption.
Cc: stable@vger.kernel.org
Fixes: a88927b534ba ("firmware: add Exynos ACPM protocol driver")
Closes: https://sashiko.dev/#/patchset/20260420-acpm-tmu-v3-0-3dc8e93f0b26%40linaro.org [1]
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
drivers/firmware/samsung/exynos-acpm.c | 45 +++++++++++++++++++++++-----------
1 file changed, 31 insertions(+), 14 deletions(-)
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index fd2e46e9f7e9..a2cac913b2bd 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -13,6 +13,7 @@
#include <linux/container_of.h>
#include <linux/delay.h>
#include <linux/device.h>
+#include <linux/find.h>
#include <linux/firmware/samsung/exynos-acpm-protocol.h>
#include <linux/io.h>
#include <linux/iopoll.h>
@@ -394,34 +395,48 @@ static int acpm_wait_for_queue_slots(struct acpm_chan *achan, u32 next_tx_front)
* TX queue.
* @achan: ACPM channel info.
* @xfer: reference to the transfer being prepared.
+ *
+ * Return: 0 on success, -errno otherwise.
*/
-static void acpm_prepare_xfer(struct acpm_chan *achan,
- const struct acpm_xfer *xfer)
+static int acpm_prepare_xfer(struct acpm_chan *achan,
+ const struct acpm_xfer *xfer)
{
struct acpm_rx_data *rx_data;
u32 *txd = (u32 *)xfer->txd;
+ unsigned long size = ACPM_SEQNUM_MAX - 1;
+ unsigned long bit = achan->seqnum;
+
+ bit = find_next_zero_bit(achan->bitmap_seqnum, size, bit);
+ if (bit >= size) {
+ bit = find_first_zero_bit(achan->bitmap_seqnum, size);
+ if (bit >= size) {
+ dev_err_ratelimited(achan->acpm->dev,
+ "ACPM sequence number pool exhausted\n");
+ return -EBUSY;
+ }
+ }
/*
- * Prevent chan->seqnum from being re-used.
- * test_and_set_bit_lock() provides formal LKMM Acquire semantics.
- * It pairs with the RX thread's clear_bit_unlock() to ensure the CPU
- * does not speculatively execute the rx_data buffer wipe (memset)
- * before the sequence number is safely claimed.
+ * Execute the atomic set to formally claim the bit and establish
+ * LKMM Acquire semantics against the RX thread's clear_bit_unlock().
+ * A loop is unnecessary because allocations are strictly serialized
+ * by tx_lock.
*/
- do {
- if (++achan->seqnum == ACPM_SEQNUM_MAX)
- achan->seqnum = 1;
- /* Flag the index based on seqnum. (seqnum: 1~63, bitmap: 0~62) */
- } while (test_and_set_bit_lock(achan->seqnum - 1, achan->bitmap_seqnum));
+ if (WARN_ON_ONCE(test_and_set_bit_lock(bit, achan->bitmap_seqnum)))
+ return -EIO;
+ /* Flag the index based on seqnum. (seqnum: 1~63, bitmap: 0~62) */
+ achan->seqnum = bit + 1;
txd[0] |= FIELD_PREP(ACPM_PROTOCOL_SEQNUM, achan->seqnum);
/* Clear data for upcoming responses */
- rx_data = &achan->rx_data[achan->seqnum - 1];
+ rx_data = &achan->rx_data[bit];
rx_data->completed = false;
memset(rx_data->cmd, 0, sizeof(*rx_data->cmd) * rx_data->n_cmd);
/* zero means no response expected */
rx_data->rxcnt = xfer->rxcnt;
+
+ return 0;
}
/**
@@ -481,7 +496,9 @@ int acpm_do_xfer(struct acpm_handle *handle, const struct acpm_xfer *xfer)
if (ret)
return ret;
- acpm_prepare_xfer(achan, xfer);
+ ret = acpm_prepare_xfer(achan, xfer);
+ if (ret)
+ return ret;
/* Write TX command. */
__iowrite32_copy(achan->tx.base + achan->mlen * tx_front,
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH v5 0/7] firmware: samsung: acpm: Various fixes for sashiko bug reports
2026-05-05 13:12 [PATCH v5 0/7] firmware: samsung: acpm: Various fixes for sashiko bug reports Tudor Ambarus
` (6 preceding siblings ...)
2026-05-05 13:13 ` [PATCH v5 7/7] firmware: samsung: acpm: Fix infinite loop on sequence number exhaustion Tudor Ambarus
@ 2026-05-06 8:29 ` Tudor Ambarus
7 siblings, 0 replies; 10+ messages in thread
From: Tudor Ambarus @ 2026-05-06 8:29 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar
Cc: linux-kernel, linux-samsung-soc, linux-arm-kernel, peter.griffin,
andre.draszik, jyescas, kernel-team, stable, Titouan Ameline
Hi Krzysztof!
I checked Sashiko's review feedback on the set, and in my opinion the
set is ready to be queued. I'm going to send the cleanup and the
ACPM TMU helper driver patches now.
Thanks!
ta
^ permalink raw reply [flat|nested] 10+ messages in thread