From e9ad8bc142ee048512d775bc28cc6184ca2a1ddd Mon Sep 17 00:00:00 2001 From: Heinrich Toews Date: Thu, 23 Apr 2026 12:26:09 +0200 Subject: [PATCH] tools: mtd: add mram_io byte-wise read/write tool for MTD_RAM devices mram_io is a small user-space utility that maps a MTD_RAM device (e.g. Everspin MRAM on Cadence OSPI in DAC mode) into its address space via mmap() and performs byte-granular reads or writes without any kernel copy overhead. Usage: mram_io --device --read --offset --length mram_io --device --write --offset --data [b1 b2 ...] Options: --device / -D MTD character device, e.g. /dev/mtd0 --offset / -o Byte offset (hex or decimal) --length / -l Number of bytes to read --data / -d Remaining arguments are bytes to write Read output is a classic hex-dump with offset, hex columns (split 8+8) and an ASCII column. Write mode logs each byte as it is written, then performs an immediate read-back and reports OK / MISMATCH per byte. Bytes are written as volatile stores to prevent the compiler from optimising away individual byte accesses; the mmap region is mapped noncached by the kernel (pgprot_noncached) so every store goes straight to the hardware. Argument parsing uses getopt_long and utilizes the device path as a named option (--device / -D) to eliminate positional ambiguity when passing data bytes. Signed-off-by: Heinrich Toews --- tools/mtd/Makefile | 33 +++++ tools/mtd/mram_io.c | 334 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 367 insertions(+) create mode 100644 tools/mtd/Makefile create mode 100644 tools/mtd/mram_io.c diff --git a/tools/mtd/Makefile b/tools/mtd/Makefile new file mode 100644 index 000000000000..771e3771b3af --- /dev/null +++ b/tools/mtd/Makefile @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0-only +include ../scripts/Makefile.include + +bindir ?= /usr/sbin + +ifeq ($(srctree),) +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +endif + +# Do not use make's built-in rules +# (this improves performance and avoids hard-to-debug behaviour); +MAKEFLAGS += -r + +CFLAGS += -O2 -Wall -Wextra -g -D_GNU_SOURCE + +ALL_TARGETS := mram_io +ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS)) + +all: $(ALL_PROGRAMS) + +export srctree OUTPUT CC LD CFLAGS +include $(srctree)/tools/build/Makefile.include + +$(OUTPUT)mram_io: mram_io.c + $(CC) $(CFLAGS) -o $@ $< + +clean: + $(RM) $(ALL_PROGRAMS) + +install: all + install -d $(DESTDIR)$(bindir) + install -m 755 $(ALL_PROGRAMS) $(DESTDIR)$(bindir) diff --git a/tools/mtd/mram_io.c b/tools/mtd/mram_io.c new file mode 100644 index 000000000000..479c53f4c2e6 --- /dev/null +++ b/tools/mtd/mram_io.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mram_io - Byte-wise read/write tool for MTD_RAM devices via mmap() + * + * Requires a MTD device that supports mmap() (e.g. Everspin MRAM on + * Cadence OSPI in DAC mode, registered as MTD_RAM). + * + * Usage: + * mram_io --read --device --offset --length + * mram_io --write --device --offset --data [b1 b2 ...] + * + * Examples: + * mram_io --read --device /dev/mtd0 --offset 0x100 --length 16 + * mram_io --write --device /dev/mtd0 --offset 0x100 --data 0xde 0xad 0xbe 0xef + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Helpers */ +/* ------------------------------------------------------------------ */ + +static void usage(const char *prog) +{ + fprintf(stderr, + "Usage:\n" + " %s --read --device --offset --length \n" + " %s --write --device --offset --data [b1 ...]\n" + "\n" + "Options:\n" + " --device, -D MTD character device, e.g. /dev/mtd0\n" + " --offset, -o Byte offset into the device (hex or dec)\n" + " --length, -l Number of bytes to read\n" + " --data, -d Following args are bytes to write (hex or dec)\n" + " --read, -r Read mode\n" + " --write, -w Write mode\n" + " --help, -h Show this help\n" + "\n" + "Output (read): hex-dump with offset, hex columns and ASCII column.\n", + prog, prog); +} + +static unsigned long parse_num(const char *s) +{ + char *end; + unsigned long v = strtoul(s, &end, 0); + + if (*end != '\0') { + fprintf(stderr, "Invalid number: '%s'\n", s); + exit(EXIT_FAILURE); + } + return v; +} + +/* Returns the size of the MTD device via lseek(). */ +static off_t mtd_size(int fd) +{ + off_t sz = lseek(fd, 0, SEEK_END); + + if (sz < 0) { + perror("lseek (SEEK_END)"); + exit(EXIT_FAILURE); + } + lseek(fd, 0, SEEK_SET); + return sz; +} + +/* ------------------------------------------------------------------ */ +/* main */ +/* ------------------------------------------------------------------ */ + +enum mode { MODE_NONE, MODE_READ, MODE_WRITE }; + +int main(int argc, char *argv[]) +{ + static const struct option long_opts[] = { + { "read", no_argument, NULL, 'r' }, + { "write", no_argument, NULL, 'w' }, + { "device", required_argument, NULL, 'D' }, + { "offset", required_argument, NULL, 'o' }, + { "length", required_argument, NULL, 'l' }, + { "data", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 }, + }; + + enum mode mode = MODE_NONE; + const char *device = NULL; + unsigned long offset = 0; + unsigned long length = 0; + int have_offset = 0; + int have_length = 0; + int data_mode = 0; + + int opt; + int opt_idx = 0; + + /* + * Parse all named options first. Stop at --data/-d: everything + * after it is a raw byte value for --write, not an option. + */ + while (!data_mode && + (opt = getopt_long(argc, argv, "rwD:o:l:dh", + long_opts, &opt_idx)) != -1) { + switch (opt) { + case 'r': + mode = MODE_READ; + break; + case 'w': + mode = MODE_WRITE; + break; + case 'D': + device = optarg; + break; + case 'o': + offset = parse_num(optarg); + have_offset = 1; + break; + case 'l': + length = parse_num(optarg); + have_length = 1; + break; + case 'd': + data_mode = 1; + break; + case 'h': + usage(argv[0]); + return EXIT_SUCCESS; + default: + usage(argv[0]); + return EXIT_FAILURE; + } + } + + /* --- Validate ------------------------------------------------- */ + if (!device) { + fprintf(stderr, "Error: --device is required.\n\n"); + usage(argv[0]); + return EXIT_FAILURE; + } + + if (mode == MODE_NONE) { + fprintf(stderr, "Error: specify --read or --write.\n\n"); + usage(argv[0]); + return EXIT_FAILURE; + } + + if (!have_offset) { + fprintf(stderr, "Error: --offset is required.\n\n"); + usage(argv[0]); + return EXIT_FAILURE; + } + + /* Collect write bytes from remaining argv (after --data) */ + uint8_t *write_buf = NULL; + size_t write_cnt = 0; + + if (mode == MODE_WRITE) { + if (!data_mode) { + fprintf(stderr, + "Error: --write requires --data [b1 ...].\n\n"); + usage(argv[0]); + return EXIT_FAILURE; + } + + write_cnt = (size_t)(argc - optind); + if (write_cnt == 0) { + fprintf(stderr, "Error: no data bytes given after --data.\n\n"); + usage(argv[0]); + return EXIT_FAILURE; + } + + write_buf = malloc(write_cnt); + if (!write_buf) { + perror("malloc"); + return EXIT_FAILURE; + } + + for (size_t i = 0; i < write_cnt; i++) { + unsigned long v = parse_num(argv[optind + i]); + if (v > 0xff) { + fprintf(stderr, + "Error: byte value 0x%lx out of range.\n", v); + free(write_buf); + return EXIT_FAILURE; + } + write_buf[i] = (uint8_t)v; + } + + length = write_cnt; + } + + if (mode == MODE_READ && !have_length) { + fprintf(stderr, "Error: --read requires --length.\n\n"); + usage(argv[0]); + return EXIT_FAILURE; + } + + /* --- Open device ---------------------------------------------- */ + int flags = (mode == MODE_WRITE) ? (O_RDWR | O_SYNC) : O_RDONLY; + int fd = open(device, flags); + + if (fd < 0) { + fprintf(stderr, "open(%s): %s\n", device, strerror(errno)); + free(write_buf); + return EXIT_FAILURE; + } + + off_t dev_size = mtd_size(fd); + + if ((unsigned long)dev_size == 0) { + fprintf(stderr, "Error: device size is 0.\n"); + goto err_close; + } + + if (offset >= (unsigned long)dev_size) { + fprintf(stderr, + "Error: offset 0x%lx is beyond device size 0x%lx.\n", + offset, (unsigned long)dev_size); + goto err_close; + } + + if (offset + length > (unsigned long)dev_size) { + fprintf(stderr, + "Warning: clamping length from %lu to %lu (end of device).\n", + length, (unsigned long)dev_size - offset); + length = (unsigned long)dev_size - offset; + } + + /* --- mmap ----------------------------------------------------- */ + /* + * mmap() the whole device. The kernel (mtdchar_mmap) maps the + * physical AHB window directly into our address space; accesses + * go straight to the MRAM without any kernel copy. + */ + int prot = PROT_READ | ((mode == MODE_WRITE) ? PROT_WRITE : 0); + void *map = mmap(NULL, (size_t)dev_size, prot, MAP_SHARED, fd, 0); + + if (map == MAP_FAILED) { + fprintf(stderr, "mmap(%s): %s\n", device, strerror(errno)); + fprintf(stderr, + "Note: device must be of type MTD_RAM with a physical " + "AHB window (e.g. Everspin MRAM on Cadence OSPI).\n"); + goto err_close; + } + + uint8_t *base = (uint8_t *)map + offset; + + /* --- Read ----------------------------------------------------- */ + if (mode == MODE_READ) { + printf("Reading %lu byte(s) from %s at offset 0x%lx:\n\n", + length, device, offset); + printf(" Offset 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f" + " ASCII\n"); + printf(" -------- -----------------------------------------------" + " ----------------\n"); + + for (unsigned long i = 0; i < length; ) { + unsigned long row_off = offset + i; + unsigned long row_start = i; + + /* Offset column */ + printf(" %08lx ", row_off); + + /* Hex bytes: 16 per row, split 8+8 */ + for (int col = 0; col < 16; col++) { + if (col == 8) + printf(" "); + if (row_start + col < length) + printf("%02x ", base[row_start + col]); + else + printf(" "); + } + + /* ASCII column */ + printf(" |"); + for (int col = 0; col < 16 && row_start + col < length; col++) { + uint8_t c = base[row_start + col]; + printf("%c", (c >= 0x20 && c < 0x7f) ? c : '.'); + } + printf("|\n"); + + i += 16; + } + printf("\n"); + } + + /* --- Write ---------------------------------------------------- */ + if (mode == MODE_WRITE) { + printf("Writing %lu byte(s) to %s at offset 0x%lx:\n", + length, device, offset); + + for (size_t i = 0; i < write_cnt; i++) { + /* + * Volatile write: ensure each byte reaches the hardware + * window. The mmap region is pgprot_noncached, but being + * explicit prevents compiler from merging byte stores. + */ + volatile uint8_t *dst = (volatile uint8_t *)base + i; + *dst = write_buf[i]; + printf(" [0x%08lx] <- 0x%02x\n", + offset + i, write_buf[i]); + } + + /* Read back for verification */ + printf("\nVerification (read-back):\n"); + for (size_t i = 0; i < write_cnt; i++) { + uint8_t got = *((volatile uint8_t *)base + i); + const char *status = (got == write_buf[i]) ? "OK" : "MISMATCH"; + + printf(" [0x%08lx] wrote 0x%02x read 0x%02x %s\n", + offset + i, write_buf[i], got, status); + } + } + + munmap(map, (size_t)dev_size); + free(write_buf); + close(fd); + return EXIT_SUCCESS; + +err_close: + free(write_buf); + close(fd); + return EXIT_FAILURE; +}