[PATCH 6/7] spi: spidev_test: add option to split message into multiple transfers
From: Jonas Rebmann
Date: Mon Sep 14 2026 - 12:06:28 EST
From: Marc Kleine-Budde <mkl@xxxxxxxxxxxxxx>
SPI_IOC_MESSAGE() submits a single SPI message containing one or more
transfers. Introduce an option to allow splitting the message into
multiple transfers.
Signed-off-by: Marc Kleine-Budde <mkl@xxxxxxxxxxxxxx>
Signed-off-by: Jonas Rebmann <jre@xxxxxxxxxxxxxx>
---
tools/spi/spidev_test.c | 92 ++++++++++++++++++++++++++++++++-----------------
1 file changed, 61 insertions(+), 31 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index 4d35ac0d9a48..a07c05f2d8b8 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -23,6 +23,7 @@
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <linux/kernel.h>
+#include <linux/align.h>
static void pabort(const char *s)
{
@@ -44,6 +45,7 @@ static uint16_t delay;
static uint16_t word_delay;
static int verbose;
static int transfer_size;
+static int transfers = 1;
static int iterations;
static int interval = 5; /* interval in seconds for showing transfer rate */
static int compare;
@@ -119,40 +121,60 @@ static int unescape(char *_dst, char *_src, size_t len)
return ret;
}
-static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
+static void transfer(int fd, uint8_t const * const tx, uint8_t const * const rx, size_t len)
{
int ret;
int out_fd;
- struct spi_ioc_transfer tr = {
- .tx_buf = (unsigned long)tx,
- .rx_buf = (unsigned long)rx,
- .len = len,
- .delay_usecs = delay,
- .word_delay_usecs = word_delay,
- .speed_hz = speed,
- .bits_per_word = bits,
- };
-
- if (mode & SPI_TX_OCTAL)
- tr.tx_nbits = 8;
- else if (mode & SPI_TX_QUAD)
- tr.tx_nbits = 4;
- else if (mode & SPI_TX_DUAL)
- tr.tx_nbits = 2;
- if (mode & SPI_RX_OCTAL)
- tr.rx_nbits = 8;
- else if (mode & SPI_RX_QUAD)
- tr.rx_nbits = 4;
- else if (mode & SPI_RX_DUAL)
- tr.rx_nbits = 2;
- if (!(mode & SPI_LOOP)) {
- if (mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL))
- tr.rx_buf = 0;
- else if (mode & (SPI_RX_OCTAL | SPI_RX_QUAD | SPI_RX_DUAL))
- tr.tx_buf = 0;
+ size_t bytes_per_word = DIV_ROUND_UP(bits, 8);
+ int effective_transfers = min_t(int, transfers, DIV_ROUND_UP(len, bytes_per_word));
+ struct spi_ioc_transfer tr[effective_transfers];
+ size_t len_per_transfer = ALIGN(DIV_ROUND_UP(len, effective_transfers), bytes_per_word);
+ const uint8_t *tx_buf = tx;
+ const uint8_t *rx_buf = rx;
+ size_t rem = len;
+
+ for (int i = 0; i < effective_transfers; i++) {
+ size_t n = min(rem, len_per_transfer);
+
+ tr[i] = (struct spi_ioc_transfer) {
+ .tx_buf = (unsigned long)tx_buf,
+ .rx_buf = (unsigned long)rx_buf,
+ .len = n,
+ .delay_usecs = delay,
+ .word_delay_usecs = word_delay,
+ .speed_hz = speed,
+ .bits_per_word = bits,
+ };
+
+ if (tx_buf)
+ tx_buf += n;
+
+ if (rx_buf)
+ rx_buf += n;
+
+ rem -= n;
+
+ if (mode & SPI_TX_OCTAL)
+ tr[i].tx_nbits = 8;
+ else if (mode & SPI_TX_QUAD)
+ tr[i].tx_nbits = 4;
+ else if (mode & SPI_TX_DUAL)
+ tr[i].tx_nbits = 2;
+ if (mode & SPI_RX_OCTAL)
+ tr[i].rx_nbits = 8;
+ else if (mode & SPI_RX_QUAD)
+ tr[i].rx_nbits = 4;
+ else if (mode & SPI_RX_DUAL)
+ tr[i].rx_nbits = 2;
+ if (!(mode & SPI_LOOP)) {
+ if (mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL))
+ tr[i].rx_buf = 0;
+ else if (mode & (SPI_RX_OCTAL | SPI_RX_QUAD | SPI_RX_DUAL))
+ tr[i].tx_buf = 0;
+ }
}
- ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
+ ret = ioctl(fd, SPI_IOC_MESSAGE(effective_transfers), &tr);
if (ret < 1)
pabort("can't send spi message");
@@ -177,7 +199,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
static void print_usage(const char *prog)
{
- printf("Usage: %s [-2348CDFHILMNORSZbdilctropsvwzP]\n", prog);
+ printf("Usage: %s [-2348CDFHILMNORSZbdilctropsvwTzP]\n", prog);
puts("general device settings:\n"
" -D --device device to use (default /dev/spidev1.1)\n"
" -s --speed max speed (Hz)\n"
@@ -212,6 +234,7 @@ static void print_usage(const char *prog)
" -N --no-cs no chip select\n"
" -R --ready slave pulls low to pause\n"
" -M --mosi-idle-low leave mosi line low when idle\n"
+ " -T --transfers number of transfers\n"
"misc:\n"
" -v --verbose Verbose (show tx buffer)\n");
exit(1);
@@ -249,12 +272,13 @@ static void parse_opts(int argc, char *argv[])
{ "ready", 0, 0, 'R' },
{ "mosi-idle-low", 0, 0, 'M' },
{ "predictable", 0, 0, 'P' },
+ { "transfers", 1, 0, 'T' },
{ "verbose", 0, 0, 'v' },
{ NULL, 0, 0, 0 },
};
int c;
- c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:PvS:zI:",
+ c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:PT:vS:zI:",
lopts, NULL);
if (c == -1)
@@ -319,6 +343,9 @@ static void parse_opts(int argc, char *argv[])
case 'M':
mode |= SPI_MOSI_IDLE_LOW;
break;
+ case 'T':
+ transfers = atoi(optarg);
+ break;
case 'N':
mode |= SPI_NO_CS;
break;
@@ -496,6 +523,9 @@ int main(int argc, char *argv[])
if (compare && (!do_tx || !do_rx))
pabort("-l/-c conflict with -t or -r");
+ if (transfers < 1)
+ pabort("-T (--transfers) must be 1 or above");
+
fd = open(device, O_RDWR);
if (fd < 0)
pabort("can't open device");
--
2.55.0.806.gb8242b093d