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.1 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    Error *err = NULL;
 182    const char *name;
 183    const void *data;
 184    const void *load_data;
 185    hwaddr load_addr;
 186    int img_off;
 187    size_t sz;
 188    int ret;
 189
 190    name = fdt_getprop(itb, cfg, "fdt", NULL);
 191    if (!name) {
 192        return 0;
 193    }
 194
 195    load_data = data = fit_load_image_alloc(itb, name, &img_off, &sz, errp);
 196    if (!data) {
 197        error_prepend(errp, "unable to load FDT image from FIT: ");
 198        return -EINVAL;
 199    }
 200
 201    ret = fit_image_addr(itb, img_off, "load", &load_addr, &err);
 202    if (ret == -ENOENT) {
 203        load_addr = ROUND_UP(kernel_end, 64 * KiB) + (10 * MiB);
 204        error_free(err);
 205    } else if (ret) {
 206        error_propagate_prepend(errp, err,
 207                                "unable to read FDT load address from FIT: ");
 208        goto out;
 209    }
 210
 211    if (ldr->fdt_filter) {
 212        load_data = ldr->fdt_filter(opaque, data, match_data, &load_addr);
 213    }
 214
 215    load_addr = ldr->addr_to_phys(opaque, load_addr);
 216    sz = fdt_totalsize(load_data);
 217    rom_add_blob_fixed(name, load_data, sz, load_addr);
 218
 219    ret = 0;
 220out:
 221    g_free((void *) data);
 222    if (data != load_data) {
 223        g_free((void *) load_data);
 224    }
 225    return ret;
 226}
 227
 228static bool fit_cfg_compatible(const void *itb, int cfg, const char *compat)
 229{
 230    const void *fdt;
 231    const char *fdt_name;
 232    bool ret;
 233
 234    fdt_name = fdt_getprop(itb, cfg, "fdt", NULL);
 235    if (!fdt_name) {
 236        return false;
 237    }
 238
 239    fdt = fit_load_image_alloc(itb, fdt_name, NULL, NULL, NULL);
 240    if (!fdt) {
 241        return false;
 242    }
 243
 244    if (fdt_check_header(fdt)) {
 245        ret = false;
 246        goto out;
 247    }
 248
 249    if (fdt_node_check_compatible(fdt, 0, compat)) {
 250        ret = false;
 251        goto out;
 252    }
 253
 254    ret = true;
 255out:
 256    g_free((void *) fdt);
 257    return ret;
 258}
 259
 260int load_fit(const struct fit_loader *ldr, const char *filename, void *opaque)
 261{
 262    Error *err = NULL;
 263    const struct fit_loader_match *match;
 264    const void *itb, *match_data = NULL;
 265    const char *def_cfg_name;
 266    char path[FIT_LOADER_MAX_PATH];
 267    int itb_size, configs, cfg_off, off;
 268    hwaddr kernel_end;
 269    int ret;
 270
 271    itb = load_device_tree(filename, &itb_size);
 272    if (!itb) {
 273        return -EINVAL;
 274    }
 275
 276    configs = fdt_path_offset(itb, "/configurations");
 277    if (configs < 0) {
 278        error_report("can't find node /configurations");
 279        ret = configs;
 280        goto out;
 281    }
 282
 283    cfg_off = -FDT_ERR_NOTFOUND;
 284
 285    if (ldr->matches) {
 286        for (match = ldr->matches; match->compatible; match++) {
 287            off = fdt_first_subnode(itb, configs);
 288            while (off >= 0) {
 289                if (fit_cfg_compatible(itb, off, match->compatible)) {
 290                    cfg_off = off;
 291                    match_data = match->data;
 292                    break;
 293                }
 294
 295                off = fdt_next_subnode(itb, off);
 296            }
 297
 298            if (cfg_off >= 0) {
 299                break;
 300            }
 301        }
 302    }
 303
 304    if (cfg_off < 0) {
 305        def_cfg_name = fdt_getprop(itb, configs, "default", NULL);
 306        if (def_cfg_name) {
 307            snprintf(path, sizeof(path), "/configurations/%s", def_cfg_name);
 308            cfg_off = fdt_path_offset(itb, path);
 309        }
 310    }
 311
 312    if (cfg_off < 0) {
 313        error_report("can't find configuration");
 314        ret = cfg_off;
 315        goto out;
 316    }
 317
 318    ret = fit_load_kernel(ldr, itb, cfg_off, opaque, &kernel_end, &err);
 319    if (ret) {
 320        error_report_err(err);
 321        goto out;
 322    }
 323
 324    ret = fit_load_fdt(ldr, itb, cfg_off, opaque, match_data, kernel_end,
 325                       &err);
 326    if (ret) {
 327        error_report_err(err);
 328        goto out;
 329    }
 330
 331    ret = 0;
 332out:
 333    g_free((void *) itb);
 334    return ret;
 335}
 336