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 "qemu/units.h" 22 #include "exec/memory.h" 23 #include "hw/loader.h" 24 #include "hw/loader-fit.h" 25 #include "qemu/cutils.h" 26 #include "qemu/error-report.h" 27 #include "sysemu/device_tree.h" 28 #include "sysemu/sysemu.h" 29 30 #include <libfdt.h> 31 #include <zlib.h> 32 33 #define FIT_LOADER_MAX_PATH (128) 34 35 static const void *fit_load_image_alloc(const void *itb, const char *name, 36 int *poff, size_t *psz) 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 return NULL; 50 } 51 if (poff) { 52 *poff = off; 53 } 54 55 data = fdt_getprop(itb, off, "data", &sz); 56 if (!data) { 57 return NULL; 58 } 59 60 comp = fdt_getprop(itb, off, "compression", NULL); 61 if (!comp || !strcmp(comp, "none")) { 62 if (psz) { 63 *psz = sz; 64 } 65 uncomp_data = g_malloc(sz); 66 memmove(uncomp_data, data, sz); 67 return uncomp_data; 68 } 69 70 if (!strcmp(comp, "gzip")) { 71 uncomp_len = UBOOT_MAX_GUNZIP_BYTES; 72 uncomp_data = g_malloc(uncomp_len); 73 74 uncomp_len = gunzip(uncomp_data, uncomp_len, (void *) data, sz); 75 if (uncomp_len < 0) { 76 error_printf("unable to decompress %s image\n", name); 77 g_free(uncomp_data); 78 return NULL; 79 } 80 81 data = g_realloc(uncomp_data, uncomp_len); 82 if (psz) { 83 *psz = uncomp_len; 84 } 85 return data; 86 } 87 88 error_printf("unknown compression '%s'\n", comp); 89 return NULL; 90 } 91 92 static int fit_image_addr(const void *itb, int img, const char *name, 93 hwaddr *addr) 94 { 95 const void *prop; 96 int len; 97 98 prop = fdt_getprop(itb, img, name, &len); 99 if (!prop) { 100 return -ENOENT; 101 } 102 103 switch (len) { 104 case 4: 105 *addr = fdt32_to_cpu(*(fdt32_t *)prop); 106 return 0; 107 case 8: 108 *addr = fdt64_to_cpu(*(fdt64_t *)prop); 109 return 0; 110 default: 111 error_printf("invalid %s address length %d\n", name, len); 112 return -EINVAL; 113 } 114 } 115 116 static int fit_load_kernel(const struct fit_loader *ldr, const void *itb, 117 int cfg, void *opaque, hwaddr *pend) 118 { 119 const char *name; 120 const void *data; 121 const void *load_data; 122 hwaddr load_addr, entry_addr; 123 int img_off, err; 124 size_t sz; 125 int ret; 126 127 name = fdt_getprop(itb, cfg, "kernel", NULL); 128 if (!name) { 129 error_printf("no kernel specified by FIT configuration\n"); 130 return -EINVAL; 131 } 132 133 load_data = data = fit_load_image_alloc(itb, name, &img_off, &sz); 134 if (!data) { 135 error_printf("unable to load kernel image from FIT\n"); 136 return -EINVAL; 137 } 138 139 err = fit_image_addr(itb, img_off, "load", &load_addr); 140 if (err) { 141 error_printf("unable to read kernel load address from FIT\n"); 142 ret = err; 143 goto out; 144 } 145 146 err = fit_image_addr(itb, img_off, "entry", &entry_addr); 147 if (err) { 148 error_printf("unable to read kernel entry address from FIT\n"); 149 ret = err; 150 goto out; 151 } 152 153 if (ldr->kernel_filter) { 154 load_data = ldr->kernel_filter(opaque, data, &load_addr, &entry_addr); 155 } 156 157 if (pend) { 158 *pend = load_addr + sz; 159 } 160 161 load_addr = ldr->addr_to_phys(opaque, load_addr); 162 rom_add_blob_fixed(name, load_data, sz, load_addr); 163 164 ret = 0; 165 out: 166 g_free((void *) data); 167 if (data != load_data) { 168 g_free((void *) load_data); 169 } 170 return ret; 171 } 172 173 static int fit_load_fdt(const struct fit_loader *ldr, const void *itb, 174 int cfg, void *opaque, const void *match_data, 175 hwaddr kernel_end) 176 { 177 const char *name; 178 const void *data; 179 const void *load_data; 180 hwaddr load_addr; 181 int img_off, err; 182 size_t sz; 183 int ret; 184 185 name = fdt_getprop(itb, cfg, "fdt", NULL); 186 if (!name) { 187 return 0; 188 } 189 190 load_data = data = fit_load_image_alloc(itb, name, &img_off, &sz); 191 if (!data) { 192 error_printf("unable to load FDT image from FIT\n"); 193 return -EINVAL; 194 } 195 196 err = fit_image_addr(itb, img_off, "load", &load_addr); 197 if (err == -ENOENT) { 198 load_addr = ROUND_UP(kernel_end, 64 * KiB) + (10 * MiB); 199 } else if (err) { 200 ret = err; 201 goto out; 202 } 203 204 if (ldr->fdt_filter) { 205 load_data = ldr->fdt_filter(opaque, data, match_data, &load_addr); 206 } 207 208 load_addr = ldr->addr_to_phys(opaque, load_addr); 209 sz = fdt_totalsize(load_data); 210 rom_add_blob_fixed(name, load_data, sz, load_addr); 211 212 ret = 0; 213 out: 214 g_free((void *) data); 215 if (data != load_data) { 216 g_free((void *) load_data); 217 } 218 return ret; 219 } 220 221 static bool fit_cfg_compatible(const void *itb, int cfg, const char *compat) 222 { 223 const void *fdt; 224 const char *fdt_name; 225 bool ret; 226 227 fdt_name = fdt_getprop(itb, cfg, "fdt", NULL); 228 if (!fdt_name) { 229 return false; 230 } 231 232 fdt = fit_load_image_alloc(itb, fdt_name, NULL, NULL); 233 if (!fdt) { 234 return false; 235 } 236 237 if (fdt_check_header(fdt)) { 238 ret = false; 239 goto out; 240 } 241 242 if (fdt_node_check_compatible(fdt, 0, compat)) { 243 ret = false; 244 goto out; 245 } 246 247 ret = true; 248 out: 249 g_free((void *) fdt); 250 return ret; 251 } 252 253 int load_fit(const struct fit_loader *ldr, const char *filename, void *opaque) 254 { 255 const struct fit_loader_match *match; 256 const void *itb, *match_data = NULL; 257 const char *def_cfg_name; 258 char path[FIT_LOADER_MAX_PATH]; 259 int itb_size, configs, cfg_off, off, err; 260 hwaddr kernel_end; 261 int ret; 262 263 itb = load_device_tree(filename, &itb_size); 264 if (!itb) { 265 return -EINVAL; 266 } 267 268 configs = fdt_path_offset(itb, "/configurations"); 269 if (configs < 0) { 270 ret = configs; 271 goto out; 272 } 273 274 cfg_off = -FDT_ERR_NOTFOUND; 275 276 if (ldr->matches) { 277 for (match = ldr->matches; match->compatible; match++) { 278 off = fdt_first_subnode(itb, configs); 279 while (off >= 0) { 280 if (fit_cfg_compatible(itb, off, match->compatible)) { 281 cfg_off = off; 282 match_data = match->data; 283 break; 284 } 285 286 off = fdt_next_subnode(itb, off); 287 } 288 289 if (cfg_off >= 0) { 290 break; 291 } 292 } 293 } 294 295 if (cfg_off < 0) { 296 def_cfg_name = fdt_getprop(itb, configs, "default", NULL); 297 if (def_cfg_name) { 298 snprintf(path, sizeof(path), "/configurations/%s", def_cfg_name); 299 cfg_off = fdt_path_offset(itb, path); 300 } 301 } 302 303 if (cfg_off < 0) { 304 /* couldn't find a configuration to use */ 305 ret = cfg_off; 306 goto out; 307 } 308 309 err = fit_load_kernel(ldr, itb, cfg_off, opaque, &kernel_end); 310 if (err) { 311 ret = err; 312 goto out; 313 } 314 315 err = fit_load_fdt(ldr, itb, cfg_off, opaque, match_data, kernel_end); 316 if (err) { 317 ret = err; 318 goto out; 319 } 320 321 ret = 0; 322 out: 323 g_free((void *) itb); 324 return ret; 325 } 326