1/* $NetBSD: mkubootimage.c,v 1.33 2024/05/21 04:01:26 gutteridge Exp $ */
2
3/*-
4 * Copyright (c) 2010 Jared D. McNeill <jmcneill@invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. The name of the author may not be used to endorse or promote products
13 *    derived from this software without specific prior written permission.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
22 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#if HAVE_NBTOOL_CONFIG_H
29#include "nbtool_config.h"
30#endif
31
32#include <sys/cdefs.h>
33__RCSID("$NetBSD: mkubootimage.c,v 1.33 2024/05/21 04:01:26 gutteridge Exp $");
34
35#include <sys/mman.h>
36#include <sys/stat.h>
37#include <sys/endian.h>
38#include <sys/param.h>
39#include <sys/uio.h>
40#include <err.h>
41#include <errno.h>
42#include <fcntl.h>
43#include <inttypes.h>
44#include <limits.h>
45#include <stdint.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <time.h>
50#include <unistd.h>
51
52#include "uboot.h"
53#include "arm64.h"
54#include "crc32.h"
55
56#ifndef __arraycount
57#define __arraycount(__x)	(sizeof(__x) / sizeof(__x[0]))
58#endif
59
60enum image_format {
61	FMT_UNKNOWN,
62	FMT_UIMG,	/* Legacy U-Boot image */
63	FMT_ARM64,	/* Linux ARM64 image (booti) */
64};
65
66static enum uboot_image_os image_os = IH_OS_NETBSD;
67static enum uboot_image_arch image_arch = IH_ARCH_UNKNOWN;
68static enum uboot_image_type image_type = IH_TYPE_UNKNOWN;
69static enum uboot_image_comp image_comp = IH_COMP_NONE;
70static uint32_t image_loadaddr = 0;
71static uint32_t image_entrypoint = 0;
72static char *image_name;
73static uint32_t image_magic = IH_MAGIC;
74static enum image_format image_format = FMT_UIMG;
75static int update_image = 0;
76
77static const struct uboot_image_format {
78	enum image_format format;
79	const char *name;
80} uboot_image_format[] = {
81	{ FMT_UIMG,		"uimg" },
82	{ FMT_ARM64,		"arm64" },
83};
84
85static enum image_format
86get_image_format(const char *name)
87{
88	unsigned int i;
89
90	for (i = 0; i < __arraycount(uboot_image_format); i++) {
91		if (strcmp(uboot_image_format[i].name, name) == 0)
92			return uboot_image_format[i].format;
93	}
94
95	return FMT_UNKNOWN;
96}
97
98static const char *
99get_image_format_name(enum image_format format)
100{
101	unsigned int i;
102
103	for (i = 0; i < __arraycount(uboot_image_format); i++) {
104		if (uboot_image_format[i].format == format)
105			return uboot_image_format[i].name;
106	}
107
108	return "Unknown";
109}
110
111static const struct uboot_os {
112	enum uboot_image_os os;
113	const char *name;
114} uboot_os[] = {
115	{ IH_OS_OPENBSD,	"openbsd" },
116	{ IH_OS_NETBSD,		"netbsd" },
117	{ IH_OS_FREEBSD,	"freebsd" },
118	{ IH_OS_LINUX,		"linux" },
119};
120
121static enum uboot_image_os
122get_os(const char *name)
123{
124	unsigned int i;
125
126	for (i = 0; i < __arraycount(uboot_os); i++) {
127		if (strcmp(uboot_os[i].name, name) == 0)
128			return uboot_os[i].os;
129	}
130
131	return IH_OS_UNKNOWN;
132}
133
134static const char *
135get_os_name(enum uboot_image_os os)
136{
137	unsigned int i;
138
139	for (i = 0; i < __arraycount(uboot_os); i++) {
140		if (uboot_os[i].os == os)
141			return uboot_os[i].name;
142	}
143
144	return "Unknown";
145}
146
147static const struct uboot_arch {
148	enum uboot_image_arch arch;
149	const char *name;
150} uboot_arch[] = {
151	{ IH_ARCH_ARM,		"arm" },
152	{ IH_ARCH_ARM64,	"arm64" },
153	{ IH_ARCH_I386,		"i386" },
154	{ IH_ARCH_MIPS,		"mips" },
155	{ IH_ARCH_MIPS64,	"mips64" },
156	{ IH_ARCH_PPC,		"powerpc" },
157	{ IH_ARCH_OPENRISC,	"or1k" },
158	{ IH_ARCH_RISCV,	"riscv" },
159	{ IH_ARCH_SH,		"sh" },
160};
161
162static enum uboot_image_arch
163get_arch(const char *name)
164{
165	unsigned int i;
166
167	for (i = 0; i < __arraycount(uboot_arch); i++) {
168		if (strcmp(uboot_arch[i].name, name) == 0)
169			return uboot_arch[i].arch;
170	}
171
172	return IH_ARCH_UNKNOWN;
173}
174
175static const char *
176get_arch_name(enum uboot_image_arch arch)
177{
178	unsigned int i;
179
180	for (i = 0; i < __arraycount(uboot_arch); i++) {
181		if (uboot_arch[i].arch == arch)
182			return uboot_arch[i].name;
183	}
184
185	return "Unknown";
186}
187
188static const struct uboot_type {
189	enum uboot_image_type type;
190	const char *name;
191} uboot_type[] = {
192	{ IH_TYPE_STANDALONE,		"standalone" },
193	{ IH_TYPE_KERNEL,		"kernel" },
194	{ IH_TYPE_KERNEL_NOLOAD,	"kernel_noload" },
195	{ IH_TYPE_RAMDISK,		"ramdisk" },
196	{ IH_TYPE_FILESYSTEM,		"fs" },
197	{ IH_TYPE_SCRIPT,		"script" },
198};
199
200static enum uboot_image_type
201get_type(const char *name)
202{
203	unsigned int i;
204
205	for (i = 0; i < __arraycount(uboot_type); i++) {
206		if (strcmp(uboot_type[i].name, name) == 0)
207			return uboot_type[i].type;
208	}
209
210	return IH_TYPE_UNKNOWN;
211}
212
213static const char *
214get_type_name(enum uboot_image_type type)
215{
216	unsigned int i;
217
218	for (i = 0; i < __arraycount(uboot_type); i++) {
219		if (uboot_type[i].type == type)
220			return uboot_type[i].name;
221	}
222
223	return "Unknown";
224}
225
226static const struct uboot_comp {
227	enum uboot_image_comp comp;
228	const char *name;
229} uboot_comp[] = {
230	{ IH_COMP_NONE,		"none" },
231	{ IH_COMP_GZIP,		"gz" },
232	{ IH_COMP_BZIP2,	"bz2" },
233	{ IH_COMP_LZMA,		"lzma" },
234	{ IH_COMP_LZO,		"lzo" },
235};
236
237static enum uboot_image_comp
238get_comp(const char *name)
239{
240	unsigned int i;
241
242	for (i = 0; i < __arraycount(uboot_comp); i++) {
243		if (strcmp(uboot_comp[i].name, name) == 0)
244			return uboot_comp[i].comp;
245	}
246
247	return IH_COMP_NONE;
248}
249
250static const char *
251get_comp_name(enum uboot_image_comp comp)
252{
253	unsigned int i;
254
255	for (i = 0; i < __arraycount(uboot_comp); i++) {
256		if (uboot_comp[i].comp == comp)
257			return uboot_comp[i].name;
258	}
259
260	return "Unknown";
261}
262
263__dead static void
264usage(void)
265{
266	fprintf(stderr,
267"Usage: %s [-hu] -A <arm|arm64|i386|mips|mips64|or1k|powerpc|riscv|sh>\n"
268"\t-a address [-C <bz2|gz|lzma|lzo|none>] [-E address] [-e address] \n"
269"\t[-f <arm64|uimg>] [-m magic] -n image [-O <freebsd|linux|netbsd|openbsd>]\n"
270"\t-T <fs|kernel|kernel_noload|ramdisk|script|standalone> [-t timestamp]\n"
271"\tsource destination\n", getprogname());
272
273	exit(EXIT_FAILURE);
274}
275
276static void
277dump_header_uimg(struct uboot_image_header *hdr)
278{
279	time_t tm = ntohl(hdr->ih_time);
280
281	printf(" magic:       0x%08x\n", ntohl(hdr->ih_magic));
282	printf(" time:        %s", ctime(&tm));
283	printf(" size:        %u\n", ntohl(hdr->ih_size));
284	printf(" load addr:   0x%08x\n", ntohl(hdr->ih_load));
285	printf(" entry point: 0x%08x\n", ntohl(hdr->ih_ep));
286	printf(" data crc:    0x%08x\n", ntohl(hdr->ih_dcrc));
287	printf(" os:          %d (%s)\n", hdr->ih_os,
288	    get_os_name(hdr->ih_os));
289	printf(" arch:        %d (%s)\n", hdr->ih_arch,
290	    get_arch_name(hdr->ih_arch));
291	printf(" type:        %d (%s)\n", hdr->ih_type,
292	    get_type_name(hdr->ih_type));
293	printf(" comp:        %d (%s)\n", hdr->ih_comp,
294	    get_comp_name(hdr->ih_comp));
295	printf(" name:        %s\n", hdr->ih_name);
296	printf(" header crc:  0x%08x\n", hdr->ih_hcrc);
297}
298
299static int
300generate_header_uimg(struct uboot_image_header *hdr, time_t repro_time,
301    int kernel_fd)
302{
303	uint8_t *p;
304	struct stat st;
305	uint32_t crc, dsize, size_buf[2];
306	int error;
307
308	error = fstat(kernel_fd, &st);
309	if (error == -1) {
310		perror("stat");
311		return errno;
312	}
313
314	if (st.st_size + sizeof(*hdr) > UINT32_MAX) {
315		fprintf(stderr, "fatal: kernel too big\n");
316		return EINVAL;
317	}
318
319	p = mmap(0, st.st_size, PROT_READ, MAP_FILE|MAP_SHARED, kernel_fd, 0);
320	if (p == MAP_FAILED) {
321		perror("mmap kernel");
322		return EINVAL;
323	}
324	if (image_type == IH_TYPE_SCRIPT) {
325		struct iovec iov[3];
326		dsize = (uint32_t)(st.st_size + (sizeof(uint32_t) * 2));
327		size_buf[0] = htonl(st.st_size);
328		size_buf[1] = htonl(0);
329		iov[0].iov_base = &size_buf[0];
330		iov[0].iov_len = sizeof(size_buf[0]);
331		iov[1].iov_base = &size_buf[1];
332		iov[1].iov_len = sizeof(size_buf[1]);
333		iov[2].iov_base = p;
334		iov[2].iov_len = st.st_size;
335		crc = crc32v(iov, 3);
336	} else {
337		dsize = update_image ? (uint32_t)(st.st_size - sizeof(*hdr)) :
338		    (uint32_t)st.st_size;
339		crc = crc32(p, st.st_size);
340	}
341	munmap(p, st.st_size);
342
343	memset(hdr, 0, sizeof(*hdr));
344	hdr->ih_magic = htonl(image_magic);
345	hdr->ih_time = htonl(repro_time ? repro_time : st.st_mtime);
346	hdr->ih_size = htonl(dsize);
347	hdr->ih_load = htonl(image_loadaddr);
348	hdr->ih_ep = htonl(image_entrypoint);
349	hdr->ih_dcrc = htonl(crc);
350	hdr->ih_os = image_os;
351	hdr->ih_arch = image_arch;
352	hdr->ih_type = image_type;
353	hdr->ih_comp = image_comp;
354	strlcpy((char *)hdr->ih_name, image_name, sizeof(hdr->ih_name));
355	crc = crc32((void *)hdr, sizeof(*hdr));
356	hdr->ih_hcrc = htonl(crc);
357
358	dump_header_uimg(hdr);
359
360	return 0;
361}
362
363static void
364dump_header_arm64(struct arm64_image_header *hdr)
365{
366	printf(" magic:       0x%" PRIx32 "\n", le32toh(hdr->magic));
367	printf(" text offset: 0x%" PRIx64 "\n", le64toh(hdr->text_offset));
368	printf(" image size:  %" PRIu64 "\n", le64toh(hdr->image_size));
369	printf(" flags:       0x%" PRIx64 "\n", le64toh(hdr->flags));
370}
371
372static int
373generate_header_arm64(struct arm64_image_header *hdr, int kernel_fd)
374{
375	struct stat st;
376	uint32_t flags;
377	int error;
378
379	error = fstat(kernel_fd, &st);
380	if (error == -1) {
381		perror("stat");
382		return errno;
383	}
384
385	flags = 0;
386	flags |= ARM64_FLAGS_PAGE_SIZE_4K;
387#if 0
388	flags |= ARM64_FLAGS_PHYS_PLACEMENT_ANY;
389#endif
390
391	const uint64_t dsize = update_image ?
392	    (uint64_t)st.st_size : (uint64_t)st.st_size + sizeof(*hdr);
393
394	memset(hdr, 0, sizeof(*hdr));
395	hdr->code0 = htole32(ARM64_CODE0);
396	hdr->text_offset = htole64(image_entrypoint);
397	hdr->image_size = htole64(dsize);
398	hdr->flags = htole32(flags);
399	hdr->magic = htole32(ARM64_MAGIC);
400
401	dump_header_arm64(hdr);
402
403	return 0;
404}
405
406static int
407write_image(void *hdr, size_t hdrlen, int kernel_fd, int image_fd)
408{
409	uint8_t buf[4096];
410	ssize_t rlen, wlen;
411	struct stat st;
412	uint32_t size_buf[2];
413	int error;
414
415	error = fstat(kernel_fd, &st);
416	if (error == -1) {
417		perror("stat");
418		return errno;
419	}
420
421	wlen = write(image_fd, hdr, hdrlen);
422	if (wlen != (ssize_t)hdrlen) {
423		perror("short write");
424		return errno;
425	}
426
427	if (image_type == IH_TYPE_SCRIPT) {
428		size_buf[0] = htonl(st.st_size);
429		size_buf[1] = htonl(0);
430		wlen = write(image_fd, &size_buf, sizeof(size_buf));
431		if (wlen != sizeof(size_buf)) {
432			perror("short write");
433			return errno;
434		}
435	}
436
437	if (update_image) {
438		if (lseek(kernel_fd, hdrlen, SEEK_SET) != (off_t)hdrlen) {
439			perror("seek failed");
440			return errno;
441		}
442	}
443
444	while ((rlen = read(kernel_fd, buf, sizeof(buf))) > 0) {
445		wlen = write(image_fd, buf, rlen);
446		if (wlen != rlen) {
447			perror("short write");
448			return errno;
449		}
450	}
451
452	return 0;
453}
454
455int
456main(int argc, char *argv[])
457{
458	struct uboot_image_header hdr_uimg;
459	struct arm64_image_header hdr_arm64;
460	const char *src, *dest;
461	char *ep;
462	int kernel_fd, image_fd;
463	int ch;
464	unsigned long long num;
465	time_t repro_time = 0;
466
467	while ((ch = getopt(argc, argv, "A:C:E:O:T:a:e:f:hm:n:t:u")) != -1) {
468		switch (ch) {
469		case 'A':	/* arch */
470			image_arch = get_arch(optarg);
471			break;
472		case 'C':	/* comp */
473			image_comp = get_comp(optarg);
474			break;
475		case 'O':	/* os */
476			image_os = get_os(optarg);
477			break;
478		case 'T':	/* type */
479			image_type = get_type(optarg);
480			break;
481		case 'a':	/* addr */
482			errno = 0;
483			num = strtoull(optarg, &ep, 0);
484			if (*ep != '\0' || (errno == ERANGE &&
485			    (num == ULLONG_MAX || num == 0)) ||
486			    ((signed long long)num != (int32_t)num &&
487			     num != (uint32_t)num))
488				errx(1, "illegal number -- %s", optarg);
489			image_loadaddr = (uint32_t)num;
490			break;
491		case 'E':	/* ep (byte swapped) */
492		case 'e':	/* ep */
493			errno = 0;
494			num = strtoull(optarg, &ep, 0);
495			if (*ep != '\0' || (errno == ERANGE &&
496			    (num == ULLONG_MAX || num == 0)) ||
497			    ((signed long long)num != (int32_t)num &&
498			     num != (uint32_t)num))
499				errx(1, "illegal number -- %s", optarg);
500			image_entrypoint = (uint32_t)num;
501			if (ch == 'E')
502				image_entrypoint = bswap32(image_entrypoint);
503			break;
504		case 'f':	/* image format */
505			image_format = get_image_format(optarg);
506			break;
507		case 'm':	/* magic */
508			errno = 0;
509			num = strtoul(optarg, &ep, 0);
510			if (*ep != '\0' || (errno == ERANGE &&
511			    (num == ULONG_MAX || num == 0)))
512				errx(1, "illegal number -- %s", optarg);
513			image_magic = (uint32_t)num;
514			break;
515		case 'n':	/* name */
516			image_name = strdup(optarg);
517			break;
518		case 't':       /* FS timestamp */
519			repro_time = atoll(optarg);
520			break;
521		case 'u':	/* update image */
522			update_image = 1;
523			break;
524		case 'h':
525		default:
526			usage();
527			/* NOTREACHED */
528		}
529	}
530	argc -= optind;
531	argv += optind;
532
533	if (argc != 2)
534		usage();
535
536	if (image_entrypoint == 0)
537		image_entrypoint = image_loadaddr;
538
539	switch (image_format) {
540	case FMT_UIMG:
541		if (image_arch == IH_ARCH_UNKNOWN ||
542		    image_type == IH_TYPE_UNKNOWN ||
543		    image_name == NULL)
544			usage();
545			/* NOTREACHED */
546
547		switch (image_type) {
548		case IH_TYPE_SCRIPT:
549		case IH_TYPE_RAMDISK:
550		case IH_TYPE_KERNEL_NOLOAD:
551			break;
552		default:
553			if (image_loadaddr == 0)
554				usage();
555				/* NOTREACHED */
556			break;
557		}
558		break;
559
560	case FMT_ARM64:
561		if (image_arch != IH_ARCH_UNKNOWN &&
562		    image_arch != IH_ARCH_ARM64)
563			usage();
564			/* NOTREACHED */
565
566		break;
567
568	default:
569		usage();
570		/* NOTREACHED */
571	}
572
573	src = argv[0];
574	dest = argv[1];
575
576	kernel_fd = open(src, O_RDONLY);
577	if (kernel_fd == -1) {
578		perror("open kernel");
579		return EXIT_FAILURE;
580	}
581	image_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, 0666);
582	if (image_fd == -1) {
583		perror("open image");
584		return EXIT_FAILURE;
585	}
586
587	printf(" image type:  %s\n", get_image_format_name(image_format));
588
589	switch (image_format) {
590	case FMT_UIMG:
591		if (generate_header_uimg(&hdr_uimg, repro_time, kernel_fd) != 0)
592			return EXIT_FAILURE;
593
594		if (write_image(&hdr_uimg, sizeof(hdr_uimg),
595		    kernel_fd, image_fd) != 0)
596			return EXIT_FAILURE;
597
598		break;
599	case FMT_ARM64:
600		if (generate_header_arm64(&hdr_arm64, kernel_fd) != 0)
601			return EXIT_FAILURE;
602
603		if (write_image(&hdr_arm64, sizeof(hdr_arm64),
604		    kernel_fd, image_fd) != 0)
605			return EXIT_FAILURE;
606
607		break;
608	default:
609		break;
610	}
611
612	close(image_fd);
613	close(kernel_fd);
614
615	return EXIT_SUCCESS;
616}
617