Merge branch 'arm64-efi-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip into next

Pull ARM64 EFI update from Peter Anvin:
 "By agreement with the ARM64 EFI maintainers, we have agreed to make
  -tip the upstream for all EFI patches.  That is why this patchset
  comes from me :)

  This patchset enables EFI stub support for ARM64, like we already have
  on x86"

* 'arm64-efi-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  arm64: efi: only attempt efi map setup if booting via EFI
  efi/arm64: ignore dtb= when UEFI SecureBoot is enabled
  doc: arm64: add description of EFI stub support
  arm64: efi: add EFI stub
  doc: arm: add UEFI support documentation
  arm64: add EFI runtime services
  efi: Add shared FDT related functions for ARM/ARM64
  arm64: Add function to create identity mappings
  efi: add helper function to get UEFI params from FDT
  doc: efi-stub.txt updates for ARM
  lib: add fdt_empty_tree.c
This commit is contained in:
Linus Torvalds 2014-06-05 13:15:32 -07:00
commit c3c55a0720
21 changed files with 1619 additions and 26 deletions

View file

@ -47,6 +47,13 @@ config EFI_RUNTIME_MAP
See also Documentation/ABI/testing/sysfs-firmware-efi-runtime-map.
config EFI_PARAMS_FROM_FDT
bool
help
Select this config option from the architecture Kconfig if
the EFI runtime support gets system table address, memory
map address, and other parameters from the device tree.
endmenu
config UEFI_CPER

View file

@ -0,0 +1,278 @@
/*
* EFI stub implementation that is shared by arm and arm64 architectures.
* This should be #included by the EFI stub implementation files.
*
* Copyright (C) 2013,2014 Linaro Limited
* Roy Franz <roy.franz@linaro.org
* Copyright (C) 2013 Red Hat, Inc.
* Mark Salter <msalter@redhat.com>
*
* This file is part of the Linux kernel, and is made available under the
* terms of the GNU General Public License version 2.
*
*/
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;
}
}
static 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;
}
static efi_status_t efi_file_close(void *handle)
{
efi_file_handle_t *fh = handle;
return fh->close(handle);
}
static 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);
}
static 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;
}
static 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.
*/
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 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;
}

View file

@ -20,6 +20,8 @@
#include <linux/init.h>
#include <linux/device.h>
#include <linux/efi.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/io.h>
struct efi __read_mostly efi = {
@ -318,3 +320,80 @@ int __init efi_config_init(efi_config_table_type_t *arch_tables)
return 0;
}
#ifdef CONFIG_EFI_PARAMS_FROM_FDT
#define UEFI_PARAM(name, prop, field) \
{ \
{ name }, \
{ prop }, \
offsetof(struct efi_fdt_params, field), \
FIELD_SIZEOF(struct efi_fdt_params, field) \
}
static __initdata struct {
const char name[32];
const char propname[32];
int offset;
int size;
} dt_params[] = {
UEFI_PARAM("System Table", "linux,uefi-system-table", system_table),
UEFI_PARAM("MemMap Address", "linux,uefi-mmap-start", mmap),
UEFI_PARAM("MemMap Size", "linux,uefi-mmap-size", mmap_size),
UEFI_PARAM("MemMap Desc. Size", "linux,uefi-mmap-desc-size", desc_size),
UEFI_PARAM("MemMap Desc. Version", "linux,uefi-mmap-desc-ver", desc_ver)
};
struct param_info {
int verbose;
void *params;
};
static int __init fdt_find_uefi_params(unsigned long node, const char *uname,
int depth, void *data)
{
struct param_info *info = data;
void *prop, *dest;
unsigned long len;
u64 val;
int i;
if (depth != 1 ||
(strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
return 0;
pr_info("Getting parameters from FDT:\n");
for (i = 0; i < ARRAY_SIZE(dt_params); i++) {
prop = of_get_flat_dt_prop(node, dt_params[i].propname, &len);
if (!prop) {
pr_err("Can't find %s in device tree!\n",
dt_params[i].name);
return 0;
}
dest = info->params + dt_params[i].offset;
val = of_read_number(prop, len / sizeof(u32));
if (dt_params[i].size == sizeof(u32))
*(u32 *)dest = val;
else
*(u64 *)dest = val;
if (info->verbose)
pr_info(" %s: 0x%0*llx\n", dt_params[i].name,
dt_params[i].size * 2, val);
}
return 1;
}
int __init efi_get_fdt_params(struct efi_fdt_params *params, int verbose)
{
struct param_info info;
info.verbose = verbose;
info.params = params;
return of_scan_flat_dt(fdt_find_uefi_params, &info);
}
#endif /* CONFIG_EFI_PARAMS_FROM_FDT */

285
drivers/firmware/efi/fdt.c Normal file
View file

@ -0,0 +1,285 @@
/*
* 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.
*
*/
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)
{
int node, prev;
int status;
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)) {
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;
}
static 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;
}