/* * 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.1 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 . */ #include "qemu/osdep.h" #include "qapi/error.h" #include "qemu/units.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 #include #define FIT_LOADER_MAX_PATH (128) static const void *fit_load_image_alloc(const void *itb, const char *name, int *poff, size_t *psz, Error **errp) { 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) { error_setg(errp, "can't find node %s", path); return NULL; } if (poff) { *poff = off; } data = fdt_getprop(itb, off, "data", &sz); if (!data) { error_setg(errp, "can't get %s/data", path); 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_setg(errp, "unable to decompress %s image", name); g_free(uncomp_data); return NULL; } data = g_realloc(uncomp_data, uncomp_len); if (psz) { *psz = uncomp_len; } return data; } error_setg(errp, "unknown compression '%s'", comp); return NULL; } static int fit_image_addr(const void *itb, int img, const char *name, hwaddr *addr, Error **errp) { const void *prop; int len; prop = fdt_getprop(itb, img, name, &len); if (!prop) { error_setg(errp, "can't find %s address", name); 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_setg(errp, "invalid %s address length %d", name, len); return -EINVAL; } } static int fit_load_kernel(const struct fit_loader *ldr, const void *itb, int cfg, void *opaque, hwaddr *pend, Error **errp) { 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_setg(errp, "no kernel specified by FIT configuration"); return -EINVAL; } load_data = data = fit_load_image_alloc(itb, name, &img_off, &sz, errp); if (!data) { error_prepend(errp, "unable to load kernel image from FIT: "); return -EINVAL; } err = fit_image_addr(itb, img_off, "load", &load_addr, errp); if (err) { error_prepend(errp, "unable to read kernel load address from FIT: "); ret = err; goto out; } err = fit_image_addr(itb, img_off, "entry", &entry_addr, errp); if (err) { error_prepend(errp, "unable to read kernel entry address from FIT: "); 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, Error **errp) { Error *err = NULL; const char *name; const void *data; const void *load_data; hwaddr load_addr; int img_off; 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, errp); if (!data) { error_prepend(errp, "unable to load FDT image from FIT: "); return -EINVAL; } ret = fit_image_addr(itb, img_off, "load", &load_addr, &err); if (ret == -ENOENT) { load_addr = ROUND_UP(kernel_end, 64 * KiB) + (10 * MiB); error_free(err); } else if (ret) { error_propagate_prepend(errp, err, "unable to read FDT load address from FIT: "); 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, 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) { Error *err = NULL; 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; 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) { error_report("can't find node /configurations"); 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) { error_report("can't find configuration"); ret = cfg_off; goto out; } ret = fit_load_kernel(ldr, itb, cfg_off, opaque, &kernel_end, &err); if (ret) { error_report_err(err); goto out; } ret = fit_load_fdt(ldr, itb, cfg_off, opaque, match_data, kernel_end, &err); if (ret) { error_report_err(err); goto out; } ret = 0; out: g_free((void *) itb); return ret; }