* [PATCH v4 1/9] spi: spidev_test: include tools/include
2026-09-18 15:22 [PATCH v4 0/9] spi: spidev_test: new features Jonas Rebmann
@ 2026-09-18 15:22 ` Jonas Rebmann
2026-09-18 15:22 ` [PATCH v4 2/9] spi: spidev_test: clarify help text for --size Jonas Rebmann
` (7 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Jonas Rebmann @ 2026-09-18 15:22 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.
Remove the obsolete comment with cross-compile instructions from the
header since spidev_test is integrated with the tools buildsystem since
commit f325b73dc4db ("spi: tools: move to tools buildsystem").
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
tools/spi/Makefile | 2 +-
tools/spi/spidev_test.c | 5 +----
2 files changed, 2 insertions(+), 5 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..bd5ee7a6ac82 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -4,8 +4,6 @@
*
* Copyright (c) 2007 MontaVista Software, Inc.
* Copyright (c) 2007 Anton Vorontsov <avorontsov@ru.mvista.com>
- *
- * Cross-compile with cross-gcc -I/path/to/cross-kernel/include
*/
#include <stdint.h>
@@ -22,8 +20,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.56.0.rc0.108.gf0ef1b96a0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v4 2/9] spi: spidev_test: clarify help text for --size
2026-09-18 15:22 [PATCH v4 0/9] spi: spidev_test: new features Jonas Rebmann
2026-09-18 15:22 ` [PATCH v4 1/9] spi: spidev_test: include tools/include Jonas Rebmann
@ 2026-09-18 15:22 ` Jonas Rebmann
2026-09-18 15:22 ` [PATCH v4 3/9] spi: spidev_test: add compare mode Jonas Rebmann
` (6 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Jonas Rebmann @ 2026-09-18 15:22 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel, Jonas Rebmann
It is slightly confusing that spidev_test by default transmits the
predetermined message default_tx but random bytes given the --size
parameter.
Clarify this in the help text.
While at it, make the -p help text consistently lowercase.
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
tools/spi/spidev_test.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index bd5ee7a6ac82..f5ab67236fac 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -191,8 +191,8 @@ static void print_usage(const char *prog)
"data:\n"
" -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"
- " -S --size transfer size\n"
+ " -p send data (e.g. \"1234\\xde\\xad\")\n"
+ " -S --size transfer the given number of random bytes\n"
" -I --iter iterations\n"
"additional parameters:\n"
" -b --bpw bits per word\n"
--
2.56.0.rc0.108.gf0ef1b96a0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v4 3/9] spi: spidev_test: add compare mode
2026-09-18 15:22 [PATCH v4 0/9] spi: spidev_test: new features Jonas Rebmann
2026-09-18 15:22 ` [PATCH v4 1/9] spi: spidev_test: include tools/include Jonas Rebmann
2026-09-18 15:22 ` [PATCH v4 2/9] spi: spidev_test: clarify help text for --size Jonas Rebmann
@ 2026-09-18 15:22 ` Jonas Rebmann
2026-09-18 15:22 ` [PATCH v4 4/9] spi: spidev_test: allow disabling rx or tx buffers Jonas Rebmann
` (5 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Jonas Rebmann @ 2026-09-18 15:22 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 | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index f5ab67236fac..7993eae88c44 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -44,6 +44,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,
@@ -171,13 +172,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 [-2348CDFHILMNORSZbcdilopsvw]\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"
@@ -215,6 +217,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' },
@@ -238,7 +241,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)
@@ -269,6 +272,9 @@ static void parse_opts(int argc, char *argv[])
case 'l':
mode |= SPI_LOOP;
break;
+ case 'c':
+ compare = 1;
+ break;
case 'H':
mode |= SPI_CPHA;
break;
@@ -425,7 +431,7 @@ static void transfer_buf(int fd, int len)
_write_count += len;
_read_count += len;
- if (mode & SPI_LOOP) {
+ if (compare || mode & SPI_LOOP) {
if (memcmp(tx, rx, len)) {
fprintf(stderr, "transfer error !\n");
hex_dump(tx, len, 32, "TX");
@@ -449,6 +455,9 @@ int main(int argc, char *argv[])
if (input_tx && input_file)
pabort("only one of -p and --input may be selected");
+ if (compare && mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL))
+ pabort("-c (--compare) conflicts with -2 (--dual), -4 (--quad) or -8 (--octal)");
+
fd = open(device, O_RDWR);
if (fd < 0)
pabort("can't open device");
--
2.56.0.rc0.108.gf0ef1b96a0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v4 4/9] spi: spidev_test: allow disabling rx or tx buffers
2026-09-18 15:22 [PATCH v4 0/9] spi: spidev_test: new features Jonas Rebmann
` (2 preceding siblings ...)
2026-09-18 15:22 ` [PATCH v4 3/9] spi: spidev_test: add compare mode Jonas Rebmann
@ 2026-09-18 15:22 ` Jonas Rebmann
2026-09-18 15:22 ` [PATCH v4 5/9] spi: spidev_test: don't send 0x0 or 0xff Jonas Rebmann
` (4 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Jonas Rebmann @ 2026-09-18 15:22 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 | 90 +++++++++++++++++++++++++++++++++----------------
1 file changed, 61 insertions(+), 29 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index 7993eae88c44..906f9997b10a 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -45,6 +45,8 @@ 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 int input_choices;
static uint8_t default_tx[] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
@@ -151,7 +153,7 @@ 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) {
@@ -166,13 +168,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 [-2348CDFHILMNORSZbcdilopsvw]\n", prog);
+ printf("Usage: %s [-2348CDFHILMNORSZbcdiloprstvw]\n", prog);
puts("general device settings:\n"
" -D --device device to use (default /dev/spidev1.1)\n"
" -s --speed max speed (Hz)\n"
@@ -180,6 +182,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"
@@ -218,6 +222,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' },
@@ -241,7 +247,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)
@@ -265,6 +271,7 @@ static void parse_opts(int argc, char *argv[])
break;
case 'i':
input_file = optarg;
+ input_choices++;
break;
case 'o':
output_file = optarg;
@@ -275,6 +282,13 @@ static void parse_opts(int argc, char *argv[])
case 'c':
compare = 1;
break;
+ case 't':
+ do_tx = 0;
+ input_choices++;
+ break;
+ case 'r':
+ do_rx = 0;
+ break;
case 'H':
mode |= SPI_CPHA;
break;
@@ -310,6 +324,7 @@ static void parse_opts(int argc, char *argv[])
break;
case 'p':
input_tx = optarg;
+ input_choices++;
break;
case '2':
mode |= SPI_TX_DUAL;
@@ -322,6 +337,7 @@ static void parse_opts(int argc, char *argv[])
break;
case 'S':
transfer_size = atoi(optarg);
+ input_choices++;
break;
case 'I':
iterations = atoi(optarg);
@@ -344,15 +360,17 @@ static void transfer_escaped_string(int fd, char *str)
{
size_t size = strlen(str);
uint8_t *tx;
- uint8_t *rx;
+ uint8_t *rx = NULL;
tx = malloc(size);
if (!tx)
pabort("can't allocate tx buffer");
- rx = malloc(size);
- if (!rx)
- pabort("can't allocate rx buffer");
+ if (do_rx) {
+ rx = malloc(size);
+ if (!rx)
+ pabort("can't allocate rx buffer");
+ }
size = unescape((char *)tx, str, size);
transfer(fd, tx, rx, size);
@@ -366,7 +384,7 @@ static void transfer_file(int fd, char *filename)
struct stat sb;
int tx_fd;
uint8_t *tx;
- uint8_t *rx;
+ uint8_t *rx = NULL;
if (stat(filename, &sb) == -1)
pabort("can't stat input file");
@@ -379,9 +397,12 @@ static void transfer_file(int fd, char *filename)
if (!tx)
pabort("can't allocate tx buffer");
- rx = malloc(sb.st_size);
- if (!rx)
- pabort("can't allocate rx buffer");
+
+ if (do_rx) {
+ rx = malloc(sb.st_size);
+ if (!rx)
+ pabort("can't allocate rx buffer");
+ }
bytes = read(tx_fd, tx, sb.st_size);
if (bytes != sb.st_size)
@@ -412,26 +433,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 || mode & SPI_LOOP) {
+ if (tx && rx && (compare || mode & SPI_LOOP)) {
if (memcmp(tx, rx, len)) {
fprintf(stderr, "transfer error !\n");
hex_dump(tx, len, 32, "TX");
@@ -452,8 +478,14 @@ 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_choices > 1)
+ pabort("only one of -S (--size), -p, -i (--input), -t (--no-tx) may be selected");
+
+ if (compare && (!do_tx || !do_rx))
+ pabort("-c (--compare) conflicts with -t (--no-tx) or -r (--no-rx)");
+
+ if (!do_rx && output_file)
+ pabort("-r (--no-rx) conflicts with -o (--output)");
if (compare && mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL))
pabort("-c (--compare) conflicts with -2 (--dual), -4 (--quad) or -8 (--octal)");
@@ -532,7 +564,7 @@ int main(int argc, char *argv[])
printf("total: tx %.1fKB, rx %.1fKB\n",
_write_count/1024.0, _read_count/1024.0);
} else
- transfer(fd, default_tx, default_rx, sizeof(default_tx));
+ transfer(fd, do_tx ? default_tx : NULL, do_rx ? default_rx : NULL, sizeof(default_tx));
close(fd);
--
2.56.0.rc0.108.gf0ef1b96a0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v4 5/9] spi: spidev_test: don't send 0x0 or 0xff
2026-09-18 15:22 [PATCH v4 0/9] spi: spidev_test: new features Jonas Rebmann
` (3 preceding siblings ...)
2026-09-18 15:22 ` [PATCH v4 4/9] spi: spidev_test: allow disabling rx or tx buffers Jonas Rebmann
@ 2026-09-18 15:22 ` Jonas Rebmann
2026-09-18 15:22 ` [PATCH v4 6/9] spi: spidev_test: send predictable data Jonas Rebmann
` (3 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Jonas Rebmann @ 2026-09-18 15:22 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 | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index 906f9997b10a..c34601558646 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -45,6 +45,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 int input_choices;
@@ -174,7 +175,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 [-2348CDFHILMNORSZbcdiloprstvw]\n", prog);
+ printf("Usage: %s [-2348CDFHILMNORSZbcdiloprstvwz]\n", prog);
puts("general device settings:\n"
" -D --device device to use (default /dev/spidev1.1)\n"
" -s --speed max speed (Hz)\n"
@@ -198,6 +199,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 the given number of random bytes\n"
" -I --iter iterations\n"
"additional parameters:\n"
@@ -235,6 +237,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' },
@@ -247,7 +250,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)
@@ -339,6 +342,9 @@ static void parse_opts(int argc, char *argv[])
transfer_size = atoi(optarg);
input_choices++;
break;
+ case 'z':
+ nonzero = 1;
+ break;
case 'I':
iterations = atoi(optarg);
break;
@@ -441,8 +447,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) {
@@ -481,6 +490,9 @@ int main(int argc, char *argv[])
if (input_choices > 1)
pabort("only one of -S (--size), -p, -i (--input), -t (--no-tx) may be selected");
+ if (nonzero && !transfer_size)
+ pabort("-z (--nonzero) is only implemented for -S (--size)");
+
if (compare && (!do_tx || !do_rx))
pabort("-c (--compare) conflicts with -t (--no-tx) or -r (--no-rx)");
--
2.56.0.rc0.108.gf0ef1b96a0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v4 6/9] spi: spidev_test: send predictable data
2026-09-18 15:22 [PATCH v4 0/9] spi: spidev_test: new features Jonas Rebmann
` (4 preceding siblings ...)
2026-09-18 15:22 ` [PATCH v4 5/9] spi: spidev_test: don't send 0x0 or 0xff Jonas Rebmann
@ 2026-09-18 15:22 ` Jonas Rebmann
2026-09-22 11:57 ` Mark Brown
2026-09-18 15:22 ` [PATCH v4 7/9] spi: spidev_test: add option to split message into multiple transfers Jonas Rebmann
` (2 subsequent siblings)
8 siblings, 1 reply; 11+ messages in thread
From: Jonas Rebmann @ 2026-09-18 15:22 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 with the same
option set 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 | 23 +++++++++++++++++------
1 file changed, 17 insertions(+), 6 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index c34601558646..21a631d346a4 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -47,6 +47,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 int input_choices;
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 [-2348CDFHILMNORSZbcdiloprstvwz]\n", prog);
+ printf("Usage: %s [-2348CDFHILMNOPRSZbcdiloprstvwz]\n", prog);
puts("general device settings:\n"
" -D --device device to use (default /dev/spidev1.1)\n"
" -s --speed max speed (Hz)\n"
@@ -200,6 +201,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 the given number of random bytes\n"
" -I --iter iterations\n"
"additional parameters:\n"
@@ -245,12 +247,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)
@@ -329,6 +332,10 @@ static void parse_opts(int argc, char *argv[])
input_tx = optarg;
input_choices++;
break;
+ case 'P':
+ predictable = 1;
+ input_choices++;
+ break;
case '2':
mode |= SPI_TX_DUAL;
break;
@@ -448,9 +455,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));
+ }
}
}
@@ -488,7 +499,7 @@ int main(int argc, char *argv[])
parse_opts(argc, argv);
if (input_choices > 1)
- pabort("only one of -S (--size), -p, -i (--input), -t (--no-tx) may be selected");
+ pabort("only one of -S (--size), -p, -i (--input), -t (--no-tx), -P (--predictable) may be selected");
if (nonzero && !transfer_size)
pabort("-z (--nonzero) is only implemented for -S (--size)");
--
2.56.0.rc0.108.gf0ef1b96a0
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH v4 6/9] spi: spidev_test: send predictable data
2026-09-18 15:22 ` [PATCH v4 6/9] spi: spidev_test: send predictable data Jonas Rebmann
@ 2026-09-22 11:57 ` Mark Brown
0 siblings, 0 replies; 11+ messages in thread
From: Mark Brown @ 2026-09-22 11:57 UTC (permalink / raw)
To: Jonas Rebmann; +Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel
[-- Attachment #1: Type: text/plain, Size: 449 bytes --]
On Fri, Sep 18, 2026 at 05:22:52PM +0200, Jonas Rebmann wrote:
> 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 with the same
> option set e.g. with an oscilloscope.
> + case 'P':
> + predictable = 1;
> + input_choices++;
> + break;
-z doesn't increment input_choices, seems one of them is wrong.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v4 7/9] spi: spidev_test: add option to split message into multiple transfers
2026-09-18 15:22 [PATCH v4 0/9] spi: spidev_test: new features Jonas Rebmann
` (5 preceding siblings ...)
2026-09-18 15:22 ` [PATCH v4 6/9] spi: spidev_test: send predictable data Jonas Rebmann
@ 2026-09-18 15:22 ` Jonas Rebmann
2026-09-18 15:22 ` [PATCH v4 8/9] spi: spidev_test: print TX on error Jonas Rebmann
2026-09-18 15:22 ` [PATCH v4 9/9] spi: spidev_test: rewrite unescape() to stay in bounds Jonas Rebmann
8 siblings, 0 replies; 11+ messages in thread
From: Jonas Rebmann @ 2026-09-18 15:22 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 | 98 +++++++++++++++++++++++++++++++++----------------
1 file changed, 67 insertions(+), 31 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index 21a631d346a4..9aa1e0849a88 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -42,6 +42,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;
@@ -118,40 +119,64 @@ 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 = calloc(effective_transfers, sizeof(*tr));
+ size_t len_per_transfer = roundup(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;
+
+ if (!tr)
+ pabort("can't allocate transfer array");
+
+ 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);
+ free(tr);
if (ret < 1)
pabort("can't send spi message");
@@ -176,7 +201,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"
@@ -211,6 +236,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);
@@ -248,12 +274,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 +346,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;
@@ -513,6 +543,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");
--
2.56.0.rc0.108.gf0ef1b96a0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v4 8/9] spi: spidev_test: print TX on error
2026-09-18 15:22 [PATCH v4 0/9] spi: spidev_test: new features Jonas Rebmann
` (6 preceding siblings ...)
2026-09-18 15:22 ` [PATCH v4 7/9] spi: spidev_test: add option to split message into multiple transfers Jonas Rebmann
@ 2026-09-18 15:22 ` Jonas Rebmann
2026-09-18 15:22 ` [PATCH v4 9/9] spi: spidev_test: rewrite unescape() to stay in bounds Jonas Rebmann
8 siblings, 0 replies; 11+ messages in thread
From: Jonas Rebmann @ 2026-09-18 15:22 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 its first up to 256 bytes 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 | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index 9aa1e0849a88..1910facdd3da 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -177,8 +177,18 @@ static void transfer(int fd, uint8_t const * const tx, uint8_t const * const rx,
ret = ioctl(fd, SPI_IOC_MESSAGE(effective_transfers), tr);
free(tr);
- if (ret < 1)
+ if (ret < 1) {
+ const size_t dump_len = min_t(size_t, len, 256);
+ int saved_errno = errno;
+
+ if (tx) {
+ hex_dump(tx, dump_len, 32, "TX");
+ if (len > dump_len)
+ printf("... (%zu more bytes)\n", len - dump_len);
+ }
+ errno = saved_errno;
pabort("can't send spi message");
+ }
if (verbose && tx)
hex_dump(tx, len, 32, "TX");
--
2.56.0.rc0.108.gf0ef1b96a0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v4 9/9] spi: spidev_test: rewrite unescape() to stay in bounds
2026-09-18 15:22 [PATCH v4 0/9] spi: spidev_test: new features Jonas Rebmann
` (7 preceding siblings ...)
2026-09-18 15:22 ` [PATCH v4 8/9] spi: spidev_test: print TX on error Jonas Rebmann
@ 2026-09-18 15:22 ` Jonas Rebmann
8 siblings, 0 replies; 11+ messages in thread
From: Jonas Rebmann @ 2026-09-18 15:22 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel, Jonas Rebmann
unescape() could read the source buffer out of bounds for inputs like
"\\x" and could write the destination buffer out of bounds due to lack
of checking against the len parameter.
For better readability and consistency, rewrite it as a simplified
version of unescape_string() in string_helpers.h. This includes
rearranging src and dst parameters.
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
tools/spi/spidev_test.c | 38 +++++++++++++++++++++-----------------
1 file changed, 21 insertions(+), 17 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index 1910facdd3da..95d5d37788ba 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -11,6 +11,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <fcntl.h>
@@ -95,28 +96,31 @@ static void hex_dump(const void *src, size_t length, size_t line_size,
* Unescape - process hexadecimal escape character
* converts shell input "\x23" -> 0x23
*/
-static int unescape(char *_dst, char *_src, size_t len)
+static int unescape(char *src, char *dst, size_t size)
{
- int ret = 0;
- int match;
- char *src = _src;
- char *dst = _dst;
+ char *out = dst;
unsigned int ch;
- while (*src) {
- if (*src == '\\' && *(src+1) == 'x') {
- match = sscanf(src + 2, "%2x", &ch);
- if (!match)
- pabort("malformed input string");
+ while (*src && size--) {
+ if (src[0] == '\\' && src[1] != '\0') {
+ src++;
+
+ if (src[0] == 'x' &&
+ isxdigit((unsigned char)src[1]) &&
+ isxdigit((unsigned char)src[2]) &&
+ sscanf(&src[1], "%2x", &ch)) {
+ *out++ = (unsigned char)ch;
+ src += 3;
+ continue;
+ }
- src += 4;
- *dst++ = (unsigned char)ch;
- } else {
- *dst++ = *src++;
+ *out++ = '\\';
+ if (!size--)
+ break;
}
- ret++;
+ *out++ = *src++;
}
- return ret;
+ return out - dst;
}
static void transfer(int fd, uint8_t const * const tx, uint8_t const * const rx, size_t len)
@@ -425,7 +429,7 @@ static void transfer_escaped_string(int fd, char *str)
pabort("can't allocate rx buffer");
}
- size = unescape((char *)tx, str, size);
+ size = unescape(str, (char *)tx, size);
transfer(fd, tx, rx, size);
free(rx);
free(tx);
--
2.56.0.rc0.108.gf0ef1b96a0
^ permalink raw reply [flat|nested] 11+ messages in thread