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); +}