[RFC PATCH v3 1/5] HID: spi-hid: isolate DMA transfers from protocol buffers

From: Jizhou Tong

Date: Thu Nov 05 2026 - 14:28:54 EST


SPI controllers may DMA-map the transfer buffers. Pointers into the
packed protocol buffers do not guarantee DMA alignment or isolation from
adjacent data during cache maintenance.

Allocate a separate transfer buffer with explicitly aligned TX and RX
regions, and copy protocol data into and out of it under io_lock. Keep
the original allocation pointer for managed ownership and publish the
new capacity only after allocation succeeds.

Signed-off-by: Jizhou Tong <fqwqf@xxxxxxxxx>
---
drivers/hid/spi-hid/spi-hid-core.c | 33 ++++++++++++++++++++++++------
drivers/hid/spi-hid/spi-hid-core.h | 3 +++
2 files changed, 30 insertions(+), 6 deletions(-)

diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-hid-core.c
index f2b2106600..db04322517 100644
--- a/drivers/hid/spi-hid/spi-hid-core.c
+++ b/drivers/hid/spi-hid/spi-hid-core.c
@@ -193,14 +193,19 @@ static void spi_hid_populate_output_header(u8 *buf,
static int spi_hid_input_sync(struct spi_hid *shid, void *buf, u16 length,
bool is_header)
{
+ u8 *rx = shid->xfer_buf + ARCH_DMA_MINALIGN;
int error;

- shid->input_transfer[0].tx_buf = is_header ?
- shid->read_approval_header :
- shid->read_approval_body;
+ if (length > shid->xfer_size - ARCH_DMA_MINALIGN)
+ return -EMSGSIZE;
+
+ memcpy(shid->xfer_buf, is_header ? shid->read_approval_header :
+ shid->read_approval_body,
+ SPI_HID_READ_APPROVAL_LEN);
+ shid->input_transfer[0].tx_buf = shid->xfer_buf;
shid->input_transfer[0].len = SPI_HID_READ_APPROVAL_LEN;

- shid->input_transfer[1].rx_buf = buf;
+ shid->input_transfer[1].rx_buf = rx;
shid->input_transfer[1].len = length;

spi_message_init_with_transfers(&shid->input_message,
@@ -217,12 +222,17 @@ static int spi_hid_input_sync(struct spi_hid *shid, void *buf, u16 length,
return error;
}

+ memcpy(buf, rx, length);
return 0;
}

static int spi_hid_output(struct spi_hid *shid, const void *buf, u16 length)
{
- return spi_write(shid->spi, buf, length);
+ if (length > shid->xfer_size)
+ return -EMSGSIZE;
+
+ memcpy(shid->xfer_buf, buf, length);
+ return spi_write(shid->spi, shid->xfer_buf, length);
}

static const char *spi_hid_power_mode_string(enum hidspi_power_state power_state)
@@ -1203,6 +1213,7 @@ static int spi_hid_alloc_buffers(struct spi_hid *shid, size_t report_size)
int inbufsize = round_up(sizeof(shid->input->header) +
sizeof(shid->input->body) + report_size, 4);
int outbufsize = round_up(sizeof(shid->output->header) + report_size, 4);
+ size_t xfer_size;
void *tmp;

tmp = devm_krealloc(dev, shid->output, outbufsize, GFP_KERNEL | __GFP_ZERO);
@@ -1220,8 +1231,18 @@ static int spi_hid_alloc_buffers(struct spi_hid *shid, size_t report_size)
return -ENOMEM;
shid->response = tmp;

- if (!shid->output || !shid->input || !shid->response)
+ /* io_lock serializes the separate, DMA-aligned TX and RX regions. */
+ xfer_size = ALIGN(max(inbufsize, outbufsize), ARCH_DMA_MINALIGN) +
+ ARCH_DMA_MINALIGN;
+ /* Keep ownership separate from the explicitly aligned DMA view. */
+ tmp = devm_krealloc(dev, shid->xfer_alloc,
+ xfer_size + ARCH_DMA_MINALIGN - 1,
+ GFP_KERNEL | __GFP_ZERO);
+ if (!tmp)
return -ENOMEM;
+ shid->xfer_alloc = tmp;
+ shid->xfer_buf = PTR_ALIGN(tmp, ARCH_DMA_MINALIGN);
+ shid->xfer_size = xfer_size;

shid->bufsize = report_size;

diff --git a/drivers/hid/spi-hid/spi-hid-core.h b/drivers/hid/spi-hid/spi-hid-core.h
index 28e9880ad4..3419472844 100644
--- a/drivers/hid/spi-hid/spi-hid-core.h
+++ b/drivers/hid/spi-hid/spi-hid-core.h
@@ -44,6 +44,9 @@ struct spi_hid_device_descriptor {

/* Driver context */
struct spi_hid {
+ void *xfer_alloc;
+ u8 *xfer_buf;
+ size_t xfer_size;
struct spi_device *spi; /* spi device. */
struct hid_device *hid; /* pointer to corresponding HID dev. */