* [PATCH 1/7] spi: spidev_test: include tools/include
2026-09-14 14:50 [PATCH 0/7] spi: spidev_test: new features Jonas Rebmann
@ 2026-09-14 14:50 ` Jonas Rebmann
2026-09-14 14:50 ` [PATCH 2/7] spi: spidev_test: add compare mode Jonas Rebmann
` (5 subsequent siblings)
6 siblings, 0 replies; 13+ messages in thread
From: Jonas Rebmann @ 2026-09-14 14:50 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel, Jonas Rebmann
Add tools/include to include paths to allow using macro helpers.
Use ARRAY_SIZE() from tools/include/linux/kernel.h instead of
maintaining a copy.
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
tools/spi/Makefile | 2 +-
tools/spi/spidev_test.c | 3 +--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/tools/spi/Makefile b/tools/spi/Makefile
index 7fccd245a535..8cf21ff8cd03 100644
--- a/tools/spi/Makefile
+++ b/tools/spi/Makefile
@@ -12,7 +12,7 @@ endif
# (this improves performance and avoids hard-to-debug behaviour);
MAKEFLAGS += -r
-CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include
+CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include -I$(srctree)/tools/include
ALL_TARGETS := spidev_test spidev_fdx
ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS))
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index f2135d619a0b..8058a7830b50 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -22,8 +22,7 @@
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
-
-#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#include <linux/kernel.h>
static void pabort(const char *s)
{
--
2.55.0.806.gb8242b093d
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 2/7] spi: spidev_test: add compare mode
2026-09-14 14:50 [PATCH 0/7] spi: spidev_test: new features Jonas Rebmann
2026-09-14 14:50 ` [PATCH 1/7] spi: spidev_test: include tools/include Jonas Rebmann
@ 2026-09-14 14:50 ` Jonas Rebmann
2026-09-14 17:03 ` Mark Brown
2026-09-14 14:50 ` [PATCH 3/7] spi: spidev_test: allow disabling rx or tx buffers Jonas Rebmann
` (4 subsequent siblings)
6 siblings, 1 reply; 13+ messages in thread
From: Jonas Rebmann @ 2026-09-14 14:50 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel, Jonas Rebmann
From: Marc Kleine-Budde <mkl@pengutronix.de>
Check whether the received data correspond to the transferred data but
without enabling loopback mode in the controller.
This is for testing with TX physically bridged to RX which is useful if
a loopback mode is unavailable or insufficient.
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
tools/spi/spidev_test.c | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index 8058a7830b50..05c585ea71b2 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -46,6 +46,7 @@ static int verbose;
static int transfer_size;
static int iterations;
static int interval = 5; /* interval in seconds for showing transfer rate */
+static int compare;
static uint8_t default_tx[] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
@@ -173,13 +174,14 @@ 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 [-2348CDFHILMNORSZbdilopsvw]\n", prog);
+ printf("Usage: %s [-2348CDFHILMNORSZbdilcopsvw]\n", prog);
puts("general device settings:\n"
" -D --device device to use (default /dev/spidev1.1)\n"
" -s --speed max speed (Hz)\n"
" -d --delay delay (usec)\n"
" -w --word-delay word delay (usec)\n"
" -l --loop loopback\n"
+ " -c --compare compare RX'ed and TX'ed data\n"
"spi mode:\n"
" -H --cpha clock phase\n"
" -O --cpol clock polarity\n"
@@ -217,6 +219,7 @@ static void parse_opts(int argc, char *argv[])
{ "delay", 1, 0, 'd' },
{ "word-delay", 1, 0, 'w' },
{ "loop", 0, 0, 'l' },
+ { "compare", 0, 0, 'c' },
{ "cpha", 0, 0, 'H' },
{ "cpol", 0, 0, 'O' },
{ "rx-cpha-flip", 0, 0, 'F' },
@@ -240,7 +243,7 @@ static void parse_opts(int argc, char *argv[])
};
int c;
- c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lHOLC3ZFMNR248p:vS:I:",
+ c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lcHOLC3ZFMNR248p:vS:I:",
lopts, NULL);
if (c == -1)
@@ -270,6 +273,10 @@ static void parse_opts(int argc, char *argv[])
break;
case 'l':
mode |= SPI_LOOP;
+ compare = 1;
+ break;
+ case 'c':
+ compare = 1;
break;
case 'H':
mode |= SPI_CPHA;
@@ -427,7 +434,7 @@ static void transfer_buf(int fd, int len)
_write_count += len;
_read_count += len;
- if (mode & SPI_LOOP) {
+ if (compare) {
if (memcmp(tx, rx, len)) {
fprintf(stderr, "transfer error !\n");
hex_dump(tx, len, 32, "TX");
--
2.55.0.806.gb8242b093d
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH 2/7] spi: spidev_test: add compare mode
2026-09-14 14:50 ` [PATCH 2/7] spi: spidev_test: add compare mode Jonas Rebmann
@ 2026-09-14 17:03 ` Mark Brown
0 siblings, 0 replies; 13+ messages in thread
From: Mark Brown @ 2026-09-14 17:03 UTC (permalink / raw)
To: Jonas Rebmann; +Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel
[-- Attachment #1: Type: text/plain, Size: 416 bytes --]
On Mon, Sep 14, 2026 at 04:50:41PM +0200, Jonas Rebmann wrote:
> From: Marc Kleine-Budde <mkl@pengutronix.de>
>
> Check whether the received data correspond to the transferred data but
> without enabling loopback mode in the controller.
> - printf("Usage: %s [-2348CDFHILMNORSZbdilopsvw]\n", prog);
> + printf("Usage: %s [-2348CDFHILMNORSZbdilcopsvw]\n", prog);
This is sorted alphabetically currently.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 3/7] spi: spidev_test: allow disabling rx or tx buffers
2026-09-14 14:50 [PATCH 0/7] spi: spidev_test: new features Jonas Rebmann
2026-09-14 14:50 ` [PATCH 1/7] spi: spidev_test: include tools/include Jonas Rebmann
2026-09-14 14:50 ` [PATCH 2/7] spi: spidev_test: add compare mode Jonas Rebmann
@ 2026-09-14 14:50 ` Jonas Rebmann
2026-09-14 17:20 ` Mark Brown
2026-09-14 14:50 ` [PATCH 4/7] spi: spidev_test: don't send 0x0 or 0xff Jonas Rebmann
` (3 subsequent siblings)
6 siblings, 1 reply; 13+ messages in thread
From: Jonas Rebmann @ 2026-09-14 14:50 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel, Jonas Rebmann
From: Marc Kleine-Budde <mkl@pengutronix.de>
Allow not providing rx or tx buffers. This is useful to check if drivers
that don't use SPI_CONTROLLER_MUST_RX (or -TX respectively) handle their
operations correctly without a buffer.
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
tools/spi/spidev_test.c | 61 ++++++++++++++++++++++++++++++++-----------------
1 file changed, 40 insertions(+), 21 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index 05c585ea71b2..6b0e839e6c33 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -47,6 +47,7 @@ static int transfer_size;
static int iterations;
static int interval = 5; /* interval in seconds for showing transfer rate */
static int compare;
+static int do_tx = 1, do_rx = 1;
static uint8_t default_tx[] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
@@ -153,10 +154,10 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
if (ret < 1)
pabort("can't send spi message");
- if (verbose)
+ if (verbose && tx)
hex_dump(tx, len, 32, "TX");
- if (output_file) {
+ if (rx && output_file) {
out_fd = open(output_file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (out_fd < 0)
pabort("could not open output file");
@@ -168,13 +169,13 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
close(out_fd);
}
- if (verbose)
+ if (verbose && rx)
hex_dump(rx, len, 32, "RX");
}
static void print_usage(const char *prog)
{
- printf("Usage: %s [-2348CDFHILMNORSZbdilcopsvw]\n", prog);
+ printf("Usage: %s [-2348CDFHILMNORSZbdilctropsvw]\n", prog);
puts("general device settings:\n"
" -D --device device to use (default /dev/spidev1.1)\n"
" -s --speed max speed (Hz)\n"
@@ -182,6 +183,8 @@ static void print_usage(const char *prog)
" -w --word-delay word delay (usec)\n"
" -l --loop loopback\n"
" -c --compare compare RX'ed and TX'ed data\n"
+ " -t --no-tx don't send data\n"
+ " -r --no-rx don't receive data\n"
"spi mode:\n"
" -H --cpha clock phase\n"
" -O --cpol clock polarity\n"
@@ -220,6 +223,8 @@ static void parse_opts(int argc, char *argv[])
{ "word-delay", 1, 0, 'w' },
{ "loop", 0, 0, 'l' },
{ "compare", 0, 0, 'c' },
+ { "no-tx", 0, 0, 't' },
+ { "no-rx", 0, 0, 'r' },
{ "cpha", 0, 0, 'H' },
{ "cpol", 0, 0, 'O' },
{ "rx-cpha-flip", 0, 0, 'F' },
@@ -243,7 +248,7 @@ static void parse_opts(int argc, char *argv[])
};
int c;
- c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lcHOLC3ZFMNR248p:vS:I:",
+ c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:vS:I:",
lopts, NULL);
if (c == -1)
@@ -278,6 +283,12 @@ static void parse_opts(int argc, char *argv[])
case 'c':
compare = 1;
break;
+ case 't':
+ do_tx = 0;
+ break;
+ case 'r':
+ do_rx = 0;
+ break;
case 'H':
mode |= SPI_CPHA;
break;
@@ -415,26 +426,31 @@ static void show_transfer_rate(void)
static void transfer_buf(int fd, int len)
{
- uint8_t *tx;
- uint8_t *rx;
+ uint8_t *tx = NULL;
+ uint8_t *rx = NULL;
int i;
- tx = malloc(len);
- if (!tx)
- pabort("can't allocate tx buffer");
- for (i = 0; i < len; i++)
- tx[i] = random();
+ if (do_tx) {
+ tx = malloc(len);
+ if (!tx)
+ pabort("can't allocate tx buffer");
+ for (i = 0; i < len; i++)
+ tx[i] = random();
+ }
- rx = malloc(len);
- if (!rx)
- pabort("can't allocate rx buffer");
+ if (do_rx) {
+ rx = malloc(len);
+ if (!rx)
+ pabort("can't allocate rx buffer");
+ }
transfer(fd, tx, rx, len);
+ if (do_tx)
+ _write_count += len;
+ if (do_rx)
+ _read_count += len;
- _write_count += len;
- _read_count += len;
-
- if (compare) {
+ if (tx && rx && compare) {
if (memcmp(tx, rx, len)) {
fprintf(stderr, "transfer error !\n");
hex_dump(tx, len, 32, "TX");
@@ -455,8 +471,11 @@ int main(int argc, char *argv[])
parse_opts(argc, argv);
- if (input_tx && input_file)
- pabort("only one of -p and --input may be selected");
+ if (!!input_tx + !!input_file + !do_tx > 0)
+ pabort("only one of -p, -i (--input), -t (--no-tx) may be selected");
+
+ if (compare && (!do_tx || !do_rx))
+ pabort("-l/-c conflict with -t or -r");
fd = open(device, O_RDWR);
if (fd < 0)
--
2.55.0.806.gb8242b093d
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH 3/7] spi: spidev_test: allow disabling rx or tx buffers
2026-09-14 14:50 ` [PATCH 3/7] spi: spidev_test: allow disabling rx or tx buffers Jonas Rebmann
@ 2026-09-14 17:20 ` Mark Brown
0 siblings, 0 replies; 13+ messages in thread
From: Mark Brown @ 2026-09-14 17:20 UTC (permalink / raw)
To: Jonas Rebmann; +Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel
[-- Attachment #1: Type: text/plain, Size: 925 bytes --]
On Mon, Sep 14, 2026 at 04:50:42PM +0200, Jonas Rebmann wrote:
> From: Marc Kleine-Budde <mkl@pengutronix.de>
>
> Allow not providing rx or tx buffers. This is useful to check if drivers
> that don't use SPI_CONTROLLER_MUST_RX (or -TX respectively) handle their
> operations correctly without a buffer.
> - if (output_file) {
> + if (rx && output_file) {
> out_fd = open(output_file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
> if (out_fd < 0)
> pabort("could not open output file");
It'd be nice to complain if the user asks for output and no RX
simultaneously, but OTOH I'm sure the target audience can cope.
> - if (input_tx && input_file)
> - pabort("only one of -p and --input may be selected");
> + if (!!input_tx + !!input_file + !do_tx > 0)
> + pabort("only one of -p, -i (--input), -t (--no-tx) may be selected");
This logic is probably a bit *too* cute; it's far too hard to read.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 4/7] spi: spidev_test: don't send 0x0 or 0xff
2026-09-14 14:50 [PATCH 0/7] spi: spidev_test: new features Jonas Rebmann
` (2 preceding siblings ...)
2026-09-14 14:50 ` [PATCH 3/7] spi: spidev_test: allow disabling rx or tx buffers Jonas Rebmann
@ 2026-09-14 14:50 ` Jonas Rebmann
2026-09-14 17:22 ` Mark Brown
2026-09-14 14:50 ` [PATCH 5/7] spi: spidev_test: send predictable data Jonas Rebmann
` (2 subsequent siblings)
6 siblings, 1 reply; 13+ messages in thread
From: Jonas Rebmann @ 2026-09-14 14:50 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel, Jonas Rebmann
From: Marc Kleine-Budde <mkl@pengutronix.de>
Particularly when using compare mode, if the controller fails to
transfer any data, asserting on a read of 0x00 or 0xff may lead a false
negative test, indicating a byte was successfully transferred when the
values simply originate from the pull-up or pull-down of RX.
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
tools/spi/spidev_test.c | 21 +++++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index 6b0e839e6c33..235b267c02f2 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -47,6 +47,7 @@ static int transfer_size;
static int iterations;
static int interval = 5; /* interval in seconds for showing transfer rate */
static int compare;
+static int nonzero;
static int do_tx = 1, do_rx = 1;
static uint8_t default_tx[] = {
@@ -175,7 +176,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 [-2348CDFHILMNORSZbdilctropsvw]\n", prog);
+ printf("Usage: %s [-2348CDFHILMNORSZbdilctropsvwz]\n", prog);
puts("general device settings:\n"
" -D --device device to use (default /dev/spidev1.1)\n"
" -s --speed max speed (Hz)\n"
@@ -199,6 +200,7 @@ static void print_usage(const char *prog)
" -i --input input data from a file (e.g. \"test.bin\")\n"
" -o --output output data to a file (e.g. \"results.bin\")\n"
" -p Send data (e.g. \"1234\\xde\\xad\")\n"
+ " -z --nonzero Don't send 0x00 or 0xff bytes\n"
" -S --size transfer size\n"
" -I --iter iterations\n"
"additional parameters:\n"
@@ -236,6 +238,7 @@ static void parse_opts(int argc, char *argv[])
{ "input", 1, 0, 'i' },
{ "output", 1, 0, 'o' },
{ "size", 1, 0, 'S' },
+ { "nonzero", 0, 0, 'z' },
{ "iter", 1, 0, 'I' },
{ "bpw", 1, 0, 'b' },
{ "lsb", 0, 0, 'L' },
@@ -248,7 +251,7 @@ static void parse_opts(int argc, char *argv[])
};
int c;
- c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:vS:I:",
+ c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:vS:zI:",
lopts, NULL);
if (c == -1)
@@ -337,6 +340,9 @@ static void parse_opts(int argc, char *argv[])
case 'S':
transfer_size = atoi(optarg);
break;
+ case 'z':
+ nonzero = 1;
+ break;
case 'I':
iterations = atoi(optarg);
break;
@@ -434,8 +440,11 @@ static void transfer_buf(int fd, int len)
tx = malloc(len);
if (!tx)
pabort("can't allocate tx buffer");
- for (i = 0; i < len; i++)
- tx[i] = random();
+ for (i = 0; i < len; i++) {
+ do
+ tx[i] = random();
+ while (nonzero && (tx[i] == 0x0 || tx[i] == 0xff));
+ }
}
if (do_rx) {
@@ -471,8 +480,8 @@ int main(int argc, char *argv[])
parse_opts(argc, argv);
- if (!!input_tx + !!input_file + !do_tx > 0)
- pabort("only one of -p, -i (--input), -t (--no-tx) may be selected");
+ if (!!input_tx + !!input_file + !do_tx + !!nonzero > 1)
+ pabort("only one of -p, -i (--input), -t (--no-tx), -z (--nonzero) may be selected");
if (compare && (!do_tx || !do_rx))
pabort("-l/-c conflict with -t or -r");
--
2.55.0.806.gb8242b093d
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 5/7] spi: spidev_test: send predictable data
2026-09-14 14:50 [PATCH 0/7] spi: spidev_test: new features Jonas Rebmann
` (3 preceding siblings ...)
2026-09-14 14:50 ` [PATCH 4/7] spi: spidev_test: don't send 0x0 or 0xff Jonas Rebmann
@ 2026-09-14 14:50 ` Jonas Rebmann
2026-09-14 14:50 ` [PATCH 6/7] spi: spidev_test: add option to split message into multiple transfers Jonas Rebmann
2026-09-14 14:50 ` [PATCH 7/7] spi: spidev_test: print TX on error Jonas Rebmann
6 siblings, 0 replies; 13+ messages in thread
From: Jonas Rebmann @ 2026-09-14 14:50 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel, Jonas Rebmann
From: Marc Kleine-Budde <mkl@pengutronix.de>
Introduce a flag to test on a predictable byte sequence instead of
random bytes. This is useful when comparing multiple runs e.g. with an
oscilloscope.
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
tools/spi/spidev_test.c | 24 +++++++++++++++++-------
1 file changed, 17 insertions(+), 7 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index 235b267c02f2..4d35ac0d9a48 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -49,6 +49,7 @@ static int interval = 5; /* interval in seconds for showing transfer rate */
static int compare;
static int nonzero;
static int do_tx = 1, do_rx = 1;
+static int predictable;
static uint8_t default_tx[] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
@@ -176,7 +177,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 [-2348CDFHILMNORSZbdilctropsvwz]\n", prog);
+ printf("Usage: %s [-2348CDFHILMNORSZbdilctropsvwzP]\n", prog);
puts("general device settings:\n"
" -D --device device to use (default /dev/spidev1.1)\n"
" -s --speed max speed (Hz)\n"
@@ -201,6 +202,7 @@ static void print_usage(const char *prog)
" -o --output output data to a file (e.g. \"results.bin\")\n"
" -p Send data (e.g. \"1234\\xde\\xad\")\n"
" -z --nonzero Don't send 0x00 or 0xff bytes\n"
+ " -P --predictable Send a predictable sequence instead of random numbers\n"
" -S --size transfer size\n"
" -I --iter iterations\n"
"additional parameters:\n"
@@ -246,12 +248,13 @@ static void parse_opts(int argc, char *argv[])
{ "no-cs", 0, 0, 'N' },
{ "ready", 0, 0, 'R' },
{ "mosi-idle-low", 0, 0, 'M' },
+ { "predictable", 0, 0, 'P' },
{ "verbose", 0, 0, 'v' },
{ NULL, 0, 0, 0 },
};
int c;
- c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:vS:zI:",
+ c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:PvS:zI:",
lopts, NULL);
if (c == -1)
@@ -328,6 +331,9 @@ static void parse_opts(int argc, char *argv[])
case 'p':
input_tx = optarg;
break;
+ case 'P':
+ predictable = 1;
+ break;
case '2':
mode |= SPI_TX_DUAL;
break;
@@ -441,9 +447,13 @@ static void transfer_buf(int fd, int len)
if (!tx)
pabort("can't allocate tx buffer");
for (i = 0; i < len; i++) {
- do
- tx[i] = random();
- while (nonzero && (tx[i] == 0x0 || tx[i] == 0xff));
+ if (predictable) {
+ tx[i] = i - iterations;
+ } else {
+ do
+ tx[i] = random();
+ while (nonzero && (tx[i] == 0x0 || tx[i] == 0xff));
+ }
}
}
@@ -480,8 +490,8 @@ int main(int argc, char *argv[])
parse_opts(argc, argv);
- if (!!input_tx + !!input_file + !do_tx + !!nonzero > 1)
- pabort("only one of -p, -i (--input), -t (--no-tx), -z (--nonzero) may be selected");
+ if (!!input_tx + !!input_file + !do_tx + !!nonzero + !!predictable > 1)
+ pabort("only one of -p, -i (--input), -t (--no-tx), -z (--nonzero), -P (--predictable) may be selected");
if (compare && (!do_tx || !do_rx))
pabort("-l/-c conflict with -t or -r");
--
2.55.0.806.gb8242b093d
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 6/7] spi: spidev_test: add option to split message into multiple transfers
2026-09-14 14:50 [PATCH 0/7] spi: spidev_test: new features Jonas Rebmann
` (4 preceding siblings ...)
2026-09-14 14:50 ` [PATCH 5/7] spi: spidev_test: send predictable data Jonas Rebmann
@ 2026-09-14 14:50 ` Jonas Rebmann
2026-09-14 18:04 ` Mark Brown
2026-09-14 14:50 ` [PATCH 7/7] spi: spidev_test: print TX on error Jonas Rebmann
6 siblings, 1 reply; 13+ messages in thread
From: Jonas Rebmann @ 2026-09-14 14:50 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel, Jonas Rebmann
From: Marc Kleine-Budde <mkl@pengutronix.de>
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>
---
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
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH 6/7] spi: spidev_test: add option to split message into multiple transfers
2026-09-14 14:50 ` [PATCH 6/7] spi: spidev_test: add option to split message into multiple transfers Jonas Rebmann
@ 2026-09-14 18:04 ` Mark Brown
0 siblings, 0 replies; 13+ messages in thread
From: Mark Brown @ 2026-09-14 18:04 UTC (permalink / raw)
To: Jonas Rebmann; +Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel
[-- Attachment #1: Type: text/plain, Size: 779 bytes --]
On Mon, Sep 14, 2026 at 04:50:45PM +0200, Jonas Rebmann wrote:
> SPI_IOC_MESSAGE() submits a single SPI message containing one or more
> transfers. Introduce an option to allow splitting the message into
> multiple transfers.
> + size_t bytes_per_word = DIV_ROUND_UP(bits, 8);
> + int effective_transfers = min_t(int, transfers, DIV_ROUND_UP(len, bytes_per_word));
We divide by this and nothing stops us triggering a divide by 0.
> + struct spi_ioc_transfer tr[effective_transfers];
This could easily get very big if someone decides to test lots of small
transfers, it's probably worth moving off the stack at this point.
> + size_t len_per_transfer = ALIGN(DIV_ROUND_UP(len, effective_transfers), bytes_per_word);
The kernel's ALIGN() macro only works for powers of two.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 7/7] spi: spidev_test: print TX on error
2026-09-14 14:50 [PATCH 0/7] spi: spidev_test: new features Jonas Rebmann
` (5 preceding siblings ...)
2026-09-14 14:50 ` [PATCH 6/7] spi: spidev_test: add option to split message into multiple transfers Jonas Rebmann
@ 2026-09-14 14:50 ` Jonas Rebmann
2026-09-14 18:07 ` Mark Brown
6 siblings, 1 reply; 13+ messages in thread
From: Jonas Rebmann @ 2026-09-14 14:50 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel, Jonas Rebmann
From: Marc Kleine-Budde <mkl@pengutronix.de>
If sending an spi message fails, dump it to the console.
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
tools/spi/spidev_test.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index a07c05f2d8b8..7f5b314b9e67 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -175,8 +175,11 @@ static void transfer(int fd, uint8_t const * const tx, uint8_t const * const rx,
}
ret = ioctl(fd, SPI_IOC_MESSAGE(effective_transfers), &tr);
- if (ret < 1)
+ if (ret < 1) {
+ if (tx)
+ hex_dump(tx, len, 32, "TX");
pabort("can't send spi message");
+ }
if (verbose && tx)
hex_dump(tx, len, 32, "TX");
--
2.55.0.806.gb8242b093d
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH 7/7] spi: spidev_test: print TX on error
2026-09-14 14:50 ` [PATCH 7/7] spi: spidev_test: print TX on error Jonas Rebmann
@ 2026-09-14 18:07 ` Mark Brown
0 siblings, 0 replies; 13+ messages in thread
From: Mark Brown @ 2026-09-14 18:07 UTC (permalink / raw)
To: Jonas Rebmann; +Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel
[-- Attachment #1: Type: text/plain, Size: 447 bytes --]
On Mon, Sep 14, 2026 at 04:50:46PM +0200, Jonas Rebmann wrote:
> From: Marc Kleine-Budde <mkl@pengutronix.de>
>
> If sending an spi message fails, dump it to the console.
It could be very large... possibly gate on verbosity, or limit the
length?
> - if (ret < 1)
> + if (ret < 1) {
> + if (tx)
> + hex_dump(tx, len, 32, "TX");
> pabort("can't send spi message");
> + }
Writing to the console could overwrite errno here.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread