[PATCH 19/27] scripts: add pxa-image

Sascha Hauer s.hauer at pengutronix.de
Sun Aug 16 10:56:39 PDT 2026


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 at 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




More information about the barebox mailing list