Merge tag 'samsung-drivers-fixes-7.1-2' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux into arm/fixes

Samsung SoC driver fixes for v7.1

Fix several concurrency issues present in Samsung ACPM firmware drivers,
used currently only on Google GS101.  Tudor with help of Sashiko
identified several missing barriers and incomplete synchronization,
leading to possible transfer data corruption or use after free.  Few
other issues related to probe, including missing mailbox cleanup, were
also fixed.

* tag 'samsung-drivers-fixes-7.1-2' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux:
  firmware: samsung: acpm: Fix infinite loop on sequence number exhaustion
  firmware: samsung: acpm: Fix missing LKMM barriers in sequence allocator
  firmware: samsung: acpm: Fix false timeouts and Use-After-Free in polling
  firmware: samsung: acpm: Fix mailbox channel leak on probe error
  firmware: samsung: acpm: Fix cross-thread RX length corruption

Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
Arnd Bergmann
2026-05-29 23:37:36 +02:00
2 changed files with 100 additions and 44 deletions
@@ -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;
}
}
+97 -44
View File
@@ -7,11 +7,12 @@
#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>
#include <linux/device.h>
#include <linux/find.h>
#include <linux/firmware/samsung/exynos-acpm-protocol.h>
#include <linux/io.h>
#include <linux/iopoll.h>
@@ -104,12 +105,15 @@ 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.
* @completed: flag indicating if the firmware response has been fully
* processed.
*/
struct acpm_rx_data {
u32 *cmd;
size_t n_cmd;
bool response;
size_t rxcnt;
bool completed;
};
#define ACPM_SEQNUM_MAX 64
@@ -199,31 +203,33 @@ 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]);
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);
@@ -232,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;
@@ -256,11 +260,16 @@ 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;
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.
@@ -268,10 +277,23 @@ 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);
/*
* 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;
@@ -280,13 +302,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;
}
@@ -301,6 +316,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;
@@ -309,12 +325,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_unlock(seqnum - 1, achan->bitmap_seqnum);
return 0;
}
/* Determined experimentally. */
udelay(20);
@@ -362,29 +391,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;
/* Prevent chan->seqnum from being re-used */
do {
if (++achan->seqnum == ACPM_SEQNUM_MAX)
achan->seqnum = 1;
} while (test_bit(achan->seqnum - 1, achan->bitmap_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;
}
}
/*
* 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.
*/
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);
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);
return 0;
}
/**
@@ -444,7 +492,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,
@@ -526,10 +576,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++)
@@ -557,6 +608,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++) {
@@ -578,10 +633,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;