From ee87bfb933cdb595718bd8ddadf6552c3fa8895d Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Mon, 21 Feb 2022 17:52:58 +0000 Subject: [PATCH] Add support code for dealing with U-Boot uImages Adds a loader for the legacy uImage format that is commonly used on embedded Linux systems. It verifies checksums and supports uncompressed and gzipped images. Supports arbitrary reader functions to allow the images to be streamed off any storage device, for optimal RAM use. Change-Id: I93c35f9a6f323999a22a07300e05627fabfcbd2c --- firmware/SOURCES | 2 +- firmware/export/linuxboot.h | 189 +++++++++++++++++++++++++++++++ firmware/linuxboot.c | 218 ++++++++++++++++++++++++++++++++++++ 3 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 firmware/export/linuxboot.h create mode 100644 firmware/linuxboot.c diff --git a/firmware/SOURCES b/firmware/SOURCES index 4ea922af1b..2e2f13bbe9 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -33,6 +33,7 @@ logf.c #endif /* ROCKBOX_HAS_LOGF */ #if (CONFIG_PLATFORM & PLATFORM_NATIVE) load_code.c +linuxboot.c #ifdef RB_PROFILE profile.c #endif /* RB_PROFILE */ @@ -46,7 +47,6 @@ timer.c debug.c #endif /* PLATFORM_NATIVE */ panic.c - #if (CONFIG_PLATFORM & PLATFORM_HOSTED) && defined(BOOTFILE) target/hosted/rolo.c #endif diff --git a/firmware/export/linuxboot.h b/firmware/export/linuxboot.h new file mode 100644 index 0000000000..7dbc213012 --- /dev/null +++ b/firmware/export/linuxboot.h @@ -0,0 +1,189 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2022 Aidan MacDonald + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef __LINUXBOOT_H__ +#define __LINUXBOOT_H__ + +#include "system.h" +#include +#include + +/* + * From u-boot's include/image.h + * SPDX-License-Identifier: GPL-2.0+ + * (C) Copyright 2008 Semihalf + * (C) Copyright 2000-2005 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#define IH_MAGIC 0x27051956 +#define IH_NMLEN 32 + +enum +{ + IH_ARCH_INVALID, + IH_ARCH_ALPHA, + IH_ARCH_ARM, + IH_ARCH_I386, + IH_ARCH_IA64, + IH_ARCH_MIPS, + IH_ARCH_MIPS64, + IH_ARCH_PPC, + IH_ARCH_S390, + IH_ARCH_SH, + IH_ARCH_SPARC, + IH_ARCH_SPARC64, + IH_ARCH_M68K, + /* NOTE: Other archs not relevant and omitted here, can add if needed */ + IH_ARCH_COUNT, +}; + +enum +{ + IH_TYPE_INVALID = 0, + IN_TYPE_STANDALONE, + IH_TYPE_KERNEL, + IH_TYPE_RAMDISK, + IH_TYPE_MULTI, + IH_TYPE_FIRMWARE, + IH_TYPE_SCRIPT, + IH_TYPE_FILESYSTEM, + IH_TYPE_FLATDT, + /* NOTE: Other types not relevant and omitted here, can add if needed */ + IH_TYPE_COUNT, +}; + +enum +{ + IH_COMP_NONE = 0, + IH_COMP_GZIP, + IH_COMP_BZIP2, + IH_COMP_LZMA, + IH_COMP_LZO, + IH_COMP_LZ4, + IH_COMP_ZSTD, + IH_COMP_COUNT, +}; + +/* Legacy U-Boot image header as produced by mkimage(1). + * + * WARNING: all fields are big-endian so you usually do not want to + * access them directly, use the accessor functions instead. + */ +struct uimage_header +{ + uint32_t ih_magic; + uint32_t ih_hcrc; + uint32_t ih_time; + uint32_t ih_size; + uint32_t ih_load; + uint32_t ih_ep; + uint32_t ih_dcrc; + uint8_t ih_os; + uint8_t ih_arch; + uint8_t ih_type; + uint8_t ih_comp; + uint8_t ih_name[IH_NMLEN]; +}; + +#define _uimage_get32_f(name) \ + static inline uint32_t uimage_get_##name(const struct uimage_header* uh) \ + { return betoh32(uh->ih_##name); } +_uimage_get32_f(magic) +_uimage_get32_f(hcrc) +_uimage_get32_f(time) +_uimage_get32_f(size) +_uimage_get32_f(load) +_uimage_get32_f(ep) +_uimage_get32_f(dcrc) +#undef _uimage_get32_f + +#define _uimage_set32_f(name) \ + static inline void uimage_set_##name(struct uimage_header* uh, uint32_t val) \ + { uh->ih_##name = htobe32(val); } +_uimage_set32_f(magic) +_uimage_set32_f(hcrc) +_uimage_set32_f(time) +_uimage_set32_f(size) +_uimage_set32_f(load) +_uimage_set32_f(ep) +_uimage_set32_f(dcrc) +#undef _uimage_set32_f + +#define _uimage_get8_f(name) \ + static inline uint8_t uimage_get_##name(const struct uimage_header* uh) \ + { return uh->ih_##name; } +_uimage_get8_f(os) +_uimage_get8_f(arch) +_uimage_get8_f(type) +_uimage_get8_f(comp) +#undef _uimage_get8_f + +#define _uimage_set8_f(name) \ + static inline void uimage_set_##name(struct uimage_header* uh, uint8_t val) \ + { uh->ih_##name = val; } +_uimage_set8_f(os) +_uimage_set8_f(arch) +_uimage_set8_f(type) +_uimage_set8_f(comp) +#undef _uimage_set8_f + +/* + * uImage utilities + */ + +/** Reader callback for use with `uimage_load` + * + * \param buf Buffer to write into + * \param size Number of bytes to read + * \param ctx State argument + * \return Number of bytes actually read, or -1 on error. + */ +typedef ssize_t(*uimage_reader)(void* buf, size_t size, void* ctx); + +/** Calculate U-Boot style CRC */ +uint32_t uimage_crc(uint32_t crc, const void* data, size_t size); + +/** Calculate CRC of a uImage header */ +uint32_t uimage_calc_hcrc(const struct uimage_header* uh); + +/** Load and decompress a uImage + * + * \param uh Returned header struct (will be filled with read data) + * \param out_size Returned size of the decompressed data + * \param reader Data reader function + * \param rctx Context argument for the reader function + * \return Buflib handle containing the decompressed data, or negative on error + * + * This function will read a uImage, verify checksums and decompress the image + * data into a non-moveable buflib allocation. The length of the compressed + * data will be taken from the uImage header. + */ +int uimage_load(struct uimage_header* uh, size_t* out_size, + uimage_reader reader, void* rctx); + +/** File reader for use with `uimage_load` + * + * \param ctx File descriptor, casted to `void*` + */ +ssize_t uimage_fd_reader(void* buf, size_t size, void* ctx); + +#endif /* __LINUXBOOT_H__ */ diff --git a/firmware/linuxboot.c b/firmware/linuxboot.c new file mode 100644 index 0000000000..5b6ab314b3 --- /dev/null +++ b/firmware/linuxboot.c @@ -0,0 +1,218 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2022 Aidan MacDonald + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "linuxboot.h" +#include "system.h" +#include "core_alloc.h" +#include "crc32.h" +#include "inflate.h" +#include "file.h" +#include + +/* compression support options - can be decided per target if needed, + * for now default to enabling everything */ +#define HAVE_UIMAGE_COMP_NONE +#define HAVE_UIMAGE_COMP_GZIP + +uint32_t uimage_crc(uint32_t crc, const void* data, size_t size) +{ + /* is this endian swapping required...? */ + return letoh32(crc_32r(data, size, htole32(crc ^ 0xffffffff))) ^ 0xffffffff; +} + +uint32_t uimage_calc_hcrc(const struct uimage_header* uh) +{ + struct uimage_header h = *uh; + uimage_set_hcrc(&h, 0); + return uimage_crc(0, &h, sizeof(h)); +} + +static int uimage_check_header(const struct uimage_header* uh) +{ + if(uimage_get_magic(uh) != IH_MAGIC) + return -1; + + if(uimage_get_hcrc(uh) != uimage_calc_hcrc(uh)) + return -2; + + return 0; +} + +static int uimage_alloc_state(const struct uimage_header* uh) +{ + size_t size; + + switch(uimage_get_comp(uh)) { +#ifdef HAVE_UIMAGE_COMP_NONE + case IH_COMP_NONE: + return 0; +#endif + +#ifdef HAVE_UIMAGE_COMP_GZIP + case IH_COMP_GZIP: + size = inflate_size + inflate_align - 1; + return core_alloc_ex("inflate", size, &buflib_ops_locked); +#endif + + default: + return -1; + } +} + +#ifdef HAVE_UIMAGE_COMP_GZIP +struct uimage_inflatectx +{ + uimage_reader reader; + void* rctx; + uint32_t dcrc; + size_t remain; +}; + +static uint32_t uimage_inflate_reader(void* block, uint32_t block_size, void* ctx) +{ + struct uimage_inflatectx* c = ctx; + ssize_t len = c->reader(block, block_size, c->rctx); + if(len > 0) { + len = MIN(c->remain, (size_t)len); + c->remain -= len; + c->dcrc = uimage_crc(c->dcrc, block, len); + } + + return len; +} + +static int uimage_decompress_gzip(const struct uimage_header* uh, int state_h, + void* out, size_t* out_size, + uimage_reader reader, void* rctx) +{ + size_t hbufsz = inflate_size + inflate_align - 1; + void* hbuf = core_get_data(state_h); + ALIGN_BUFFER(hbuf, hbufsz, inflate_align); + + struct uimage_inflatectx r_ctx; + r_ctx.reader = reader; + r_ctx.rctx = rctx; + r_ctx.dcrc = 0; + r_ctx.remain = uimage_get_size(uh); + + struct inflate_bufferctx w_ctx; + w_ctx.buf = out; + w_ctx.end = out + *out_size; + + int ret = inflate(hbuf, INFLATE_GZIP, + uimage_inflate_reader, &r_ctx, + inflate_buffer_writer, &w_ctx); + if(ret) + return ret; + + if(r_ctx.remain > 0) + return -1; + if(r_ctx.dcrc != uimage_get_dcrc(uh)) + return -2; + + *out_size = w_ctx.end - w_ctx.buf; + return 0; +} +#endif /* HAVE_UIMAGE_COMP_GZIP */ + +static int uimage_decompress(const struct uimage_header* uh, int state_h, + void* out, size_t* out_size, + uimage_reader reader, void* rctx) +{ + size_t in_size = uimage_get_size(uh); + ssize_t len; + + switch(uimage_get_comp(uh)) { +#ifdef HAVE_UIMAGE_COMP_NONE + case IH_COMP_NONE: + if(*out_size < in_size) + return -2; + + len = reader(out, in_size, rctx); + if(len < 0 || (size_t)len != in_size) + return -3; + + if(uimage_crc(0, out, in_size) != uimage_get_dcrc(uh)) + return -4; + + *out_size = in_size; + break; +#endif + +#ifdef HAVE_UIMAGE_COMP_GZIP + case IH_COMP_GZIP: + return uimage_decompress_gzip(uh, state_h, out, out_size, reader, rctx); +#endif + + default: + return -1; + } + + return 0; +} + +int uimage_load(struct uimage_header* uh, size_t* out_size, + uimage_reader reader, void* rctx) +{ + if(reader(uh, sizeof(*uh), rctx) != (ssize_t)sizeof(*uh)) + return -1; /* read error */ + + int ret = uimage_check_header(uh); + if(ret) + return ret; + + int state_h = uimage_alloc_state(uh); + if(state_h < 0) + return state_h; + + *out_size = 0; + int out_h = core_alloc_maximum("uimage", out_size, &buflib_ops_locked); + if(out_h <= 0) { + ret = -1; + goto err; + } + + ret = uimage_decompress(uh, state_h, core_get_data(out_h), out_size, + reader, rctx); + if(ret) + goto err; + + core_shrink(out_h, core_get_data(out_h), *out_size); + ret = 0; + + err: + if(state_h > 0) + core_free(state_h); + if(out_h > 0) { + if(ret == 0) + ret = out_h; + else + core_free(out_h); + } + + return ret; +} + +ssize_t uimage_fd_reader(void* buf, size_t size, void* ctx) +{ + int fd = (intptr_t)ctx; + return read(fd, buf, size); +}