diff options
Diffstat (limited to 'kernel/drivers/firmware/efi/libstub')
-rw-r--r-- | kernel/drivers/firmware/efi/libstub/Makefile | 41 | ||||
-rw-r--r-- | kernel/drivers/firmware/efi/libstub/arm-stub.c | 355 | ||||
-rw-r--r-- | kernel/drivers/firmware/efi/libstub/efi-stub-helper.c | 699 | ||||
-rw-r--r-- | kernel/drivers/firmware/efi/libstub/efistub.h | 50 | ||||
-rw-r--r-- | kernel/drivers/firmware/efi/libstub/fdt.c | 348 |
5 files changed, 1493 insertions, 0 deletions
diff --git a/kernel/drivers/firmware/efi/libstub/Makefile b/kernel/drivers/firmware/efi/libstub/Makefile new file mode 100644 index 000000000..280bc0a63 --- /dev/null +++ b/kernel/drivers/firmware/efi/libstub/Makefile @@ -0,0 +1,41 @@ +# +# The stub may be linked into the kernel proper or into a separate boot binary, +# but in either case, it executes before the kernel does (with MMU disabled) so +# things like ftrace and stack-protector are likely to cause trouble if left +# enabled, even if doing so doesn't break the build. +# +cflags-$(CONFIG_X86_32) := -march=i386 +cflags-$(CONFIG_X86_64) := -mcmodel=small +cflags-$(CONFIG_X86) += -m$(BITS) -D__KERNEL__ $(LINUX_INCLUDE) -O2 \ + -fPIC -fno-strict-aliasing -mno-red-zone \ + -mno-mmx -mno-sse -DDISABLE_BRANCH_PROFILING + +cflags-$(CONFIG_ARM64) := $(subst -pg,,$(KBUILD_CFLAGS)) +cflags-$(CONFIG_ARM) := $(subst -pg,,$(KBUILD_CFLAGS)) \ + -fno-builtin -fpic -mno-single-pic-base + +KBUILD_CFLAGS := $(cflags-y) \ + $(call cc-option,-ffreestanding) \ + $(call cc-option,-fno-stack-protector) + +GCOV_PROFILE := n +KASAN_SANITIZE := n + +lib-y := efi-stub-helper.o +lib-$(CONFIG_EFI_ARMSTUB) += arm-stub.o fdt.o + +CFLAGS_fdt.o += -I$(srctree)/scripts/dtc/libfdt/ + +# +# arm64 puts the stub in the kernel proper, which will unnecessarily retain all +# code indefinitely unless it is annotated as __init/__initdata/__initconst etc. +# So let's apply the __init annotations at the section level, by prefixing +# the section names directly. This will ensure that even all the inline string +# literals are covered. +# +extra-$(CONFIG_ARM64) := $(lib-y) +lib-$(CONFIG_ARM64) := $(patsubst %.o,%.init.o,$(lib-y)) + +OBJCOPYFLAGS := --prefix-alloc-sections=.init +$(obj)/%.init.o: $(obj)/%.o FORCE + $(call if_changed,objcopy) diff --git a/kernel/drivers/firmware/efi/libstub/arm-stub.c b/kernel/drivers/firmware/efi/libstub/arm-stub.c new file mode 100644 index 000000000..e29560e6b --- /dev/null +++ b/kernel/drivers/firmware/efi/libstub/arm-stub.c @@ -0,0 +1,355 @@ +/* + * EFI stub implementation that is shared by arm and arm64 architectures. + * This should be #included by the EFI stub implementation files. + * + * Copyright (C) 2013,2014 Linaro Limited + * Roy Franz <roy.franz@linaro.org + * Copyright (C) 2013 Red Hat, Inc. + * Mark Salter <msalter@redhat.com> + * + * This file is part of the Linux kernel, and is made available under the + * terms of the GNU General Public License version 2. + * + */ + +#include <linux/efi.h> +#include <asm/efi.h> + +#include "efistub.h" + +static int efi_secureboot_enabled(efi_system_table_t *sys_table_arg) +{ + static efi_guid_t const var_guid = EFI_GLOBAL_VARIABLE_GUID; + static efi_char16_t const var_name[] = { + 'S', 'e', 'c', 'u', 'r', 'e', 'B', 'o', 'o', 't', 0 }; + + efi_get_variable_t *f_getvar = sys_table_arg->runtime->get_variable; + unsigned long size = sizeof(u8); + efi_status_t status; + u8 val; + + status = f_getvar((efi_char16_t *)var_name, (efi_guid_t *)&var_guid, + NULL, &size, &val); + + switch (status) { + case EFI_SUCCESS: + return val; + case EFI_NOT_FOUND: + return 0; + default: + return 1; + } +} + +efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg, + void *__image, void **__fh) +{ + efi_file_io_interface_t *io; + efi_loaded_image_t *image = __image; + efi_file_handle_t *fh; + efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID; + efi_status_t status; + void *handle = (void *)(unsigned long)image->device_handle; + + status = sys_table_arg->boottime->handle_protocol(handle, + &fs_proto, (void **)&io); + if (status != EFI_SUCCESS) { + efi_printk(sys_table_arg, "Failed to handle fs_proto\n"); + return status; + } + + status = io->open_volume(io, &fh); + if (status != EFI_SUCCESS) + efi_printk(sys_table_arg, "Failed to open volume\n"); + + *__fh = fh; + return status; +} + +efi_status_t efi_file_close(void *handle) +{ + efi_file_handle_t *fh = handle; + + return fh->close(handle); +} + +efi_status_t +efi_file_read(void *handle, unsigned long *size, void *addr) +{ + efi_file_handle_t *fh = handle; + + return fh->read(handle, size, addr); +} + + +efi_status_t +efi_file_size(efi_system_table_t *sys_table_arg, void *__fh, + efi_char16_t *filename_16, void **handle, u64 *file_sz) +{ + efi_file_handle_t *h, *fh = __fh; + efi_file_info_t *info; + efi_status_t status; + efi_guid_t info_guid = EFI_FILE_INFO_ID; + unsigned long info_sz; + + status = fh->open(fh, &h, filename_16, EFI_FILE_MODE_READ, (u64)0); + if (status != EFI_SUCCESS) { + efi_printk(sys_table_arg, "Failed to open file: "); + efi_char16_printk(sys_table_arg, filename_16); + efi_printk(sys_table_arg, "\n"); + return status; + } + + *handle = h; + + info_sz = 0; + status = h->get_info(h, &info_guid, &info_sz, NULL); + if (status != EFI_BUFFER_TOO_SMALL) { + efi_printk(sys_table_arg, "Failed to get file info size\n"); + return status; + } + +grow: + status = sys_table_arg->boottime->allocate_pool(EFI_LOADER_DATA, + info_sz, (void **)&info); + if (status != EFI_SUCCESS) { + efi_printk(sys_table_arg, "Failed to alloc mem for file info\n"); + return status; + } + + status = h->get_info(h, &info_guid, &info_sz, + info); + if (status == EFI_BUFFER_TOO_SMALL) { + sys_table_arg->boottime->free_pool(info); + goto grow; + } + + *file_sz = info->file_size; + sys_table_arg->boottime->free_pool(info); + + if (status != EFI_SUCCESS) + efi_printk(sys_table_arg, "Failed to get initrd info\n"); + + return status; +} + + + +void efi_char16_printk(efi_system_table_t *sys_table_arg, + efi_char16_t *str) +{ + struct efi_simple_text_output_protocol *out; + + out = (struct efi_simple_text_output_protocol *)sys_table_arg->con_out; + out->output_string(out, str); +} + + +/* + * This function handles the architcture specific differences between arm and + * arm64 regarding where the kernel image must be loaded and any memory that + * must be reserved. On failure it is required to free all + * all allocations it has made. + */ +efi_status_t handle_kernel_image(efi_system_table_t *sys_table, + unsigned long *image_addr, + unsigned long *image_size, + unsigned long *reserve_addr, + unsigned long *reserve_size, + unsigned long dram_base, + efi_loaded_image_t *image); +/* + * EFI entry point for the arm/arm64 EFI stubs. This is the entrypoint + * that is described in the PE/COFF header. Most of the code is the same + * for both archictectures, with the arch-specific code provided in the + * handle_kernel_image() function. + */ +unsigned long efi_entry(void *handle, efi_system_table_t *sys_table, + unsigned long *image_addr) +{ + efi_loaded_image_t *image; + efi_status_t status; + unsigned long image_size = 0; + unsigned long dram_base; + /* addr/point and size pairs for memory management*/ + unsigned long initrd_addr; + u64 initrd_size = 0; + unsigned long fdt_addr = 0; /* Original DTB */ + unsigned long fdt_size = 0; + char *cmdline_ptr = NULL; + int cmdline_size = 0; + unsigned long new_fdt_addr; + efi_guid_t loaded_image_proto = LOADED_IMAGE_PROTOCOL_GUID; + unsigned long reserve_addr = 0; + unsigned long reserve_size = 0; + + /* Check if we were booted by the EFI firmware */ + if (sys_table->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE) + goto fail; + + pr_efi(sys_table, "Booting Linux Kernel...\n"); + + /* + * Get a handle to the loaded image protocol. This is used to get + * information about the running image, such as size and the command + * line. + */ + status = sys_table->boottime->handle_protocol(handle, + &loaded_image_proto, (void *)&image); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Failed to get loaded image protocol\n"); + goto fail; + } + + dram_base = get_dram_base(sys_table); + if (dram_base == EFI_ERROR) { + pr_efi_err(sys_table, "Failed to find DRAM base\n"); + goto fail; + } + status = handle_kernel_image(sys_table, image_addr, &image_size, + &reserve_addr, + &reserve_size, + dram_base, image); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Failed to relocate kernel\n"); + goto fail; + } + + /* + * Get the command line from EFI, using the LOADED_IMAGE + * protocol. We are going to copy the command line into the + * device tree, so this can be allocated anywhere. + */ + cmdline_ptr = efi_convert_cmdline(sys_table, image, &cmdline_size); + if (!cmdline_ptr) { + pr_efi_err(sys_table, "getting command line via LOADED_IMAGE_PROTOCOL\n"); + goto fail_free_image; + } + + status = efi_parse_options(cmdline_ptr); + if (status != EFI_SUCCESS) + pr_efi_err(sys_table, "Failed to parse EFI cmdline options\n"); + + /* + * Unauthenticated device tree data is a security hazard, so + * ignore 'dtb=' unless UEFI Secure Boot is disabled. + */ + if (efi_secureboot_enabled(sys_table)) { + pr_efi(sys_table, "UEFI Secure Boot is enabled.\n"); + } else { + status = handle_cmdline_files(sys_table, image, cmdline_ptr, + "dtb=", + ~0UL, &fdt_addr, &fdt_size); + + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Failed to load device tree!\n"); + goto fail_free_cmdline; + } + } + + if (fdt_addr) { + pr_efi(sys_table, "Using DTB from command line\n"); + } else { + /* Look for a device tree configuration table entry. */ + fdt_addr = (uintptr_t)get_fdt(sys_table, &fdt_size); + if (fdt_addr) + pr_efi(sys_table, "Using DTB from configuration table\n"); + } + + if (!fdt_addr) + pr_efi(sys_table, "Generating empty DTB\n"); + + status = handle_cmdline_files(sys_table, image, cmdline_ptr, + "initrd=", dram_base + SZ_512M, + (unsigned long *)&initrd_addr, + (unsigned long *)&initrd_size); + if (status != EFI_SUCCESS) + pr_efi_err(sys_table, "Failed initrd from command line!\n"); + + new_fdt_addr = fdt_addr; + status = allocate_new_fdt_and_exit_boot(sys_table, handle, + &new_fdt_addr, dram_base + MAX_FDT_OFFSET, + initrd_addr, initrd_size, cmdline_ptr, + fdt_addr, fdt_size); + + /* + * If all went well, we need to return the FDT address to the + * calling function so it can be passed to kernel as part of + * the kernel boot protocol. + */ + if (status == EFI_SUCCESS) + return new_fdt_addr; + + pr_efi_err(sys_table, "Failed to update FDT and exit boot services\n"); + + efi_free(sys_table, initrd_size, initrd_addr); + efi_free(sys_table, fdt_size, fdt_addr); + +fail_free_cmdline: + efi_free(sys_table, cmdline_size, (unsigned long)cmdline_ptr); + +fail_free_image: + efi_free(sys_table, image_size, *image_addr); + efi_free(sys_table, reserve_size, reserve_addr); +fail: + return EFI_ERROR; +} + +/* + * This is the base address at which to start allocating virtual memory ranges + * for UEFI Runtime Services. This is in the low TTBR0 range so that we can use + * any allocation we choose, and eliminate the risk of a conflict after kexec. + * The value chosen is the largest non-zero power of 2 suitable for this purpose + * both on 32-bit and 64-bit ARM CPUs, to maximize the likelihood that it can + * be mapped efficiently. + */ +#define EFI_RT_VIRTUAL_BASE 0x40000000 + +/* + * efi_get_virtmap() - create a virtual mapping for the EFI memory map + * + * This function populates the virt_addr fields of all memory region descriptors + * in @memory_map whose EFI_MEMORY_RUNTIME attribute is set. Those descriptors + * are also copied to @runtime_map, and their total count is returned in @count. + */ +void efi_get_virtmap(efi_memory_desc_t *memory_map, unsigned long map_size, + unsigned long desc_size, efi_memory_desc_t *runtime_map, + int *count) +{ + u64 efi_virt_base = EFI_RT_VIRTUAL_BASE; + efi_memory_desc_t *out = runtime_map; + int l; + + for (l = 0; l < map_size; l += desc_size) { + efi_memory_desc_t *in = (void *)memory_map + l; + u64 paddr, size; + + if (!(in->attribute & EFI_MEMORY_RUNTIME)) + continue; + + /* + * Make the mapping compatible with 64k pages: this allows + * a 4k page size kernel to kexec a 64k page size kernel and + * vice versa. + */ + paddr = round_down(in->phys_addr, SZ_64K); + size = round_up(in->num_pages * EFI_PAGE_SIZE + + in->phys_addr - paddr, SZ_64K); + + /* + * Avoid wasting memory on PTEs by choosing a virtual base that + * is compatible with section mappings if this region has the + * appropriate size and physical alignment. (Sections are 2 MB + * on 4k granule kernels) + */ + if (IS_ALIGNED(in->phys_addr, SZ_2M) && size >= SZ_2M) + efi_virt_base = round_up(efi_virt_base, SZ_2M); + + in->virt_addr = efi_virt_base + in->phys_addr - paddr; + efi_virt_base += size; + + memcpy(out, in, desc_size); + out = (void *)out + desc_size; + ++*count; + } +} diff --git a/kernel/drivers/firmware/efi/libstub/efi-stub-helper.c b/kernel/drivers/firmware/efi/libstub/efi-stub-helper.c new file mode 100644 index 000000000..f07d4a67f --- /dev/null +++ b/kernel/drivers/firmware/efi/libstub/efi-stub-helper.c @@ -0,0 +1,699 @@ +/* + * Helper functions used by the EFI stub on multiple + * architectures. This should be #included by the EFI stub + * implementation files. + * + * Copyright 2011 Intel Corporation; author Matt Fleming + * + * This file is part of the Linux kernel, and is made available + * under the terms of the GNU General Public License version 2. + * + */ + +#include <linux/efi.h> +#include <asm/efi.h> + +#include "efistub.h" + +/* + * Some firmware implementations have problems reading files in one go. + * A read chunk size of 1MB seems to work for most platforms. + * + * Unfortunately, reading files in chunks triggers *other* bugs on some + * platforms, so we provide a way to disable this workaround, which can + * be done by passing "efi=nochunk" on the EFI boot stub command line. + * + * If you experience issues with initrd images being corrupt it's worth + * trying efi=nochunk, but chunking is enabled by default because there + * are far more machines that require the workaround than those that + * break with it enabled. + */ +#define EFI_READ_CHUNK_SIZE (1024 * 1024) + +static unsigned long __chunk_size = EFI_READ_CHUNK_SIZE; + +/* + * Allow the platform to override the allocation granularity: this allows + * systems that have the capability to run with a larger page size to deal + * with the allocations for initrd and fdt more efficiently. + */ +#ifndef EFI_ALLOC_ALIGN +#define EFI_ALLOC_ALIGN EFI_PAGE_SIZE +#endif + +struct file_info { + efi_file_handle_t *handle; + u64 size; +}; + +void efi_printk(efi_system_table_t *sys_table_arg, char *str) +{ + char *s8; + + for (s8 = str; *s8; s8++) { + efi_char16_t ch[2] = { 0 }; + + ch[0] = *s8; + if (*s8 == '\n') { + efi_char16_t nl[2] = { '\r', 0 }; + efi_char16_printk(sys_table_arg, nl); + } + + efi_char16_printk(sys_table_arg, ch); + } +} + +efi_status_t efi_get_memory_map(efi_system_table_t *sys_table_arg, + efi_memory_desc_t **map, + unsigned long *map_size, + unsigned long *desc_size, + u32 *desc_ver, + unsigned long *key_ptr) +{ + efi_memory_desc_t *m = NULL; + efi_status_t status; + unsigned long key; + u32 desc_version; + + *map_size = sizeof(*m) * 32; +again: + /* + * Add an additional efi_memory_desc_t because we're doing an + * allocation which may be in a new descriptor region. + */ + *map_size += sizeof(*m); + status = efi_call_early(allocate_pool, EFI_LOADER_DATA, + *map_size, (void **)&m); + if (status != EFI_SUCCESS) + goto fail; + + *desc_size = 0; + key = 0; + status = efi_call_early(get_memory_map, map_size, m, + &key, desc_size, &desc_version); + if (status == EFI_BUFFER_TOO_SMALL) { + efi_call_early(free_pool, m); + goto again; + } + + if (status != EFI_SUCCESS) + efi_call_early(free_pool, m); + + if (key_ptr && status == EFI_SUCCESS) + *key_ptr = key; + if (desc_ver && status == EFI_SUCCESS) + *desc_ver = desc_version; + +fail: + *map = m; + return status; +} + + +unsigned long get_dram_base(efi_system_table_t *sys_table_arg) +{ + efi_status_t status; + unsigned long map_size; + unsigned long membase = EFI_ERROR; + struct efi_memory_map map; + efi_memory_desc_t *md; + + status = efi_get_memory_map(sys_table_arg, (efi_memory_desc_t **)&map.map, + &map_size, &map.desc_size, NULL, NULL); + if (status != EFI_SUCCESS) + return membase; + + map.map_end = map.map + map_size; + + for_each_efi_memory_desc(&map, md) + if (md->attribute & EFI_MEMORY_WB) + if (membase > md->phys_addr) + membase = md->phys_addr; + + efi_call_early(free_pool, map.map); + + return membase; +} + +/* + * Allocate at the highest possible address that is not above 'max'. + */ +efi_status_t efi_high_alloc(efi_system_table_t *sys_table_arg, + unsigned long size, unsigned long align, + unsigned long *addr, unsigned long max) +{ + unsigned long map_size, desc_size; + efi_memory_desc_t *map; + efi_status_t status; + unsigned long nr_pages; + u64 max_addr = 0; + int i; + + status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size, + NULL, NULL); + if (status != EFI_SUCCESS) + goto fail; + + /* + * Enforce minimum alignment that EFI requires when requesting + * a specific address. We are doing page-based allocations, + * so we must be aligned to a page. + */ + if (align < EFI_ALLOC_ALIGN) + align = EFI_ALLOC_ALIGN; + + nr_pages = round_up(size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE; +again: + for (i = 0; i < map_size / desc_size; i++) { + efi_memory_desc_t *desc; + unsigned long m = (unsigned long)map; + u64 start, end; + + desc = (efi_memory_desc_t *)(m + (i * desc_size)); + if (desc->type != EFI_CONVENTIONAL_MEMORY) + continue; + + if (desc->num_pages < nr_pages) + continue; + + start = desc->phys_addr; + end = start + desc->num_pages * (1UL << EFI_PAGE_SHIFT); + + if (end > max) + end = max; + + if ((start + size) > end) + continue; + + if (round_down(end - size, align) < start) + continue; + + start = round_down(end - size, align); + + /* + * Don't allocate at 0x0. It will confuse code that + * checks pointers against NULL. + */ + if (start == 0x0) + continue; + + if (start > max_addr) + max_addr = start; + } + + if (!max_addr) + status = EFI_NOT_FOUND; + else { + status = efi_call_early(allocate_pages, + EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA, + nr_pages, &max_addr); + if (status != EFI_SUCCESS) { + max = max_addr; + max_addr = 0; + goto again; + } + + *addr = max_addr; + } + + efi_call_early(free_pool, map); +fail: + return status; +} + +/* + * Allocate at the lowest possible address. + */ +efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg, + unsigned long size, unsigned long align, + unsigned long *addr) +{ + unsigned long map_size, desc_size; + efi_memory_desc_t *map; + efi_status_t status; + unsigned long nr_pages; + int i; + + status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size, + NULL, NULL); + if (status != EFI_SUCCESS) + goto fail; + + /* + * Enforce minimum alignment that EFI requires when requesting + * a specific address. We are doing page-based allocations, + * so we must be aligned to a page. + */ + if (align < EFI_ALLOC_ALIGN) + align = EFI_ALLOC_ALIGN; + + nr_pages = round_up(size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE; + for (i = 0; i < map_size / desc_size; i++) { + efi_memory_desc_t *desc; + unsigned long m = (unsigned long)map; + u64 start, end; + + desc = (efi_memory_desc_t *)(m + (i * desc_size)); + + if (desc->type != EFI_CONVENTIONAL_MEMORY) + continue; + + if (desc->num_pages < nr_pages) + continue; + + start = desc->phys_addr; + end = start + desc->num_pages * (1UL << EFI_PAGE_SHIFT); + + /* + * Don't allocate at 0x0. It will confuse code that + * checks pointers against NULL. Skip the first 8 + * bytes so we start at a nice even number. + */ + if (start == 0x0) + start += 8; + + start = round_up(start, align); + if ((start + size) > end) + continue; + + status = efi_call_early(allocate_pages, + EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA, + nr_pages, &start); + if (status == EFI_SUCCESS) { + *addr = start; + break; + } + } + + if (i == map_size / desc_size) + status = EFI_NOT_FOUND; + + efi_call_early(free_pool, map); +fail: + return status; +} + +void efi_free(efi_system_table_t *sys_table_arg, unsigned long size, + unsigned long addr) +{ + unsigned long nr_pages; + + if (!size) + return; + + nr_pages = round_up(size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE; + efi_call_early(free_pages, addr, nr_pages); +} + +/* + * Parse the ASCII string 'cmdline' for EFI options, denoted by the efi= + * option, e.g. efi=nochunk. + * + * It should be noted that efi= is parsed in two very different + * environments, first in the early boot environment of the EFI boot + * stub, and subsequently during the kernel boot. + */ +efi_status_t efi_parse_options(char *cmdline) +{ + char *str; + + /* + * If no EFI parameters were specified on the cmdline we've got + * nothing to do. + */ + str = strstr(cmdline, "efi="); + if (!str) + return EFI_SUCCESS; + + /* Skip ahead to first argument */ + str += strlen("efi="); + + /* + * Remember, because efi= is also used by the kernel we need to + * skip over arguments we don't understand. + */ + while (*str) { + if (!strncmp(str, "nochunk", 7)) { + str += strlen("nochunk"); + __chunk_size = -1UL; + } + + /* Group words together, delimited by "," */ + while (*str && *str != ',') + str++; + + if (*str == ',') + str++; + } + + return EFI_SUCCESS; +} + +/* + * Check the cmdline for a LILO-style file= arguments. + * + * We only support loading a file from the same filesystem as + * the kernel image. + */ +efi_status_t handle_cmdline_files(efi_system_table_t *sys_table_arg, + efi_loaded_image_t *image, + char *cmd_line, char *option_string, + unsigned long max_addr, + unsigned long *load_addr, + unsigned long *load_size) +{ + struct file_info *files; + unsigned long file_addr; + u64 file_size_total; + efi_file_handle_t *fh = NULL; + efi_status_t status; + int nr_files; + char *str; + int i, j, k; + + file_addr = 0; + file_size_total = 0; + + str = cmd_line; + + j = 0; /* See close_handles */ + + if (!load_addr || !load_size) + return EFI_INVALID_PARAMETER; + + *load_addr = 0; + *load_size = 0; + + if (!str || !*str) + return EFI_SUCCESS; + + for (nr_files = 0; *str; nr_files++) { + str = strstr(str, option_string); + if (!str) + break; + + str += strlen(option_string); + + /* Skip any leading slashes */ + while (*str == '/' || *str == '\\') + str++; + + while (*str && *str != ' ' && *str != '\n') + str++; + } + + if (!nr_files) + return EFI_SUCCESS; + + status = efi_call_early(allocate_pool, EFI_LOADER_DATA, + nr_files * sizeof(*files), (void **)&files); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table_arg, "Failed to alloc mem for file handle list\n"); + goto fail; + } + + str = cmd_line; + for (i = 0; i < nr_files; i++) { + struct file_info *file; + efi_char16_t filename_16[256]; + efi_char16_t *p; + + str = strstr(str, option_string); + if (!str) + break; + + str += strlen(option_string); + + file = &files[i]; + p = filename_16; + + /* Skip any leading slashes */ + while (*str == '/' || *str == '\\') + str++; + + while (*str && *str != ' ' && *str != '\n') { + if ((u8 *)p >= (u8 *)filename_16 + sizeof(filename_16)) + break; + + if (*str == '/') { + *p++ = '\\'; + str++; + } else { + *p++ = *str++; + } + } + + *p = '\0'; + + /* Only open the volume once. */ + if (!i) { + status = efi_open_volume(sys_table_arg, image, + (void **)&fh); + if (status != EFI_SUCCESS) + goto free_files; + } + + status = efi_file_size(sys_table_arg, fh, filename_16, + (void **)&file->handle, &file->size); + if (status != EFI_SUCCESS) + goto close_handles; + + file_size_total += file->size; + } + + if (file_size_total) { + unsigned long addr; + + /* + * Multiple files need to be at consecutive addresses in memory, + * so allocate enough memory for all the files. This is used + * for loading multiple files. + */ + status = efi_high_alloc(sys_table_arg, file_size_total, 0x1000, + &file_addr, max_addr); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table_arg, "Failed to alloc highmem for files\n"); + goto close_handles; + } + + /* We've run out of free low memory. */ + if (file_addr > max_addr) { + pr_efi_err(sys_table_arg, "We've run out of free low memory\n"); + status = EFI_INVALID_PARAMETER; + goto free_file_total; + } + + addr = file_addr; + for (j = 0; j < nr_files; j++) { + unsigned long size; + + size = files[j].size; + while (size) { + unsigned long chunksize; + if (size > __chunk_size) + chunksize = __chunk_size; + else + chunksize = size; + + status = efi_file_read(files[j].handle, + &chunksize, + (void *)addr); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table_arg, "Failed to read file\n"); + goto free_file_total; + } + addr += chunksize; + size -= chunksize; + } + + efi_file_close(files[j].handle); + } + + } + + efi_call_early(free_pool, files); + + *load_addr = file_addr; + *load_size = file_size_total; + + return status; + +free_file_total: + efi_free(sys_table_arg, file_size_total, file_addr); + +close_handles: + for (k = j; k < i; k++) + efi_file_close(files[k].handle); +free_files: + efi_call_early(free_pool, files); +fail: + *load_addr = 0; + *load_size = 0; + + return status; +} +/* + * Relocate a kernel image, either compressed or uncompressed. + * In the ARM64 case, all kernel images are currently + * uncompressed, and as such when we relocate it we need to + * allocate additional space for the BSS segment. Any low + * memory that this function should avoid needs to be + * unavailable in the EFI memory map, as if the preferred + * address is not available the lowest available address will + * be used. + */ +efi_status_t efi_relocate_kernel(efi_system_table_t *sys_table_arg, + unsigned long *image_addr, + unsigned long image_size, + unsigned long alloc_size, + unsigned long preferred_addr, + unsigned long alignment) +{ + unsigned long cur_image_addr; + unsigned long new_addr = 0; + efi_status_t status; + unsigned long nr_pages; + efi_physical_addr_t efi_addr = preferred_addr; + + if (!image_addr || !image_size || !alloc_size) + return EFI_INVALID_PARAMETER; + if (alloc_size < image_size) + return EFI_INVALID_PARAMETER; + + cur_image_addr = *image_addr; + + /* + * The EFI firmware loader could have placed the kernel image + * anywhere in memory, but the kernel has restrictions on the + * max physical address it can run at. Some architectures + * also have a prefered address, so first try to relocate + * to the preferred address. If that fails, allocate as low + * as possible while respecting the required alignment. + */ + nr_pages = round_up(alloc_size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE; + status = efi_call_early(allocate_pages, + EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA, + nr_pages, &efi_addr); + new_addr = efi_addr; + /* + * If preferred address allocation failed allocate as low as + * possible. + */ + if (status != EFI_SUCCESS) { + status = efi_low_alloc(sys_table_arg, alloc_size, alignment, + &new_addr); + } + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table_arg, "Failed to allocate usable memory for kernel.\n"); + return status; + } + + /* + * We know source/dest won't overlap since both memory ranges + * have been allocated by UEFI, so we can safely use memcpy. + */ + memcpy((void *)new_addr, (void *)cur_image_addr, image_size); + + /* Return the new address of the relocated image. */ + *image_addr = new_addr; + + return status; +} + +/* + * Get the number of UTF-8 bytes corresponding to an UTF-16 character. + * This overestimates for surrogates, but that is okay. + */ +static int efi_utf8_bytes(u16 c) +{ + return 1 + (c >= 0x80) + (c >= 0x800); +} + +/* + * Convert an UTF-16 string, not necessarily null terminated, to UTF-8. + */ +static u8 *efi_utf16_to_utf8(u8 *dst, const u16 *src, int n) +{ + unsigned int c; + + while (n--) { + c = *src++; + if (n && c >= 0xd800 && c <= 0xdbff && + *src >= 0xdc00 && *src <= 0xdfff) { + c = 0x10000 + ((c & 0x3ff) << 10) + (*src & 0x3ff); + src++; + n--; + } + if (c >= 0xd800 && c <= 0xdfff) + c = 0xfffd; /* Unmatched surrogate */ + if (c < 0x80) { + *dst++ = c; + continue; + } + if (c < 0x800) { + *dst++ = 0xc0 + (c >> 6); + goto t1; + } + if (c < 0x10000) { + *dst++ = 0xe0 + (c >> 12); + goto t2; + } + *dst++ = 0xf0 + (c >> 18); + *dst++ = 0x80 + ((c >> 12) & 0x3f); + t2: + *dst++ = 0x80 + ((c >> 6) & 0x3f); + t1: + *dst++ = 0x80 + (c & 0x3f); + } + + return dst; +} + +/* + * Convert the unicode UEFI command line to ASCII to pass to kernel. + * Size of memory allocated return in *cmd_line_len. + * Returns NULL on error. + */ +char *efi_convert_cmdline(efi_system_table_t *sys_table_arg, + efi_loaded_image_t *image, + int *cmd_line_len) +{ + const u16 *s2; + u8 *s1 = NULL; + unsigned long cmdline_addr = 0; + int load_options_chars = image->load_options_size / 2; /* UTF-16 */ + const u16 *options = image->load_options; + int options_bytes = 0; /* UTF-8 bytes */ + int options_chars = 0; /* UTF-16 chars */ + efi_status_t status; + u16 zero = 0; + + if (options) { + s2 = options; + while (*s2 && *s2 != '\n' + && options_chars < load_options_chars) { + options_bytes += efi_utf8_bytes(*s2++); + options_chars++; + } + } + + if (!options_chars) { + /* No command line options, so return empty string*/ + options = &zero; + } + + options_bytes++; /* NUL termination */ + + status = efi_low_alloc(sys_table_arg, options_bytes, 0, &cmdline_addr); + if (status != EFI_SUCCESS) + return NULL; + + s1 = (u8 *)cmdline_addr; + s2 = (const u16 *)options; + + s1 = efi_utf16_to_utf8(s1, s2, options_chars); + *s1 = '\0'; + + *cmd_line_len = options_bytes; + return (char *)cmdline_addr; +} diff --git a/kernel/drivers/firmware/efi/libstub/efistub.h b/kernel/drivers/firmware/efi/libstub/efistub.h new file mode 100644 index 000000000..e334a01cf --- /dev/null +++ b/kernel/drivers/firmware/efi/libstub/efistub.h @@ -0,0 +1,50 @@ + +#ifndef _DRIVERS_FIRMWARE_EFI_EFISTUB_H +#define _DRIVERS_FIRMWARE_EFI_EFISTUB_H + +/* error code which can't be mistaken for valid address */ +#define EFI_ERROR (~0UL) + +#undef memcpy +#undef memset +#undef memmove + +void efi_char16_printk(efi_system_table_t *, efi_char16_t *); + +efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg, void *__image, + void **__fh); + +efi_status_t efi_file_size(efi_system_table_t *sys_table_arg, void *__fh, + efi_char16_t *filename_16, void **handle, + u64 *file_sz); + +efi_status_t efi_file_read(void *handle, unsigned long *size, void *addr); + +efi_status_t efi_file_close(void *handle); + +unsigned long get_dram_base(efi_system_table_t *sys_table_arg); + +efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt, + unsigned long orig_fdt_size, + void *fdt, int new_fdt_size, char *cmdline_ptr, + u64 initrd_addr, u64 initrd_size, + efi_memory_desc_t *memory_map, + unsigned long map_size, unsigned long desc_size, + u32 desc_ver); + +efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table, + void *handle, + unsigned long *new_fdt_addr, + unsigned long max_addr, + u64 initrd_addr, u64 initrd_size, + char *cmdline_ptr, + unsigned long fdt_addr, + unsigned long fdt_size); + +void *get_fdt(efi_system_table_t *sys_table, unsigned long *fdt_size); + +void efi_get_virtmap(efi_memory_desc_t *memory_map, unsigned long map_size, + unsigned long desc_size, efi_memory_desc_t *runtime_map, + int *count); + +#endif diff --git a/kernel/drivers/firmware/efi/libstub/fdt.c b/kernel/drivers/firmware/efi/libstub/fdt.c new file mode 100644 index 000000000..ef5d764e2 --- /dev/null +++ b/kernel/drivers/firmware/efi/libstub/fdt.c @@ -0,0 +1,348 @@ +/* + * FDT related Helper functions used by the EFI stub on multiple + * architectures. This should be #included by the EFI stub + * implementation files. + * + * Copyright 2013 Linaro Limited; author Roy Franz + * + * This file is part of the Linux kernel, and is made available + * under the terms of the GNU General Public License version 2. + * + */ + +#include <linux/efi.h> +#include <linux/libfdt.h> +#include <asm/efi.h> + +#include "efistub.h" + +efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt, + unsigned long orig_fdt_size, + void *fdt, int new_fdt_size, char *cmdline_ptr, + u64 initrd_addr, u64 initrd_size, + efi_memory_desc_t *memory_map, + unsigned long map_size, unsigned long desc_size, + u32 desc_ver) +{ + int node, prev, num_rsv; + int status; + u32 fdt_val32; + u64 fdt_val64; + + /* Do some checks on provided FDT, if it exists*/ + if (orig_fdt) { + if (fdt_check_header(orig_fdt)) { + pr_efi_err(sys_table, "Device Tree header not valid!\n"); + return EFI_LOAD_ERROR; + } + /* + * We don't get the size of the FDT if we get if from a + * configuration table. + */ + if (orig_fdt_size && fdt_totalsize(orig_fdt) > orig_fdt_size) { + pr_efi_err(sys_table, "Truncated device tree! foo!\n"); + return EFI_LOAD_ERROR; + } + } + + if (orig_fdt) + status = fdt_open_into(orig_fdt, fdt, new_fdt_size); + else + status = fdt_create_empty_tree(fdt, new_fdt_size); + + if (status != 0) + goto fdt_set_fail; + + /* + * Delete any memory nodes present. We must delete nodes which + * early_init_dt_scan_memory may try to use. + */ + prev = 0; + for (;;) { + const char *type; + int len; + + node = fdt_next_node(fdt, prev, NULL); + if (node < 0) + break; + + type = fdt_getprop(fdt, node, "device_type", &len); + if (type && strncmp(type, "memory", len) == 0) { + fdt_del_node(fdt, node); + continue; + } + + prev = node; + } + + /* + * Delete all memory reserve map entries. When booting via UEFI, + * kernel will use the UEFI memory map to find reserved regions. + */ + num_rsv = fdt_num_mem_rsv(fdt); + while (num_rsv-- > 0) + fdt_del_mem_rsv(fdt, num_rsv); + + node = fdt_subnode_offset(fdt, 0, "chosen"); + if (node < 0) { + node = fdt_add_subnode(fdt, 0, "chosen"); + if (node < 0) { + status = node; /* node is error code when negative */ + goto fdt_set_fail; + } + } + + if ((cmdline_ptr != NULL) && (strlen(cmdline_ptr) > 0)) { + status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr, + strlen(cmdline_ptr) + 1); + if (status) + goto fdt_set_fail; + } + + /* Set initrd address/end in device tree, if present */ + if (initrd_size != 0) { + u64 initrd_image_end; + u64 initrd_image_start = cpu_to_fdt64(initrd_addr); + + status = fdt_setprop(fdt, node, "linux,initrd-start", + &initrd_image_start, sizeof(u64)); + if (status) + goto fdt_set_fail; + initrd_image_end = cpu_to_fdt64(initrd_addr + initrd_size); + status = fdt_setprop(fdt, node, "linux,initrd-end", + &initrd_image_end, sizeof(u64)); + if (status) + goto fdt_set_fail; + } + + /* Add FDT entries for EFI runtime services in chosen node. */ + node = fdt_subnode_offset(fdt, 0, "chosen"); + fdt_val64 = cpu_to_fdt64((u64)(unsigned long)sys_table); + status = fdt_setprop(fdt, node, "linux,uefi-system-table", + &fdt_val64, sizeof(fdt_val64)); + if (status) + goto fdt_set_fail; + + fdt_val64 = cpu_to_fdt64((u64)(unsigned long)memory_map); + status = fdt_setprop(fdt, node, "linux,uefi-mmap-start", + &fdt_val64, sizeof(fdt_val64)); + if (status) + goto fdt_set_fail; + + fdt_val32 = cpu_to_fdt32(map_size); + status = fdt_setprop(fdt, node, "linux,uefi-mmap-size", + &fdt_val32, sizeof(fdt_val32)); + if (status) + goto fdt_set_fail; + + fdt_val32 = cpu_to_fdt32(desc_size); + status = fdt_setprop(fdt, node, "linux,uefi-mmap-desc-size", + &fdt_val32, sizeof(fdt_val32)); + if (status) + goto fdt_set_fail; + + fdt_val32 = cpu_to_fdt32(desc_ver); + status = fdt_setprop(fdt, node, "linux,uefi-mmap-desc-ver", + &fdt_val32, sizeof(fdt_val32)); + if (status) + goto fdt_set_fail; + + /* + * Add kernel version banner so stub/kernel match can be + * verified. + */ + status = fdt_setprop_string(fdt, node, "linux,uefi-stub-kern-ver", + linux_banner); + if (status) + goto fdt_set_fail; + + return EFI_SUCCESS; + +fdt_set_fail: + if (status == -FDT_ERR_NOSPACE) + return EFI_BUFFER_TOO_SMALL; + + return EFI_LOAD_ERROR; +} + +#ifndef EFI_FDT_ALIGN +#define EFI_FDT_ALIGN EFI_PAGE_SIZE +#endif + +/* + * Allocate memory for a new FDT, then add EFI, commandline, and + * initrd related fields to the FDT. This routine increases the + * FDT allocation size until the allocated memory is large + * enough. EFI allocations are in EFI_PAGE_SIZE granules, + * which are fixed at 4K bytes, so in most cases the first + * allocation should succeed. + * EFI boot services are exited at the end of this function. + * There must be no allocations between the get_memory_map() + * call and the exit_boot_services() call, so the exiting of + * boot services is very tightly tied to the creation of the FDT + * with the final memory map in it. + */ + +efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table, + void *handle, + unsigned long *new_fdt_addr, + unsigned long max_addr, + u64 initrd_addr, u64 initrd_size, + char *cmdline_ptr, + unsigned long fdt_addr, + unsigned long fdt_size) +{ + unsigned long map_size, desc_size; + u32 desc_ver; + unsigned long mmap_key; + efi_memory_desc_t *memory_map, *runtime_map; + unsigned long new_fdt_size; + efi_status_t status; + int runtime_entry_count = 0; + + /* + * Get a copy of the current memory map that we will use to prepare + * the input for SetVirtualAddressMap(). We don't have to worry about + * subsequent allocations adding entries, since they could not affect + * the number of EFI_MEMORY_RUNTIME regions. + */ + status = efi_get_memory_map(sys_table, &runtime_map, &map_size, + &desc_size, &desc_ver, &mmap_key); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Unable to retrieve UEFI memory map.\n"); + return status; + } + + pr_efi(sys_table, + "Exiting boot services and installing virtual address map...\n"); + + /* + * Estimate size of new FDT, and allocate memory for it. We + * will allocate a bigger buffer if this ends up being too + * small, so a rough guess is OK here. + */ + new_fdt_size = fdt_size + EFI_PAGE_SIZE; + while (1) { + status = efi_high_alloc(sys_table, new_fdt_size, EFI_FDT_ALIGN, + new_fdt_addr, max_addr); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Unable to allocate memory for new device tree.\n"); + goto fail; + } + + /* + * Now that we have done our final memory allocation (and free) + * we can get the memory map key needed for + * exit_boot_services(). + */ + status = efi_get_memory_map(sys_table, &memory_map, &map_size, + &desc_size, &desc_ver, &mmap_key); + if (status != EFI_SUCCESS) + goto fail_free_new_fdt; + + status = update_fdt(sys_table, + (void *)fdt_addr, fdt_size, + (void *)*new_fdt_addr, new_fdt_size, + cmdline_ptr, initrd_addr, initrd_size, + memory_map, map_size, desc_size, desc_ver); + + /* Succeeding the first time is the expected case. */ + if (status == EFI_SUCCESS) + break; + + if (status == EFI_BUFFER_TOO_SMALL) { + /* + * We need to allocate more space for the new + * device tree, so free existing buffer that is + * too small. Also free memory map, as we will need + * to get new one that reflects the free/alloc we do + * on the device tree buffer. + */ + efi_free(sys_table, new_fdt_size, *new_fdt_addr); + sys_table->boottime->free_pool(memory_map); + new_fdt_size += EFI_PAGE_SIZE; + } else { + pr_efi_err(sys_table, "Unable to constuct new device tree.\n"); + goto fail_free_mmap; + } + } + + /* + * Update the memory map with virtual addresses. The function will also + * populate @runtime_map with copies of just the EFI_MEMORY_RUNTIME + * entries so that we can pass it straight into SetVirtualAddressMap() + */ + efi_get_virtmap(memory_map, map_size, desc_size, runtime_map, + &runtime_entry_count); + + /* Now we are ready to exit_boot_services.*/ + status = sys_table->boottime->exit_boot_services(handle, mmap_key); + + if (status == EFI_SUCCESS) { + efi_set_virtual_address_map_t *svam; + + /* Install the new virtual address map */ + svam = sys_table->runtime->set_virtual_address_map; + status = svam(runtime_entry_count * desc_size, desc_size, + desc_ver, runtime_map); + + /* + * We are beyond the point of no return here, so if the call to + * SetVirtualAddressMap() failed, we need to signal that to the + * incoming kernel but proceed normally otherwise. + */ + if (status != EFI_SUCCESS) { + int l; + + /* + * Set the virtual address field of all + * EFI_MEMORY_RUNTIME entries to 0. This will signal + * the incoming kernel that no virtual translation has + * been installed. + */ + for (l = 0; l < map_size; l += desc_size) { + efi_memory_desc_t *p = (void *)memory_map + l; + + if (p->attribute & EFI_MEMORY_RUNTIME) + p->virt_addr = 0; + } + } + return EFI_SUCCESS; + } + + pr_efi_err(sys_table, "Exit boot services failed.\n"); + +fail_free_mmap: + sys_table->boottime->free_pool(memory_map); + +fail_free_new_fdt: + efi_free(sys_table, new_fdt_size, *new_fdt_addr); + +fail: + sys_table->boottime->free_pool(runtime_map); + return EFI_LOAD_ERROR; +} + +void *get_fdt(efi_system_table_t *sys_table, unsigned long *fdt_size) +{ + efi_guid_t fdt_guid = DEVICE_TREE_GUID; + efi_config_table_t *tables; + void *fdt; + int i; + + tables = (efi_config_table_t *) sys_table->tables; + fdt = NULL; + + for (i = 0; i < sys_table->nr_tables; i++) + if (efi_guidcmp(tables[i].guid, fdt_guid) == 0) { + fdt = (void *) tables[i].table; + if (fdt_check_header(fdt) != 0) { + pr_efi_err(sys_table, "Invalid header detected on UEFI supplied FDT, ignoring ...\n"); + return NULL; + } + *fdt_size = fdt_totalsize(fdt); + break; + } + + return fdt; +} |