spi: spidev_test: add option to split message into multiple transfers

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@pengutronix.de>
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-11-126e31a07fdc@pengutronix.de
Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
Marc Kleine-Budde 2026-09-24 15:44:01 +02:00 committed by Mark Brown
parent c807c71d21
commit 31ce698070
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0

View File

@ -42,6 +42,7 @@ static uint16_t delay;
static uint16_t word_delay;
static int verbose;
static int transfer_size = -1;
static int transfers = 1;
static int iterations;
static int interval = 5; /* interval in seconds for showing transfer rate */
static int compare;
@ -123,40 +124,79 @@ 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,
};
size_t bytes_per_word = DIV_ROUND_UP(bits, 8);
int effective_transfers;
struct spi_ioc_transfer *tr;
size_t len_per_transfer;
const uint8_t *tx_buf = tx;
const uint8_t *rx_buf = rx;
size_t rem;
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;
if (len) {
effective_transfers = min_t(size_t, transfers, DIV_ROUND_UP(len, bytes_per_word));
len_per_transfer = roundup(DIV_ROUND_UP(len, effective_transfers), bytes_per_word);
} else {
/* zero-length transfers may be handled by the driver, deliver as ordered */
effective_transfers = transfers;
len_per_transfer = 0;
}
ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
if (SPI_MSGSIZE(effective_transfers) == 0)
pabort("too many transfers, SPI_IOC_MESSAGE size limit exceeded");
tr = calloc(effective_transfers, sizeof(*tr));
if (!tr)
pabort("can't allocate transfer array");
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(effective_transfers), tr);
free(tr);
if (ret < 0)
pabort("can't send spi message");
@ -190,7 +230,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 [-2348CDFHILMNOPRSZbcdiloprstvwz]\n", prog);
printf("Usage: %s [-2348CDFHILMNOPRSTZbcdiloprstvwz]\n", prog);
puts("general device settings:\n"
" -D --device device to use (default /dev/spidev1.1)\n"
" -s --speed max speed (Hz)\n"
@ -225,6 +265,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);
@ -262,12 +303,13 @@ static void parse_opts(int argc, char *argv[])
{ "ready", 0, 0, 'R' },
{ "mosi-idle-low", 0, 0, 'M' },
{ "predictable", 1, 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:P:vS:zI:",
c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:P:T:vS:zI:",
lopts, NULL);
if (c == -1)
@ -332,6 +374,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;
@ -533,6 +578,12 @@ int main(int argc, char *argv[])
if (compare && mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL))
pabort("-c (--compare) conflicts with -2 (--dual), -4 (--quad) or -8 (--octal)");
if (transfers < 1)
pabort("-T (--transfers) must be 1 or above");
if (bits < 1)
pabort("-b (--bpw) must be 1 or above");
fd = open(device, O_RDWR);
if (fd < 0)
pabort("can't open device");