mirror of
https://github.com/torvalds/linux.git
synced 2026-06-05 13:06:59 +02:00
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. -----BEGIN PGP SIGNATURE----- iQJEBAABCgAuFiEE3dJiKD0RGyM7briowTdm5oaLg9cFAmoZgsoQHGtyemtAa2Vy bmVsLm9yZwAKCRDBN2bmhouD1wmbEACCXrSzY82MLt6XmVByWQy9U9OBLkC471qA GhcVdC0uMs6hD5prvJCnHVP236XKUZnT6EZjpBW+8Me3Yw4z4j9Wdd5j0z1haEF5 DdldsjBjrawsXazVVlj4J7iE1fv83dZrgOASDXFhP+GHRPsfrCYWlNIPhWcG8wwp 68yYY8FT4hQ4/i5otWixHSdeaadPV/TWf+W90l/1Z6uXdt/9CzAcavHiLOkLWji+ dy87ZNTcpZoCqa12sPz2wkmMXj5ET5vUsuvoVZu3376ViU8q/6kSIRYtIQjCgM8J cMNArDequv6g24ra+W8D234qo50fBAVpeYHFyZKDJYekkJ9HJyQ2NXarJz/kvEUw ZkmUA/SgZUc0hM5i0J+5O6K7lTed6eMmkR72QTT/uQZOAcEkqsFpkuZhAetxguR8 vO7p99+QAS/grHKiXaLKTOYgpoxitqx0int0FZ1shWbeO/OYorrZ00x0OkK++V++ WsI7SJVNco/LM4APdSIRBphZ5ULl6xgu2SLO05J7uQFgiUWU0Q9v+t1MxIbvmL0T HH5H1STGr12LOBwnJDjK/AYbAhFFM9pZxgPJU9S4NzKu3peQJjgLvnOeEEmLkmeQ 47bKUlrOMQIP5lpzLBYtL9NT/7fyQKfl8fbdq5O3KwthMvOiit6mGD+ExDoptIp4 4T+NBiG08A== =hb6T -----END PGP SIGNATURE----- gpgsig -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEo6/YBQwIrVS28WGKmmx57+YAGNkFAmoaByIACgkQmmx57+YA GNmeiA/9EZ+LO2JUrUVhMNXOfUmDGrpfL/mVqcGL9ksK+EkOW+9Fae8f9hWrvBFh exeMRTwKDcA2hag6AnBiKhctLYFIZ7+nRb/xu2ybasdIAloR10oDKlmgzH/Yj4c9 MsIgfuK+q5FnOxyhtNEZ4JszYYvAl3wfFvBR1241L52tJ+vyOLxEjk/8rhESFv1P rTeCXlRA/dJ4hqpP9j+QflgrvrIYe1vhsBdNFXQEcjUgQnqySPY7Ynlnr1BJMyWQ N4x+gJ9ZITr75kzMJCwqGXfktwPP6+BahBHDk8i6U6zfFwb2DJFHx0OSYf3Z1s1w V7wxIMiGr/XZ/r0D6prlqYAjLZS8KL9sYEC/UB+WB95SwnFWm3J1K895rt4awuzY HD6jhgPlyvIBEJRI7ZNMB4Hg0p9Z5pXse1EDseDdAnAth9brbFN7d/JbbtTMKfny jyQJG8rEUvmSF+No35VprFW6JhyDHFlnrQvoSQdLgxsKHrMRcI1xYnQXi2aILlYI cYCYzL2lAeteaMHVMrPDoLQGCbKnvTz+pAhiOXTX2ie6XS8JuYPhf1H+Pp8GTozp P+Iy995FZ92aaCyPmINa8lN2hfuC3qhxNZehZ8bEShKQ+44wab1SV6q/shM+HPt2 i1OLt9Kg/CWt6hVXW3jkZvdNry1HuPP3f4jrQwquOFh6h2VlnXw= =zqbF -----END PGP SIGNATURE----- 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:
commit
f5e70b3f30
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user