[PATCH] spi: ch341: handle transfers without a TX or RX buffer
From: Weibin Liu
Date: Tue Sep 29 2026 - 04:14:38 EST
ch341_transfer_one() unconditionally copies from trans->tx_buf into the
TX packet and hands trans->rx_buf to usb_bulk_msg() for the readback.
The SPI core allows half-duplex transfers where either of the buffers
is NULL, so a transfer without TX data crashes the kernel on the memcpy
and a transfer without RX data makes usb_bulk_msg() store the received
data at address 0.
Only copy TX data when the transfer carries a TX buffer and fall back
to the TX packet buffer as a scratch area for the readback when the
transfer has no RX buffer. The packet buffer is 32 bytes, which covers
the maximum readback length of 31 bytes, and it is not used by
anything else while the readback runs.
Fixes: 8846739f52af ("spi: add ch341a usb2spi driver")
Cc: stable@xxxxxxxxxxxxxxx # 6.11+
Signed-off-by: Weibin Liu <liuwb@xxxxxxxxxxxx>
---
Reviewer notes:
- Both failure modes are reachable from unprivileged userspace through
spidev: SPI_IOC_MESSAGE accepts transfers with either of the buffers
set to NULL, and the driver passes them on as-is.
- The readback fallback reuses the 32-byte TX packet buffer, which
covers the maximum readback length of 31 bytes. It is only used by
ch341_transfer_one() and ch341_set_cs(), both of which run
synchronously from the SPI core's transfer handling, so nothing
touches the buffer while the readback is in flight.
- Pre-fix reproducer: open /dev/spidev0.0 and issue a single
SPI_IOC_MESSAGE transfer with tx_buf = NULL (memcpy from NULL in
ch341_transfer_one()) or with rx_buf = NULL (usb_bulk_msg() stores
the readback at address 0).
Functionally verified in QEMU on x86_64: a CH341a adapter was emulated
with gadgetfs + dummy_hcd (the emulated device echoes every SPI stream
packet's payload back on the bulk IN endpoint). With this patch
applied the probe completes, and tx-only, rx-only and full-duplex
transfers issued through spidev succeed, with the full-duplex readback
matching the sent payload, and without a splat. The emulator and the
test program are local test tooling and are not part of this
submission.
drivers/spi/spi-ch341.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/drivers/spi/spi-ch341.c b/drivers/spi/spi-ch341.c
index 6448a44a8..6c6eb4ac7 100644
--- a/drivers/spi/spi-ch341.c
+++ b/drivers/spi/spi-ch341.c
@@ -78,14 +78,21 @@ static int ch341_transfer_one(struct spi_controller *host,
ch341->tx_buf[0] = CH341A_CMD_SPI_STREAM;
- memcpy(ch341->tx_buf + 1, trans->tx_buf, len - 1);
+ if (trans->tx_buf)
+ memcpy(ch341->tx_buf + 1, trans->tx_buf, len - 1);
ret = usb_bulk_msg(ch341->udev, ch341->write_pipe, ch341->tx_buf, len,
NULL, CH341_DEFAULT_TIMEOUT);
if (ret)
return ret;
- return usb_bulk_msg(ch341->udev, ch341->read_pipe, trans->rx_buf,
+ /*
+ * Half-duplex transfers can come without a RX buffer; the packet
+ * buffer is not used by anything else during the synchronous
+ * transfer, so reuse it as a scratch area for the readback.
+ */
+ return usb_bulk_msg(ch341->udev, ch341->read_pipe,
+ trans->rx_buf ? trans->rx_buf : ch341->tx_buf,
len - 1, NULL, CH341_DEFAULT_TIMEOUT);
}
base-commit: 72d3fcf802c45d00b300f25b848a93c3a2bd7c7e
prerequisite-patch-id: e48582f6ffe124b3806593af6e22b74682c8a83f
prerequisite-patch-id: 369f74b9a7ecde56141b13ec671f9200345a3bab
--
2.50.1