| /* |
| * Copyright (c) 2016 MediaTek Inc. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files |
| * (the "Software"), to deal in the Software without restriction, |
| * including without limitation the rights to use, copy, modify, merge, |
| * publish, distribute, sublicense, and/or sell copies of the Software, |
| * and to permit persons to whom the Software is furnished to do so, |
| * subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be |
| * included in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
| * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
| * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
| * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| */ |
| |
| #include <arch/ops.h> |
| #include <errno.h> |
| #include <lib/bio.h> |
| #include <libfdt.h> |
| #include <lib/decompress.h> |
| #include <lib/mempool.h> |
| #include <kernel/thread.h> |
| #include <kernel/vm.h> |
| #include <trace.h> |
| |
| #include "fit.h" |
| #include "image.h" |
| |
| #define LOCAL_TRACE 0 |
| |
| #define uswap_32(x) \ |
| ((((x) & 0xff000000) >> 24) | \ |
| (((x) & 0x00ff0000) >> 8) | \ |
| (((x) & 0x0000ff00) << 8) | \ |
| (((x) & 0x000000ff) << 24)) |
| |
| int fit_image_get_node(const void *fit, const char *image_uname) |
| { |
| int noffset, images_noffset; |
| |
| images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH); |
| if (images_noffset < 0) { |
| dprintf(CRITICAL,"Can't find images parent node '%s' (%s)\n", |
| FIT_IMAGES_PATH, fdt_strerror(images_noffset)); |
| return images_noffset; |
| } |
| |
| noffset = fdt_subnode_offset(fit, images_noffset, image_uname); |
| if (noffset < 0) { |
| dprintf(CRITICAL,"Can't get node offset for image name: '%s' (%s)\n", |
| image_uname, fdt_strerror(noffset)); |
| } |
| |
| return noffset; |
| } |
| |
| int fit_image_get_data(const void *fit, int noffset, |
| const void **data, uint32_t *size) |
| { |
| int len; |
| *data = fdt_getprop(fit, noffset, FDT_DATA_NODE, &len); |
| if (*data == NULL) |
| return -1; |
| |
| *size = len; |
| |
| return 0; |
| } |
| |
| int fit_conf_get_prop_node(const void *fit, int noffset, |
| const char *prop_name) |
| { |
| char *uname; |
| int len; |
| |
| /* get kernel image unit name from configuration kernel property */ |
| uname = (char *)fdt_getprop(fit, noffset, prop_name, &len); |
| if (uname == NULL) |
| return len; |
| |
| return fit_image_get_node(fit, uname); |
| } |
| |
| /** |
| * fit_get_img_subnode_offset() - get a subnode offset for a given image name |
| * |
| * This finds subnode offset using given image name within node "/images" |
| * |
| * @fit: fit image start address |
| * @image_name: image name. "kernel", "fdt" or "ramdisk"... |
| * |
| * returns: |
| * great than or equal 0, on success |
| * otherwise, on failure |
| * |
| */ |
| static int fit_get_img_subnode_offset(void *fit, const char *image_name) |
| { |
| int noffset; |
| |
| /* get image node offset */ |
| noffset = fdt_path_offset(fit, "/images"); |
| if (noffset < 0) { |
| dprintf(CRITICAL, "Can't find image node(%s)\n", fdt_strerror(noffset)); |
| return noffset; |
| } |
| |
| /* get subnode offset */ |
| noffset = fdt_subnode_offset(fit, noffset, image_name); |
| if (noffset < 0) |
| dprintf(CRITICAL, "Can't get node offset for image name: '%s' (%s)\n", |
| image_name, fdt_strerror(noffset)); |
| |
| return noffset; |
| } |
| |
| /** |
| * fit_get_def_cfg_offset() - get a subnode offset from node "/configurations" |
| * |
| * This finds configuration subnode offset in node "configruations". |
| * If "conf" is not given, it will find property "default" for the case. |
| * |
| * @fit: fit image start address |
| * @conf: configuration name |
| * |
| * returns: |
| * great than or equal 0, on success |
| * otherwise, on failure |
| * |
| */ |
| static int fit_get_def_cfg_offset(void *fit, const char *conf) |
| { |
| int noffset, cfg_noffset, len; |
| |
| noffset = fdt_path_offset(fit, "/configurations"); |
| if (noffset < 0) { |
| dprintf(CRITICAL, "can't find configuration node\n"); |
| return noffset; |
| } |
| |
| if (conf == NULL) { |
| conf = (char *)fdt_getprop(fit, noffset, |
| "default", &len); |
| if (conf == NULL) { |
| dprintf(CRITICAL, "Can't get default conf name\n"); |
| return len; |
| } |
| dprintf(SPEW, "got default conf: %s\n", conf); |
| } |
| |
| cfg_noffset = fdt_subnode_offset(fit, noffset, conf); |
| if (cfg_noffset < 0) |
| dprintf(CRITICAL, "Can't get conf subnode\n"); |
| else |
| dprintf(SPEW, "got conf: %s subnode\n", conf); |
| |
| return cfg_noffset; |
| } |
| |
| int fit_get_image(const char *label, void **load_buf) |
| { |
| bdev_t *bdev; |
| struct fdt_header fdt; |
| size_t totalsize; |
| int fdt_len, ret = 0; |
| void *fit_buf = NULL; |
| |
| fdt_len = sizeof(struct fdt_header); |
| bdev = bio_open_by_label(label) ? : bio_open(label); |
| if (!bdev) { |
| dprintf(CRITICAL, "Partition [%s] is not exist.\n", label); |
| return -ENODEV; |
| } |
| |
| if (bio_read(bdev, &fdt, 0, fdt_len) < fdt_len) { |
| ret = -EIO; |
| goto closebdev; |
| } |
| |
| ret = fdt_check_header(&fdt); |
| if (ret) { |
| dprintf(CRITICAL, "[%s] check header failed\n", label); |
| goto closebdev; |
| } |
| |
| totalsize = fdt_totalsize(&fdt); |
| fit_buf = mempool_alloc(totalsize, MEMPOOL_ANY); |
| if (!fit_buf) { |
| ret = -ENOMEM; |
| goto closebdev; |
| } |
| |
| if (bio_read(bdev, fit_buf, 0, totalsize) < totalsize) { |
| ret = -EIO; |
| goto closebdev; |
| } |
| *load_buf = fit_buf; |
| |
| closebdev: |
| bio_close(bdev); |
| if ((ret != 0) && (fit_buf != NULL)) |
| mempool_free(fit_buf); |
| |
| return ret; |
| } |
| |
| int fit_processing_data(void *fit, const char *image_name, int noffset, |
| addr_t *load, size_t *load_size, paddr_t *entry) |
| { |
| int len, ret, ac; |
| size_t size; |
| const char *type; |
| const void *data, *compression; |
| const uint32_t *load_prop, *entry_prop; |
| addr_t load_addr; |
| paddr_t entry_addr; |
| |
| data = fdt_getprop(fit, noffset, "data", &len); |
| if (!data) { |
| dprintf(CRITICAL, "%s can't get prop data\n", image_name); |
| return len; |
| } |
| size = len; |
| |
| compression = fdt_getprop(fit, noffset, "compression", &len); |
| if (!compression) { |
| dprintf(CRITICAL, "%s compression is not specified\n", image_name); |
| return -EINVAL; |
| } |
| |
| type = fdt_getprop(fit, noffset, "type", &len); |
| if (!type) { |
| dprintf(CRITICAL, "%s image type is not specified\n", image_name); |
| return -EINVAL; |
| } |
| |
| /* read address-cells from root */ |
| ac = fdt_address_cells(fit, 0); |
| if (ac <= 0 || (ac > sizeof(ulong) / sizeof(uint))) { |
| LTRACEF("%s #address-cells with a bad format or value\n", image_name); |
| return -EINVAL; |
| } |
| |
| load_prop = fdt_getprop(fit, noffset, "load", &len); |
| if (!load_prop && |
| (!strcmp(type, "kernel") || (!strcmp(type, "loadable")))) { |
| dprintf(CRITICAL, "%s need load addr\n", image_name); |
| return -EINVAL; |
| } |
| |
| /* load address determination: |
| * 1. "load" property exist: use address in "load" property |
| * 2. "load" property not exist: use runtime address of "data" property |
| */ |
| load_addr = (addr_t)data; |
| if (load_prop) { |
| load_addr = (addr_t)uswap_32(load_prop[0]); |
| if (ac == 2) |
| load_addr = (load_addr << 32) | (addr_t)uswap_32(load_prop[1]); |
| #if WITH_KERNEL_VM |
| load_addr = (addr_t)paddr_to_kvaddr(load_addr); |
| #endif |
| } |
| |
| if (!strcmp((char *)compression, "lz4")) { |
| ret = unlz4(data, size - 4, (void *)(load_addr)); |
| if (ret != LZ4_OK) { |
| dprintf(ALWAYS, "lz4 decompress failure\n"); |
| return -LZ4_FAIL; |
| } |
| /* In lz4 kernel image, the last four bytes are the uncompressed |
| * kernel image size */ |
| size = *(u32 *)(data + size - 4); |
| } else if (!strcmp((char *)compression, "none")) { |
| memmove((void *)(load_addr), data, size); |
| } else { |
| dprintf(CRITICAL, "%s compression does not support\n", image_name); |
| return -EINVAL; |
| } |
| |
| #if WITH_KERNEL_VM |
| /* always flush cache to PoC */ |
| arch_clean_cache_range(load_addr, size); |
| #endif |
| |
| LTRACEF("[%s] load_addr 0x%lx\n", image_name, load_addr); |
| LTRACEF("[%s] fit = %p\n", image_name, fit); |
| LTRACEF("[%s] data = %p\n", image_name, data); |
| LTRACEF("[%s] size = %zu\n", image_name, size); |
| |
| /* return load, load_size and entry address if caller spcified */ |
| if (load) |
| *load = load_addr; |
| |
| if (load_size) |
| *load_size = size; |
| |
| if (entry) { |
| /* |
| * entry address determination: |
| * 1. "entry" property not exist: entry address = load address |
| * 2. "entry" & "load" properties both exist: "entry" property |
| * contains the absolute address of entry, thus |
| * entry address = "entry" |
| * 3. only "entry" property exist: "entry" property contains the |
| * entry offset to load address, thus |
| * entry address = "entry" + load address |
| */ |
| |
| #if WITH_KERNEL_VM |
| load_addr = kvaddr_to_paddr((void *)load_addr); |
| #endif |
| entry_addr = load_addr; |
| entry_prop = fdt_getprop(fit, noffset, "entry", &len); |
| if (entry_prop) { |
| entry_addr = (paddr_t)uswap_32(entry_prop[0]); |
| if (ac == 2) { |
| entry_addr = (entry_addr << 32) | |
| (paddr_t)uswap_32(entry_prop[1]); |
| } |
| entry_addr += load_prop ? 0 : load_addr; |
| } |
| *entry = entry_addr; |
| |
| LTRACEF("[%s] entry_addr 0x%lx\n", image_name, *entry); |
| } |
| |
| return 0; |
| } |
| |
| int fit_load_loadable_image(void *fit, const char *sub_node_name, addr_t *load) |
| { |
| int noffset; |
| int ret; |
| |
| noffset = fit_get_img_subnode_offset(fit, sub_node_name); |
| if (noffset < 0) { |
| LTRACEF("%s: fit_get_img_subnode_offset fail\n", sub_node_name); |
| return noffset; |
| } |
| |
| if (hash_check_enabled()) { |
| ret = fit_image_integrity_verify(fit, noffset); |
| LTRACEF("%s: integrity check %s\n", |
| sub_node_name, ret ? "fail" : "pass"); |
| if (ret) |
| return -EACCES; |
| } |
| |
| return fit_processing_data(fit, sub_node_name, noffset, load, NULL, NULL); |
| } |
| |
| int fit_conf_verify_sig(const char *conf, void *fit) |
| { |
| int ret; |
| int noffset; |
| |
| /* get defualt configuration offset (conf@1, conf@2,...or confg@n) */ |
| noffset = fit_get_def_cfg_offset(fit, conf); |
| if (noffset < 0) |
| return noffset; |
| |
| /* verify config signature */ |
| if (rsa_check_enabled()) { |
| ret = fit_verify_sign(fit, noffset); |
| dprintf(ALWAYS, "Verify sign: %s\n", ret ? "fail" : "pass"); |
| if (ret) |
| return -EACCES; |
| } |
| |
| return 0; |
| } |
| |
| static int fit_image_integrity_check_process(void *arg) |
| { |
| int ret; |
| struct verify_data *verify_info; |
| |
| verify_info = (struct verify_data *)arg; |
| ret = fit_image_integrity_verify(verify_info->fit_image, |
| verify_info->noffset); |
| |
| return ret; |
| } |
| |
| int fit_load_image(const char *conf, const char *img_pro, void *fit, |
| addr_t *load, size_t *load_size, paddr_t *entry, |
| bool need_verified) |
| { |
| int noffset, len, cfg_noffset; |
| int ret, rc; |
| const char *image_name; |
| thread_t *integrity_verify_t; |
| |
| /* get defualt configuration offset (conf@1, conf@2,...or confg@n) */ |
| cfg_noffset = fit_get_def_cfg_offset(fit, conf); |
| if (cfg_noffset < 0) |
| return cfg_noffset; |
| |
| /* unit name: fdt@1, kernel@2, ramdisk@3 and so on */ |
| image_name = (char *)fdt_getprop(fit, cfg_noffset, img_pro, &len); |
| if (image_name == NULL) { |
| LTRACEF("%s get image name failed\n", img_pro); |
| return -ENOENT; |
| } |
| |
| /* get this sub image node offset */ |
| noffset = fit_get_img_subnode_offset(fit, image_name); |
| if (noffset < 0) { |
| dprintf(CRITICAL, "get sub image node (%s) failed\n", image_name); |
| return noffset; |
| } |
| |
| /* verify integrity of this image */ |
| if (hash_check_enabled() && need_verified) { |
| #if WITH_SMP |
| struct verify_data verify_info; |
| verify_info.fit_image = fit; |
| verify_info.noffset = noffset; |
| |
| integrity_verify_t = thread_create("integrity_verify_t", |
| &fit_image_integrity_check_process, &verify_info, |
| DEFAULT_PRIORITY, DEFAULT_STACK_SIZE); |
| |
| /* Assigned the thread to active cpu */ |
| extern __WEAK void plat_mp_assign_workcpu(thread_t *t); |
| plat_mp_assign_workcpu(integrity_verify_t); |
| thread_resume(integrity_verify_t); |
| #else |
| ret = fit_image_integrity_verify(fit, noffset); |
| LTRACEF_LEVEL(CRITICAL, "check %s integrity: %s\n", |
| image_name, ret ? "fail" : "pass"); |
| if (ret < 0) |
| return -EACCES; |
| #endif |
| } /* verify end */ |
| |
| rc = fit_processing_data(fit, image_name, noffset, load, load_size, entry); |
| |
| #if WITH_SMP |
| if (hash_check_enabled() && need_verified) { |
| thread_join(integrity_verify_t, &ret, INFINITE_TIME); |
| LTRACEF_LEVEL(CRITICAL, "check %s integrity: %s\n", |
| image_name, ret ? "fail" : "pass"); |
| if (ret < 0) |
| return -EACCES; |
| } |
| #endif |
| |
| return rc; |
| } |