summaryrefslogtreecommitdiff
path: root/hw/core
diff options
context:
space:
mode:
Diffstat (limited to 'hw/core')
-rw-r--r--hw/core/Makefile.objs1
-rw-r--r--hw/core/loader-fit.c325
-rw-r--r--hw/core/loader.c7
3 files changed, 327 insertions, 6 deletions
diff --git a/hw/core/Makefile.objs b/hw/core/Makefile.objs
index 7f8c9dc659..91450b2eab 100644
--- a/hw/core/Makefile.objs
+++ b/hw/core/Makefile.objs
@@ -13,6 +13,7 @@ common-obj-$(CONFIG_PTIMER) += ptimer.o
common-obj-$(CONFIG_SOFTMMU) += sysbus.o
common-obj-$(CONFIG_SOFTMMU) += machine.o
common-obj-$(CONFIG_SOFTMMU) += loader.o
+common-obj-$(CONFIG_FITLOADER) += loader-fit.o
common-obj-$(CONFIG_SOFTMMU) += qdev-properties-system.o
common-obj-$(CONFIG_SOFTMMU) += register.o
common-obj-$(CONFIG_SOFTMMU) += or-irq.o
diff --git a/hw/core/loader-fit.c b/hw/core/loader-fit.c
new file mode 100644
index 0000000000..0c4a7207f4
--- /dev/null
+++ b/hw/core/loader-fit.c
@@ -0,0 +1,325 @@
+/*
+ * Flattened Image Tree loader.
+ *
+ * Copyright (c) 2016 Imagination Technologies
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "exec/address-spaces.h"
+#include "exec/memory.h"
+#include "hw/loader.h"
+#include "hw/loader-fit.h"
+#include "qemu/cutils.h"
+#include "qemu/error-report.h"
+#include "sysemu/device_tree.h"
+#include "sysemu/sysemu.h"
+
+#include <libfdt.h>
+#include <zlib.h>
+
+#define FIT_LOADER_MAX_PATH (128)
+
+static const void *fit_load_image_alloc(const void *itb, const char *name,
+ int *poff, size_t *psz)
+{
+ const void *data;
+ const char *comp;
+ void *uncomp_data;
+ char path[FIT_LOADER_MAX_PATH];
+ int off, sz;
+ ssize_t uncomp_len;
+
+ snprintf(path, sizeof(path), "/images/%s", name);
+
+ off = fdt_path_offset(itb, path);
+ if (off < 0) {
+ return NULL;
+ }
+ if (poff) {
+ *poff = off;
+ }
+
+ data = fdt_getprop(itb, off, "data", &sz);
+ if (!data) {
+ return NULL;
+ }
+
+ comp = fdt_getprop(itb, off, "compression", NULL);
+ if (!comp || !strcmp(comp, "none")) {
+ if (psz) {
+ *psz = sz;
+ }
+ uncomp_data = g_malloc(sz);
+ memmove(uncomp_data, data, sz);
+ return uncomp_data;
+ }
+
+ if (!strcmp(comp, "gzip")) {
+ uncomp_len = UBOOT_MAX_GUNZIP_BYTES;
+ uncomp_data = g_malloc(uncomp_len);
+
+ uncomp_len = gunzip(uncomp_data, uncomp_len, (void *) data, sz);
+ if (uncomp_len < 0) {
+ error_printf("unable to decompress %s image\n", name);
+ g_free(uncomp_data);
+ return NULL;
+ }
+
+ data = g_realloc(uncomp_data, uncomp_len);
+ if (psz) {
+ *psz = uncomp_len;
+ }
+ return data;
+ }
+
+ error_printf("unknown compression '%s'\n", comp);
+ return NULL;
+}
+
+static int fit_image_addr(const void *itb, int img, const char *name,
+ hwaddr *addr)
+{
+ const void *prop;
+ int len;
+
+ prop = fdt_getprop(itb, img, name, &len);
+ if (!prop) {
+ return -ENOENT;
+ }
+
+ switch (len) {
+ case 4:
+ *addr = fdt32_to_cpu(*(fdt32_t *)prop);
+ return 0;
+ case 8:
+ *addr = fdt64_to_cpu(*(fdt64_t *)prop);
+ return 0;
+ default:
+ error_printf("invalid %s address length %d\n", name, len);
+ return -EINVAL;
+ }
+}
+
+static int fit_load_kernel(const struct fit_loader *ldr, const void *itb,
+ int cfg, void *opaque, hwaddr *pend)
+{
+ const char *name;
+ const void *data;
+ const void *load_data;
+ hwaddr load_addr, entry_addr;
+ int img_off, err;
+ size_t sz;
+ int ret;
+
+ name = fdt_getprop(itb, cfg, "kernel", NULL);
+ if (!name) {
+ error_printf("no kernel specified by FIT configuration\n");
+ return -EINVAL;
+ }
+
+ load_data = data = fit_load_image_alloc(itb, name, &img_off, &sz);
+ if (!data) {
+ error_printf("unable to load kernel image from FIT\n");
+ return -EINVAL;
+ }
+
+ err = fit_image_addr(itb, img_off, "load", &load_addr);
+ if (err) {
+ error_printf("unable to read kernel load address from FIT\n");
+ ret = err;
+ goto out;
+ }
+
+ err = fit_image_addr(itb, img_off, "entry", &entry_addr);
+ if (err) {
+ error_printf("unable to read kernel entry address from FIT\n");
+ ret = err;
+ goto out;
+ }
+
+ if (ldr->kernel_filter) {
+ load_data = ldr->kernel_filter(opaque, data, &load_addr, &entry_addr);
+ }
+
+ if (pend) {
+ *pend = load_addr + sz;
+ }
+
+ load_addr = ldr->addr_to_phys(opaque, load_addr);
+ rom_add_blob_fixed(name, load_data, sz, load_addr);
+
+ ret = 0;
+out:
+ g_free((void *) data);
+ if (data != load_data) {
+ g_free((void *) load_data);
+ }
+ return ret;
+}
+
+static int fit_load_fdt(const struct fit_loader *ldr, const void *itb,
+ int cfg, void *opaque, const void *match_data,
+ hwaddr kernel_end)
+{
+ const char *name;
+ const void *data;
+ const void *load_data;
+ hwaddr load_addr;
+ int img_off, err;
+ size_t sz;
+ int ret;
+
+ name = fdt_getprop(itb, cfg, "fdt", NULL);
+ if (!name) {
+ return 0;
+ }
+
+ load_data = data = fit_load_image_alloc(itb, name, &img_off, &sz);
+ if (!data) {
+ error_printf("unable to load FDT image from FIT\n");
+ return -EINVAL;
+ }
+
+ err = fit_image_addr(itb, img_off, "load", &load_addr);
+ if (err == -ENOENT) {
+ load_addr = ROUND_UP(kernel_end, 64 * K_BYTE) + (10 * M_BYTE);
+ } else if (err) {
+ ret = err;
+ goto out;
+ }
+
+ if (ldr->fdt_filter) {
+ load_data = ldr->fdt_filter(opaque, data, match_data, &load_addr);
+ }
+
+ load_addr = ldr->addr_to_phys(opaque, load_addr);
+ sz = fdt_totalsize(load_data);
+ rom_add_blob_fixed(name, load_data, sz, load_addr);
+
+ ret = 0;
+out:
+ g_free((void *) data);
+ if (data != load_data) {
+ g_free((void *) load_data);
+ }
+ return ret;
+}
+
+static bool fit_cfg_compatible(const void *itb, int cfg, const char *compat)
+{
+ const void *fdt;
+ const char *fdt_name;
+ bool ret;
+
+ fdt_name = fdt_getprop(itb, cfg, "fdt", NULL);
+ if (!fdt_name) {
+ return false;
+ }
+
+ fdt = fit_load_image_alloc(itb, fdt_name, NULL, NULL);
+ if (!fdt) {
+ return false;
+ }
+
+ if (fdt_check_header(fdt)) {
+ ret = false;
+ goto out;
+ }
+
+ if (fdt_node_check_compatible(fdt, 0, compat)) {
+ ret = false;
+ goto out;
+ }
+
+ ret = true;
+out:
+ g_free((void *) fdt);
+ return ret;
+}
+
+int load_fit(const struct fit_loader *ldr, const char *filename, void *opaque)
+{
+ const struct fit_loader_match *match;
+ const void *itb, *match_data = NULL;
+ const char *def_cfg_name;
+ char path[FIT_LOADER_MAX_PATH];
+ int itb_size, configs, cfg_off, off, err;
+ hwaddr kernel_end;
+ int ret;
+
+ itb = load_device_tree(filename, &itb_size);
+ if (!itb) {
+ return -EINVAL;
+ }
+
+ configs = fdt_path_offset(itb, "/configurations");
+ if (configs < 0) {
+ ret = configs;
+ goto out;
+ }
+
+ cfg_off = -FDT_ERR_NOTFOUND;
+
+ if (ldr->matches) {
+ for (match = ldr->matches; match->compatible; match++) {
+ off = fdt_first_subnode(itb, configs);
+ while (off >= 0) {
+ if (fit_cfg_compatible(itb, off, match->compatible)) {
+ cfg_off = off;
+ match_data = match->data;
+ break;
+ }
+
+ off = fdt_next_subnode(itb, off);
+ }
+
+ if (cfg_off >= 0) {
+ break;
+ }
+ }
+ }
+
+ if (cfg_off < 0) {
+ def_cfg_name = fdt_getprop(itb, configs, "default", NULL);
+ if (def_cfg_name) {
+ snprintf(path, sizeof(path), "/configurations/%s", def_cfg_name);
+ cfg_off = fdt_path_offset(itb, path);
+ }
+ }
+
+ if (cfg_off < 0) {
+ /* couldn't find a configuration to use */
+ ret = cfg_off;
+ goto out;
+ }
+
+ err = fit_load_kernel(ldr, itb, cfg_off, opaque, &kernel_end);
+ if (err) {
+ ret = err;
+ goto out;
+ }
+
+ err = fit_load_fdt(ldr, itb, cfg_off, opaque, match_data, kernel_end);
+ if (err) {
+ ret = err;
+ goto out;
+ }
+
+ ret = 0;
+out:
+ g_free((void *) itb);
+ return ret;
+}
diff --git a/hw/core/loader.c b/hw/core/loader.c
index ee5abd6eb7..8b980e91fb 100644
--- a/hw/core/loader.c
+++ b/hw/core/loader.c
@@ -527,12 +527,7 @@ static void zfree(void *x, void *addr)
#define DEFLATED 8
-/* This is the usual maximum in uboot, so if a uImage overflows this, it would
- * overflow on real hardware too. */
-#define UBOOT_MAX_GUNZIP_BYTES (64 << 20)
-
-static ssize_t gunzip(void *dst, size_t dstlen, uint8_t *src,
- size_t srclen)
+ssize_t gunzip(void *dst, size_t dstlen, uint8_t *src, size_t srclen)
{
z_stream s;
ssize_t dstbytes;