mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/7] spi: spidev_test: new features
@ 2026-09-14 14:50 Jonas Rebmann
  2026-09-14 14:50 ` [PATCH 1/7] spi: spidev_test: include tools/include Jonas Rebmann
                   ` (6 more replies)
  0 siblings, 7 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

A collection of new features for spidev_test which have collected during
testing/development of spi drivers.

Patch 2 is a small preparatory commit to share macro definitions with
the rest of the tools tree instead of keeping local copies.

Patches 3 to 8 add the following options:

  -c, --compare        Compare RX against TX without enabling controller
		       loopback mode; useful when TX is physically
		       bridged to RX.
  -t, --no-tx          Do not provide a TX buffer.
  -r, --no-rx          Do not provide an RX buffer.
  -z, --nonzero        Skip 0x00 and 0xff bytes in the generated
                       random TX pattern so that a stuck line (pulled
                       high or low) does not produce a false positive
                       in compare mode.
  -P, --predictable    Send a deterministic byte sequence instead of
                       random data, so that runs can be compared e.g.
                       on an oscilloscope.
  -T, --transfers N    Split the message into N SPI transfers inside a
                       single SPI_IOC_MESSAGE() ioctl, exercising the
                       controller's multi-transfer handling.

Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
Jonas Rebmann (1):
      spi: spidev_test: include tools/include

Marc Kleine-Budde (6):
      spi: spidev_test: add compare mode
      spi: spidev_test: allow disabling rx or tx buffers
      spi: spidev_test: don't send 0x0 or 0xff
      spi: spidev_test: send predictable data
      spi: spidev_test: add option to split message into multiple transfers
      spi: spidev_test: print TX on error

 tools/spi/Makefile      |   2 +-
 tools/spi/spidev_test.c | 183 ++++++++++++++++++++++++++++++++++--------------
 2 files changed, 131 insertions(+), 54 deletions(-)
---
base-commit: 89a312991dc6e638a36adc43ccb91dbc25504c04
change-id: 20260910-spi-sun4i-spidev_test-881b33fdc3b4

Best regards,
--  
Jonas Rebmann <jre@pengutronix.de>


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [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

* [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

* [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

* [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 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

* 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

* Re: [PATCH 4/7] spi: spidev_test: don't send 0x0 or 0xff
  2026-09-14 14:50 ` [PATCH 4/7] spi: spidev_test: don't send 0x0 or 0xff Jonas Rebmann
@ 2026-09-14 17:22   ` Mark Brown
  0 siblings, 0 replies; 13+ messages in thread
From: Mark Brown @ 2026-09-14 17:22 UTC (permalink / raw)
  To: Jonas Rebmann; +Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel

[-- Attachment #1: Type: text/plain, Size: 305 bytes --]

On Mon, Sep 14, 2026 at 04:50:43PM +0200, Jonas Rebmann wrote:

> -		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));
> +		}

Keep {} at all levels to help with legibiity please.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ 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

* 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

end of thread, other threads:[~2026-09-14 18:07 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 17:03   ` Mark Brown
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
2026-09-14 14:50 ` [PATCH 4/7] spi: spidev_test: don't send 0x0 or 0xff 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
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
2026-09-14 14:50 ` [PATCH 7/7] spi: spidev_test: print TX on error Jonas Rebmann
2026-09-14 18:07   ` Mark Brown

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®