diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-22 07:16:16 -1000 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-22 07:16:16 -1000 |
| commit | 6ef8f742f9008132db003313bf7a6ca2edb8bbae (patch) | |
| tree | a0320a0403df3621a688d2e9424c676aa81ae850 /tools | |
| parent | 003e5a0c479e16f06b7684a1b82543aa941a5043 (diff) | |
| parent | d5acef12e819c1627c8bf6a0325845ab39d8c99d (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/Makefile | 2 | ||||
| -rw-r--r-- | tools/spi/spidev_test.c | 330 |
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); |
