diff options
author | Chen-Yu Tsai <wens@csie.org> | 2021-03-15 15:15:09 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-15 15:15:09 +0300 |
commit | 6c02224448970ee3840e512cc99d5dcbeee9f2a0 (patch) | |
tree | aee5db8390e39ec4b5d48f917a6e333793e7fe59 | |
parent | 7a6a2221ad887e547fb29e3f8e35d0c5c581911c (diff) | |
parent | 14b3492e410ce0771a497219ac7eaeff3d615286 (diff) |
Merge pull request #152 from apritzel/fit
sunxi-fel FIT image support
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | fel.c | 49 | ||||
-rw-r--r-- | fel_lib.c | 10 | ||||
-rw-r--r-- | fel_lib.h | 4 | ||||
-rw-r--r-- | fit_image.c | 282 | ||||
-rw-r--r-- | fit_image.h | 32 |
6 files changed, 371 insertions, 10 deletions
@@ -137,9 +137,9 @@ SOC_INFO := soc_info.c soc_info.h FEL_LIB := fel_lib.c fel_lib.h SPI_FLASH:= fel-spiflash.c fel-spiflash.h fel-remotefunc-spi-data-transfer.h -sunxi-fel: fel.c thunks/fel-to-spl-thunk.h $(PROGRESS) $(SOC_INFO) $(FEL_LIB) $(SPI_FLASH) +sunxi-fel: fel.c fit_image.c thunks/fel-to-spl-thunk.h $(PROGRESS) $(SOC_INFO) $(FEL_LIB) $(SPI_FLASH) $(CC) $(HOST_CFLAGS) $(LIBUSB_CFLAGS) $(ZLIB_CFLAGS) $(LDFLAGS) -o $@ \ - $(filter %.c,$^) $(LIBS) $(LIBUSB_LIBS) $(ZLIB_LIBS) + $(filter %.c,$^) $(LIBS) $(LIBUSB_LIBS) $(ZLIB_LIBS) -lfdt sunxi-nand-part: nand-part-main.c nand-part.c nand-part-a10.h nand-part-a20.h $(CC) $(HOST_CFLAGS) -c -o nand-part-main.o nand-part-main.c @@ -19,6 +19,7 @@ #include "portable_endian.h" #include "fel_lib.h" #include "fel-spiflash.h" +#include "fit_image.h" #include <assert.h> #include <ctype.h> @@ -31,9 +32,10 @@ #include <zlib.h> #include <sys/stat.h> -static bool verbose = false; /* If set, makes the 'fel' tool more talkative */ +bool verbose = false; /* If set, makes the 'fel' tool more talkative */ static uint32_t uboot_entry = 0; /* entry point (address) of U-Boot */ static uint32_t uboot_size = 0; /* size of U-Boot binary */ +static bool enter_in_aarch64 = false; /* printf-style output, but only if "verbose" flag is active */ #define pr_info(...) \ @@ -45,6 +47,7 @@ static uint32_t uboot_size = 0; /* size of U-Boot binary */ #define IH_TYPE_INVALID 0 /* Invalid Image */ #define IH_TYPE_FIRMWARE 5 /* Firmware Image */ #define IH_TYPE_SCRIPT 6 /* Script file */ +#define IH_TYPE_FLATDT 8 /* DTB or FIT image */ #define IH_NMLEN 32 /* Image Name Length */ /* Additional error codes, newly introduced for get_image_type() */ @@ -90,6 +93,8 @@ int get_image_type(const uint8_t *buf, size_t len) if (len <= HEADER_SIZE) /* insufficient length/size */ return IH_TYPE_INVALID; + if (be32toh(hdr->ih_magic) == 0xd00dfeed) + return IH_TYPE_FLATDT; if (be32toh(hdr->ih_magic) != IH_MAGIC) /* signature mismatch */ return IH_TYPE_INVALID; /* For sunxi, we always expect ARM architecture here */ @@ -864,7 +869,8 @@ uint32_t aw_fel_write_and_execute_spl(feldev_handle *dev, uint8_t *buf, size_t l * address stored within the image header; and the function preserves the * U-Boot entry point (offset) and size values. */ -void aw_fel_write_uboot_image(feldev_handle *dev, uint8_t *buf, size_t len) +static void aw_fel_write_uboot_image(feldev_handle *dev, uint8_t *buf, + size_t len, const char *dt_name) { if (len <= HEADER_SIZE) return; /* Insufficient size (no actual data), just bail out */ @@ -887,6 +893,13 @@ void aw_fel_write_uboot_image(feldev_handle *dev, uint8_t *buf, size_t len) } exit(1); } + if (image_type == IH_TYPE_FLATDT) { /* FIT image */ + uboot_entry = load_fit_images(dev, buf, dt_name, + &enter_in_aarch64); + uboot_size = 4; /* dummy value to pass check below */ + return; + } + if (image_type != IH_TYPE_FIRMWARE) pr_fatal("U-Boot image type mismatch: " "expected IH_TYPE_FIRMWARE, got %02X\n", image_type); @@ -923,6 +936,28 @@ void aw_fel_write_uboot_image(feldev_handle *dev, uint8_t *buf, size_t len) uboot_size = data_size; } +static const char *spl_get_dtb_name(uint8_t *spl_buf) +{ + uint32_t dt_offset; + + if (memcmp(spl_buf + 4, "eGON.BT0", 8)) + return NULL; + + if (memcmp(spl_buf + 0x14, "SPL", 3)) + return NULL; + + if (spl_buf[0x17] < 0x2) /* only since v0.2 */ + return NULL; + + memcpy(&dt_offset, spl_buf + 0x20, 4); + dt_offset = le32toh(dt_offset); + + if (verbose) + printf("found DT name in SPL header: %s\n", spl_buf + dt_offset); + + return (char *)spl_buf + dt_offset; +} + /* * This function handles the common part of both "spl" and "uboot" commands. */ @@ -932,15 +967,18 @@ void aw_fel_process_spl_and_uboot(feldev_handle *dev, const char *filename) uint32_t offset; /* load file into memory buffer */ uint8_t *buf = load_file(filename, &size); + const char *dt_name = spl_get_dtb_name(buf); /* write and execute the SPL from the buffer */ offset = aw_fel_write_and_execute_spl(dev, buf, size); + /* check for optional main U-Boot binary (and transfer it, if applicable) */ if (size > offset) { /* U-Boot pads to at least 32KB */ if (offset < SPL_MIN_OFFSET) offset = SPL_MIN_OFFSET; - aw_fel_write_uboot_image(dev, buf + offset, size - offset); + aw_fel_write_uboot_image(dev, buf + offset, size - offset, + dt_name); } free(buf); } @@ -1405,7 +1443,10 @@ int main(int argc, char **argv) /* auto-start U-Boot if requested (by the "uboot" command) */ if (uboot_autostart) { pr_info("Starting U-Boot (0x%08X).\n", uboot_entry); - aw_fel_execute(handle, uboot_entry); + if (enter_in_aarch64) + aw_rmr_request(handle, uboot_entry, true); + else + aw_fel_execute(handle, uboot_entry); } feldev_done(handle); @@ -214,8 +214,11 @@ void aw_fel_read(feldev_handle *dev, uint32_t offset, void *buf, size_t len) } /* AW_FEL_1_WRITE request */ -void aw_fel_write(feldev_handle *dev, void *buf, uint32_t offset, size_t len) +void aw_fel_write(feldev_handle *dev, const void *buf, uint32_t offset, size_t len) { + if (len == 0) + return; + aw_send_fel_request(dev, AW_FEL_1_WRITE, offset, len); aw_usb_write(dev, buf, len, false); aw_read_fel_status(dev); @@ -233,9 +236,12 @@ void aw_fel_execute(feldev_handle *dev, uint32_t offset) * Unlike aw_fel_write() above - which is reserved for internal use - this * routine optionally allows progress callbacks. */ -void aw_fel_write_buffer(feldev_handle *dev, void *buf, uint32_t offset, +void aw_fel_write_buffer(feldev_handle *dev, const void *buf, uint32_t offset, size_t len, bool progress) { + if (len == 0) + return; + aw_send_fel_request(dev, AW_FEL_1_WRITE, offset, len); aw_usb_write(dev, buf, len, progress); aw_read_fel_status(dev); @@ -59,8 +59,8 @@ feldev_list_entry *list_fel_devices(size_t *count); /* FEL functions */ void aw_fel_read(feldev_handle *dev, uint32_t offset, void *buf, size_t len); -void aw_fel_write(feldev_handle *dev, void *buf, uint32_t offset, size_t len); -void aw_fel_write_buffer(feldev_handle *dev, void *buf, uint32_t offset, +void aw_fel_write(feldev_handle *dev, const void *buf, uint32_t offset, size_t len); +void aw_fel_write_buffer(feldev_handle *dev, const void *buf, uint32_t offset, size_t len, bool progress); void aw_fel_execute(feldev_handle *dev, uint32_t offset); diff --git a/fit_image.c b/fit_image.c new file mode 100644 index 0000000..899e868 --- /dev/null +++ b/fit_image.c @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2018-2020 Andre Przywara <osp@andrep.de> + * + * 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; under version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdint.h> +#include <libfdt.h> + +#include "common.h" +#include "fel_lib.h" +#include "fit_image.h" + +/* defined in fel.c */ +extern bool verbose; + +#define IH_ARCH_INVALID 0 +#define IH_ARCH_ARM 2 +#define IH_ARCH_ARM64 22 + +#define IH_OS_INVALID 0 +#define IH_OS_LINUX 5 +#define IH_OS_U_BOOT 17 +#define IH_OS_ARM_TRUSTED_FIRMWARE 25 +#define IH_OS_EFI 28 + +struct fit_image_info { + const char *description; + const char *data; + uint32_t data_size; + uint32_t load_addr; + uint32_t entry_point; + uint8_t os; + uint8_t arch; +}; + +static int fit_parse_os(const char *value) +{ + if (!value || !*value) + return IH_OS_INVALID; + + if (!strcmp(value, "u-boot")) + return IH_OS_U_BOOT; + if (!strcmp(value, "linux")) + return IH_OS_LINUX; + if (!strcmp(value, "arm-trusted-firmware")) + return IH_OS_ARM_TRUSTED_FIRMWARE; + if (!strcmp(value, "efi")) + return IH_OS_EFI; + + return IH_OS_INVALID; +} + +static int fit_parse_arch(const char *value) +{ + if (!value || !*value) + return IH_ARCH_INVALID; + + if (!strcmp(value, "arm")) + return IH_ARCH_ARM; + if (!strcmp(value, "arm64")) + return IH_ARCH_ARM64; + + return IH_ARCH_INVALID; +} + +static uint32_t fdt_getprop_u32(const void *fdt, int node, const char *name) +{ + const struct fdt_property *prop; + + prop = fdt_get_property(fdt, node, name, NULL); + if (!prop) + return ~0U; + + return be32toh(*(uint32_t *)prop->data); +} + +static const char *fdt_getprop_str(const void *fdt, int node, const char *name) +{ + const struct fdt_property *prop; + + prop = fdt_get_property(fdt, node, name, NULL); + if (!prop) + return NULL; + + return prop->data; +} + +/* + * Find the image with the given name under the /images node, and parse + * its information into the fit_image_info struct. + * Returns 0 on success, and a negative error value otherwise. + */ +static int fit_get_image_info(const void *fit, const char *name, + struct fit_image_info *info) +{ + int node; + const char *str; + uint32_t data_offset; + + node = fdt_path_offset(fit, "/images"); + if (node < 0) + return -1; + node = fdt_subnode_offset(fit, node, name); + if (node < 0) + return -1; + + info->load_addr = fdt_getprop_u32(fit, node, "load"); + info->entry_point = fdt_getprop_u32(fit, node, "entry"); + info->description = fdt_getprop_str(fit, node, "description"); + /* properties used for FIT images with external data */ + info->data_size = fdt_getprop_u32(fit, node, "data-size"); + data_offset = fdt_getprop_u32(fit, node, "data-offset"); + + /* check for embedded data (when invalid external data properties) */ + if (info->data_size == ~0U || data_offset == ~0U) { + const struct fdt_property *prop; + int len; + + prop = fdt_get_property(fit, node, "data", &len); + info->data_size = len; + info->data = prop->data; + } else { + /* external data is appended at the end of the FIT DTB blob */ + info->data = (const char *)fit + ((fdt_totalsize(fit) + 3) & ~3U); + info->data += data_offset; + } + + info->os = fit_parse_os(fdt_getprop_str(fit, node, "os")); + info->arch = fit_parse_arch(fdt_getprop_str(fit, node, "arch")); + + str = fdt_getprop_str(fit, node, "compression"); + /* The current SPL does not support compression either. */ + if (str && strcmp(str, "none")) { + printf("compression \"%s\" not supported for image \"%s\"\n", + str, info->description); + return -2; + } + + return 0; +} + +static int entry_arch; +static uint32_t dtb_addr; + +/* + * Upload the image described by its fit_image_info struct to the board. + * Detect if an image contains an entry point and return that. + * Set entry_arch to arm or arm64 on the way. Also detect the image + * containing U-Boot and record its end address, so that the DTB can be + * appended later on. + * Returns the entry point if any is specified, or 0 otherwise. + */ +static uint32_t fit_load_image(feldev_handle *dev, struct fit_image_info *img) +{ + uint32_t ret = 0; + + if (verbose) + printf("loading image \"%s\" (%d bytes) to 0x%x\n", + img->description, img->data_size, img->load_addr); + aw_fel_write_buffer(dev, img->data, + img->load_addr, img->data_size, true); + + if (img->entry_point != ~0U) { + ret = img->entry_point; + entry_arch = img->arch; + } + /* either explicitly marked as U-Boot, or the first invalid one */ + if (img->os == IH_OS_U_BOOT || + (!dtb_addr && img->os == IH_OS_INVALID)) + dtb_addr = img->load_addr + img->data_size; + + return ret; +} + +uint32_t load_fit_images(feldev_handle *dev, const void *fit, + const char *dt_name, bool *use_aarch64) +{ + const struct fdt_property *prop; + struct fit_image_info img; + const char *str; + int node, len; + uint32_t entry_point = 0; + + node = fdt_path_offset(fit, "/configurations"); + if (node < 0) { + pr_error("invalid FIT image, no /configurations node\n"); + return 0; + } + + /* + * Find the right configuration node, either by using the provided + * DT name as an identifier, falling back to the node titled "default", + * or by using just the first node. + */ + if (dt_name) { + for (node = fdt_first_subnode(fit, node); + node >= 0; + node = fdt_next_subnode(fit, node)) { + prop = fdt_get_property(fit, node, "description", NULL); + if (prop && !strcmp(prop->data, dt_name)) + break; + } + if (node < 0) { + pr_error("no matching FIT configuration node for \"%s\"\n", + dt_name); + return 0; + } + } else { + prop = fdt_get_property(fit, node, "default", NULL); + if (!prop) + node = fdt_first_subnode(fit, node); + else + node = fdt_subnode_offset(fit, node, prop->data); + if (node < 0) { + pr_error("no default FIT configuration node\n"); + return 0; + } + } + + entry_arch = IH_ARCH_INVALID; + dtb_addr = 0; + + /* Load the image described as "firmware". */ + str = fdt_getprop_str(fit, node, "firmware"); + if (str && !fit_get_image_info(fit, str, &img)) { + uint32_t addr = fit_load_image(dev, &img); + + if (addr != 0) + entry_point = addr; + } else { + printf("WARNING: no valid \"firmware\" image entry in FIT\n"); + } + + /* load all loadables, at their respective load addresses */ + prop = fdt_get_property(fit, node, "loadables", &len); + for (str = prop ? prop->data : NULL; + prop && (str - prop->data) < len && *str; + str += strlen(str) + 1) { + uint32_t addr; + + if (fit_get_image_info(fit, str, &img)) { + printf("Can't load loadable \"%s\", skipping.\n", str); + continue; + } + addr = fit_load_image(dev, &img); + if (addr != 0) + entry_point = addr; + } + + if (use_aarch64) + *use_aarch64 = (entry_arch == IH_ARCH_ARM64); + + if (!dtb_addr) { + printf("Warning: no U-Boot image found, not loading DTB\n"); + return entry_point; + } + + /* load .dtb right after the U-Boot image (appended DTB) */ + str = fdt_getprop_str(fit, node, "fdt"); + if (!str || fit_get_image_info(fit, str, &img)) { + printf("Warning: no FDT found in FIT image\n"); + return entry_point; + } + if (verbose) + printf("loading DTB \"%s\" (%d bytes)\n", img.description, + img.data_size); + aw_fel_write_buffer(dev, img.data, dtb_addr, img.data_size, false); + + return entry_point; +} diff --git a/fit_image.h b/fit_image.h new file mode 100644 index 0000000..30813a3 --- /dev/null +++ b/fit_image.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2020 Andre Przywara <osp@andrep.de> + * + * 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; under version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __FIT_IMAGE_H__ +#define __FIT_IMAGE_H__ + +#include <stdint.h> +#include "fel_lib.h" + +/* + * Load all images referenced in the given U-Boot FIT image. @dt_name will + * be used to select one of the configurations. @use_aarch64 contains the + * target architecture of the entry point. + * Returns the entry point address of the image to be started. + */ +uint32_t load_fit_images(feldev_handle *dev, const void *fit, + const char *dt_name, bool *use_aarch64); + +#endif |