From a10138822433f1aad607f11232122de257a833ba Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:20:42 +0300 Subject: [PATCH] tools: fall back to PAGE_SIZE mmap when /dev/mem rejects 64 KiB window mem_reg() always mmap()s a 64 KiB window so consecutive register reads in the same SoC block hit a cache. On kernels built with CONFIG_IO_STRICT_DEVMEM=y (the kernel default since v4.6, and what the OpenIPC hi3516cv6xx defconfig currently ships) any /dev/mem mapping whose range overlaps a page already claimed by a driver via request_mem_region() is rejected with EPERM, even for root. On Hi3516CV608 the very first probe touches SCSYSID at 0x11020EE0. The 64 KiB window [0x11020000..0x1102FFFF] covers 0x11029000, claimed by the PWM driver, so mmap fails and ipctool aborts before printing the chip name -- even though the SYSCTRL page itself is unclaimed and readable. When EPERM is observed, retry the mmap with a single PAGE_SIZE window aligned to the requested address. That unblocks chip-ID, HPM, DDR PHY and any other register block whose own page isn't kernel-claimed. Pages that *are* claimed (CRG/clocks, I2C, SPI, GPIO, UART) still need the kernel-side companion fix in the OpenIPC firmware defconfig. Cache invalidation is rewritten to track the actual loaded window range instead of comparing against a fixed 64 KiB offset, so the fallback path's smaller window still benefits from the read-side cache for adjacent registers in the same page. Verified on OpenIPC hi3516cv6xx (kernel 5.10.221, CONFIG_STRICT_DEVMEM and CONFIG_IO_STRICT_DEVMEM both =y): before the patch ipctool exited with "read_mem_reg mmap error: Operation not permitted (1)"; after, it correctly identifies model 3516CV608 and emits NOR/RAM/firmware sections of the YAML report. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/tools.c | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/tools.c b/src/tools.c index 29be1b4..3b14aca 100644 --- a/src/tools.c +++ b/src/tools.c @@ -41,18 +41,20 @@ bool mem_reg(uint32_t addr, uint32_t *data, enum REG_OPS op) { return true; } - uint32_t offset = addr & 0xffff0000; - uint32_t size = 0xffff; - if (!addr || (loaded_area && offset != loaded_offset)) { + bool in_cache = loaded_area && addr >= loaded_offset && + addr - loaded_offset < loaded_size; + if (!addr || (loaded_area && !in_cache)) { int res = munmap(loaded_area, loaded_size); if (res) { fprintf(stderr, "read_mem_reg error: %s (%d)\n", strerror(errno), errno); } + loaded_area = NULL; } if (!addr) { close(mem_fd); + mem_fd = 0; return true; } @@ -65,16 +67,23 @@ bool mem_reg(uint32_t addr, uint32_t *data, enum REG_OPS op) { } volatile char *mapped_area; - if (offset != loaded_offset) { - mapped_area = - mmap(NULL, // Any adddress in our space will do - size, // Map length - PROT_READ | - PROT_WRITE, // Enable reading & writting to mapped memory - MAP_SHARED, // Shared with other processes - mem_fd, // File to map - offset // Offset to base address - ); + if (!loaded_area) { + uint32_t offset = addr & 0xffff0000; + uint32_t size = 0xffff; + mapped_area = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, + mem_fd, offset); + if (mapped_area == MAP_FAILED && errno == EPERM) { + // CONFIG_IO_STRICT_DEVMEM blocks any /dev/mem mmap whose range + // overlaps a driver-claimed page. Retry with a single page so + // the read at least succeeds when our target page itself isn't + // claimed (the 64 KiB window may have caught an unrelated + // sibling). See OpenIPC firmware PR for the kernel-side fix. + uint32_t page = (uint32_t)sysconf(_SC_PAGESIZE); + offset = addr & ~(page - 1); + size = page; + mapped_area = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, + mem_fd, offset); + } if (mapped_area == MAP_FAILED) { fprintf(stderr, "read_mem_reg mmap error: %s (%d)\n", strerror(errno), errno); @@ -87,9 +96,9 @@ bool mem_reg(uint32_t addr, uint32_t *data, enum REG_OPS op) { mapped_area = loaded_area; if (op == OP_READ) - *data = *(volatile uint32_t *)(mapped_area + (addr - offset)); + *data = *(volatile uint32_t *)(mapped_area + (addr - loaded_offset)); else if (op == OP_WRITE) - *(volatile uint32_t *)(mapped_area + (addr - offset)) = *data; + *(volatile uint32_t *)(mapped_area + (addr - loaded_offset)) = *data; return true; }