* [PATCH v5 00/13] spi: spidev_test: new features
@ 2026-09-23 20:09 Jonas Rebmann
2026-09-23 20:09 ` [PATCH v5 01/13] spi: spidev_test: include tools/include Jonas Rebmann
` (12 more replies)
0 siblings, 13 replies; 14+ messages in thread
From: Jonas Rebmann @ 2026-09-23 20:09 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 1 is a small preparatory commit to share macro definitions with
the rest of the tools tree instead of keeping local copies.
Patch 2 improves usage help.
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.
Patch 9 resolves out of bounds issues when parsing escape sequences in
the -i (--input) parameter.
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
Changes in v5:
- Add patch 3 to make size argument mandatory for --size
- Add patch 4 to reject transfers of size 0
- Add patch 5 to abort when -I is selected without -S
- Add patch 6 to always compare rx and tx in loopback mode
- 7/13: always compare rx and tx in compare mode
- 8/13: Conflict --no-tx only with input options other than -S to allow specifying size
- 8/13: Move conflict check between -S and other input options to 2/10
- 10/13: make -S and -P are mutually exclusive, -P now takes a mandatory size argument
- 10/13: make -P compatible with -z
- 11/13: Avoid zero-size corner case via patch 4
- Link to v4: https://patch.msgid.link/20260918-spi-sun4i-spidev_test-v4-0-5ffacb1a4ebd@pengutronix.de
Changes in v4:
- Add patch 2 to fix help text for --size
- Consistently use initial lower case for help texts
- 3/9: Only make -c, not -l conflict with -2/-4/-8 (sashiko)
- 4/9: Make all supported input data modes work with --no-rx (sashiko)
- 4/9: Make -S conflict with --no-tx (sashiko)
- 5/9: Make --nonzero depend on -S (sashiko)
- 7/9: Guard allocation (sashiko)
- 7/9: Fix double pointer indirection for tr (sashiko)
- Link to v3: https://patch.msgid.link/20260918-spi-sun4i-spidev_test-v3-0-d3ca218d9f76@pengutronix.de
Changes in v3:
- 1/8: Remove old obsolete comment (sashiko)
- 2/8: Fix conflict check introduced in v2 (sashiko)
- 3/8: Do not initialise statics to 0 (checkpatch)
- 4/8: Fix typo: "-r (--no-rx)" (not -t)
- 6/8: free() transfer-array heap-allocated since v2 (sashiko)
- 7/8: Insert blank line after declarations (checkpatch)
- Link to v2: https://patch.msgid.link/20260916-spi-sun4i-spidev_test-v2-0-e7d25bf96e4e@pengutronix.de
Changes in v2:
- Keep options sorted alphabetically in usage help (Thanks, Mark)
- Count mutually exclusive input choices during argparse to avoid
overwhelmingly cute construct during verification (Thanks, Mark)
- 3/7: Error on conflicting --no-rx --output <file> early (Thanks, Mark)
- 3/7: Make usage error message more clear
- 4/7: Braces for legibility (Thanks, Mark)
- 6/7: Heap-allocate transfer-array tr (Thanks, Mark)
- 6/7: Ensure --bpw is greater than zero, prevents division by zero (Thanks, Mark)
- 6/7: Use roundup instead of ALIGN so -T works when --bpw is no power of two (Thanks, Mark)
- 7/7: Limit hexdump to 256 characters (Thanks, Mark)
- 7/7: Preserve errno (Thanks, Mark)
- Add patch 8 to deal with a preexisting issue (sashiko)
- 2/7: Conflict -c with -2/-4/-8 (sashiko)
- Link to v1: https://patch.msgid.link/20260914-spi-sun4i-spidev_test-v1-0-72d6a3b38cee@pengutronix.de
---
Jonas Rebmann (8):
spi: spidev_test: include tools/include
spi: spidev_test: clarify usage for --size
spi: spidev_test: make size argument mandatory for --size
spi: spidev_test: reject zero-length transfers
spi: spidev_test: abort when -I is selected without -S
spi: spidev_test: always compare loopback data
spi: spidev_test: send predictable data
spi: spidev_test: rewrite unescape() to stay in bounds
Marc Kleine-Budde (5):
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: add option to split message into multiple transfers
spi: spidev_test: print TX on error
tools/spi/Makefile | 2 +-
tools/spi/spidev_test.c | 309 ++++++++++++++++++++++++++++++++++--------------
2 files changed, 220 insertions(+), 91 deletions(-)
---
base-commit: 89a312991dc6e638a36adc43ccb91dbc25504c04
change-id: 20260910-spi-sun4i-spidev_test-881b33fdc3b4
Best regards,
--
Jonas Rebmann <jre@pengutronix.de>
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v5 01/13] spi: spidev_test: include tools/include
2026-09-23 20:09 [PATCH v5 00/13] spi: spidev_test: new features Jonas Rebmann
@ 2026-09-23 20:09 ` Jonas Rebmann
2026-09-23 20:09 ` [PATCH v5 02/13] spi: spidev_test: clarify usage for --size Jonas Rebmann
` (11 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jonas Rebmann @ 2026-09-23 20:09 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] 14+ messages in thread
* [PATCH v5 02/13] spi: spidev_test: clarify usage for --size
2026-09-23 20:09 [PATCH v5 00/13] spi: spidev_test: new features Jonas Rebmann
2026-09-23 20:09 ` [PATCH v5 01/13] spi: spidev_test: include tools/include Jonas Rebmann
@ 2026-09-23 20:09 ` Jonas Rebmann
2026-09-23 20:09 ` [PATCH v5 03/13] spi: spidev_test: make size argument mandatory " Jonas Rebmann
` (10 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jonas Rebmann @ 2026-09-23 20:09 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel, Jonas Rebmann
By default, spidev_test transmits the predetermined message default_tx.
Given the --size parameter, the given number of random bytes are
transmitted instead.
Make all the input options (random bytes, string, file) mutually
exclusive.
Clarify the help text for --size.
While at it, make the -p help text consistently lowercase.
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
tools/spi/spidev_test.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index bd5ee7a6ac82..3721ed15352f 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 input_choices;
static uint8_t default_tx[] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
@@ -191,8 +192,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"
@@ -262,6 +263,7 @@ static void parse_opts(int argc, char *argv[])
break;
case 'i':
input_file = optarg;
+ input_choices++;
break;
case 'o':
output_file = optarg;
@@ -304,6 +306,7 @@ static void parse_opts(int argc, char *argv[])
break;
case 'p':
input_tx = optarg;
+ input_choices++;
break;
case '2':
mode |= SPI_TX_DUAL;
@@ -316,6 +319,7 @@ static void parse_opts(int argc, char *argv[])
break;
case 'S':
transfer_size = atoi(optarg);
+ input_choices++;
break;
case 'I':
iterations = atoi(optarg);
@@ -446,8 +450,8 @@ 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) may be selected");
fd = open(device, O_RDWR);
if (fd < 0)
--
2.56.0.rc0.108.gf0ef1b96a0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v5 03/13] spi: spidev_test: make size argument mandatory for --size
2026-09-23 20:09 [PATCH v5 00/13] spi: spidev_test: new features Jonas Rebmann
2026-09-23 20:09 ` [PATCH v5 01/13] spi: spidev_test: include tools/include Jonas Rebmann
2026-09-23 20:09 ` [PATCH v5 02/13] spi: spidev_test: clarify usage for --size Jonas Rebmann
@ 2026-09-23 20:09 ` Jonas Rebmann
2026-09-23 20:09 ` [PATCH v5 04/13] spi: spidev_test: reject zero-length transfers Jonas Rebmann
` (9 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jonas Rebmann @ 2026-09-23 20:09 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel, Jonas Rebmann
When the argument to --size is omitted, zero or not a number,
abort with a message instead of transmitting default_tx[].
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
tools/spi/spidev_test.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index 3721ed15352f..b2940a25b7a8 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 random_input;
static int input_choices;
static uint8_t default_tx[] = {
@@ -319,6 +320,7 @@ static void parse_opts(int argc, char *argv[])
break;
case 'S':
transfer_size = atoi(optarg);
+ random_input = 1;
input_choices++;
break;
case 'I':
@@ -453,6 +455,9 @@ int main(int argc, char *argv[])
if (input_choices > 1)
pabort("only one of -S (--size), -p, -i (--input) may be selected");
+ if (random_input && !transfer_size)
+ pabort("a nonzero size argument is mandatory for -S (--size)");
+
fd = open(device, O_RDWR);
if (fd < 0)
pabort("can't open device");
--
2.56.0.rc0.108.gf0ef1b96a0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v5 04/13] spi: spidev_test: reject zero-length transfers
2026-09-23 20:09 [PATCH v5 00/13] spi: spidev_test: new features Jonas Rebmann
` (2 preceding siblings ...)
2026-09-23 20:09 ` [PATCH v5 03/13] spi: spidev_test: make size argument mandatory " Jonas Rebmann
@ 2026-09-23 20:09 ` Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 05/13] spi: spidev_test: abort when -I is selected without -S Jonas Rebmann
` (8 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jonas Rebmann @ 2026-09-23 20:09 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel, Jonas Rebmann
Providing an empty file or empty string to spidev_test is bound to fail
because spidev_test expects the write to return greater than zero.
Reject requests to transfer an empty buffer earlier.
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
tools/spi/spidev_test.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index b2940a25b7a8..49d40aa6d0ec 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -129,6 +129,9 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
.bits_per_word = bits,
};
+ if (!len)
+ pabort("can't transfer an empty buffer");
+
if (mode & SPI_TX_OCTAL)
tr.tx_nbits = 8;
else if (mode & SPI_TX_QUAD)
--
2.56.0.rc0.108.gf0ef1b96a0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v5 05/13] spi: spidev_test: abort when -I is selected without -S
2026-09-23 20:09 [PATCH v5 00/13] spi: spidev_test: new features Jonas Rebmann
` (3 preceding siblings ...)
2026-09-23 20:09 ` [PATCH v5 04/13] spi: spidev_test: reject zero-length transfers Jonas Rebmann
@ 2026-09-23 20:10 ` Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 06/13] spi: spidev_test: always compare loopback data Jonas Rebmann
` (7 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jonas Rebmann @ 2026-09-23 20:10 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel, Jonas Rebmann
-I (--iter) for transferring multiple iterations of the selected input
currently only has an effect when transferring random data using -S
(--size). Error out if -I is used without -S to avoid confusion.
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
tools/spi/spidev_test.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index 49d40aa6d0ec..45c2b691bffe 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -461,6 +461,9 @@ int main(int argc, char *argv[])
if (random_input && !transfer_size)
pabort("a nonzero size argument is mandatory for -S (--size)");
+ if (iterations && !transfer_size)
+ pabort("-I (--iter) is only implemented for -S (--size)");
+
fd = open(device, O_RDWR);
if (fd < 0)
pabort("can't open device");
--
2.56.0.rc0.108.gf0ef1b96a0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v5 06/13] spi: spidev_test: always compare loopback data
2026-09-23 20:09 [PATCH v5 00/13] spi: spidev_test: new features Jonas Rebmann
` (4 preceding siblings ...)
2026-09-23 20:10 ` [PATCH v5 05/13] spi: spidev_test: abort when -I is selected without -S Jonas Rebmann
@ 2026-09-23 20:10 ` Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 07/13] spi: spidev_test: add compare mode Jonas Rebmann
` (6 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jonas Rebmann @ 2026-09-23 20:10 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel, Jonas Rebmann
In loopback mode, spidev_test verifies that the received data matches
the sent data. However this test was only performed when random data was
transferred using the -S switch. Always perform the test by moving it
from transfer_buf() to transfer().
Signed-off-by: Jonas Rebmann <jre@pengutronix.de>
---
tools/spi/spidev_test.c | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index 45c2b691bffe..b413c713174f 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -172,6 +172,15 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
if (verbose)
hex_dump(rx, len, 32, "RX");
+
+ if (mode & SPI_LOOP) {
+ if (memcmp(tx, rx, len)) {
+ fprintf(stderr, "transfer error !\n");
+ hex_dump(tx, len, 32, "TX");
+ hex_dump(rx, len, 32, "RX");
+ exit(1);
+ }
+ }
}
static void print_usage(const char *prog)
@@ -434,15 +443,6 @@ static void transfer_buf(int fd, int len)
_write_count += len;
_read_count += len;
- if (mode & SPI_LOOP) {
- if (memcmp(tx, rx, len)) {
- fprintf(stderr, "transfer error !\n");
- hex_dump(tx, len, 32, "TX");
- hex_dump(rx, len, 32, "RX");
- exit(1);
- }
- }
-
free(rx);
free(tx);
}
--
2.56.0.rc0.108.gf0ef1b96a0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v5 07/13] spi: spidev_test: add compare mode
2026-09-23 20:09 [PATCH v5 00/13] spi: spidev_test: new features Jonas Rebmann
` (5 preceding siblings ...)
2026-09-23 20:10 ` [PATCH v5 06/13] spi: spidev_test: always compare loopback data Jonas Rebmann
@ 2026-09-23 20:10 ` Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 08/13] spi: spidev_test: allow disabling rx or tx buffers Jonas Rebmann
` (5 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jonas Rebmann @ 2026-09-23 20:10 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 b413c713174f..55944386cd69 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 int random_input;
static int input_choices;
@@ -173,7 +174,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
if (verbose)
hex_dump(rx, len, 32, "RX");
- 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");
@@ -185,13 +186,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"
@@ -229,6 +231,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' },
@@ -252,7 +255,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)
@@ -284,6 +287,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;
@@ -464,6 +470,9 @@ int main(int argc, char *argv[])
if (iterations && !transfer_size)
pabort("-I (--iter) is only implemented for -S (--size)");
+ 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] 14+ messages in thread
* [PATCH v5 08/13] spi: spidev_test: allow disabling rx or tx buffers
2026-09-23 20:09 [PATCH v5 00/13] spi: spidev_test: new features Jonas Rebmann
` (6 preceding siblings ...)
2026-09-23 20:10 ` [PATCH v5 07/13] spi: spidev_test: add compare mode Jonas Rebmann
@ 2026-09-23 20:10 ` Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 09/13] spi: spidev_test: don't send 0x0 or 0xff Jonas Rebmann
` (4 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jonas Rebmann @ 2026-09-23 20:10 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 | 84 +++++++++++++++++++++++++++++++++----------------
1 file changed, 57 insertions(+), 27 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index 55944386cd69..770f40097535 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 do_tx = 1, do_rx = 1;
static int random_input;
static int input_choices;
@@ -156,7 +157,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) {
@@ -171,10 +172,10 @@ 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");
- 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");
@@ -186,7 +187,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 [-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"
@@ -194,6 +195,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"
@@ -232,6 +235,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' },
@@ -255,7 +260,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)
@@ -290,6 +295,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;
@@ -362,15 +373,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);
@@ -384,7 +397,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");
@@ -397,9 +410,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)
@@ -430,24 +446,29 @@ 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);
-
- _write_count += len;
- _read_count += len;
+ if (do_tx)
+ _write_count += len;
+ if (do_rx)
+ _read_count += len;
free(rx);
free(tx);
@@ -470,6 +491,15 @@ int main(int argc, char *argv[])
if (iterations && !transfer_size)
pabort("-I (--iter) is only implemented for -S (--size)");
+ 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 (!do_tx && !transfer_size)
+ pabort("-t (--no-tx) is only implemented for -S (--size)");
+
if (compare && mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL))
pabort("-c (--compare) conflicts with -2 (--dual), -4 (--quad) or -8 (--octal)");
@@ -547,7 +577,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] 14+ messages in thread
* [PATCH v5 09/13] spi: spidev_test: don't send 0x0 or 0xff
2026-09-23 20:09 [PATCH v5 00/13] spi: spidev_test: new features Jonas Rebmann
` (7 preceding siblings ...)
2026-09-23 20:10 ` [PATCH v5 08/13] spi: spidev_test: allow disabling rx or tx buffers Jonas Rebmann
@ 2026-09-23 20:10 ` Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 10/13] spi: spidev_test: send predictable data Jonas Rebmann
` (3 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jonas Rebmann @ 2026-09-23 20:10 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 770f40097535..e7ee7d5a701e 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 random_input;
static int input_choices;
@@ -187,7 +188,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"
@@ -211,6 +212,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"
@@ -248,6 +250,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' },
@@ -260,7 +263,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)
@@ -352,6 +355,9 @@ static void parse_opts(int argc, char *argv[])
random_input = 1;
input_choices++;
break;
+ case 'z':
+ nonzero = 1;
+ break;
case 'I':
iterations = atoi(optarg);
break;
@@ -454,8 +460,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) {
@@ -491,6 +500,9 @@ int main(int argc, char *argv[])
if (iterations && !transfer_size)
pabort("-I (--iter) is only implemented for -S (--size)");
+ 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] 14+ messages in thread
* [PATCH v5 10/13] spi: spidev_test: send predictable data
2026-09-23 20:09 [PATCH v5 00/13] spi: spidev_test: new features Jonas Rebmann
` (8 preceding siblings ...)
2026-09-23 20:10 ` [PATCH v5 09/13] spi: spidev_test: don't send 0x0 or 0xff Jonas Rebmann
@ 2026-09-23 20:10 ` Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 11/13] spi: spidev_test: add option to split message into multiple transfers Jonas Rebmann
` (2 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jonas Rebmann @ 2026-09-23 20:10 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Marc Kleine-Budde, kernel, Jonas Rebmann
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 | 36 ++++++++++++++++++++++++++----------
1 file changed, 26 insertions(+), 10 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index e7ee7d5a701e..56b71b842fb5 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -48,6 +48,7 @@ static int compare;
static int nonzero;
static int do_tx = 1, do_rx = 1;
static int random_input;
+static int predictable;
static int input_choices;
static uint8_t default_tx[] = {
@@ -188,7 +189,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"
@@ -213,6 +214,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 transfer the given number of sequential bytes\n"
" -S --size transfer the given number of random bytes\n"
" -I --iter iterations\n"
"additional parameters:\n"
@@ -258,12 +260,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:P:vS:zI:",
lopts, NULL);
if (c == -1)
@@ -341,6 +344,11 @@ static void parse_opts(int argc, char *argv[])
input_tx = optarg;
input_choices++;
break;
+ case 'P':
+ transfer_size = atoi(optarg);
+ predictable = 1;
+ input_choices++;
+ break;
case '2':
mode |= SPI_TX_DUAL;
break;
@@ -461,9 +469,17 @@ 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) {
+ int v = i - iterations;
+
+ if (nonzero)
+ v = ((v % 254) + 254) % 254 + 1;
+ tx[i] = (char)v;
+ } else {
+ do {
+ tx[i] = random();
+ } while (nonzero && (tx[i] == 0x0 || tx[i] == 0xff));
+ }
}
}
@@ -492,16 +508,16 @@ int main(int argc, char *argv[])
parse_opts(argc, argv);
if (input_choices > 1)
- pabort("only one of -S (--size), -p, -i (--input) may be selected");
+ pabort("only one of -S (--size), -p, -i (--input), -P (--predictable) may be selected");
- if (random_input && !transfer_size)
- pabort("a nonzero size argument is mandatory for -S (--size)");
+ if ((random_input || predictable) && !transfer_size)
+ pabort("a nonzero size argument is mandatory for -S (--size) and -P (--predictable)");
if (iterations && !transfer_size)
pabort("-I (--iter) is only implemented for -S (--size)");
if (nonzero && !transfer_size)
- pabort("-z (--nonzero) is only implemented for -S (--size)");
+ pabort("-z (--nonzero) is only implemented for -S (--size) and -P (--predictable)");
if (compare && (!do_tx || !do_rx))
pabort("-c (--compare) conflicts with -t (--no-tx) or -r (--no-rx)");
@@ -510,7 +526,7 @@ int main(int argc, char *argv[])
pabort("-r (--no-rx) conflicts with -o (--output)");
if (!do_tx && !transfer_size)
- pabort("-t (--no-tx) is only implemented for -S (--size)");
+ pabort("-t (--no-tx) is only implemented for -S (--size) and -P (--predictable)");
if (compare && mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL))
pabort("-c (--compare) conflicts with -2 (--dual), -4 (--quad) or -8 (--octal)");
--
2.56.0.rc0.108.gf0ef1b96a0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v5 11/13] spi: spidev_test: add option to split message into multiple transfers
2026-09-23 20:09 [PATCH v5 00/13] spi: spidev_test: new features Jonas Rebmann
` (9 preceding siblings ...)
2026-09-23 20:10 ` [PATCH v5 10/13] spi: spidev_test: send predictable data Jonas Rebmann
@ 2026-09-23 20:10 ` Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 12/13] spi: spidev_test: print TX on error Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 13/13] spi: spidev_test: rewrite unescape() to stay in bounds Jonas Rebmann
12 siblings, 0 replies; 14+ messages in thread
From: Jonas Rebmann @ 2026-09-23 20:10 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 | 96 +++++++++++++++++++++++++++++++++----------------
1 file changed, 66 insertions(+), 30 deletions(-)
diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c
index 56b71b842fb5..32dc7daac21e 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;
@@ -119,43 +120,67 @@ static int unescape(char *_dst, char *_src, size_t len)
return ret;
}
-static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
+static void transfer(int fd, uint8_t const * const tx, uint8_t const * const rx, size_t len)
{
int ret;
int out_fd;
- struct spi_ioc_transfer tr = {
- .tx_buf = (unsigned long)tx,
- .rx_buf = (unsigned long)rx,
- .len = len,
- .delay_usecs = delay,
- .word_delay_usecs = word_delay,
- .speed_hz = speed,
- .bits_per_word = bits,
- };
+ size_t bytes_per_word = DIV_ROUND_UP(bits, 8);
+ int effective_transfers = 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 (!len)
pabort("can't transfer an empty buffer");
- if (mode & SPI_TX_OCTAL)
- tr.tx_nbits = 8;
- else if (mode & SPI_TX_QUAD)
- tr.tx_nbits = 4;
- else if (mode & SPI_TX_DUAL)
- tr.tx_nbits = 2;
- if (mode & SPI_RX_OCTAL)
- tr.rx_nbits = 8;
- else if (mode & SPI_RX_QUAD)
- tr.rx_nbits = 4;
- else if (mode & SPI_RX_DUAL)
- tr.rx_nbits = 2;
- if (!(mode & SPI_LOOP)) {
- if (mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL))
- tr.rx_buf = 0;
- else if (mode & (SPI_RX_OCTAL | SPI_RX_QUAD | SPI_RX_DUAL))
- tr.tx_buf = 0;
+ if (!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");
@@ -189,7 +214,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"
@@ -224,6 +249,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);
@@ -261,12 +287,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:P:vS:zI:",
+ c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:P:T:vS:zI:",
lopts, NULL);
if (c == -1)
@@ -331,6 +358,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;
@@ -531,6 +561,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] 14+ messages in thread
* [PATCH v5 12/13] spi: spidev_test: print TX on error
2026-09-23 20:09 [PATCH v5 00/13] spi: spidev_test: new features Jonas Rebmann
` (10 preceding siblings ...)
2026-09-23 20:10 ` [PATCH v5 11/13] spi: spidev_test: add option to split message into multiple transfers Jonas Rebmann
@ 2026-09-23 20:10 ` Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 13/13] spi: spidev_test: rewrite unescape() to stay in bounds Jonas Rebmann
12 siblings, 0 replies; 14+ messages in thread
From: Jonas Rebmann @ 2026-09-23 20:10 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 32dc7daac21e..6f9e04acee9c 100644
--- a/tools/spi/spidev_test.c
+++ b/tools/spi/spidev_test.c
@@ -181,8 +181,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] 14+ messages in thread
* [PATCH v5 13/13] spi: spidev_test: rewrite unescape() to stay in bounds
2026-09-23 20:09 [PATCH v5 00/13] spi: spidev_test: new features Jonas Rebmann
` (11 preceding siblings ...)
2026-09-23 20:10 ` [PATCH v5 12/13] spi: spidev_test: print TX on error Jonas Rebmann
@ 2026-09-23 20:10 ` Jonas Rebmann
12 siblings, 0 replies; 14+ messages in thread
From: Jonas Rebmann @ 2026-09-23 20:10 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 6f9e04acee9c..68fe583c3f7c 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>
@@ -96,28 +97,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)
@@ -439,7 +443,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] 14+ messages in thread
end of thread, other threads:[~2026-09-23 20:15 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-23 20:09 [PATCH v5 00/13] spi: spidev_test: new features Jonas Rebmann
2026-09-23 20:09 ` [PATCH v5 01/13] spi: spidev_test: include tools/include Jonas Rebmann
2026-09-23 20:09 ` [PATCH v5 02/13] spi: spidev_test: clarify usage for --size Jonas Rebmann
2026-09-23 20:09 ` [PATCH v5 03/13] spi: spidev_test: make size argument mandatory " Jonas Rebmann
2026-09-23 20:09 ` [PATCH v5 04/13] spi: spidev_test: reject zero-length transfers Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 05/13] spi: spidev_test: abort when -I is selected without -S Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 06/13] spi: spidev_test: always compare loopback data Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 07/13] spi: spidev_test: add compare mode Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 08/13] spi: spidev_test: allow disabling rx or tx buffers Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 09/13] spi: spidev_test: don't send 0x0 or 0xff Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 10/13] spi: spidev_test: send predictable data Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 11/13] spi: spidev_test: add option to split message into multiple transfers Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 12/13] spi: spidev_test: print TX on error Jonas Rebmann
2026-09-23 20:10 ` [PATCH v5 13/13] spi: spidev_test: rewrite unescape() to stay in bounds Jonas Rebmann
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®