From: Sascha Hauer <s.hauer@pengutronix.de>
To: BAREBOX <barebox@lists.infradead.org>
Subject: [PATCH 19/27] scripts: add pxa-image
Date: Sun, 16 Aug 2026 19:56:39 +0200 [thread overview]
Message-ID: <20260816-pxa3xx-v1-19-f3c3d7a6c43f@pengutronix.de> (raw)
In-Reply-To: <20260816-pxa3xx-v1-0-f3c3d7a6c43f@pengutronix.de>
The PXA3xx Boot ROM does not boot a plain binary. It reads a Non-Trusted
Image Module header from the start of the boot device, copies the OBM
image listed in it into internal SRAM and jumps there; the OBM has to
load whatever comes next itself, because the Boot ROM unmaps itself
before handing over and never returns.
Add a tool that builds such an image out of an OBM and a payload. The
layout is configurable and defaults to keeping the header and the OBM in
the first erase block, which NAND chips guarantee to be good as shipped,
so that the Boot ROM never has to read a block that might be bad. The
gaps between the images are filled with 0xff
so the result can be written to erased NAND as is, and the payload's real
size is recorded in its image entry so that the OBM knows how much to
copy rather than having to assume a fixed amount.
The header format was recovered from the vendor image of a Raumfeld
speaker. Fed that device's own OBM and bootloader, this tool reproduces
55 of the 59 header words exactly; the rest are the issue date, which is
left zero for reproducibility, and the two sizes, which is the point.
Assisted-by: Claude Opus 5
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
scripts/Kconfig | 7 ++
scripts/Makefile | 1 +
scripts/pxa-image.c | 281 ++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 289 insertions(+)
diff --git a/scripts/Kconfig b/scripts/Kconfig
index 2972d5dbc7..8fff940946 100644
--- a/scripts/Kconfig
+++ b/scripts/Kconfig
@@ -21,6 +21,13 @@ config SOCFPGA_MKIMAGE
help
This enables building the image creation tool for SoCFPGA
+config PXA_IMAGE
+ bool "PXA image" if COMPILE_HOST_TOOLS
+ depends on ARCH_PXA || COMPILE_HOST_TOOLS
+ default y if ARCH_PXA
+ help
+ This enables building the image creation tool for PXA3xx
+
config ZYNQ_MKIMAGE
bool "Zynq mkimage" if COMPILE_HOST_TOOLS
depends on ARCH_ZYNQ || COMPILE_HOST_TOOLS
diff --git a/scripts/Makefile b/scripts/Makefile
index fc685f14dd..022e04ddec 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -19,6 +19,7 @@ hostprogs-always-$(CONFIG_KALLSYMS) += kallsyms
hostprogs-always-$(CONFIG_MIPS) += mips-relocs
hostprogs-always-$(CONFIG_MVEBU_HOSTTOOLS) += kwbimage kwboot mvebuimg
hostprogs-always-$(CONFIG_OMAP_IMAGE) += omap_signGP mk-omap-image
+hostprogs-always-$(CONFIG_PXA_IMAGE) += pxa-image
HOSTCFLAGS_zynq_mkimage.o = -I$(srctree) -I$(srctree)/include/mach
hostprogs-always-$(CONFIG_ZYNQ_MKIMAGE) += zynq_mkimage
hostprogs-always-$(CONFIG_SOCFPGA_MKIMAGE) += socfpga_mkimage
diff --git a/scripts/pxa-image.c b/scripts/pxa-image.c
new file mode 100644
index 0000000000..6016752c9a
--- /dev/null
+++ b/scripts/pxa-image.c
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * pxa-image - build a bootable PXA3xx NAND image
+ *
+ * The PXA3xx Boot ROM reads a Non-Trusted Image Module (NTIM) header from the
+ * start of NAND, copies the OBM image listed in it into internal SRAM and
+ * jumps there. It does not return: the OBM has to set up DRAM and load the
+ * next image itself.
+ *
+ * This tool builds such an image out of the OBM (a barebox PBL built for
+ * internal SRAM) and barebox proper:
+ *
+ * offset 0 NTIM header
+ * offset <obm-offset> OBM, loaded to internal SRAM by the Boot ROM
+ * offset <boot-offset> barebox, loaded to DRAM by the OBM
+ *
+ * The default offsets keep the first two in the first erase block, which NAND
+ * chips guarantee to be good as shipped, so that the Boot ROM never has to
+ * read a block that might be bad. Only barebox lives beyond it, and that one
+ * is read by the OBM, which is ours.
+ *
+ * The size of the barebox image is stored in its NTIM entry so that the OBM
+ * knows how much to copy; see pxa_nand_load_image() in mach-pxa.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#define NTIM_VERSION 0x00030102
+#define NTIM_ID_TIMH 0x54494d48 /* 'TIMH' */
+#define NTIM_ID_OBMI 0x4f424d49 /* 'OBMI' */
+#define NTIM_ID_BOOT 0x424f4f54 /* 'BOOT' */
+#define NTIM_ID_LAST 0xffffffff
+#define NTIM_OEM_UNIQUE_ID 0xcafeaffe
+#define NTIM_FLASH_INFO_NAND 0x4e414e06
+
+/* Where the Boot ROM puts the header itself. Fixed, see the Boot ROM manual. */
+#define NTIM_LOAD_ADDR 0x5c008000
+
+/*
+ * The vendor image declares 0xff for the header itself and rounds the OBM up
+ * to a NAND page. Stay byte compatible with it: this is the one configuration
+ * known to boot.
+ */
+#define NTIM_HEADER_CRC_SIZE 0xff
+#define NAND_PAGE_SIZE 2048
+
+struct ntim_header {
+ uint32_t version;
+ uint32_t identifier;
+ uint32_t trusted;
+ uint32_t issue_date;
+ uint32_t oem_unique_id;
+ uint32_t reserved[5];
+ uint32_t flash_info;
+ uint32_t num_images;
+ uint32_t num_keys;
+ uint32_t size_of_reserved;
+};
+
+struct ntim_image {
+ uint32_t image_id;
+ uint32_t next_image_id;
+ uint32_t flash_entry_addr;
+ uint32_t load_addr;
+ uint32_t image_size;
+ uint32_t reserved[10];
+};
+
+static void put32(void *buf, uint32_t v)
+{
+ unsigned char *p = buf;
+
+ p[0] = v; p[1] = v >> 8; p[2] = v >> 16; p[3] = v >> 24;
+}
+
+static void *read_file(const char *name, size_t *size)
+{
+ struct stat st;
+ void *buf;
+ FILE *f;
+
+ f = fopen(name, "rb");
+ if (!f) {
+ fprintf(stderr, "cannot open %s: %s\n", name, strerror(errno));
+ return NULL;
+ }
+ if (fstat(fileno(f), &st) < 0) {
+ fprintf(stderr, "cannot stat %s: %s\n", name, strerror(errno));
+ fclose(f);
+ return NULL;
+ }
+
+ buf = malloc(st.st_size);
+ if (!buf) {
+ fclose(f);
+ return NULL;
+ }
+
+ if (fread(buf, 1, st.st_size, f) != (size_t)st.st_size) {
+ fprintf(stderr, "short read on %s\n", name);
+ free(buf);
+ fclose(f);
+ return NULL;
+ }
+
+ fclose(f);
+ *size = st.st_size;
+
+ return buf;
+}
+
+/*
+ * The header and both images are written at their flash offsets, the gaps are
+ * filled with 0xff so that the result can be written to erased NAND as is.
+ */
+static int pad_to(FILE *out, size_t *pos, size_t target, const char *what)
+{
+ if (*pos > target) {
+ fprintf(stderr, "%s does not fit below 0x%zx (ends at 0x%zx)\n",
+ what, target, *pos);
+ return -1;
+ }
+
+ while (*pos < target) {
+ if (fputc(0xff, out) == EOF)
+ return -1;
+ (*pos)++;
+ }
+
+ return 0;
+}
+
+static void usage(const char *argv0)
+{
+ fprintf(stderr,
+"usage: %s -o OUT -b OBM -f BAREBOX [options]\n"
+" -o FILE output image\n"
+" -b FILE OBM image (barebox PBL for internal SRAM)\n"
+" -f FILE barebox image\n"
+" -O OFFSET flash offset of the OBM (default 0x800)\n"
+" -L ADDR load address of the OBM (default 0x5c020000)\n"
+" -F OFFSET flash offset of barebox (default 0x20000)\n"
+" -A ADDR load address of barebox (default 0x81000000)\n"
+" -M SIZE maximum total image size, 0 to disable (default 0x200000)\n",
+ argv0);
+}
+
+int main(int argc, char *argv[])
+{
+ const char *outfile = NULL, *obmfile = NULL, *bootfile = NULL;
+ unsigned long obm_offset = 0x800, obm_load = 0x5c020000;
+ unsigned long boot_offset = 0x20000, boot_load = 0x81000000;
+ unsigned long max_size = 0x200000;
+ size_t obm_size, boot_size, pos = 0;
+ void *obm, *boot;
+ struct ntim_header hdr;
+ struct ntim_image img[3];
+ FILE *out;
+ int opt;
+
+ while ((opt = getopt(argc, argv, "o:b:f:O:L:F:A:M:h")) != -1) {
+ switch (opt) {
+ case 'o': outfile = optarg; break;
+ case 'b': obmfile = optarg; break;
+ case 'f': bootfile = optarg; break;
+ case 'O': obm_offset = strtoul(optarg, NULL, 0); break;
+ case 'L': obm_load = strtoul(optarg, NULL, 0); break;
+ case 'F': boot_offset = strtoul(optarg, NULL, 0); break;
+ case 'A': boot_load = strtoul(optarg, NULL, 0); break;
+ case 'M': max_size = strtoul(optarg, NULL, 0); break;
+ default:
+ usage(argv[0]);
+ return opt == 'h' ? 0 : 1;
+ }
+ }
+
+ if (!outfile || !obmfile || !bootfile) {
+ usage(argv[0]);
+ return 1;
+ }
+
+ obm = read_file(obmfile, &obm_size);
+ if (!obm)
+ return 1;
+ boot = read_file(bootfile, &boot_size);
+ if (!boot)
+ return 1;
+
+ memset(&hdr, 0, sizeof(hdr));
+ put32(&hdr.version, NTIM_VERSION);
+ put32(&hdr.identifier, NTIM_ID_TIMH);
+ put32(&hdr.trusted, 0);
+ put32(&hdr.issue_date, 0);
+ put32(&hdr.oem_unique_id, NTIM_OEM_UNIQUE_ID);
+ memset(hdr.reserved, 0xff, sizeof(hdr.reserved));
+ put32(&hdr.flash_info, NTIM_FLASH_INFO_NAND);
+ put32(&hdr.num_images, 3);
+ put32(&hdr.num_keys, 0);
+ put32(&hdr.size_of_reserved, 0);
+
+ memset(img, 0, sizeof(img));
+
+ /* The header describes itself first. */
+ put32(&img[0].image_id, NTIM_ID_TIMH);
+ put32(&img[0].next_image_id, NTIM_ID_OBMI);
+ put32(&img[0].flash_entry_addr, 0);
+ put32(&img[0].load_addr, NTIM_LOAD_ADDR);
+ put32(&img[0].image_size, NTIM_HEADER_CRC_SIZE);
+
+ put32(&img[1].image_id, NTIM_ID_OBMI);
+ put32(&img[1].next_image_id, NTIM_ID_BOOT);
+ put32(&img[1].flash_entry_addr, obm_offset);
+ put32(&img[1].load_addr, obm_load);
+ put32(&img[1].image_size,
+ (obm_size + NAND_PAGE_SIZE - 1) & ~(NAND_PAGE_SIZE - 1));
+
+ /*
+ * image_size is what the OBM copies out of NAND, so it has to be the
+ * real size of the barebox image rather than the CRC'd part.
+ */
+ put32(&img[2].image_id, NTIM_ID_BOOT);
+ put32(&img[2].next_image_id, NTIM_ID_LAST);
+ put32(&img[2].flash_entry_addr, boot_offset);
+ put32(&img[2].load_addr, boot_load);
+ put32(&img[2].image_size, boot_size);
+
+ out = fopen(outfile, "wb");
+ if (!out) {
+ fprintf(stderr, "cannot create %s: %s\n", outfile,
+ strerror(errno));
+ return 1;
+ }
+
+ if (fwrite(&hdr, 1, sizeof(hdr), out) != sizeof(hdr))
+ goto write_error;
+ pos += sizeof(hdr);
+ if (fwrite(img, 1, sizeof(img), out) != sizeof(img))
+ goto write_error;
+ pos += sizeof(img);
+
+ if (pad_to(out, &pos, obm_offset, "NTIM header"))
+ goto error;
+ if (fwrite(obm, 1, obm_size, out) != obm_size)
+ goto write_error;
+ pos += obm_size;
+
+ if (pad_to(out, &pos, boot_offset, "OBM"))
+ goto error;
+ if (fwrite(boot, 1, boot_size, out) != boot_size)
+ goto write_error;
+ pos += boot_size;
+
+ if (max_size && pos > max_size) {
+ fprintf(stderr,
+ "image is 0x%zx bytes, exceeds the maximum of 0x%lx\n",
+ pos, max_size);
+ goto error;
+ }
+
+ fclose(out);
+
+ printf("pxa-image: NTIM + OBM (0x%zx @ 0x%lx) + barebox (0x%zx @ 0x%lx)"
+ " = 0x%zx bytes\n", obm_size, obm_offset, boot_size, boot_offset,
+ pos);
+
+ return 0;
+
+write_error:
+ fprintf(stderr, "write error on %s: %s\n", outfile, strerror(errno));
+error:
+ fclose(out);
+ remove(outfile);
+
+ return 1;
+}
--
2.47.3
next prev parent reply other threads:[~2026-08-16 18:02 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-16 17:56 [PATCH 00/27] ARM: Add pxa3xx and Raumfeld Speaker support Sascha Hauer
2026-08-16 17:56 ` [PATCH 01/27] ARM: pxa: remove PXA25x and PXA27x support Sascha Hauer
2026-08-16 17:56 ` [PATCH 02/27] video: remove the PXA framebuffer driver Sascha Hauer
2026-08-16 17:56 ` [PATCH 03/27] ARM: cache: drive the XSC3 cache with the ARMv4 functions Sascha Hauer
2026-08-16 17:56 ` [PATCH 04/27] mci: pxamci: get the clock from the clk API Sascha Hauer
2026-08-16 17:56 ` [PATCH 05/27] pwm: pxa: " Sascha Hauer
2026-08-16 17:56 ` [PATCH 06/27] serial: " Sascha Hauer
2026-08-16 17:56 ` [PATCH 07/27] clk: pxa: add a device tree clock driver for PXA3xx Sascha Hauer
2026-08-16 17:56 ` [PATCH 08/27] mtd: nand: nand_mrvl_nfc: honour marvell,nand-keep-config Sascha Hauer
2026-08-16 17:56 ` [PATCH 09/27] mtd: nand: nand_mrvl_nfc: support the nand-controller bindings Sascha Hauer
2026-08-16 17:56 ` [PATCH 10/27] mtd: nand: mrvl_nfc: keep the ready latch across a STATUS command Sascha Hauer
2026-08-16 17:56 ` [PATCH 11/27] mtd: nand: mrvl_nfc: do not report a command timeout as an error Sascha Hauer
2026-08-16 17:56 ` [PATCH 12/27] mci: pxamci: probe from the device tree Sascha Hauer
2026-08-16 17:56 ` [PATCH 13/27] serial: pxa: add device tree support Sascha Hauer
2026-08-16 17:56 ` [PATCH 14/27] serial: pxa: provide the Linux console name Sascha Hauer
2026-08-16 17:56 ` [PATCH 15/27] gpio: pxa: add a driver and switch the architecture to GPIOLIB Sascha Hauer
2026-08-16 17:56 ` [PATCH 16/27] ARM: pxa: add DEBUG_LL support Sascha Hauer
2026-08-16 17:56 ` [PATCH 17/27] ARM: pxa: let the board select the SoC Sascha Hauer
2026-08-16 17:56 ` [PATCH 18/27] ARM: pxa: enable device tree support Sascha Hauer
2026-08-16 17:56 ` Sascha Hauer [this message]
2026-08-16 17:56 ` [PATCH 20/27] ARM: pxa: add a NAND first stage loader Sascha Hauer
2026-08-16 17:56 ` [PATCH 21/27] filetype: detect PXA3xx NTIM images Sascha Hauer
2026-08-16 17:56 ` [PATCH 22/27] ARM: pxa: add a barebox update handler for NAND Sascha Hauer
2026-08-16 17:56 ` [PATCH 23/27] clocksource: add a driver for the PXA OS timer and its watchdog Sascha Hauer
2026-08-16 17:56 ` [PATCH 24/27] ARM: pxa: move over to MULTIARCH Sascha Hauer
2026-08-16 17:56 ` [PATCH 25/27] ARM: pxa: reset straight away and without complaining Sascha Hauer
2026-08-16 17:56 ` [PATCH 26/27] ARM: pxa: add Raumfeld Speaker board support Sascha Hauer
2026-08-16 17:56 ` [PATCH 27/27] ARM: multi_v5_v6_defconfig: enable PXA support Sascha Hauer
2026-08-17 7:35 ` [PATCH 00/27] ARM: Add pxa3xx and Raumfeld Speaker support Ahmad Fatoum
2026-08-19 9:26 ` Sascha Hauer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260816-pxa3xx-v1-19-f3c3d7a6c43f@pengutronix.de \
--to=s.hauer@pengutronix.de \
--cc=barebox@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox