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