[RFC PATCH 1/6] HID: spi-hid: isolate DMA transfers from protocol buffers
From: fQwQf
Date: Sat Oct 10 2026 - 13:00:02 EST
The body starts four bytes into the input buffer. Receiving DMA directly
into it can share a cache line with the header and does not meet
controllers that require aligned DMA buffers. Use separate aligned TX
and RX regions under io_lock, and copy received data only after a
successful transfer. Keep protocol parsing buffers away from streaming
DMA.
Overallocate the managed storage and align the DMA view explicitly to
ARCH_DMA_MINALIGN, keeping the allocation base for realloc/free. Publish
the new transfer 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 0046dfdd82677bb55c5a0b6742cdfaa8fec224c6..65a1cd2c72e8025f50a188e17afd2fa5b359abb1 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,
@@ -219,6 +224,7 @@ static int spi_hid_input_sync(struct spi_hid *shid, void *buf, u16 length,
return error;
}
+ memcpy(buf, rx, length);
return 0;
}
@@ -226,7 +232,11 @@ static int spi_hid_output(struct spi_hid *shid, const void *buf, u16 length)
{
int error;
- error = spi_write(shid->spi, buf, length);
+ if (length > shid->xfer_size)
+ return -EMSGSIZE;
+
+ memcpy(shid->xfer_buf, buf, length);
+ error = spi_write(shid->spi, shid->xfer_buf, length);
if (error) {
shid->bus_error_count++;
@@ -1210,6 +1220,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);
@@ -1227,8 +1238,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 631897dd9e8a2f7a7c0ca29a5ae6e79e83e12579..9cbe2e57400443bd68056057d08f97c39c403a70 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. */