diff --git a/Makefile b/Makefile index 67d5833..a4fa649 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/fel.c b/fel.c index 741b83c..8268a1a 100644 --- a/fel.c +++ b/fel.c @@ -19,6 +19,7 @@ #include "portable_endian.h" #include "fel_lib.h" #include "fel-spiflash.h" +#include "fit_image.h" #include #include @@ -31,7 +32,7 @@ #include #include -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; @@ -893,8 +894,10 @@ static void aw_fel_write_uboot_image(feldev_handle *dev, uint8_t *buf, exit(1); } if (image_type == IH_TYPE_FLATDT) { /* FIT image */ - pr_error("FIT image not yet supported.\n"); - exit(1); + 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) @@ -968,6 +971,7 @@ void aw_fel_process_spl_and_uboot(feldev_handle *dev, const char *filename) /* 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 */ 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 + * + * 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 . + */ + +#include +#include +#include + +#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 + * + * 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 . + */ + +#ifndef __FIT_IMAGE_H__ +#define __FIT_IMAGE_H__ + +#include +#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