mail archive of the barebox mailing list
 help / color / mirror / Atom feed
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




  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