qemu/hw/core/loader-fit.c
<<
>>
Prefs
   1/*
   2 * Flattened Image Tree loader.
   3 *
   4 * Copyright (c) 2016 Imagination Technologies
   5 *
   6 * This library is free software; you can redistribute it and/or
   7 * modify it under the terms of the GNU Lesser General Public
   8 * License as published by the Free Software Foundation; either
   9 * version 2 of the License, or (at your option) any later version.
  10 *
  11 * This library is distributed in the hope that it will be useful,
  12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14 * Lesser General Public License for more details.
  15 *
  16 * You should have received a copy of the GNU Lesser General Public
  17 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
  18 */
  19
  20#include "qemu/osdep.h"
  21#include "qapi/error.h"
  22#include "qemu/units.h"
  23#include "exec/memory.h"
  24#include "hw/loader.h"
  25#include "hw/loader-fit.h"
  26#include "qemu/cutils.h"
  27#include "qemu/error-report.h"
  28#include "sysemu/device_tree.h"
  29
  30#include <libfdt.h>
  31#include <zlib.h>
  32
  33#define FIT_LOADER_MAX_PATH (128)
  34
  35static const void *fit_load_image_alloc(const void *itb, const char *name,
  36                                        int *poff, size_t *psz, Error **errp)
  37{
  38    const void *data;
  39    const char *comp;
  40    void *uncomp_data;
  41    char path[FIT_LOADER_MAX_PATH];
  42    int off, sz;
  43    ssize_t uncomp_len;
  44
  45    snprintf(path, sizeof(path), "/images/%s", name);
  46
  47    off = fdt_path_offset(itb, path);
  48    if (off < 0) {
  49        error_setg(errp, "can't find node %s", path);
  50        return NULL;
  51    }
  52    if (poff) {
  53        *poff = off;
  54    }
  55
  56    data = fdt_getprop(itb, off, "data", &sz);
  57    if (!data) {
  58        error_setg(errp, "can't get %s/data", path);
  59        return NULL;
  60    }
  61
  62    comp = fdt_getprop(itb, off, "compression", NULL);
  63    if (!comp || !strcmp(comp, "none")) {
  64        if (psz) {
  65            *psz = sz;
  66        }
  67        uncomp_data = g_malloc(sz);
  68        memmove(uncomp_data, data, sz);
  69        return uncomp_data;
  70    }
  71
  72    if (!strcmp(comp, "gzip")) {
  73        uncomp_len = UBOOT_MAX_GUNZIP_BYTES;
  74        uncomp_data = g_malloc(uncomp_len);
  75
  76        uncomp_len = gunzip(uncomp_data, uncomp_len, (void *) data, sz);
  77        if (uncomp_len < 0) {
  78            error_setg(errp, "unable to decompress %s image", name);
  79            g_free(uncomp_data);
  80            return NULL;
  81        }
  82
  83        data = g_realloc(uncomp_data, uncomp_len);
  84        if (psz) {
  85            *psz = uncomp_len;
  86        }
  87        return data;
  88    }
  89
  90    error_setg(errp, "unknown compression '%s'", comp);
  91    return NULL;
  92}
  93
  94static int fit_image_addr(const void *itb, int img, const char *name,
  95                          hwaddr *addr, Error **errp)
  96{
  97    const void *prop;
  98    int len;
  99
 100    prop = fdt_getprop(itb, img, name, &len);
 101    if (!prop) {
 102        error_setg(errp, "can't find %s address", name);
 103        return -ENOENT;
 104    }
 105
 106    switch (len) {
 107    case 4:
 108        *addr = fdt32_to_cpu(*(fdt32_t *)prop);
 109        return 0;
 110    case 8:
 111        *addr = fdt64_to_cpu(*(fdt64_t *)prop);
 112        return 0;
 113    default:
 114        error_setg(errp, "invalid %s address length %d", name, len);
 115        return -EINVAL;
 116    }
 117}
 118
 119static int fit_load_kernel(const struct fit_loader *ldr, const void *itb,
 120                           int cfg, void *opaque, hwaddr *pend,
 121                           Error **errp)
 122{
 123    const char *name;
 124    const void *data;
 125    const void *load_data;
 126    hwaddr load_addr, entry_addr;
 127    int img_off, err;
 128    size_t sz;
 129    int ret;
 130
 131    name = fdt_getprop(itb, cfg, "kernel", NULL);
 132    if (!name) {
 133        error_setg(errp, "no kernel specified by FIT configuration");
 134        return -EINVAL;
 135    }
 136
 137    load_data = data = fit_load_image_alloc(itb, name, &img_off, &sz, errp);
 138    if (!data) {
 139        error_prepend(errp, "unable to load kernel image from FIT: ");
 140        return -EINVAL;
 141    }
 142
 143    err = fit_image_addr(itb, img_off, "load", &load_addr, errp);
 144    if (err) {
 145        error_prepend(errp, "unable to read kernel load address from FIT: ");
 146        ret = err;
 147        goto out;
 148    }
 149
 150    err = fit_image_addr(itb, img_off, "entry", &entry_addr, errp);
 151    if (err) {
 152        error_prepend(errp, "unable to read kernel entry address from FIT: ");
 153        ret = err;
 154        goto out;
 155    }
 156
 157    if (ldr->kernel_filter) {
 158        load_data = ldr->kernel_filter(opaque, data, &load_addr, &entry_addr);
 159    }
 160
 161    if (pend) {
 162        *pend = load_addr + sz;
 163    }
 164
 165    load_addr = ldr->addr_to_phys(opaque, load_addr);
 166    rom_add_blob_fixed(name, load_data, sz, load_addr);
 167
 168    ret = 0;
 169out:
 170    g_free((void *) data);
 171    if (data != load_data) {
 172        g_free((void *) load_data);
 173    }
 174    return ret;
 175}
 176
 177static int fit_load_fdt(const struct fit_loader *ldr, const void *itb,
 178                        int cfg, void *opaque, const void *match_data,
 179                        hwaddr kernel_end, Error **errp)
 180{
 181    const char *name;
 182    const void *data;
 183    const void *load_data;
 184    hwaddr load_addr;
 185    int img_off, err;
 186    size_t sz;
 187    int ret;
 188
 189    name = fdt_getprop(itb, cfg, "fdt", NULL);
 190    if (!name) {
 191        return 0;
 192    }
 193
 194    load_data = data = fit_load_image_alloc(itb, name, &img_off, &sz, errp);
 195    if (!data) {
 196        error_prepend(errp, "unable to load FDT image from FIT: ");
 197        return -EINVAL;
 198    }
 199
 200    err = fit_image_addr(itb, img_off, "load", &load_addr, errp);
 201    if (err == -ENOENT) {
 202        load_addr = ROUND_UP(kernel_end, 64 * KiB) + (10 * MiB);
 203        error_free(*errp);
 204    } else if (err) {
 205        error_prepend(errp, "unable to read FDT load address from FIT: ");
 206        ret = err;
 207        goto out;
 208    }
 209
 210    if (ldr->fdt_filter) {
 211        load_data = ldr->fdt_filter(opaque, data, match_data, &load_addr);
 212    }
 213
 214    load_addr = ldr->addr_to_phys(opaque, load_addr);
 215    sz = fdt_totalsize(load_data);
 216    rom_add_blob_fixed(name, load_data, sz, load_addr);
 217
 218    ret = 0;
 219out:
 220    g_free((void *) data);
 221    if (data != load_data) {
 222        g_free((void *) load_data);
 223    }
 224    return ret;
 225}
 226
 227static bool fit_cfg_compatible(const void *itb, int cfg, const char *compat)
 228{
 229    const void *fdt;
 230    const char *fdt_name;
 231    bool ret;
 232
 233    fdt_name = fdt_getprop(itb, cfg, "fdt", NULL);
 234    if (!fdt_name) {
 235        return false;
 236    }
 237
 238    fdt = fit_load_image_alloc(itb, fdt_name, NULL, NULL, NULL);
 239    if (!fdt) {
 240        return false;
 241    }
 242
 243    if (fdt_check_header(fdt)) {
 244        ret = false;
 245        goto out;
 246    }
 247
 248    if (fdt_node_check_compatible(fdt, 0, compat)) {
 249        ret = false;
 250        goto out;
 251    }
 252
 253    ret = true;
 254out:
 255    g_free((void *) fdt);
 256    return ret;
 257}
 258
 259int load_fit(const struct fit_loader *ldr, const char *filename, void *opaque)
 260{
 261    Error *err = NULL;
 262    const struct fit_loader_match *match;
 263    const void *itb, *match_data = NULL;
 264    const char *def_cfg_name;
 265    char path[FIT_LOADER_MAX_PATH];
 266    int itb_size, configs, cfg_off, off;
 267    hwaddr kernel_end;
 268    int ret;
 269
 270    itb = load_device_tree(filename, &itb_size);
 271    if (!itb) {
 272        return -EINVAL;
 273    }
 274
 275    configs = fdt_path_offset(itb, "/configurations");
 276    if (configs < 0) {
 277        error_report("can't find node /configurations");
 278        ret = configs;
 279        goto out;
 280    }
 281
 282    cfg_off = -FDT_ERR_NOTFOUND;
 283
 284    if (ldr->matches) {
 285        for (match = ldr->matches; match->compatible; match++) {
 286            off = fdt_first_subnode(itb, configs);
 287            while (off >= 0) {
 288                if (fit_cfg_compatible(itb, off, match->compatible)) {
 289                    cfg_off = off;
 290                    match_data = match->data;
 291                    break;
 292                }
 293
 294                off = fdt_next_subnode(itb, off);
 295            }
 296
 297            if (cfg_off >= 0) {
 298                break;
 299            }
 300        }
 301    }
 302
 303    if (cfg_off < 0) {
 304        def_cfg_name = fdt_getprop(itb, configs, "default", NULL);
 305        if (def_cfg_name) {
 306            snprintf(path, sizeof(path), "/configurations/%s", def_cfg_name);
 307            cfg_off = fdt_path_offset(itb, path);
 308        }
 309    }
 310
 311    if (cfg_off < 0) {
 312        error_report("can't find configuration");
 313        ret = cfg_off;
 314        goto out;
 315    }
 316
 317    ret = fit_load_kernel(ldr, itb, cfg_off, opaque, &kernel_end, &err);
 318    if (ret) {
 319        error_report_err(err);
 320        goto out;
 321    }
 322
 323    ret = fit_load_fdt(ldr, itb, cfg_off, opaque, match_data, kernel_end,
 324                       &err);
 325    if (ret) {
 326        error_report_err(err);
 327        goto out;
 328    }
 329
 330    ret = 0;
 331out:
 332    g_free((void *) itb);
 333    return ret;
 334}
 335