summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-22 07:16:16 -1000
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-22 07:16:16 -1000
commit6ef8f742f9008132db003313bf7a6ca2edb8bbae (patch)
treea0320a0403df3621a688d2e9424c676aa81ae850 /tools
parent003e5a0c479e16f06b7684a1b82543aa941a5043 (diff)
parentd5acef12e819c1627c8bf6a0325845ab39d8c99d (diff)
Merge tag 'spi-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi
Pull spi updates from Mark Brown: "Quite a lot of work in the SPI subsystem this time around, almost all in drivers rather than the core. As well as the general cleanup/fix work we're seeing over most of the kernel there's been some substantial performance work to two existing drivers and support for a number of new devices including a couple of new drivers. There was also a big overhaul of the spidev_test utility from the Pengutronix people. - Improvements for spidev_test to cover additional use cases, plus be a bit easier to use. - Substantial performance work in both DesignWare and Tegra210 QSPI drivers, making their interrupt handling more efficient to drive up hardware utilisation. - Documentation clarifications. - Fixes for some runtime PM and error handling issues. - Support for Nuvoton MA35D1, Qualcomm QPIC SNAND v2, Renesas R-Car Gen5 and X5H and Realtek RTD1625" * tag 'spi-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi: (136 commits) spi: ch341: handle transfers without a TX or RX buffer spi: dt-bindings: spi-qpic-snand: Add IPQ9650 compatible spi: spi-pci1xxxx: quiesce DMA engines on transfer timeout spi: amd: fix len==0 underflow and FIFO overflow spi: tegra114: initialize native chip selects inactive spi: bcm-qspi: reject oversized dummy bytes in mspi exec_mem_op spi: npcm-fiu: limit UMA data_size to the 16-byte staging buffer spi: omap2-mcspi: handle DMA setup failures spi: jcore: fix divide-by-zero when speed_hz is zero spi: cadence-xspi: fix stack buffer overflow in the Marvell b0 path spi: cadence-xspi: reject SDMA transfers larger than the requested length spi: meson-spicc: fix invalid DMA unmap on mapping failure spi: s3c64xx: Use snprintf() instead of sprintf() spi: omap2-mcspi: Use snprintf() instead of sprintf() spi: tle62x0: Use kstrtoul() instead of simple_strtoul() spi: lm70llp: Use strscpy() instead of strcpy() spi: butterfly: Use strscpy() instead of strcpy() spi: jcore: Use devm_platform_ioremap_resource() spi: sh-hspi: Use devm_platform_ioremap_resource() spi: ljca: validate the response length before copying the data ...
Diffstat (limited to 'tools')
-rw-r--r--tools/spi/Makefile2
-rw-r--r--tools/spi/spidev_test.c330
2 files changed, 239 insertions, 93 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..5bd3159f61a7 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>
@@ -13,6 +11,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <fcntl.h>
@@ -22,8 +21,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)
{
@@ -44,9 +42,16 @@ static uint32_t speed = 500000;
static uint16_t delay;
static uint16_t word_delay;
static int verbose;
-static int transfer_size;
+static int transfer_size = -1;
+static int transfers = 1;
static int iterations;
static int interval = 5; /* interval in seconds for showing transfer rate */
+static int compare;
+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[] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
@@ -69,6 +74,10 @@ static void hex_dump(const void *src, size_t length, size_t line_size,
unsigned char c;
printf("%s | ", prefix);
+
+ if (length == 0)
+ printf("__ ||\n");
+
while (length-- > 0) {
printf("%02X ", *address++);
if (!(++i % line_size) || (length == 0 && i % line_size)) {
@@ -92,68 +101,120 @@ 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 *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;
+ struct spi_ioc_transfer *tr;
+ size_t len_per_transfer;
+ const uint8_t *tx_buf = tx;
+ const uint8_t *rx_buf = rx;
+ size_t rem;
+
+ if (len) {
+ effective_transfers = min_t(size_t, transfers, DIV_ROUND_UP(len, bytes_per_word));
+ len_per_transfer = roundup(DIV_ROUND_UP(len, effective_transfers), bytes_per_word);
+ } else {
+ /* zero-length transfers may be handled by the driver, deliver as ordered */
+ effective_transfers = transfers;
+ len_per_transfer = 0;
+ }
+
+ if (SPI_MSGSIZE(effective_transfers) == 0)
+ pabort("too many transfers, SPI_IOC_MESSAGE size limit exceeded");
+
+ tr = calloc(effective_transfers, sizeof(*tr));
+ if (!tr)
+ pabort("can't allocate transfer array");
+
+ rem = len;
+
+ for (int i = 0; i < effective_transfers; i++) {
+ size_t n = min(rem, len_per_transfer);
+
+ tr[i] = (struct spi_ioc_transfer) {
+ .tx_buf = (unsigned long)tx_buf,
+ .rx_buf = (unsigned long)rx_buf,
+ .len = n,
+ .delay_usecs = delay,
+ .word_delay_usecs = word_delay,
+ .speed_hz = speed,
+ .bits_per_word = bits,
+ };
+
+ if (tx_buf)
+ tx_buf += n;
+
+ if (rx_buf)
+ rx_buf += n;
+
+ rem -= n;
+
+ if (mode & SPI_TX_OCTAL)
+ tr[i].tx_nbits = 8;
+ else if (mode & SPI_TX_QUAD)
+ tr[i].tx_nbits = 4;
+ else if (mode & SPI_TX_DUAL)
+ tr[i].tx_nbits = 2;
+ if (mode & SPI_RX_OCTAL)
+ tr[i].rx_nbits = 8;
+ else if (mode & SPI_RX_QUAD)
+ tr[i].rx_nbits = 4;
+ else if (mode & SPI_RX_DUAL)
+ tr[i].rx_nbits = 2;
+ if (!(mode & SPI_LOOP)) {
+ if (mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL))
+ tr[i].rx_buf = 0;
+ else if (mode & (SPI_RX_OCTAL | SPI_RX_QUAD | SPI_RX_DUAL))
+ tr[i].tx_buf = 0;
+ }
}
- ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
- if (ret < 1)
+ ret = ioctl(fd, SPI_IOC_MESSAGE(effective_transfers), tr);
+ free(tr);
+ if (ret < 0) {
+ 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)
+ if (verbose && tx)
hex_dump(tx, len, 32, "TX");
if (output_file) {
@@ -168,19 +229,31 @@ 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 (tx && rx && (compare || 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)
{
- printf("Usage: %s [-2348CDFHILMNORSZbdilopsvw]\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"
" -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"
+ " -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"
@@ -194,8 +267,10 @@ 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"
+ " -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"
" -b --bpw bits per word\n"
@@ -204,6 +279,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);
@@ -218,6 +294,9 @@ 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' },
+ { "no-tx", 0, 0, 't' },
+ { "no-rx", 0, 0, 'r' },
{ "cpha", 0, 0, 'H' },
{ "cpol", 0, 0, 'O' },
{ "rx-cpha-flip", 0, 0, 'F' },
@@ -229,6 +308,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' },
@@ -236,12 +316,14 @@ static void parse_opts(int argc, char *argv[])
{ "no-cs", 0, 0, 'N' },
{ "ready", 0, 0, 'R' },
{ "mosi-idle-low", 0, 0, 'M' },
+ { "predictable", 1, 0, 'P' },
+ { "transfers", 1, 0, 'T' },
{ "verbose", 0, 0, 'v' },
{ NULL, 0, 0, 0 },
};
int c;
- c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lHOLC3ZFMNR248p:vS:I:",
+ c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:P:T:vS:zI:",
lopts, NULL);
if (c == -1)
@@ -265,6 +347,7 @@ static void parse_opts(int argc, char *argv[])
break;
case 'i':
input_file = optarg;
+ input_choices++;
break;
case 'o':
output_file = optarg;
@@ -272,6 +355,15 @@ static void parse_opts(int argc, char *argv[])
case 'l':
mode |= SPI_LOOP;
break;
+ case 'c':
+ compare = 1;
+ break;
+ case 't':
+ do_tx = 0;
+ break;
+ case 'r':
+ do_rx = 0;
+ break;
case 'H':
mode |= SPI_CPHA;
break;
@@ -296,6 +388,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;
@@ -307,6 +402,12 @@ static void parse_opts(int argc, char *argv[])
break;
case 'p':
input_tx = optarg;
+ input_choices++;
+ break;
+ case 'P':
+ transfer_size = atoi(optarg);
+ predictable = 1;
+ input_choices++;
break;
case '2':
mode |= SPI_TX_DUAL;
@@ -319,6 +420,11 @@ static void parse_opts(int argc, char *argv[])
break;
case 'S':
transfer_size = atoi(optarg);
+ random_input = 1;
+ input_choices++;
+ break;
+ case 'z':
+ nonzero = 1;
break;
case 'I':
iterations = atoi(optarg);
@@ -341,17 +447,19 @@ 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);
+ size = unescape(str, (char *)tx, size);
transfer(fd, tx, rx, size);
free(rx);
free(tx);
@@ -363,7 +471,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");
@@ -376,9 +484,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)
@@ -409,33 +520,40 @@ 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++) {
+ 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));
+ }
+ }
+ }
- 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 (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);
- }
- }
+ if (do_tx)
+ _write_count += len;
+ if (do_rx)
+ _read_count += len;
free(rx);
free(tx);
@@ -449,8 +567,36 @@ 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("at most one of -S (--size), -p, -i (--input), -P (--predictable) may be selected, "
+ "and each may be specified only once");
+
+ if ((random_input || predictable) && transfer_size < 0)
+ pabort("a size argument is mandatory for -S (--size) and -P (--predictable)");
+
+ if (iterations && transfer_size < 0)
+ pabort("-I (--iter) is only implemented for -S (--size) and -P (--predictable)");
+
+ if (nonzero && transfer_size < 0)
+ 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)");
+
+ if (!do_rx && output_file)
+ pabort("-r (--no-rx) conflicts with -o (--output)");
+
+ if (!do_tx && transfer_size < 0)
+ 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)");
+
+ 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)
@@ -507,7 +653,7 @@ int main(int argc, char *argv[])
transfer_escaped_string(fd, input_tx);
else if (input_file)
transfer_file(fd, input_file);
- else if (transfer_size) {
+ else if (transfer_size >= 0) {
struct timespec last_stat;
clock_gettime(CLOCK_MONOTONIC, &last_stat);
@@ -526,7 +672,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, default_tx, do_rx ? default_rx : NULL, sizeof(default_tx));
close(fd);