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 <device> --read --offset <off> --length <n> mram_io --device <device> --write --offset <off> --data <b0> [b1 b2 ...] Options: --device / -D <n> MTD character device, e.g. /dev/mtd0 --offset / -o <n> Byte offset (hex or decimal) --length / -l <n> 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 <ht@twx-software.de>
This commit is contained in:
@@ -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)
|
||||
@@ -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 <dev> --offset <offset> --length <n>
|
||||
* mram_io --write --device <dev> --offset <offset> --data <b0> [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 <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void usage(const char *prog)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Usage:\n"
|
||||
" %s --read --device <dev> --offset <off> --length <n>\n"
|
||||
" %s --write --device <dev> --offset <off> --data <b0> [b1 ...]\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" --device, -D <path> MTD character device, e.g. /dev/mtd0\n"
|
||||
" --offset, -o <n> Byte offset into the device (hex or dec)\n"
|
||||
" --length, -l <n> 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 <path> 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 <b0> [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;
|
||||
}
|
||||
Reference in New Issue
Block a user