[PATCH] wilc1000: fix DMA on stack objects

From: Michael Walle
Date: Thu Jul 28 2022 - 11:20:53 EST


From: Michael Walle <mwalle@xxxxxxxxxx>

Sometimes wilc_sdio_cmd53() is called with addresses pointing to an
object on the stack. E.g. wilc_sdio_write_reg() will call it with an
address pointing to one of its arguments. Detect whether the buffer
address is not DMA-able in which case a bounce buffer is used. The bounce
buffer itself is protected from parallel accesses by sdio_claim_host().

Fixes: 5625f965d764 ("wilc1000: move wilc driver out of staging")
Signed-off-by: Michael Walle <mwalle@xxxxxxxxxx>
---
The bug itself probably goes back way more, but I don't know if it makes
any sense to use an older commit for the Fixes tag. If so, please suggest
one.

The bug leads to an actual error on an imx8mn SoC with 1GiB of RAM. But the
error will also be catched by CONFIG_DEBUG_VIRTUAL:
[ 9.817512] virt_to_phys used for non-linear address: (____ptrval____) (0xffff80000a94bc9c)

.../net/wireless/microchip/wilc1000/sdio.c | 28 ++++++++++++++++---
1 file changed, 24 insertions(+), 4 deletions(-)

diff --git a/drivers/net/wireless/microchip/wilc1000/sdio.c b/drivers/net/wireless/microchip/wilc1000/sdio.c
index 7962c11cfe84..e988bede880c 100644
--- a/drivers/net/wireless/microchip/wilc1000/sdio.c
+++ b/drivers/net/wireless/microchip/wilc1000/sdio.c
@@ -27,6 +27,7 @@ struct wilc_sdio {
bool irq_gpio;
u32 block_size;
int has_thrpt_enh3;
+ u8 *dma_buffer;
};

struct sdio_cmd52 {
@@ -89,6 +90,9 @@ static int wilc_sdio_cmd52(struct wilc *wilc, struct sdio_cmd52 *cmd)
static int wilc_sdio_cmd53(struct wilc *wilc, struct sdio_cmd53 *cmd)
{
struct sdio_func *func = container_of(wilc->dev, struct sdio_func, dev);
+ struct wilc_sdio *sdio_priv = wilc->bus_data;
+ bool need_bounce_buf = false;
+ u8 *buf = cmd->buffer;
int size, ret;

sdio_claim_host(func);
@@ -100,12 +104,20 @@ static int wilc_sdio_cmd53(struct wilc *wilc, struct sdio_cmd53 *cmd)
else
size = cmd->count;

+ if ((!virt_addr_valid(buf) || object_is_on_stack(buf)) &&
+ !WARN_ON_ONCE(size > WILC_SDIO_BLOCK_SIZE)) {
+ need_bounce_buf = true;
+ buf = sdio_priv->dma_buffer;
+ }
+
if (cmd->read_write) { /* write */
- ret = sdio_memcpy_toio(func, cmd->address,
- (void *)cmd->buffer, size);
+ if (need_bounce_buf)
+ memcpy(buf, cmd->buffer, size);
+ ret = sdio_memcpy_toio(func, cmd->address, buf, size);
} else { /* read */
- ret = sdio_memcpy_fromio(func, (void *)cmd->buffer,
- cmd->address, size);
+ ret = sdio_memcpy_fromio(func, buf, cmd->address, size);
+ if (need_bounce_buf)
+ memcpy(cmd->buffer, buf, size);
}

sdio_release_host(func);
@@ -127,6 +139,12 @@ static int wilc_sdio_probe(struct sdio_func *func,
if (!sdio_priv)
return -ENOMEM;

+ sdio_priv->dma_buffer = kzalloc(WILC_SDIO_BLOCK_SIZE, GFP_KERNEL);
+ if (!sdio_priv->dma_buffer) {
+ ret = -ENOMEM;
+ goto free;
+ }
+
ret = wilc_cfg80211_init(&wilc, &func->dev, WILC_HIF_SDIO,
&wilc_hif_sdio);
if (ret)
@@ -160,6 +178,7 @@ static int wilc_sdio_probe(struct sdio_func *func,
irq_dispose_mapping(wilc->dev_irq_num);
wilc_netdev_cleanup(wilc);
free:
+ kfree(sdio_priv->dma_buffer);
kfree(sdio_priv);
return ret;
}
@@ -171,6 +190,7 @@ static void wilc_sdio_remove(struct sdio_func *func)

clk_disable_unprepare(wilc->rtc_clk);
wilc_netdev_cleanup(wilc);
+ kfree(sdio_priv->dma_buffer);
kfree(sdio_priv);
}

--
2.30.2