From 55f3abd733602544abf04baf396fab8b44ccc2b7 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Thu, 14 May 2026 16:50:17 +0300 Subject: [PATCH] Add `bootrom` subcommand: dump or inspect the SoC mask-ROM region Replaces the build-toolchain-scp-dump dance from the #161/#162 work with a single `ipctool bootrom` call. Found during issue #160 follow-up when comparing mask-ROM accessibility across Goke V4 / HiSi V4 / V4A. Default (no flags): YAML metadata -- base, size, accessibility, first 16 bytes hex, chip name. bootrom: base: 0x04000000 size: 65536 accessible: 1 first_bytes: 10 00 00 ea 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 chip: hi3516av300 `accessible: 0` means the region reads as all zeros -- the Goke V4 case where the silicon hides the bootrom from userspace post-boot (see the corresponding kaeru lesson). For HiSi V4 / V4A boards the flag reliably reports 1. `--dump` writes the raw binary region to stdout (intended for redirect/pipe). No metadata preamble. Equivalent to the stand-alone dump_bootrom binary used in the earlier diagnostic but built into ipctool so no separate toolchain is needed. Per-family defaults sourced from the V4A mask-ROM RE (HI3516CV500-SDK/sdk/bootrom/bootrom.cpp: `BOOTROM = 0x04000000`, SRAM = 0x04010000, so 64 KB max). Currently shipping defaults for HISI_V4 (0x3516E300) and HISI_V4A (0x3516C500); other families require explicit --base / --size. Cross-board MD5 of `ipctool bootrom --dump | md5sum` reproduces the stand-alone dumper results exactly: hi3516ev300 (V4 HiSi): 669081159f4fed97ad4db0bfd5251dca (real ROM) gk7205v300 (V4 Goke): fcd6bcb56c1689fcef28b57c22475bad (all zeros) hi3516av300 (V4A): 1c2198a0a30c8258f5ab0c27aa523ff9 (real ROM, different mask) The `accessible` heuristic (all-zero buffer detector) correctly flags the Goke V4 case as `0` and the two real-ROM cases as `1`. CLI matches the existing clocks/cpubench/membw shape: ipctool bootrom [--base ADDR] [--size N] [--dump] [--json] Note: --base/--size accept any address; reading non-bootrom MMIO regions byte-by-byte (e.g. CRG at 0x12010000) will SIGBUS as the kernel signals an unaligned/word-only access. That's appropriate behavior -- the defaults are the supported path. Documented in the --help output. Co-authored-by: Claude Opus 4.7 (1M context) --- CMakeLists.txt | 2 + src/bootrom.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++++ src/bootrom.h | 6 ++ src/main.c | 6 ++ 4 files changed, 241 insertions(+) create mode 100644 src/bootrom.c create mode 100644 src/bootrom.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ad7010..e615343 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,8 @@ set(COMMON_LIB_SRC set(IPCTOOL_SRC src/backup.c src/backup.h + src/bootrom.c + src/bootrom.h src/clocks.c src/clocks.h src/cpubench.c diff --git a/src/bootrom.c b/src/bootrom.c new file mode 100644 index 0000000..0a2b457 --- /dev/null +++ b/src/bootrom.c @@ -0,0 +1,227 @@ +/* `ipctool bootrom` -- dump or inspect the SoC's mask-ROM region. + * + * Default mode (no flags): print YAML metadata -- base address, size, + * accessibility (whether the region reads as all-zeros, which is the + * Goke V4 case where the silicon hides the bootrom from userspace + * post-boot), the first 16 bytes as a hex preview, and the chip name + * for context. + * + * --dump: write the raw binary region to stdout (intended to be + * redirected to a file or piped to `md5sum` / `xxd`). No metadata + * preamble in this mode. + * + * Per-family defaults sourced from the HI3516CV500-SDK bootrom RE + * (sdk/bootrom/bootrom.cpp): BOOTROM at 0x04000000, size up to SRAM + * at 0x04010000 = 64 KB. Confirmed on V4 (hi3516ev300, gk7205v300) + * and V4A (hi3516av300) lab boards. For unsupported families the + * caller must pass --base / --size explicitly. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bootrom.h" +#include "chipid.h" +#include "cjson/cJSON.h" +#include "cjson/cYAML.h" +#include "hal/hisi/hal_hisi.h" +#include "tools.h" + +struct bootrom_default { + int chip_id; + uint32_t base; + uint32_t size; +}; + +/* Known per-family bootrom locations. Empty (base=0) entries mean + * "no first-party default known; require --base/--size". */ +static const struct bootrom_default defaults[] = { + {HISI_V4, 0x04000000, 0x10000}, + {HISI_V4A, 0x04000000, 0x10000}, +}; + +static bool default_for_chip(int chip_id, uint32_t *base, uint32_t *size) { + for (size_t i = 0; i < ARRCNT(defaults); i++) { + if (defaults[i].chip_id == chip_id) { + *base = defaults[i].base; + *size = defaults[i].size; + return true; + } + } + return false; +} + +static bool buffer_is_all_zero(const uint8_t *p, size_t n) { + for (size_t i = 0; i < n; i++) + if (p[i] != 0) + return false; + return true; +} + +static cJSON *bootrom_metadata(const uint8_t *buf, uint32_t base, + uint32_t size) { + cJSON *j_inner = cJSON_CreateObject(); + ADD_PARAM_FMT("base", "0x%08x", base); + ADD_PARAM_NUM("size", size); + ADD_PARAM_NUM("accessible", buffer_is_all_zero(buf, size) ? 0 : 1); + + /* First 16 bytes as a hex preview -- enough to spot an ARM exception + * vector table (typical opener: 1000 00ea 14f0 9fe5 ...) without + * spamming the terminal. */ + char hex[16 * 3 + 1]; + size_t prev = size < 16 ? size : 16; + char *p = hex; + for (size_t i = 0; i < prev; i++) { + snprintf(p, 4, "%s%02x", i ? " " : "", buf[i]); + p += (i ? 3 : 2); + } + ADD_PARAM("first_bytes", hex); + + const char *chip = getchipname(); + if (chip) + ADD_PARAM("chip", chip); + return j_inner; +} + +static void print_bootrom_usage(void) { + printf( + "Usage: ipctool bootrom [--base ADDR] [--size N] [--dump] [--json]\n" + "\n" + "Inspect or dump the SoC mask-ROM region.\n" + "\n" + "Default (no flags): YAML metadata (base, size, accessibility,\n" + "first 16 bytes hex, chip name). `accessible: 0` means the region\n" + "reads as all zeros -- the Goke V4 case where the silicon hides\n" + "the bootrom from userspace post-boot.\n" + "\n" + " --dump write the raw binary region to stdout (redirect\n" + " to a file, pipe to `md5sum`, etc.). No metadata\n" + " preamble.\n" + " --base ADDR override per-family default (e.g. 0x04000000)\n" + " --size N override per-family default size in bytes\n" + " --json machine-readable JSON metadata (ignored with --dump)\n" + "\n" + "Per-family defaults: V4 / V4A = 0x04000000 / 0x10000 (64 KB).\n" + "Other families: --base / --size required.\n"); +} + +int bootrom_cmd(int argc, char **argv) { + uint32_t base = 0, size = 0; + bool base_set = false, size_set = false; + bool want_dump = false; + bool want_json = false; + + const struct option long_options[] = { + {"base", required_argument, NULL, 'b'}, + {"size", required_argument, NULL, 's'}, + {"dump", no_argument, NULL, 'd'}, + {"json", no_argument, NULL, 'j'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0}, + }; + int opt; + optind = 1; + while ((opt = getopt_long(argc, argv, "b:s:djh", long_options, NULL)) != + -1) { + switch (opt) { + case 'b': + base = (uint32_t)strtoul(optarg, NULL, 0); + base_set = true; + break; + case 's': + size = (uint32_t)strtoul(optarg, NULL, 0); + if (size == 0 || size > 0x01000000) { + fprintf(stderr, "bootrom: --size must be 1..16 MB\n"); + return EXIT_FAILURE; + } + size_set = true; + break; + case 'd': + want_dump = true; + break; + case 'j': + want_json = true; + break; + case 'h': + print_bootrom_usage(); + return EXIT_SUCCESS; + default: + print_bootrom_usage(); + return EXIT_FAILURE; + } + } + + /* Fill in family defaults if the user didn't override. */ + if (!base_set || !size_set) { + if (!getchipname()) { + fprintf(stderr, + "bootrom: cannot identify chip; pass --base and --size\n"); + return EXIT_FAILURE; + } + uint32_t def_base, def_size; + if (!default_for_chip(chip_generation, &def_base, &def_size)) { + fprintf(stderr, + "bootrom: no first-party default for chip family 0x%x " + "(%s); pass --base and --size explicitly\n", + chip_generation, chip_name); + return EXIT_FAILURE; + } + if (!base_set) + base = def_base; + if (!size_set) + size = def_size; + } + + int fd = open("/dev/mem", O_RDONLY); + if (fd < 0) { + fprintf(stderr, "bootrom: open /dev/mem: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + void *map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, base); + if (map == MAP_FAILED) { + fprintf(stderr, "bootrom: mmap 0x%x size %u: %s\n", base, size, + strerror(errno)); + close(fd); + return EXIT_FAILURE; + } + + int rc = EXIT_SUCCESS; + if (want_dump) { + size_t left = size; + const uint8_t *p = map; + while (left) { + ssize_t n = write(1, p, left); + if (n <= 0) { + fprintf(stderr, "bootrom: write: %s\n", strerror(errno)); + rc = EXIT_FAILURE; + break; + } + p += n; + left -= n; + } + } else { + cJSON *meta = bootrom_metadata(map, base, size); + cJSON *root = cJSON_CreateObject(); + cJSON_AddItemToObject(root, "bootrom", meta); + char *out = want_json ? cJSON_Print(root) : cYAML_Print(root); + if (out) { + printf("%s", out); + if (want_json) + printf("\n"); + free(out); + } + cJSON_Delete(root); + } + + munmap(map, size); + close(fd); + return rc; +} diff --git a/src/bootrom.h b/src/bootrom.h new file mode 100644 index 0000000..9e0af39 --- /dev/null +++ b/src/bootrom.h @@ -0,0 +1,6 @@ +#ifndef BOOTROM_H +#define BOOTROM_H + +int bootrom_cmd(int argc, char **argv); + +#endif /* BOOTROM_H */ diff --git a/src/main.c b/src/main.c index 28a9378..75294d2 100644 --- a/src/main.c +++ b/src/main.c @@ -13,6 +13,7 @@ #include #include "backup.h" +#include "bootrom.h" #include "chipid.h" #include "cjson/cJSON.h" #include "cjson/cYAML.h" @@ -112,6 +113,9 @@ void print_usage() { " DDR bandwidth probe (memset / read scan " "/\n" " memcpy)\n" + " bootrom [--dump] [--base ADDR] [--size N] [--json]\n" + " inspect or dump the SoC mask-ROM region\n" + " (V4 / V4A: default 0x04000000, 64 KB)\n" " sensor monitor poll AE/exposure registers from the\n" " running sensor every 2s. Supported:\n" " SC2315E, IMX291, IMX385.\n" @@ -193,6 +197,8 @@ int main(int argc, char *argv[]) { return cpubench_cmd(argc - 1, argv + 1); else if (!strcmp(argv[1], "membw")) return membw_cmd(argc - 1, argv + 1); + else if (!strcmp(argv[1], "bootrom")) + return bootrom_cmd(argc - 1, argv + 1); #ifdef __arm__ else if (!strcmp(argv[1], "trace")) return ptrace_cmd(argc - 1, argv + 1);