From f49182ec8c685f4e89a50ba85ffe43b7f1e7b3e1 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Fri, 13 Jun 2014 13:11:51 +0200 Subject: efi/arm64: efistub: remove local copy of linux_banner The shared efistub code for ARM and arm64 contains a local copy of linux_banner, allowing it to be referenced from separate executables such as the ARM decompressor. However, this introduces a dependency on generated header files, causing unnecessary rebuilds of the stub itself and, in case of arm64, vmlinux which contains it. On arm64, the copy is not actually needed since we can reference the original symbol directly, and as it turns out, there may be better ways to deal with this for ARM as well, so let's remove it from the shared code. If it still needs to be reintroduced for ARM later, it should live under arch/arm anyway and not in shared code. Signed-off-by: Ard Biesheuvel Acked-by: Will Deacon Signed-off-by: Matt Fleming --- drivers/firmware/efi/fdt.c | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/efi/fdt.c b/drivers/firmware/efi/fdt.c index 5c6a8e8a9580..3aec36d7aae9 100644 --- a/drivers/firmware/efi/fdt.c +++ b/drivers/firmware/efi/fdt.c @@ -23,16 +23,6 @@ static efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt, u32 fdt_val32; u64 fdt_val64; - /* - * Copy definition of linux_banner here. Since this code is - * built as part of the decompressor for ARM v7, pulling - * in version.c where linux_banner is defined for the - * kernel brings other kernel dependencies with it. - */ - const char linux_banner[] = - "Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@" - LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n"; - /* Do some checks on provided FDT, if it exists*/ if (orig_fdt) { if (fdt_check_header(orig_fdt)) { -- cgit v1.2.3 From 022ee6c558fc933679e151f00f84332974147fa2 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Thu, 26 Jun 2014 12:09:05 +0200 Subject: efi/x86: Move UEFI Runtime Services wrappers to generic code In order for other archs (such as arm64) to be able to reuse the virtual mode function call wrappers, move them to drivers/firmware/efi/runtime-wrappers.c. Signed-off-by: Ard Biesheuvel Signed-off-by: Matt Fleming --- drivers/firmware/efi/Kconfig | 3 + drivers/firmware/efi/Makefile | 1 + drivers/firmware/efi/runtime-wrappers.c | 161 ++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 drivers/firmware/efi/runtime-wrappers.c (limited to 'drivers/firmware') diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig index d420ae2d3413..588dc47e7075 100644 --- a/drivers/firmware/efi/Kconfig +++ b/drivers/firmware/efi/Kconfig @@ -54,6 +54,9 @@ config EFI_PARAMS_FROM_FDT the EFI runtime support gets system table address, memory map address, and other parameters from the device tree. +config EFI_RUNTIME_WRAPPERS + bool + endmenu config UEFI_CPER diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile index 9553496b0f43..e1096539eedb 100644 --- a/drivers/firmware/efi/Makefile +++ b/drivers/firmware/efi/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_EFI_VARS) += efivars.o obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o obj-$(CONFIG_UEFI_CPER) += cper.o obj-$(CONFIG_EFI_RUNTIME_MAP) += runtime-map.o +obj-$(CONFIG_EFI_RUNTIME_WRAPPERS) += runtime-wrappers.o diff --git a/drivers/firmware/efi/runtime-wrappers.c b/drivers/firmware/efi/runtime-wrappers.c new file mode 100644 index 000000000000..10daa4bbb258 --- /dev/null +++ b/drivers/firmware/efi/runtime-wrappers.c @@ -0,0 +1,161 @@ +/* + * runtime-wrappers.c - Runtime Services function call wrappers + * + * Copyright (C) 2014 Linaro Ltd. + * + * Split off from arch/x86/platform/efi/efi.c + * + * Copyright (C) 1999 VA Linux Systems + * Copyright (C) 1999 Walt Drummond + * Copyright (C) 1999-2002 Hewlett-Packard Co. + * Copyright (C) 2005-2008 Intel Co. + * Copyright (C) 2013 SuSE Labs + * + * This file is released under the GPLv2. + */ + +#include +#include /* spinlock_t */ +#include + +/* + * As per commit ef68c8f87ed1 ("x86: Serialize EFI time accesses on rtc_lock"), + * the EFI specification requires that callers of the time related runtime + * functions serialize with other CMOS accesses in the kernel, as the EFI time + * functions may choose to also use the legacy CMOS RTC. + */ +__weak DEFINE_SPINLOCK(rtc_lock); + +static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc) +{ + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&rtc_lock, flags); + status = efi_call_virt(get_time, tm, tc); + spin_unlock_irqrestore(&rtc_lock, flags); + return status; +} + +static efi_status_t virt_efi_set_time(efi_time_t *tm) +{ + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&rtc_lock, flags); + status = efi_call_virt(set_time, tm); + spin_unlock_irqrestore(&rtc_lock, flags); + return status; +} + +static efi_status_t virt_efi_get_wakeup_time(efi_bool_t *enabled, + efi_bool_t *pending, + efi_time_t *tm) +{ + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&rtc_lock, flags); + status = efi_call_virt(get_wakeup_time, enabled, pending, tm); + spin_unlock_irqrestore(&rtc_lock, flags); + return status; +} + +static efi_status_t virt_efi_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm) +{ + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&rtc_lock, flags); + status = efi_call_virt(set_wakeup_time, enabled, tm); + spin_unlock_irqrestore(&rtc_lock, flags); + return status; +} + +static efi_status_t virt_efi_get_variable(efi_char16_t *name, + efi_guid_t *vendor, + u32 *attr, + unsigned long *data_size, + void *data) +{ + return efi_call_virt(get_variable, name, vendor, attr, data_size, data); +} + +static efi_status_t virt_efi_get_next_variable(unsigned long *name_size, + efi_char16_t *name, + efi_guid_t *vendor) +{ + return efi_call_virt(get_next_variable, name_size, name, vendor); +} + +static efi_status_t virt_efi_set_variable(efi_char16_t *name, + efi_guid_t *vendor, + u32 attr, + unsigned long data_size, + void *data) +{ + return efi_call_virt(set_variable, name, vendor, attr, data_size, data); +} + +static efi_status_t virt_efi_query_variable_info(u32 attr, + u64 *storage_space, + u64 *remaining_space, + u64 *max_variable_size) +{ + if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) + return EFI_UNSUPPORTED; + + return efi_call_virt(query_variable_info, attr, storage_space, + remaining_space, max_variable_size); +} + +static efi_status_t virt_efi_get_next_high_mono_count(u32 *count) +{ + return efi_call_virt(get_next_high_mono_count, count); +} + +static void virt_efi_reset_system(int reset_type, + efi_status_t status, + unsigned long data_size, + efi_char16_t *data) +{ + __efi_call_virt(reset_system, reset_type, status, data_size, data); +} + +static efi_status_t virt_efi_update_capsule(efi_capsule_header_t **capsules, + unsigned long count, + unsigned long sg_list) +{ + if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) + return EFI_UNSUPPORTED; + + return efi_call_virt(update_capsule, capsules, count, sg_list); +} + +static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules, + unsigned long count, + u64 *max_size, + int *reset_type) +{ + if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) + return EFI_UNSUPPORTED; + + return efi_call_virt(query_capsule_caps, capsules, count, max_size, + reset_type); +} + +void efi_native_runtime_setup(void) +{ + efi.get_time = virt_efi_get_time; + efi.set_time = virt_efi_set_time; + efi.get_wakeup_time = virt_efi_get_wakeup_time; + efi.set_wakeup_time = virt_efi_set_wakeup_time; + efi.get_variable = virt_efi_get_variable; + efi.get_next_variable = virt_efi_get_next_variable; + efi.set_variable = virt_efi_set_variable; + efi.get_next_high_mono_count = virt_efi_get_next_high_mono_count; + efi.reset_system = virt_efi_reset_system; + efi.query_variable_info = virt_efi_query_variable_info; + efi.update_capsule = virt_efi_update_capsule; + efi.query_capsule_caps = virt_efi_query_capsule_caps; +} -- cgit v1.2.3 From bd669475d14e3279a7f96ed917a82df5da6ad52d Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Wed, 2 Jul 2014 14:54:42 +0200 Subject: efi: efistub: Refactor stub components In order to move from the #include "../../../xxxxx.c" anti-pattern used by both the x86 and arm64 versions of the stub to a static library linked into either the kernel proper (arm64) or a separate boot executable (x86), there is some prepatory work required. This patch does the following: - move forward declarations of functions shared between the arch specific and the generic parts of the stub to include/linux/efi.h - move forward declarations of functions shared between various .c files of the generic stub code to a new local header file called "efistub.h" - add #includes to all .c files which were formerly relying on the #includor to include the correct header files - remove all static modifiers from functions which will need to be externally visible once we move to a static library Signed-off-by: Ard Biesheuvel Signed-off-by: Matt Fleming --- drivers/firmware/efi/arm-stub.c | 32 +++++++++------ drivers/firmware/efi/efi-stub-helper.c | 74 +++++++++++++++++----------------- drivers/firmware/efi/efistub.h | 42 +++++++++++++++++++ drivers/firmware/efi/fdt.c | 20 +++++---- 4 files changed, 109 insertions(+), 59 deletions(-) create mode 100644 drivers/firmware/efi/efistub.h (limited to 'drivers/firmware') diff --git a/drivers/firmware/efi/arm-stub.c b/drivers/firmware/efi/arm-stub.c index 41114ce03b01..480339b6b110 100644 --- a/drivers/firmware/efi/arm-stub.c +++ b/drivers/firmware/efi/arm-stub.c @@ -12,6 +12,11 @@ * */ +#include +#include + +#include "efistub.h" + static int __init efi_secureboot_enabled(efi_system_table_t *sys_table_arg) { static efi_guid_t const var_guid __initconst = EFI_GLOBAL_VARIABLE_GUID; @@ -36,8 +41,8 @@ static int __init efi_secureboot_enabled(efi_system_table_t *sys_table_arg) } } -static efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg, - void *__image, void **__fh) +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; @@ -60,14 +65,15 @@ static efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg, *__fh = fh; return status; } -static efi_status_t efi_file_close(void *handle) + +efi_status_t efi_file_close(void *handle) { efi_file_handle_t *fh = handle; return fh->close(handle); } -static efi_status_t +efi_status_t efi_file_read(void *handle, unsigned long *size, void *addr) { efi_file_handle_t *fh = handle; @@ -76,7 +82,7 @@ efi_file_read(void *handle, unsigned long *size, void *addr) } -static efi_status_t +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) { @@ -129,7 +135,7 @@ grow: -static void efi_char16_printk(efi_system_table_t *sys_table_arg, +void efi_char16_printk(efi_system_table_t *sys_table_arg, efi_char16_t *str) { struct efi_simple_text_output_protocol *out; @@ -145,13 +151,13 @@ static void efi_char16_printk(efi_system_table_t *sys_table_arg, * must be reserved. On failure it is required to free all * all allocations it has made. */ -static 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_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 diff --git a/drivers/firmware/efi/efi-stub-helper.c b/drivers/firmware/efi/efi-stub-helper.c index eb6d4be9e722..32d5cca30f49 100644 --- a/drivers/firmware/efi/efi-stub-helper.c +++ b/drivers/firmware/efi/efi-stub-helper.c @@ -9,18 +9,20 @@ * under the terms of the GNU General Public License version 2. * */ -#define EFI_READ_CHUNK_SIZE (1024 * 1024) -/* error code which can't be mistaken for valid address */ -#define EFI_ERROR (~0UL) +#include +#include + +#include "efistub.h" +#define EFI_READ_CHUNK_SIZE (1024 * 1024) struct file_info { efi_file_handle_t *handle; u64 size; }; -static void efi_printk(efi_system_table_t *sys_table_arg, char *str) +void efi_printk(efi_system_table_t *sys_table_arg, char *str) { char *s8; @@ -37,16 +39,12 @@ static void efi_printk(efi_system_table_t *sys_table_arg, char *str) } } -#define pr_efi(sys_table, msg) efi_printk(sys_table, "EFI stub: "msg) -#define pr_efi_err(sys_table, msg) efi_printk(sys_table, "EFI stub: ERROR: "msg) - - -static 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_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; @@ -88,7 +86,7 @@ fail: } -static unsigned long __init get_dram_base(efi_system_table_t *sys_table_arg) +unsigned long __init get_dram_base(efi_system_table_t *sys_table_arg) { efi_status_t status; unsigned long map_size; @@ -116,9 +114,9 @@ static unsigned long __init get_dram_base(efi_system_table_t *sys_table_arg) /* * Allocate at the highest possible address that is not above 'max'. */ -static 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) +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; @@ -202,9 +200,9 @@ fail: /* * Allocate at the lowest possible address. */ -static efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg, - unsigned long size, unsigned long align, - unsigned long *addr) +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; @@ -271,8 +269,8 @@ fail: return status; } -static void efi_free(efi_system_table_t *sys_table_arg, unsigned long size, - unsigned long addr) +void efi_free(efi_system_table_t *sys_table_arg, unsigned long size, + unsigned long addr) { unsigned long nr_pages; @@ -290,12 +288,12 @@ static void efi_free(efi_system_table_t *sys_table_arg, unsigned long size, * We only support loading a file from the same filesystem as * the kernel image. */ -static 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) +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; @@ -477,12 +475,12 @@ fail: * address is not available the lowest available address will * be used. */ -static 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) +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; @@ -589,9 +587,9 @@ static u8 *efi_utf16_to_utf8(u8 *dst, const u16 *src, int n) * Size of memory allocated return in *cmd_line_len. * Returns NULL on error. */ -static char *efi_convert_cmdline(efi_system_table_t *sys_table_arg, - efi_loaded_image_t *image, - int *cmd_line_len) +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; diff --git a/drivers/firmware/efi/efistub.h b/drivers/firmware/efi/efistub.h new file mode 100644 index 000000000000..304ab295ca1a --- /dev/null +++ b/drivers/firmware/efi/efistub.h @@ -0,0 +1,42 @@ + +#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) + +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); + +#endif diff --git a/drivers/firmware/efi/fdt.c b/drivers/firmware/efi/fdt.c index 3aec36d7aae9..86d2934840e2 100644 --- a/drivers/firmware/efi/fdt.c +++ b/drivers/firmware/efi/fdt.c @@ -10,13 +10,17 @@ * */ -static 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) +#include +#include +#include + +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; int status; @@ -255,7 +259,7 @@ fail: return EFI_LOAD_ERROR; } -static void *get_fdt(efi_system_table_t *sys_table) +void *get_fdt(efi_system_table_t *sys_table) { efi_guid_t fdt_guid = DEVICE_TREE_GUID; efi_config_table_t *tables; -- cgit v1.2.3 From f4f75ad5741fe0331bbe1f5c42b906cda299f26b Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Wed, 2 Jul 2014 14:54:43 +0200 Subject: efi: efistub: Convert into static library This patch changes both x86 and arm64 efistub implementations from #including shared .c files under drivers/firmware/efi to building shared code as a static library. The x86 code uses a stub built into the boot executable which uncompresses the kernel at boot time. In this case, the library is linked into the decompressor. In the arm64 case, the stub is part of the kernel proper so the library is linked into the kernel proper as well. Signed-off-by: Ard Biesheuvel Signed-off-by: Matt Fleming --- drivers/firmware/efi/Kconfig | 3 + drivers/firmware/efi/Makefile | 1 + drivers/firmware/efi/arm-stub.c | 284 ----------- drivers/firmware/efi/efi-stub-helper.c | 632 ------------------------- drivers/firmware/efi/efistub.h | 42 -- drivers/firmware/efi/fdt.c | 279 ----------- drivers/firmware/efi/libstub/Makefile | 26 + drivers/firmware/efi/libstub/arm-stub.c | 284 +++++++++++ drivers/firmware/efi/libstub/efi-stub-helper.c | 632 +++++++++++++++++++++++++ drivers/firmware/efi/libstub/efistub.h | 42 ++ drivers/firmware/efi/libstub/fdt.c | 279 +++++++++++ 11 files changed, 1267 insertions(+), 1237 deletions(-) delete mode 100644 drivers/firmware/efi/arm-stub.c delete mode 100644 drivers/firmware/efi/efi-stub-helper.c delete mode 100644 drivers/firmware/efi/efistub.h delete mode 100644 drivers/firmware/efi/fdt.c create mode 100644 drivers/firmware/efi/libstub/Makefile create mode 100644 drivers/firmware/efi/libstub/arm-stub.c create mode 100644 drivers/firmware/efi/libstub/efi-stub-helper.c create mode 100644 drivers/firmware/efi/libstub/efistub.h create mode 100644 drivers/firmware/efi/libstub/fdt.c (limited to 'drivers/firmware') diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig index 588dc47e7075..f712d47f30d8 100644 --- a/drivers/firmware/efi/Kconfig +++ b/drivers/firmware/efi/Kconfig @@ -57,6 +57,9 @@ config EFI_PARAMS_FROM_FDT config EFI_RUNTIME_WRAPPERS bool +config EFI_ARMSTUB + bool + endmenu config UEFI_CPER diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile index e1096539eedb..a204d1474cec 100644 --- a/drivers/firmware/efi/Makefile +++ b/drivers/firmware/efi/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o obj-$(CONFIG_UEFI_CPER) += cper.o obj-$(CONFIG_EFI_RUNTIME_MAP) += runtime-map.o obj-$(CONFIG_EFI_RUNTIME_WRAPPERS) += runtime-wrappers.o +obj-$(CONFIG_EFI_STUB) += libstub/ diff --git a/drivers/firmware/efi/arm-stub.c b/drivers/firmware/efi/arm-stub.c deleted file mode 100644 index 480339b6b110..000000000000 --- a/drivers/firmware/efi/arm-stub.c +++ /dev/null @@ -1,284 +0,0 @@ -/* - * 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 - * - * This file is part of the Linux kernel, and is made available under the - * terms of the GNU General Public License version 2. - * - */ - -#include -#include - -#include "efistub.h" - -static int __init efi_secureboot_enabled(efi_system_table_t *sys_table_arg) -{ - static efi_guid_t const var_guid __initconst = EFI_GLOBAL_VARIABLE_GUID; - static efi_char16_t const var_name[] __initconst = { - '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 __init 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 */ - u64 fdt_size = 0; /* We don't get size from configuration table */ - 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; - } - - /* - * 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, (unsigned long *)&fdt_addr, - (unsigned long *)&fdt_size); - - if (status != EFI_SUCCESS) { - pr_efi_err(sys_table, "Failed to load device tree!\n"); - goto fail_free_cmdline; - } - } - if (!fdt_addr) - /* Look for a device tree configuration table entry. */ - fdt_addr = (uintptr_t)get_fdt(sys_table); - - 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; -} diff --git a/drivers/firmware/efi/efi-stub-helper.c b/drivers/firmware/efi/efi-stub-helper.c deleted file mode 100644 index 32d5cca30f49..000000000000 --- a/drivers/firmware/efi/efi-stub-helper.c +++ /dev/null @@ -1,632 +0,0 @@ -/* - * 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 -#include - -#include "efistub.h" - -#define EFI_READ_CHUNK_SIZE (1024 * 1024) - -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 __init 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_PAGE_SIZE) - align = EFI_PAGE_SIZE; - - nr_pages = round_up(size, EFI_PAGE_SIZE) / 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 ((start + size) > end || (start + size) > max) - continue; - - if (end - size > max) - end = max; - - 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_PAGE_SIZE) - align = EFI_PAGE_SIZE; - - nr_pages = round_up(size, EFI_PAGE_SIZE) / 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_PAGE_SIZE) / EFI_PAGE_SIZE; - efi_call_early(free_pages, addr, nr_pages); -} - - -/* - * 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 > EFI_READ_CHUNK_SIZE) - chunksize = EFI_READ_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_PAGE_SIZE) / 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/drivers/firmware/efi/efistub.h b/drivers/firmware/efi/efistub.h deleted file mode 100644 index 304ab295ca1a..000000000000 --- a/drivers/firmware/efi/efistub.h +++ /dev/null @@ -1,42 +0,0 @@ - -#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) - -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); - -#endif diff --git a/drivers/firmware/efi/fdt.c b/drivers/firmware/efi/fdt.c deleted file mode 100644 index 86d2934840e2..000000000000 --- a/drivers/firmware/efi/fdt.c +++ /dev/null @@ -1,279 +0,0 @@ -/* - * 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 -#include -#include - -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; - 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, *name; - 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; - } - - 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; - unsigned long new_fdt_size; - efi_status_t status; - - /* - * 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; - } - } - - /* Now we are ready to exit_boot_services.*/ - status = sys_table->boottime->exit_boot_services(handle, mmap_key); - - - if (status == EFI_SUCCESS) - return status; - - 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: - return EFI_LOAD_ERROR; -} - -void *get_fdt(efi_system_table_t *sys_table) -{ - 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; - break; - } - - return fdt; -} diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile new file mode 100644 index 000000000000..b14bc2b9fb4d --- /dev/null +++ b/drivers/firmware/efi/libstub/Makefile @@ -0,0 +1,26 @@ +# +# 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 + +lib-y := efi-stub-helper.o +lib-$(CONFIG_EFI_ARMSTUB) += arm-stub.o fdt.o + +CFLAGS_fdt.o += -I$(srctree)/scripts/dtc/libfdt/ diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c new file mode 100644 index 000000000000..480339b6b110 --- /dev/null +++ b/drivers/firmware/efi/libstub/arm-stub.c @@ -0,0 +1,284 @@ +/* + * 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 + * + * This file is part of the Linux kernel, and is made available under the + * terms of the GNU General Public License version 2. + * + */ + +#include +#include + +#include "efistub.h" + +static int __init efi_secureboot_enabled(efi_system_table_t *sys_table_arg) +{ + static efi_guid_t const var_guid __initconst = EFI_GLOBAL_VARIABLE_GUID; + static efi_char16_t const var_name[] __initconst = { + '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 __init 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 */ + u64 fdt_size = 0; /* We don't get size from configuration table */ + 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; + } + + /* + * 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, (unsigned long *)&fdt_addr, + (unsigned long *)&fdt_size); + + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Failed to load device tree!\n"); + goto fail_free_cmdline; + } + } + if (!fdt_addr) + /* Look for a device tree configuration table entry. */ + fdt_addr = (uintptr_t)get_fdt(sys_table); + + 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; +} diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c new file mode 100644 index 000000000000..32d5cca30f49 --- /dev/null +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c @@ -0,0 +1,632 @@ +/* + * 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 +#include + +#include "efistub.h" + +#define EFI_READ_CHUNK_SIZE (1024 * 1024) + +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 __init 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_PAGE_SIZE) + align = EFI_PAGE_SIZE; + + nr_pages = round_up(size, EFI_PAGE_SIZE) / 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 ((start + size) > end || (start + size) > max) + continue; + + if (end - size > max) + end = max; + + 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_PAGE_SIZE) + align = EFI_PAGE_SIZE; + + nr_pages = round_up(size, EFI_PAGE_SIZE) / 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_PAGE_SIZE) / EFI_PAGE_SIZE; + efi_call_early(free_pages, addr, nr_pages); +} + + +/* + * 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 > EFI_READ_CHUNK_SIZE) + chunksize = EFI_READ_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_PAGE_SIZE) / 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/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h new file mode 100644 index 000000000000..304ab295ca1a --- /dev/null +++ b/drivers/firmware/efi/libstub/efistub.h @@ -0,0 +1,42 @@ + +#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) + +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); + +#endif diff --git a/drivers/firmware/efi/libstub/fdt.c b/drivers/firmware/efi/libstub/fdt.c new file mode 100644 index 000000000000..86d2934840e2 --- /dev/null +++ b/drivers/firmware/efi/libstub/fdt.c @@ -0,0 +1,279 @@ +/* + * 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 +#include +#include + +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; + 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, *name; + 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; + } + + 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; + unsigned long new_fdt_size; + efi_status_t status; + + /* + * 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; + } + } + + /* Now we are ready to exit_boot_services.*/ + status = sys_table->boottime->exit_boot_services(handle, mmap_key); + + + if (status == EFI_SUCCESS) + return status; + + 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: + return EFI_LOAD_ERROR; +} + +void *get_fdt(efi_system_table_t *sys_table) +{ + 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; + break; + } + + return fdt; +} -- cgit v1.2.3 From 8562c99cdd30217dea3609e268572f8764f401a5 Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Fri, 13 Jun 2014 12:22:22 +0100 Subject: efi/reboot: Add generic wrapper around EfiResetSystem() Implement efi_reboot(), which is really just a wrapper around the EfiResetSystem() EFI runtime service, but it does at least allow us to funnel all callers through a single location. It also simplifies the callsites since users no longer need to check to see whether EFI_RUNTIME_SERVICES are enabled. Cc: Tony Luck Tested-by: Mark Salter Signed-off-by: Matt Fleming --- drivers/firmware/efi/Makefile | 2 +- drivers/firmware/efi/reboot.c | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/efi/reboot.c (limited to 'drivers/firmware') diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile index a204d1474cec..d8be608a9f3b 100644 --- a/drivers/firmware/efi/Makefile +++ b/drivers/firmware/efi/Makefile @@ -1,7 +1,7 @@ # # Makefile for linux kernel # -obj-$(CONFIG_EFI) += efi.o vars.o +obj-$(CONFIG_EFI) += efi.o vars.o reboot.o obj-$(CONFIG_EFI_VARS) += efivars.o obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o obj-$(CONFIG_UEFI_CPER) += cper.o diff --git a/drivers/firmware/efi/reboot.c b/drivers/firmware/efi/reboot.c new file mode 100644 index 000000000000..81bf925f70f5 --- /dev/null +++ b/drivers/firmware/efi/reboot.c @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2014 Intel Corporation; author Matt Fleming + * Copyright (c) 2014 Red Hat, Inc., Mark Salter + */ +#include +#include + +void efi_reboot(enum reboot_mode reboot_mode, const char *__unused) +{ + int efi_mode; + + if (!efi_enabled(EFI_RUNTIME_SERVICES)) + return; + + switch (reboot_mode) { + case REBOOT_WARM: + case REBOOT_SOFT: + efi_mode = EFI_RESET_WARM; + break; + default: + efi_mode = EFI_RESET_COLD; + break; + } + + efi.reset_system(efi_mode, EFI_SUCCESS, 0, NULL); +} -- cgit v1.2.3 From 0c5ed61adbdbf2ca5de934642d5be1e971c498c1 Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Fri, 13 Jun 2014 12:35:21 +0100 Subject: efi/reboot: Allow powering off machines using EFI Not only can EfiResetSystem() be used to reboot, it can also be used to power down machines. By and large, this functionality doesn't work very well across the range of EFI machines in the wild, so it should definitely only be used as a last resort. In an ideal world, this wouldn't be needed at all. Unfortunately, we're starting to see machines where EFI is the *only* reliable way to power down, and nothing else, not PCI, not ACPI, works. efi_poweroff_required() should be implemented on a per-architecture basis, since exactly when we should be using EFI runtime services is a platform-specific decision. There's no analogue for reboot because each architecture handles reboot very differently - the x86 code in particular is pretty complex. Patches to enable this for specific classes of hardware will be submitted separately. Tested-by: Mark Salter Signed-off-by: Matt Fleming --- drivers/firmware/efi/reboot.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'drivers/firmware') diff --git a/drivers/firmware/efi/reboot.c b/drivers/firmware/efi/reboot.c index 81bf925f70f5..e9eeeb3c6345 100644 --- a/drivers/firmware/efi/reboot.c +++ b/drivers/firmware/efi/reboot.c @@ -24,3 +24,25 @@ void efi_reboot(enum reboot_mode reboot_mode, const char *__unused) efi.reset_system(efi_mode, EFI_SUCCESS, 0, NULL); } + +bool __weak efi_poweroff_required(void) +{ + return false; +} + +static void efi_power_off(void) +{ + efi.reset_system(EFI_RESET_SHUTDOWN, EFI_SUCCESS, 0, NULL); +} + +static int __init efi_shutdown_init(void) +{ + if (!efi_enabled(EFI_RUNTIME_SERVICES)) + return -ENODEV; + + if (efi_poweroff_required()) + pm_power_off = efi_power_off; + + return 0; +} +late_initcall(efi_shutdown_init); -- cgit v1.2.3 From 44be28e9dd9880dca3e2cbf7a844f2114e67f2cb Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Fri, 13 Jun 2014 12:39:55 +0100 Subject: x86/reboot: Add EFI reboot quirk for ACPI Hardware Reduced flag It appears that the BayTrail-T class of hardware requires EFI in order to powerdown and reboot and no other reliable method exists. This quirk is generally applicable to all hardware that has the ACPI Hardware Reduced bit set, since usually ACPI would be the preferred method. Cc: Len Brown Cc: Mark Salter Cc: "Rafael J. Wysocki" Signed-off-by: Matt Fleming --- drivers/firmware/efi/reboot.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers/firmware') diff --git a/drivers/firmware/efi/reboot.c b/drivers/firmware/efi/reboot.c index e9eeeb3c6345..9c59d1c795d1 100644 --- a/drivers/firmware/efi/reboot.c +++ b/drivers/firmware/efi/reboot.c @@ -5,6 +5,8 @@ #include #include +int efi_reboot_quirk_mode = -1; + void efi_reboot(enum reboot_mode reboot_mode, const char *__unused) { int efi_mode; @@ -22,6 +24,12 @@ void efi_reboot(enum reboot_mode reboot_mode, const char *__unused) break; } + /* + * If a quirk forced an EFI reset mode, always use that. + */ + if (efi_reboot_quirk_mode != -1) + efi_mode = efi_reboot_quirk_mode; + efi.reset_system(efi_mode, EFI_SUCCESS, 0, NULL); } -- cgit v1.2.3 From abc93f8eb6e46a480485f19256bdbda36ec78a84 Mon Sep 17 00:00:00 2001 From: Daniel Kiper Date: Mon, 30 Jun 2014 19:52:56 +0200 Subject: efi: Use early_mem*() instead of early_io*() Use early_mem*() instead of early_io*() because all mapped EFI regions are memory (usually RAM but they could also be ROM, EPROM, EEPROM, flash, etc.) not I/O regions. Additionally, I/O family calls do not work correctly under Xen in our case. early_ioremap() skips the PFN to MFN conversion when building the PTE. Using it for memory will attempt to map the wrong machine frame. However, all artificial EFI structures created under Xen live in dom0 memory and should be mapped/unmapped using early_mem*() family calls which map domain memory. Signed-off-by: Daniel Kiper Cc: Leif Lindholm Cc: Mark Salter Signed-off-by: Matt Fleming --- drivers/firmware/efi/efi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index cd36deb619fa..023937a63a48 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -298,7 +298,7 @@ int __init efi_config_init(efi_config_table_type_t *arch_tables) if (table64 >> 32) { pr_cont("\n"); pr_err("Table located above 4GB, disabling EFI.\n"); - early_iounmap(config_tables, + early_memunmap(config_tables, efi.systab->nr_tables * sz); return -EINVAL; } @@ -314,7 +314,7 @@ int __init efi_config_init(efi_config_table_type_t *arch_tables) tablep += sz; } pr_cont("\n"); - early_iounmap(config_tables, efi.systab->nr_tables * sz); + early_memunmap(config_tables, efi.systab->nr_tables * sz); set_bit(EFI_CONFIG_TABLES, &efi.flags); -- cgit v1.2.3 From 9f27bc543bdf92e179927037e2ab8ed0261579a9 Mon Sep 17 00:00:00 2001 From: Daniel Kiper Date: Mon, 30 Jun 2014 19:52:58 +0200 Subject: efi: Introduce EFI_PARAVIRT flag Introduce EFI_PARAVIRT flag. If it is set then kernel runs on EFI platform but it has not direct control on EFI stuff like EFI runtime, tables, structures, etc. If not this means that Linux Kernel has direct access to EFI infrastructure and everything runs as usual. This functionality is used in Xen dom0 because hypervisor has full control on EFI stuff and all calls from dom0 to EFI must be requested via special hypercall which in turn executes relevant EFI code in behalf of dom0. Signed-off-by: Daniel Kiper Signed-off-by: Matt Fleming --- drivers/firmware/efi/efi.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index 023937a63a48..ac88ec05eb70 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -104,16 +104,19 @@ static struct attribute *efi_subsys_attrs[] = { static umode_t efi_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) { - umode_t mode = attr->mode; - - if (attr == &efi_attr_fw_vendor.attr) - return (efi.fw_vendor == EFI_INVALID_TABLE_ADDR) ? 0 : mode; - else if (attr == &efi_attr_runtime.attr) - return (efi.runtime == EFI_INVALID_TABLE_ADDR) ? 0 : mode; - else if (attr == &efi_attr_config_table.attr) - return (efi.config_table == EFI_INVALID_TABLE_ADDR) ? 0 : mode; + if (attr == &efi_attr_fw_vendor.attr) { + if (efi_enabled(EFI_PARAVIRT) || + efi.fw_vendor == EFI_INVALID_TABLE_ADDR) + return 0; + } else if (attr == &efi_attr_runtime.attr) { + if (efi.runtime == EFI_INVALID_TABLE_ADDR) + return 0; + } else if (attr == &efi_attr_config_table.attr) { + if (efi.config_table == EFI_INVALID_TABLE_ADDR) + return 0; + } - return mode; + return attr->mode; } static struct attribute_group efi_subsys_attr_group = { -- cgit v1.2.3 From 28d54022e6ff9c16bf4dacb5f64a97443a38caa9 Mon Sep 17 00:00:00 2001 From: "Lee, Chun-Yi" Date: Wed, 9 Jul 2014 18:39:29 +0800 Subject: efi: Autoload efivars The original patch is from Ben Hutchings's contribution to debian kernel. Got Ben's permission to remove the code of efi-pstore.c and send to linux-efi: https://github.com/BlankOn/linux-debian/blob/master/debian/patches/features/all/efi-autoload-efivars.patch efivars is generally useful to have on EFI systems, and in some cases it may be impossible to load it after a kernel upgrade in order to complete a boot loader update. At the same time we don't want to waste memory on non-EFI systems by making them built-in. Instead, give them module aliases as if they are platform drivers, and register a corresponding platform device whenever EFI runtime services are available. This should trigger udev to load them. Signed-off-by: Lee, Chun-Yi Cc: Ben Hutchings Tested-by: Ard Biesheuvel Signed-off-by: Matt Fleming --- drivers/firmware/efi/efi.c | 15 +++++++++++++++ drivers/firmware/efi/efivars.c | 1 + 2 files changed, 16 insertions(+) (limited to 'drivers/firmware') diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index ac88ec05eb70..36ffa1747e84 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -23,6 +23,7 @@ #include #include #include +#include struct efi __read_mostly efi = { .mps = EFI_INVALID_TABLE_ADDR, @@ -324,6 +325,20 @@ int __init efi_config_init(efi_config_table_type_t *arch_tables) return 0; } +#ifdef CONFIG_EFI_VARS_MODULE +static int __init efi_load_efivars(void) +{ + struct platform_device *pdev; + + if (!efi_enabled(EFI_RUNTIME_SERVICES)) + return 0; + + pdev = platform_device_register_simple("efivars", 0, NULL, 0); + return IS_ERR(pdev) ? PTR_ERR(pdev) : 0; +} +device_initcall(efi_load_efivars); +#endif + #ifdef CONFIG_EFI_PARAMS_FROM_FDT #define UEFI_PARAM(name, prop, field) \ diff --git a/drivers/firmware/efi/efivars.c b/drivers/firmware/efi/efivars.c index 463c56545ae8..f256ecd8a176 100644 --- a/drivers/firmware/efi/efivars.c +++ b/drivers/firmware/efi/efivars.c @@ -78,6 +78,7 @@ MODULE_AUTHOR("Matt Domsch "); MODULE_DESCRIPTION("sysfs interface to EFI Variables"); MODULE_LICENSE("GPL"); MODULE_VERSION(EFIVARS_VERSION); +MODULE_ALIAS("platform:efivars"); LIST_HEAD(efivar_sysfs_list); EXPORT_SYMBOL_GPL(efivar_sysfs_list); -- cgit v1.2.3