diff --git a/elfloader-tool/include/elfloader_common.h b/elfloader-tool/include/elfloader_common.h index 84ae4eb7..63758849 100644 --- a/elfloader-tool/include/elfloader_common.h +++ b/elfloader-tool/include/elfloader_common.h @@ -7,6 +7,7 @@ #pragma once +#include #include typedef uintptr_t paddr_t; @@ -41,6 +42,9 @@ typedef uintptr_t vaddr_t; typedef struct { paddr_t phys_base; size_t size; +#ifdef CONFIG_ELFLOADER_INCLUDE_DTB + bool is_from_cpio; +#endif } dtb_info_t; /* diff --git a/elfloader-tool/src/common.c b/elfloader-tool/src/common.c index 0ae8edd9..1f7cff60 100644 --- a/elfloader-tool/src/common.c +++ b/elfloader-tool/src/common.c @@ -351,6 +351,109 @@ static int load_elf( return 0; } +/* + * Load the DTB + */ +static int load_dtb( + void const *cpio, + size_t cpio_len, + paddr_t dtb_load_phys, + dtb_info_t *dtb_info) +{ + int ret; + + void const *dtb = NULL; + +#ifdef CONFIG_ELFLOADER_INCLUDE_DTB + + /* A DTB present in the CPIO archive takes preference over a DTB passed + * from the bootloder. + */ + + printf("Looking for DTB in CPIO archive..."); + /* Note the lack of newline in the above printf(). Normally, fflush(stdout) + * must be called to ensure that the message shows up on a line-buffered + * stream, which is the POSIX default on terminal devices). However, we are + * freestanding (on the "bare metal"), and use our own unbuffered printf() + * implementation. + */ + + unsigned long cpio_dtb_size = 0; + dtb = cpio_get_file(cpio, cpio_len, "kernel.dtb", &cpio_dtb_size); + if (!dtb) { + printf("not found.\n"); + } else { + printf("found at %p.\n", dtb); + if (dtb_info) { + dtb_info->is_from_cpio = true; + } + } + +#endif /* CONFIG_ELFLOADER_INCLUDE_DTB */ + + /* If we don't have a DTB here, use the one a bootloader might have + * provided. Since 0 is a valid physical address, the size field is used to + * determin if the address is valid. A size of -1 indicates, that the actual + * size is not known - which is usually the case, because a bootloader often + * just passes an address. + */ + if (!dtb) { + if (0 == dtb_info->size) { + /* Not having a DTB is not an error. With dtb_info->size still set + * to zero, the caller can find out that no DTB was loaded and then + * act accordingly. + */ + printf("No DTB available\n"); + dtb_info->phys_base = 0; + return 0; + } + + printf("Loading DTB passed from bootloader at %p\n", + dtb_info->phys_base); + + dtb = (void const *)(dtb_info->phys_base); + } + + size_t dtb_size = fdt_size((void *)dtb); + if (0 == dtb_size) { + printf("ERROR: Invalid device tree blob supplied\n"); + return -1; + } + +#ifdef CONFIG_ELFLOADER_INCLUDE_DTB + + if (dtb_info->is_from_cpio && (dtb_size > cpio_dtb_size)) { + printf("ERROR: parsed device tree is larger (%zu byte) than CPIO file (%zu byte)\n", + dtb_size, cpio_dtb_size); + return -1; + } + +#endif /* CONFIG_ELFLOADER_INCLUDE_DTB */ + + /* Move the DTB out of the way. Check that the physical destination + * location is sane. + */ + paddr_t dtb_phys_end = dtb_load_phys + dtb_size; + ret = ensure_phys_range_valid(dtb_load_phys, dtb_phys_end); + if (0 != ret) { + printf("ERROR: Physical target address of DTB is invalid\n"); + return -1; + } + + memmove((void *)dtb_load_phys, dtb, dtb_size); + + printf("Loaded DTB from %p.\n", dtb); + printf(" paddr=[%p..%p]\n", dtb_load_phys, dtb_phys_end - 1); + + /* Set DTB values for caller. */ + if (dtb_info) { + dtb_info->phys_base = dtb_load_phys; + dtb_info->size = dtb_size; + } + + return 0; +} + /* * ELF-loader for ARM systems. * @@ -388,10 +491,7 @@ int load_images( { int ret; uint64_t kernel_phys_start, kernel_phys_end; - uintptr_t dtb_phys_start, dtb_phys_end; - paddr_t next_phys_addr; const char *elf_filename; - int has_dtb_cpio = 0; void const *cpio = _archive_start; size_t cpio_len = _archive_start_end - _archive_start; @@ -428,72 +528,23 @@ int load_images( return -1; } - void const *dtb = NULL; - -#ifdef CONFIG_ELFLOADER_INCLUDE_DTB - - printf("Looking for DTB in CPIO archive..."); - /* - * Note the lack of newline in the above printf(). Normally one would - * have an fflush(stdout) here to ensure that the message shows up on a - * line-buffered stream (which is the POSIX default on terminal - * devices). But we are freestanding (on the "bare metal"), and using - * our own unbuffered printf() implementation. + /* Load the DTB first, because this allows extracting further platform + * information from it, which may affect the system setup. The DTB is put + * after the kernel image, because this ensures it is in a place well + * aligned with our memory usage. */ - dtb = cpio_get_file(cpio, cpio_len, "kernel.dtb", NULL); - if (dtb == NULL) { - printf("not found.\n"); - } else { - has_dtb_cpio = 1; - printf("found at %p.\n", dtb); - } - -#endif /* CONFIG_ELFLOADER_INCLUDE_DTB */ - - /* If we don't have a DTB here, use the one a bootloader might have - * provided. Since 0 is a valid physical address, the size field is used to - * determin if the address is valid. A size of -1 indicates, that the actual - * size is not known - which is usually the case, because a bootloader often - * just passes an address. - */ - if (!dtb && (dtb_info->size > 0)) { - dtb = (void const *)dtb_info->phys_base; + paddr_t next_phys_addr = ROUND_UP(kernel_phys_end, PAGE_BITS); + ret = load_dtb(cpio, cpio_len, next_phys_addr, dtb_info); + if (ret != 0) { + printf("ERROR: Could not load DTB\n"); + return -1; } - /* - * Move the DTB out of the way, if it's present. + /* It's not an error here if no DTB was loaded. Eventually, the system that + * we are loading has to decide if it can handle this. */ - if (dtb) { - /* keep it page aligned */ - next_phys_addr = dtb_phys_start = ROUND_UP(kernel_phys_end, PAGE_BITS); - - size_t dtb_size = fdt_size(dtb); - if (0 == dtb_size) { - printf("ERROR: Invalid device tree blob supplied\n"); - return -1; - } - - /* Make sure this is a sane thing to do */ - ret = ensure_phys_range_valid(next_phys_addr, - next_phys_addr + dtb_size); - if (0 != ret) { - printf("ERROR: Physical address of DTB invalid\n"); - return -1; - } - - memmove((void *)next_phys_addr, dtb, dtb_size); - next_phys_addr += dtb_size; - next_phys_addr = ROUND_UP(next_phys_addr, PAGE_BITS); - dtb_phys_end = next_phys_addr; - - printf("Loaded DTB from %p.\n", dtb); - printf(" paddr=[%p..%p]\n", dtb_phys_start, dtb_phys_end - 1); - dtb_info->phys_base = dtb_phys_start; - dtb_info->size = dtb_size; - } else { - dtb_info->phys_base = 0; - dtb_info->size = 0; - next_phys_addr = ROUND_UP(kernel_phys_end, PAGE_BITS); + if (dtb_info->size > 0) { + next_phys_addr = ROUND_UP(next_phys_addr + dtb_info->size, PAGE_BITS); } /* Load the kernel */ @@ -531,10 +582,12 @@ int load_images( cpio_get_entry(cpio, cpio_len, 1, &elf_filename, NULL); ret = strcmp(elf_filename, "kernel.dtb"); if (0 != ret) { - if (has_dtb_cpio) { +#ifdef CONFIG_ELFLOADER_INCLUDE_DTB + if (dtb_info->is_from_cpio) { printf("ERROR: Kernel DTB not second image in archive\n"); return -1; } +#endif user_elf_offset = 1; }