diff options
Diffstat (limited to 'drivers/firmware/efi')
| -rw-r--r-- | drivers/firmware/efi/Kconfig | 21 | ||||
| -rw-r--r-- | drivers/firmware/efi/Makefile | 4 | ||||
| -rw-r--r-- | drivers/firmware/efi/arm-stub.c | 278 | ||||
| -rw-r--r-- | drivers/firmware/efi/cper.c | 410 | ||||
| -rw-r--r-- | drivers/firmware/efi/efi-pstore.c | 164 | ||||
| -rw-r--r-- | drivers/firmware/efi/efi-stub-helper.c | 634 | ||||
| -rw-r--r-- | drivers/firmware/efi/efi.c | 275 | ||||
| -rw-r--r-- | drivers/firmware/efi/efivars.c | 206 | ||||
| -rw-r--r-- | drivers/firmware/efi/fdt.c | 275 | ||||
| -rw-r--r-- | drivers/firmware/efi/runtime-map.c | 181 | ||||
| -rw-r--r-- | drivers/firmware/efi/vars.c | 42 | 
11 files changed, 2420 insertions, 70 deletions
diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig index b0fc7c79dfb..d420ae2d341 100644 --- a/drivers/firmware/efi/Kconfig +++ b/drivers/firmware/efi/Kconfig @@ -36,4 +36,25 @@ config EFI_VARS_PSTORE_DEFAULT_DISABLE  	  backend for pstore by default. This setting can be overridden  	  using the efivars module's pstore_disable parameter. +config EFI_RUNTIME_MAP +	bool "Export efi runtime maps to sysfs" +	depends on X86 && EFI && KEXEC +	default y +	help +	  Export efi runtime memory maps to /sys/firmware/efi/runtime-map. +	  That memory map is used for example by kexec to set up efi virtual +	  mapping the 2nd kernel, but can also be used for debugging purposes. + +	  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 +	bool diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile index 99245ab5a79..9553496b0f4 100644 --- a/drivers/firmware/efi/Makefile +++ b/drivers/firmware/efi/Makefile @@ -1,6 +1,8 @@  #  # Makefile for linux kernel  # -obj-y					+= efi.o vars.o +obj-$(CONFIG_EFI)			+= efi.o vars.o  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 diff --git a/drivers/firmware/efi/arm-stub.c b/drivers/firmware/efi/arm-stub.c new file mode 100644 index 00000000000..41114ce03b0 --- /dev/null +++ b/drivers/firmware/efi/arm-stub.c @@ -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; +} diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c new file mode 100644 index 00000000000..1491dd4f08f --- /dev/null +++ b/drivers/firmware/efi/cper.c @@ -0,0 +1,410 @@ +/* + * UEFI Common Platform Error Record (CPER) support + * + * Copyright (C) 2010, Intel Corp. + *	Author: Huang Ying <ying.huang@intel.com> + * + * CPER is the format used to describe platform hardware error by + * various tables, such as ERST, BERT and HEST etc. + * + * For more information about CPER, please refer to Appendix N of UEFI + * Specification version 2.4. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/time.h> +#include <linux/cper.h> +#include <linux/dmi.h> +#include <linux/acpi.h> +#include <linux/pci.h> +#include <linux/aer.h> + +#define INDENT_SP	" " +/* + * CPER record ID need to be unique even after reboot, because record + * ID is used as index for ERST storage, while CPER records from + * multiple boot may co-exist in ERST. + */ +u64 cper_next_record_id(void) +{ +	static atomic64_t seq; + +	if (!atomic64_read(&seq)) +		atomic64_set(&seq, ((u64)get_seconds()) << 32); + +	return atomic64_inc_return(&seq); +} +EXPORT_SYMBOL_GPL(cper_next_record_id); + +static const char *cper_severity_strs[] = { +	"recoverable", +	"fatal", +	"corrected", +	"info", +}; + +static const char *cper_severity_str(unsigned int severity) +{ +	return severity < ARRAY_SIZE(cper_severity_strs) ? +		cper_severity_strs[severity] : "unknown"; +} + +/* + * cper_print_bits - print strings for set bits + * @pfx: prefix for each line, including log level and prefix string + * @bits: bit mask + * @strs: string array, indexed by bit position + * @strs_size: size of the string array: @strs + * + * For each set bit in @bits, print the corresponding string in @strs. + * If the output length is longer than 80, multiple line will be + * printed, with @pfx is printed at the beginning of each line. + */ +void cper_print_bits(const char *pfx, unsigned int bits, +		     const char * const strs[], unsigned int strs_size) +{ +	int i, len = 0; +	const char *str; +	char buf[84]; + +	for (i = 0; i < strs_size; i++) { +		if (!(bits & (1U << i))) +			continue; +		str = strs[i]; +		if (!str) +			continue; +		if (len && len + strlen(str) + 2 > 80) { +			printk("%s\n", buf); +			len = 0; +		} +		if (!len) +			len = snprintf(buf, sizeof(buf), "%s%s", pfx, str); +		else +			len += snprintf(buf+len, sizeof(buf)-len, ", %s", str); +	} +	if (len) +		printk("%s\n", buf); +} + +static const char * const cper_proc_type_strs[] = { +	"IA32/X64", +	"IA64", +}; + +static const char * const cper_proc_isa_strs[] = { +	"IA32", +	"IA64", +	"X64", +}; + +static const char * const cper_proc_error_type_strs[] = { +	"cache error", +	"TLB error", +	"bus error", +	"micro-architectural error", +}; + +static const char * const cper_proc_op_strs[] = { +	"unknown or generic", +	"data read", +	"data write", +	"instruction execution", +}; + +static const char * const cper_proc_flag_strs[] = { +	"restartable", +	"precise IP", +	"overflow", +	"corrected", +}; + +static void cper_print_proc_generic(const char *pfx, +				    const struct cper_sec_proc_generic *proc) +{ +	if (proc->validation_bits & CPER_PROC_VALID_TYPE) +		printk("%s""processor_type: %d, %s\n", pfx, proc->proc_type, +		       proc->proc_type < ARRAY_SIZE(cper_proc_type_strs) ? +		       cper_proc_type_strs[proc->proc_type] : "unknown"); +	if (proc->validation_bits & CPER_PROC_VALID_ISA) +		printk("%s""processor_isa: %d, %s\n", pfx, proc->proc_isa, +		       proc->proc_isa < ARRAY_SIZE(cper_proc_isa_strs) ? +		       cper_proc_isa_strs[proc->proc_isa] : "unknown"); +	if (proc->validation_bits & CPER_PROC_VALID_ERROR_TYPE) { +		printk("%s""error_type: 0x%02x\n", pfx, proc->proc_error_type); +		cper_print_bits(pfx, proc->proc_error_type, +				cper_proc_error_type_strs, +				ARRAY_SIZE(cper_proc_error_type_strs)); +	} +	if (proc->validation_bits & CPER_PROC_VALID_OPERATION) +		printk("%s""operation: %d, %s\n", pfx, proc->operation, +		       proc->operation < ARRAY_SIZE(cper_proc_op_strs) ? +		       cper_proc_op_strs[proc->operation] : "unknown"); +	if (proc->validation_bits & CPER_PROC_VALID_FLAGS) { +		printk("%s""flags: 0x%02x\n", pfx, proc->flags); +		cper_print_bits(pfx, proc->flags, cper_proc_flag_strs, +				ARRAY_SIZE(cper_proc_flag_strs)); +	} +	if (proc->validation_bits & CPER_PROC_VALID_LEVEL) +		printk("%s""level: %d\n", pfx, proc->level); +	if (proc->validation_bits & CPER_PROC_VALID_VERSION) +		printk("%s""version_info: 0x%016llx\n", pfx, proc->cpu_version); +	if (proc->validation_bits & CPER_PROC_VALID_ID) +		printk("%s""processor_id: 0x%016llx\n", pfx, proc->proc_id); +	if (proc->validation_bits & CPER_PROC_VALID_TARGET_ADDRESS) +		printk("%s""target_address: 0x%016llx\n", +		       pfx, proc->target_addr); +	if (proc->validation_bits & CPER_PROC_VALID_REQUESTOR_ID) +		printk("%s""requestor_id: 0x%016llx\n", +		       pfx, proc->requestor_id); +	if (proc->validation_bits & CPER_PROC_VALID_RESPONDER_ID) +		printk("%s""responder_id: 0x%016llx\n", +		       pfx, proc->responder_id); +	if (proc->validation_bits & CPER_PROC_VALID_IP) +		printk("%s""IP: 0x%016llx\n", pfx, proc->ip); +} + +static const char *cper_mem_err_type_strs[] = { +	"unknown", +	"no error", +	"single-bit ECC", +	"multi-bit ECC", +	"single-symbol chipkill ECC", +	"multi-symbol chipkill ECC", +	"master abort", +	"target abort", +	"parity error", +	"watchdog timeout", +	"invalid address", +	"mirror Broken", +	"memory sparing", +	"scrub corrected error", +	"scrub uncorrected error", +	"physical memory map-out event", +}; + +static void cper_print_mem(const char *pfx, const struct cper_sec_mem_err *mem) +{ +	if (mem->validation_bits & CPER_MEM_VALID_ERROR_STATUS) +		printk("%s""error_status: 0x%016llx\n", pfx, mem->error_status); +	if (mem->validation_bits & CPER_MEM_VALID_PA) +		printk("%s""physical_address: 0x%016llx\n", +		       pfx, mem->physical_addr); +	if (mem->validation_bits & CPER_MEM_VALID_PA_MASK) +		printk("%s""physical_address_mask: 0x%016llx\n", +		       pfx, mem->physical_addr_mask); +	if (mem->validation_bits & CPER_MEM_VALID_NODE) +		pr_debug("node: %d\n", mem->node); +	if (mem->validation_bits & CPER_MEM_VALID_CARD) +		pr_debug("card: %d\n", mem->card); +	if (mem->validation_bits & CPER_MEM_VALID_MODULE) +		pr_debug("module: %d\n", mem->module); +	if (mem->validation_bits & CPER_MEM_VALID_RANK_NUMBER) +		pr_debug("rank: %d\n", mem->rank); +	if (mem->validation_bits & CPER_MEM_VALID_BANK) +		pr_debug("bank: %d\n", mem->bank); +	if (mem->validation_bits & CPER_MEM_VALID_DEVICE) +		pr_debug("device: %d\n", mem->device); +	if (mem->validation_bits & CPER_MEM_VALID_ROW) +		pr_debug("row: %d\n", mem->row); +	if (mem->validation_bits & CPER_MEM_VALID_COLUMN) +		pr_debug("column: %d\n", mem->column); +	if (mem->validation_bits & CPER_MEM_VALID_BIT_POSITION) +		pr_debug("bit_position: %d\n", mem->bit_pos); +	if (mem->validation_bits & CPER_MEM_VALID_REQUESTOR_ID) +		pr_debug("requestor_id: 0x%016llx\n", mem->requestor_id); +	if (mem->validation_bits & CPER_MEM_VALID_RESPONDER_ID) +		pr_debug("responder_id: 0x%016llx\n", mem->responder_id); +	if (mem->validation_bits & CPER_MEM_VALID_TARGET_ID) +		pr_debug("target_id: 0x%016llx\n", mem->target_id); +	if (mem->validation_bits & CPER_MEM_VALID_ERROR_TYPE) { +		u8 etype = mem->error_type; +		printk("%s""error_type: %d, %s\n", pfx, etype, +		       etype < ARRAY_SIZE(cper_mem_err_type_strs) ? +		       cper_mem_err_type_strs[etype] : "unknown"); +	} +	if (mem->validation_bits & CPER_MEM_VALID_MODULE_HANDLE) { +		const char *bank = NULL, *device = NULL; +		dmi_memdev_name(mem->mem_dev_handle, &bank, &device); +		if (bank != NULL && device != NULL) +			printk("%s""DIMM location: %s %s", pfx, bank, device); +		else +			printk("%s""DIMM DMI handle: 0x%.4x", +			       pfx, mem->mem_dev_handle); +	} +} + +static const char *cper_pcie_port_type_strs[] = { +	"PCIe end point", +	"legacy PCI end point", +	"unknown", +	"unknown", +	"root port", +	"upstream switch port", +	"downstream switch port", +	"PCIe to PCI/PCI-X bridge", +	"PCI/PCI-X to PCIe bridge", +	"root complex integrated endpoint device", +	"root complex event collector", +}; + +static void cper_print_pcie(const char *pfx, const struct cper_sec_pcie *pcie, +			    const struct acpi_generic_data *gdata) +{ +	if (pcie->validation_bits & CPER_PCIE_VALID_PORT_TYPE) +		printk("%s""port_type: %d, %s\n", pfx, pcie->port_type, +		       pcie->port_type < ARRAY_SIZE(cper_pcie_port_type_strs) ? +		       cper_pcie_port_type_strs[pcie->port_type] : "unknown"); +	if (pcie->validation_bits & CPER_PCIE_VALID_VERSION) +		printk("%s""version: %d.%d\n", pfx, +		       pcie->version.major, pcie->version.minor); +	if (pcie->validation_bits & CPER_PCIE_VALID_COMMAND_STATUS) +		printk("%s""command: 0x%04x, status: 0x%04x\n", pfx, +		       pcie->command, pcie->status); +	if (pcie->validation_bits & CPER_PCIE_VALID_DEVICE_ID) { +		const __u8 *p; +		printk("%s""device_id: %04x:%02x:%02x.%x\n", pfx, +		       pcie->device_id.segment, pcie->device_id.bus, +		       pcie->device_id.device, pcie->device_id.function); +		printk("%s""slot: %d\n", pfx, +		       pcie->device_id.slot >> CPER_PCIE_SLOT_SHIFT); +		printk("%s""secondary_bus: 0x%02x\n", pfx, +		       pcie->device_id.secondary_bus); +		printk("%s""vendor_id: 0x%04x, device_id: 0x%04x\n", pfx, +		       pcie->device_id.vendor_id, pcie->device_id.device_id); +		p = pcie->device_id.class_code; +		printk("%s""class_code: %02x%02x%02x\n", pfx, p[0], p[1], p[2]); +	} +	if (pcie->validation_bits & CPER_PCIE_VALID_SERIAL_NUMBER) +		printk("%s""serial number: 0x%04x, 0x%04x\n", pfx, +		       pcie->serial_number.lower, pcie->serial_number.upper); +	if (pcie->validation_bits & CPER_PCIE_VALID_BRIDGE_CONTROL_STATUS) +		printk( +	"%s""bridge: secondary_status: 0x%04x, control: 0x%04x\n", +	pfx, pcie->bridge.secondary_status, pcie->bridge.control); +} + +static void cper_estatus_print_section( +	const char *pfx, const struct acpi_generic_data *gdata, int sec_no) +{ +	uuid_le *sec_type = (uuid_le *)gdata->section_type; +	__u16 severity; +	char newpfx[64]; + +	severity = gdata->error_severity; +	printk("%s""Error %d, type: %s\n", pfx, sec_no, +	       cper_severity_str(severity)); +	if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID) +		printk("%s""fru_id: %pUl\n", pfx, (uuid_le *)gdata->fru_id); +	if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT) +		printk("%s""fru_text: %.20s\n", pfx, gdata->fru_text); + +	snprintf(newpfx, sizeof(newpfx), "%s%s", pfx, INDENT_SP); +	if (!uuid_le_cmp(*sec_type, CPER_SEC_PROC_GENERIC)) { +		struct cper_sec_proc_generic *proc_err = (void *)(gdata + 1); +		printk("%s""section_type: general processor error\n", newpfx); +		if (gdata->error_data_length >= sizeof(*proc_err)) +			cper_print_proc_generic(newpfx, proc_err); +		else +			goto err_section_too_small; +	} else if (!uuid_le_cmp(*sec_type, CPER_SEC_PLATFORM_MEM)) { +		struct cper_sec_mem_err *mem_err = (void *)(gdata + 1); +		printk("%s""section_type: memory error\n", newpfx); +		if (gdata->error_data_length >= sizeof(*mem_err)) +			cper_print_mem(newpfx, mem_err); +		else +			goto err_section_too_small; +	} else if (!uuid_le_cmp(*sec_type, CPER_SEC_PCIE)) { +		struct cper_sec_pcie *pcie = (void *)(gdata + 1); +		printk("%s""section_type: PCIe error\n", newpfx); +		if (gdata->error_data_length >= sizeof(*pcie)) +			cper_print_pcie(newpfx, pcie, gdata); +		else +			goto err_section_too_small; +	} else +		printk("%s""section type: unknown, %pUl\n", newpfx, sec_type); + +	return; + +err_section_too_small: +	pr_err(FW_WARN "error section length is too small\n"); +} + +void cper_estatus_print(const char *pfx, +			const struct acpi_generic_status *estatus) +{ +	struct acpi_generic_data *gdata; +	unsigned int data_len, gedata_len; +	int sec_no = 0; +	char newpfx[64]; +	__u16 severity; + +	severity = estatus->error_severity; +	if (severity == CPER_SEV_CORRECTED) +		printk("%s%s\n", pfx, +		       "It has been corrected by h/w " +		       "and requires no further action"); +	printk("%s""event severity: %s\n", pfx, cper_severity_str(severity)); +	data_len = estatus->data_length; +	gdata = (struct acpi_generic_data *)(estatus + 1); +	snprintf(newpfx, sizeof(newpfx), "%s%s", pfx, INDENT_SP); +	while (data_len >= sizeof(*gdata)) { +		gedata_len = gdata->error_data_length; +		cper_estatus_print_section(newpfx, gdata, sec_no); +		data_len -= gedata_len + sizeof(*gdata); +		gdata = (void *)(gdata + 1) + gedata_len; +		sec_no++; +	} +} +EXPORT_SYMBOL_GPL(cper_estatus_print); + +int cper_estatus_check_header(const struct acpi_generic_status *estatus) +{ +	if (estatus->data_length && +	    estatus->data_length < sizeof(struct acpi_generic_data)) +		return -EINVAL; +	if (estatus->raw_data_length && +	    estatus->raw_data_offset < sizeof(*estatus) + estatus->data_length) +		return -EINVAL; + +	return 0; +} +EXPORT_SYMBOL_GPL(cper_estatus_check_header); + +int cper_estatus_check(const struct acpi_generic_status *estatus) +{ +	struct acpi_generic_data *gdata; +	unsigned int data_len, gedata_len; +	int rc; + +	rc = cper_estatus_check_header(estatus); +	if (rc) +		return rc; +	data_len = estatus->data_length; +	gdata = (struct acpi_generic_data *)(estatus + 1); +	while (data_len >= sizeof(*gdata)) { +		gedata_len = gdata->error_data_length; +		if (gedata_len > data_len - sizeof(*gdata)) +			return -EINVAL; +		data_len -= gedata_len + sizeof(*gdata); +		gdata = (void *)(gdata + 1) + gedata_len; +	} +	if (data_len) +		return -EINVAL; + +	return 0; +} +EXPORT_SYMBOL_GPL(cper_estatus_check); diff --git a/drivers/firmware/efi/efi-pstore.c b/drivers/firmware/efi/efi-pstore.c index 5002d50e378..e992abc5ef2 100644 --- a/drivers/firmware/efi/efi-pstore.c +++ b/drivers/firmware/efi/efi-pstore.c @@ -18,14 +18,12 @@ module_param_named(pstore_disable, efivars_pstore_disable, bool, 0644);  static int efi_pstore_open(struct pstore_info *psi)  { -	efivar_entry_iter_begin();  	psi->data = NULL;  	return 0;  }  static int efi_pstore_close(struct pstore_info *psi)  { -	efivar_entry_iter_end();  	psi->data = NULL;  	return 0;  } @@ -39,6 +37,12 @@ struct pstore_read_data {  	char **buf;  }; +static inline u64 generic_id(unsigned long timestamp, +			     unsigned int part, int count) +{ +	return ((u64) timestamp * 100 + part) * 1000 + count; +} +  static int efi_pstore_read_func(struct efivar_entry *entry, void *data)  {  	efi_guid_t vendor = LINUX_EFI_CRASH_GUID; @@ -57,7 +61,7 @@ static int efi_pstore_read_func(struct efivar_entry *entry, void *data)  	if (sscanf(name, "dump-type%u-%u-%d-%lu-%c",  		   cb_data->type, &part, &cnt, &time, &data_type) == 5) { -		*cb_data->id = part; +		*cb_data->id = generic_id(time, part, cnt);  		*cb_data->count = cnt;  		cb_data->timespec->tv_sec = time;  		cb_data->timespec->tv_nsec = 0; @@ -67,7 +71,7 @@ static int efi_pstore_read_func(struct efivar_entry *entry, void *data)  			*cb_data->compressed = false;  	} else if (sscanf(name, "dump-type%u-%u-%d-%lu",  		   cb_data->type, &part, &cnt, &time) == 4) { -		*cb_data->id = part; +		*cb_data->id = generic_id(time, part, cnt);  		*cb_data->count = cnt;  		cb_data->timespec->tv_sec = time;  		cb_data->timespec->tv_nsec = 0; @@ -79,7 +83,7 @@ static int efi_pstore_read_func(struct efivar_entry *entry, void *data)  		 * which doesn't support holding  		 * multiple logs, remains.  		 */ -		*cb_data->id = part; +		*cb_data->id = generic_id(time, part, 0);  		*cb_data->count = 0;  		cb_data->timespec->tv_sec = time;  		cb_data->timespec->tv_nsec = 0; @@ -91,19 +95,125 @@ static int efi_pstore_read_func(struct efivar_entry *entry, void *data)  	__efivar_entry_get(entry, &entry->var.Attributes,  			   &entry->var.DataSize, entry->var.Data);  	size = entry->var.DataSize; +	memcpy(*cb_data->buf, entry->var.Data, +	       (size_t)min_t(unsigned long, EFIVARS_DATA_SIZE_MAX, size)); -	*cb_data->buf = kmemdup(entry->var.Data, size, GFP_KERNEL); -	if (*cb_data->buf == NULL) -		return -ENOMEM;  	return size;  } +/** + * efi_pstore_scan_sysfs_enter + * @entry: scanning entry + * @next: next entry + * @head: list head + */ +static void efi_pstore_scan_sysfs_enter(struct efivar_entry *pos, +					struct efivar_entry *next, +					struct list_head *head) +{ +	pos->scanning = true; +	if (&next->list != head) +		next->scanning = true; +} + +/** + * __efi_pstore_scan_sysfs_exit + * @entry: deleting entry + * @turn_off_scanning: Check if a scanning flag should be turned off + */ +static inline void __efi_pstore_scan_sysfs_exit(struct efivar_entry *entry, +						bool turn_off_scanning) +{ +	if (entry->deleting) { +		list_del(&entry->list); +		efivar_entry_iter_end(); +		efivar_unregister(entry); +		efivar_entry_iter_begin(); +	} else if (turn_off_scanning) +		entry->scanning = false; +} + +/** + * efi_pstore_scan_sysfs_exit + * @pos: scanning entry + * @next: next entry + * @head: list head + * @stop: a flag checking if scanning will stop + */ +static void efi_pstore_scan_sysfs_exit(struct efivar_entry *pos, +				       struct efivar_entry *next, +				       struct list_head *head, bool stop) +{ +	__efi_pstore_scan_sysfs_exit(pos, true); +	if (stop) +		__efi_pstore_scan_sysfs_exit(next, &next->list != head); +} + +/** + * efi_pstore_sysfs_entry_iter + * + * @data: function-specific data to pass to callback + * @pos: entry to begin iterating from + * + * You MUST call efivar_enter_iter_begin() before this function, and + * efivar_entry_iter_end() afterwards. + * + * It is possible to begin iteration from an arbitrary entry within + * the list by passing @pos. @pos is updated on return to point to + * the next entry of the last one passed to efi_pstore_read_func(). + * To begin iterating from the beginning of the list @pos must be %NULL. + */ +static int efi_pstore_sysfs_entry_iter(void *data, struct efivar_entry **pos) +{ +	struct efivar_entry *entry, *n; +	struct list_head *head = &efivar_sysfs_list; +	int size = 0; + +	if (!*pos) { +		list_for_each_entry_safe(entry, n, head, list) { +			efi_pstore_scan_sysfs_enter(entry, n, head); + +			size = efi_pstore_read_func(entry, data); +			efi_pstore_scan_sysfs_exit(entry, n, head, size < 0); +			if (size) +				break; +		} +		*pos = n; +		return size; +	} + +	list_for_each_entry_safe_from((*pos), n, head, list) { +		efi_pstore_scan_sysfs_enter((*pos), n, head); + +		size = efi_pstore_read_func((*pos), data); +		efi_pstore_scan_sysfs_exit((*pos), n, head, size < 0); +		if (size) +			break; +	} +	*pos = n; +	return size; +} + +/** + * efi_pstore_read + * + * This function returns a size of NVRAM entry logged via efi_pstore_write(). + * The meaning and behavior of efi_pstore/pstore are as below. + * + * size > 0: Got data of an entry logged via efi_pstore_write() successfully, + *           and pstore filesystem will continue reading subsequent entries. + * size == 0: Entry was not logged via efi_pstore_write(), + *            and efi_pstore driver will continue reading subsequent entries. + * size < 0: Failed to get data of entry logging via efi_pstore_write(), + *           and pstore will stop reading entry. + */  static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,  			       int *count, struct timespec *timespec,  			       char **buf, bool *compressed,  			       struct pstore_info *psi)  {  	struct pstore_read_data data; +	ssize_t size;  	data.id = id;  	data.type = type; @@ -112,8 +222,17 @@ static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,  	data.compressed = compressed;  	data.buf = buf; -	return __efivar_entry_iter(efi_pstore_read_func, &efivar_sysfs_list, &data, -				   (struct efivar_entry **)&psi->data); +	*data.buf = kzalloc(EFIVARS_DATA_SIZE_MAX, GFP_KERNEL); +	if (!*data.buf) +		return -ENOMEM; + +	efivar_entry_iter_begin(); +	size = efi_pstore_sysfs_entry_iter(&data, +					   (struct efivar_entry **)&psi->data); +	efivar_entry_iter_end(); +	if (size <= 0) +		kfree(*data.buf); +	return size;  }  static int efi_pstore_write(enum pstore_type_id type, @@ -184,9 +303,17 @@ static int efi_pstore_erase_func(struct efivar_entry *entry, void *data)  			return 0;  	} +	if (entry->scanning) { +		/* +		 * Skip deletion because this entry will be deleted +		 * after scanning is completed. +		 */ +		entry->deleting = true; +	} else +		list_del(&entry->list); +  	/* found */  	__efivar_entry_delete(entry); -	list_del(&entry->list);  	return 1;  } @@ -199,14 +326,16 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,  	char name[DUMP_NAME_LEN];  	efi_char16_t efi_name[DUMP_NAME_LEN];  	int found, i; +	unsigned int part; -	sprintf(name, "dump-type%u-%u-%d-%lu", type, (unsigned int)id, count, -		time.tv_sec); +	do_div(id, 1000); +	part = do_div(id, 100); +	sprintf(name, "dump-type%u-%u-%d-%lu", type, part, count, time.tv_sec);  	for (i = 0; i < DUMP_NAME_LEN; i++)  		efi_name[i] = name[i]; -	edata.id = id; +	edata.id = part;  	edata.type = type;  	edata.count = count;  	edata.time = time; @@ -214,10 +343,12 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,  	efivar_entry_iter_begin();  	found = __efivar_entry_iter(efi_pstore_erase_func, &efivar_sysfs_list, &edata, &entry); -	efivar_entry_iter_end(); -	if (found) +	if (found && !entry->scanning) { +		efivar_entry_iter_end();  		efivar_unregister(entry); +	} else +		efivar_entry_iter_end();  	return 0;  } @@ -225,6 +356,7 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,  static struct pstore_info efi_pstore_info = {  	.owner		= THIS_MODULE,  	.name		= "efi", +	.flags		= PSTORE_FLAGS_FRAGILE,  	.open		= efi_pstore_open,  	.close		= efi_pstore_close,  	.read		= efi_pstore_read, diff --git a/drivers/firmware/efi/efi-stub-helper.c b/drivers/firmware/efi/efi-stub-helper.c new file mode 100644 index 00000000000..eb6d4be9e72 --- /dev/null +++ b/drivers/firmware/efi/efi-stub-helper.c @@ -0,0 +1,634 @@ +/* + * 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. + * + */ +#define EFI_READ_CHUNK_SIZE	(1024 * 1024) + +/* error code which can't be mistaken for valid address */ +#define EFI_ERROR	(~0UL) + + +struct file_info { +	efi_file_handle_t *handle; +	u64 size; +}; + +static 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); +	} +} + +#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_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; +} + + +static 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'. + */ +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) +{ +	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. + */ +static 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; +} + +static 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. + */ +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) +{ +	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. + */ +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) +{ +	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. + */ +static 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/efi.c b/drivers/firmware/efi/efi.c index 5145fa344ad..dc79346689e 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -13,11 +13,32 @@   * This file is released under the GPLv2.   */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +  #include <linux/kobject.h>  #include <linux/module.h>  #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 = { +	.mps        = EFI_INVALID_TABLE_ADDR, +	.acpi       = EFI_INVALID_TABLE_ADDR, +	.acpi20     = EFI_INVALID_TABLE_ADDR, +	.smbios     = EFI_INVALID_TABLE_ADDR, +	.sal_systab = EFI_INVALID_TABLE_ADDR, +	.boot_info  = EFI_INVALID_TABLE_ADDR, +	.hcdp       = EFI_INVALID_TABLE_ADDR, +	.uga        = EFI_INVALID_TABLE_ADDR, +	.uv_systab  = EFI_INVALID_TABLE_ADDR, +	.fw_vendor  = EFI_INVALID_TABLE_ADDR, +	.runtime    = EFI_INVALID_TABLE_ADDR, +	.config_table  = EFI_INVALID_TABLE_ADDR, +}; +EXPORT_SYMBOL(efi);  static struct kobject *efi_kobj;  static struct kobject *efivars_kobj; @@ -55,13 +76,49 @@ static ssize_t systab_show(struct kobject *kobj,  static struct kobj_attribute efi_attr_systab =  			__ATTR(systab, 0400, systab_show, NULL); +#define EFI_FIELD(var) efi.var + +#define EFI_ATTR_SHOW(name) \ +static ssize_t name##_show(struct kobject *kobj, \ +				struct kobj_attribute *attr, char *buf) \ +{ \ +	return sprintf(buf, "0x%lx\n", EFI_FIELD(name)); \ +} + +EFI_ATTR_SHOW(fw_vendor); +EFI_ATTR_SHOW(runtime); +EFI_ATTR_SHOW(config_table); + +static struct kobj_attribute efi_attr_fw_vendor = __ATTR_RO(fw_vendor); +static struct kobj_attribute efi_attr_runtime = __ATTR_RO(runtime); +static struct kobj_attribute efi_attr_config_table = __ATTR_RO(config_table); +  static struct attribute *efi_subsys_attrs[] = {  	&efi_attr_systab.attr, -	NULL,	/* maybe more in the future? */ +	&efi_attr_fw_vendor.attr, +	&efi_attr_runtime.attr, +	&efi_attr_config_table.attr, +	NULL,  }; +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; + +	return mode; +} +  static struct attribute_group efi_subsys_attr_group = {  	.attrs = efi_subsys_attrs, +	.is_visible = efi_attr_is_visible,  };  static struct efivars generic_efivars; @@ -112,6 +169,10 @@ static int __init efisubsys_init(void)  		goto err_unregister;  	} +	error = efi_runtime_map_init(efi_kobj); +	if (error) +		goto err_remove_group; +  	/* and the standard mountpoint for efivarfs */  	efivars_kobj = kobject_create_and_add("efivars", efi_kobj);  	if (!efivars_kobj) { @@ -132,3 +193,215 @@ err_put:  }  subsys_initcall(efisubsys_init); + + +/* + * We can't ioremap data in EFI boot services RAM, because we've already mapped + * it as RAM.  So, look it up in the existing EFI memory map instead.  Only + * callable after efi_enter_virtual_mode and before efi_free_boot_services. + */ +void __iomem *efi_lookup_mapped_addr(u64 phys_addr) +{ +	struct efi_memory_map *map; +	void *p; +	map = efi.memmap; +	if (!map) +		return NULL; +	if (WARN_ON(!map->map)) +		return NULL; +	for (p = map->map; p < map->map_end; p += map->desc_size) { +		efi_memory_desc_t *md = p; +		u64 size = md->num_pages << EFI_PAGE_SHIFT; +		u64 end = md->phys_addr + size; +		if (!(md->attribute & EFI_MEMORY_RUNTIME) && +		    md->type != EFI_BOOT_SERVICES_CODE && +		    md->type != EFI_BOOT_SERVICES_DATA) +			continue; +		if (!md->virt_addr) +			continue; +		if (phys_addr >= md->phys_addr && phys_addr < end) { +			phys_addr += md->virt_addr - md->phys_addr; +			return (__force void __iomem *)(unsigned long)phys_addr; +		} +	} +	return NULL; +} + +static __initdata efi_config_table_type_t common_tables[] = { +	{ACPI_20_TABLE_GUID, "ACPI 2.0", &efi.acpi20}, +	{ACPI_TABLE_GUID, "ACPI", &efi.acpi}, +	{HCDP_TABLE_GUID, "HCDP", &efi.hcdp}, +	{MPS_TABLE_GUID, "MPS", &efi.mps}, +	{SAL_SYSTEM_TABLE_GUID, "SALsystab", &efi.sal_systab}, +	{SMBIOS_TABLE_GUID, "SMBIOS", &efi.smbios}, +	{UGA_IO_PROTOCOL_GUID, "UGA", &efi.uga}, +	{NULL_GUID, NULL, NULL}, +}; + +static __init int match_config_table(efi_guid_t *guid, +				     unsigned long table, +				     efi_config_table_type_t *table_types) +{ +	u8 str[EFI_VARIABLE_GUID_LEN + 1]; +	int i; + +	if (table_types) { +		efi_guid_unparse(guid, str); + +		for (i = 0; efi_guidcmp(table_types[i].guid, NULL_GUID); i++) { +			efi_guid_unparse(&table_types[i].guid, str); + +			if (!efi_guidcmp(*guid, table_types[i].guid)) { +				*(table_types[i].ptr) = table; +				pr_cont(" %s=0x%lx ", +					table_types[i].name, table); +				return 1; +			} +		} +	} + +	return 0; +} + +int __init efi_config_init(efi_config_table_type_t *arch_tables) +{ +	void *config_tables, *tablep; +	int i, sz; + +	if (efi_enabled(EFI_64BIT)) +		sz = sizeof(efi_config_table_64_t); +	else +		sz = sizeof(efi_config_table_32_t); + +	/* +	 * Let's see what config tables the firmware passed to us. +	 */ +	config_tables = early_memremap(efi.systab->tables, +				       efi.systab->nr_tables * sz); +	if (config_tables == NULL) { +		pr_err("Could not map Configuration table!\n"); +		return -ENOMEM; +	} + +	tablep = config_tables; +	pr_info(""); +	for (i = 0; i < efi.systab->nr_tables; i++) { +		efi_guid_t guid; +		unsigned long table; + +		if (efi_enabled(EFI_64BIT)) { +			u64 table64; +			guid = ((efi_config_table_64_t *)tablep)->guid; +			table64 = ((efi_config_table_64_t *)tablep)->table; +			table = table64; +#ifndef CONFIG_64BIT +			if (table64 >> 32) { +				pr_cont("\n"); +				pr_err("Table located above 4GB, disabling EFI.\n"); +				early_iounmap(config_tables, +					       efi.systab->nr_tables * sz); +				return -EINVAL; +			} +#endif +		} else { +			guid = ((efi_config_table_32_t *)tablep)->guid; +			table = ((efi_config_table_32_t *)tablep)->table; +		} + +		if (!match_config_table(&guid, table, common_tables)) +			match_config_table(&guid, table, arch_tables); + +		tablep += sz; +	} +	pr_cont("\n"); +	early_iounmap(config_tables, efi.systab->nr_tables * sz); + +	set_bit(EFI_CONFIG_TABLES, &efi.flags); + +	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; +	int found; +	void *params; +}; + +static int __init fdt_find_uefi_params(unsigned long node, const char *uname, +				       int depth, void *data) +{ +	struct param_info *info = data; +	const void *prop; +	void *dest; +	u64 val; +	int i, len; + +	if (depth != 1 || +	    (strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0)) +		return 0; + +	for (i = 0; i < ARRAY_SIZE(dt_params); i++) { +		prop = of_get_flat_dt_prop(node, dt_params[i].propname, &len); +		if (!prop) +			return 0; +		dest = info->params + dt_params[i].offset; +		info->found++; + +		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; +	int ret; + +	pr_info("Getting EFI parameters from FDT:\n"); + +	info.verbose = verbose; +	info.found = 0; +	info.params = params; + +	ret = of_scan_flat_dt(fdt_find_uefi_params, &info); +	if (!info.found) +		pr_info("UEFI not found.\n"); +	else if (!ret) +		pr_err("Can't find '%s' in device tree!\n", +		       dt_params[info.found].name); + +	return ret; +} +#endif /* CONFIG_EFI_PARAMS_FROM_FDT */ diff --git a/drivers/firmware/efi/efivars.c b/drivers/firmware/efi/efivars.c index 8a7432a4b41..463c56545ae 100644 --- a/drivers/firmware/efi/efivars.c +++ b/drivers/firmware/efi/efivars.c @@ -69,6 +69,7 @@  #include <linux/module.h>  #include <linux/slab.h>  #include <linux/ucs2_string.h> +#include <linux/compat.h>  #define EFIVARS_VERSION "0.08"  #define EFIVARS_DATE "2004-May-17" @@ -86,6 +87,15 @@ static struct kset *efivars_kset;  static struct bin_attribute *efivars_new_var;  static struct bin_attribute *efivars_del_var; +struct compat_efi_variable { +	efi_char16_t  VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)]; +	efi_guid_t    VendorGuid; +	__u32         DataSize; +	__u8          Data[1024]; +	__u32         Status; +	__u32         Attributes; +} __packed; +  struct efivar_attribute {  	struct attribute attr;  	ssize_t (*show) (struct efivar_entry *entry, char *buf); @@ -189,45 +199,107 @@ efivar_data_read(struct efivar_entry *entry, char *buf)  	memcpy(buf, var->Data, var->DataSize);  	return var->DataSize;  } -/* - * We allow each variable to be edited via rewriting the - * entire efi variable structure. - */ -static ssize_t -efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count) -{ -	struct efi_variable *new_var, *var = &entry->var; -	int err; -	if (count != sizeof(struct efi_variable)) -		return -EINVAL; - -	new_var = (struct efi_variable *)buf; +static inline int +sanity_check(struct efi_variable *var, efi_char16_t *name, efi_guid_t vendor, +	     unsigned long size, u32 attributes, u8 *data) +{  	/*  	 * If only updating the variable data, then the name  	 * and guid should remain the same  	 */ -	if (memcmp(new_var->VariableName, var->VariableName, sizeof(var->VariableName)) || -		efi_guidcmp(new_var->VendorGuid, var->VendorGuid)) { +	if (memcmp(name, var->VariableName, sizeof(var->VariableName)) || +		efi_guidcmp(vendor, var->VendorGuid)) {  		printk(KERN_ERR "efivars: Cannot edit the wrong variable!\n");  		return -EINVAL;  	} -	if ((new_var->DataSize <= 0) || (new_var->Attributes == 0)){ +	if ((size <= 0) || (attributes == 0)){  		printk(KERN_ERR "efivars: DataSize & Attributes must be valid!\n");  		return -EINVAL;  	} -	if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 || -	    efivar_validate(new_var, new_var->Data, new_var->DataSize) == false) { +	if ((attributes & ~EFI_VARIABLE_MASK) != 0 || +	    efivar_validate(name, data, size) == false) {  		printk(KERN_ERR "efivars: Malformed variable content\n");  		return -EINVAL;  	} -	memcpy(&entry->var, new_var, count); +	return 0; +} + +static inline bool is_compat(void) +{ +	if (IS_ENABLED(CONFIG_COMPAT) && is_compat_task()) +		return true; + +	return false; +} + +static void +copy_out_compat(struct efi_variable *dst, struct compat_efi_variable *src) +{ +	memcpy(dst->VariableName, src->VariableName, EFI_VAR_NAME_LEN); +	memcpy(dst->Data, src->Data, sizeof(src->Data)); + +	dst->VendorGuid = src->VendorGuid; +	dst->DataSize = src->DataSize; +	dst->Attributes = src->Attributes; +} + +/* + * We allow each variable to be edited via rewriting the + * entire efi variable structure. + */ +static ssize_t +efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count) +{ +	struct efi_variable *new_var, *var = &entry->var; +	efi_char16_t *name; +	unsigned long size; +	efi_guid_t vendor; +	u32 attributes; +	u8 *data; +	int err; + +	if (is_compat()) { +		struct compat_efi_variable *compat; + +		if (count != sizeof(*compat)) +			return -EINVAL; + +		compat = (struct compat_efi_variable *)buf; +		attributes = compat->Attributes; +		vendor = compat->VendorGuid; +		name = compat->VariableName; +		size = compat->DataSize; +		data = compat->Data; + +		err = sanity_check(var, name, vendor, size, attributes, data); +		if (err) +			return err; + +		copy_out_compat(&entry->var, compat); +	} else { +		if (count != sizeof(struct efi_variable)) +			return -EINVAL; + +		new_var = (struct efi_variable *)buf; + +		attributes = new_var->Attributes; +		vendor = new_var->VendorGuid; +		name = new_var->VariableName; +		size = new_var->DataSize; +		data = new_var->Data; + +		err = sanity_check(var, name, vendor, size, attributes, data); +		if (err) +			return err; + +		memcpy(&entry->var, new_var, count); +	} -	err = efivar_entry_set(entry, new_var->Attributes, -			       new_var->DataSize, new_var->Data, false); +	err = efivar_entry_set(entry, attributes, size, data, NULL);  	if (err) {  		printk(KERN_WARNING "efivars: set_variable() failed: status=%d\n", err);  		return -EIO; @@ -240,6 +312,8 @@ static ssize_t  efivar_show_raw(struct efivar_entry *entry, char *buf)  {  	struct efi_variable *var = &entry->var; +	struct compat_efi_variable *compat; +	size_t size;  	if (!entry || !buf)  		return 0; @@ -249,9 +323,23 @@ efivar_show_raw(struct efivar_entry *entry, char *buf)  			     &entry->var.DataSize, entry->var.Data))  		return -EIO; -	memcpy(buf, var, sizeof(*var)); +	if (is_compat()) { +		compat = (struct compat_efi_variable *)buf; + +		size = sizeof(*compat); +		memcpy(compat->VariableName, var->VariableName, +			EFI_VAR_NAME_LEN); +		memcpy(compat->Data, var->Data, sizeof(compat->Data)); + +		compat->VendorGuid = var->VendorGuid; +		compat->DataSize = var->DataSize; +		compat->Attributes = var->Attributes; +	} else { +		size = sizeof(*var); +		memcpy(buf, var, size); +	} -	return sizeof(*var); +	return size;  }  /* @@ -326,15 +414,39 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,  			     struct bin_attribute *bin_attr,  			     char *buf, loff_t pos, size_t count)  { +	struct compat_efi_variable *compat = (struct compat_efi_variable *)buf;  	struct efi_variable *new_var = (struct efi_variable *)buf;  	struct efivar_entry *new_entry; +	bool need_compat = is_compat(); +	efi_char16_t *name; +	unsigned long size; +	u32 attributes; +	u8 *data;  	int err;  	if (!capable(CAP_SYS_ADMIN))  		return -EACCES; -	if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 || -	    efivar_validate(new_var, new_var->Data, new_var->DataSize) == false) { +	if (need_compat) { +		if (count != sizeof(*compat)) +			return -EINVAL; + +		attributes = compat->Attributes; +		name = compat->VariableName; +		size = compat->DataSize; +		data = compat->Data; +	} else { +		if (count != sizeof(*new_var)) +			return -EINVAL; + +		attributes = new_var->Attributes; +		name = new_var->VariableName; +		size = new_var->DataSize; +		data = new_var->Data; +	} + +	if ((attributes & ~EFI_VARIABLE_MASK) != 0 || +	    efivar_validate(name, data, size) == false) {  		printk(KERN_ERR "efivars: Malformed variable content\n");  		return -EINVAL;  	} @@ -343,10 +455,13 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,  	if (!new_entry)  		return -ENOMEM; -	memcpy(&new_entry->var, new_var, sizeof(*new_var)); +	if (need_compat) +		copy_out_compat(&new_entry->var, compat); +	else +		memcpy(&new_entry->var, new_var, sizeof(*new_var)); -	err = efivar_entry_set(new_entry, new_var->Attributes, new_var->DataSize, -			       new_var->Data, &efivar_sysfs_list); +	err = efivar_entry_set(new_entry, attributes, size, +			       data, &efivar_sysfs_list);  	if (err) {  		if (err == -EEXIST)  			err = -EINVAL; @@ -369,26 +484,47 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,  			     char *buf, loff_t pos, size_t count)  {  	struct efi_variable *del_var = (struct efi_variable *)buf; +	struct compat_efi_variable *compat;  	struct efivar_entry *entry; +	efi_char16_t *name; +	efi_guid_t vendor;  	int err = 0;  	if (!capable(CAP_SYS_ADMIN))  		return -EACCES; +	if (is_compat()) { +		if (count != sizeof(*compat)) +			return -EINVAL; + +		compat = (struct compat_efi_variable *)buf; +		name = compat->VariableName; +		vendor = compat->VendorGuid; +	} else { +		if (count != sizeof(*del_var)) +			return -EINVAL; + +		name = del_var->VariableName; +		vendor = del_var->VendorGuid; +	} +  	efivar_entry_iter_begin(); -	entry = efivar_entry_find(del_var->VariableName, del_var->VendorGuid, -				  &efivar_sysfs_list, true); +	entry = efivar_entry_find(name, vendor, &efivar_sysfs_list, true);  	if (!entry)  		err = -EINVAL;  	else if (__efivar_entry_delete(entry))  		err = -EIO; -	efivar_entry_iter_end(); - -	if (err) +	if (err) { +		efivar_entry_iter_end();  		return err; +	} -	efivar_unregister(entry); +	if (!entry->scanning) { +		efivar_entry_iter_end(); +		efivar_unregister(entry); +	} else +		efivar_entry_iter_end();  	/* It's dead Jim.... */  	return count; @@ -564,7 +700,7 @@ static int efivar_sysfs_destroy(struct efivar_entry *entry, void *data)  	return 0;  } -void efivars_sysfs_exit(void) +static void efivars_sysfs_exit(void)  {  	/* Remove all entries and destroy */  	__efivar_entry_iter(efivar_sysfs_destroy, &efivar_sysfs_list, NULL, NULL); diff --git a/drivers/firmware/efi/fdt.c b/drivers/firmware/efi/fdt.c new file mode 100644 index 00000000000..507a3df46a5 --- /dev/null +++ b/drivers/firmware/efi/fdt.c @@ -0,0 +1,275 @@ +/* + * 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; + +	/* Do some checks on provided FDT, if it exists*/ +	if (orig_fdt) { +		if (fdt_check_header(orig_fdt)) { +			pr_efi_err(sys_table, "Device Tree header not valid!\n"); +			return EFI_LOAD_ERROR; +		} +		/* +		 * We don't get the size of the FDT if we get if from a +		 * configuration table. +		 */ +		if (orig_fdt_size && fdt_totalsize(orig_fdt) > orig_fdt_size) { +			pr_efi_err(sys_table, "Truncated device tree! foo!\n"); +			return EFI_LOAD_ERROR; +		} +	} + +	if (orig_fdt) +		status = fdt_open_into(orig_fdt, fdt, new_fdt_size); +	else +		status = fdt_create_empty_tree(fdt, new_fdt_size); + +	if (status != 0) +		goto fdt_set_fail; + +	/* +	 * Delete any memory nodes present. We must delete nodes which +	 * early_init_dt_scan_memory may try to use. +	 */ +	prev = 0; +	for (;;) { +		const char *type; +		int len; + +		node = fdt_next_node(fdt, prev, NULL); +		if (node < 0) +			break; + +		type = fdt_getprop(fdt, node, "device_type", &len); +		if (type && strncmp(type, "memory", len) == 0) { +			fdt_del_node(fdt, node); +			continue; +		} + +		prev = node; +	} + +	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; +} diff --git a/drivers/firmware/efi/runtime-map.c b/drivers/firmware/efi/runtime-map.c new file mode 100644 index 00000000000..97cdd16a216 --- /dev/null +++ b/drivers/firmware/efi/runtime-map.c @@ -0,0 +1,181 @@ +/* + * linux/drivers/efi/runtime-map.c + * Copyright (C) 2013 Red Hat, Inc., Dave Young <dyoung@redhat.com> + * + * This file is released under the GPLv2. + */ + +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/efi.h> +#include <linux/slab.h> + +#include <asm/setup.h> + +static void *efi_runtime_map; +static int nr_efi_runtime_map; +static u32 efi_memdesc_size; + +struct efi_runtime_map_entry { +	efi_memory_desc_t md; +	struct kobject kobj;   /* kobject for each entry */ +}; + +static struct efi_runtime_map_entry **map_entries; + +struct map_attribute { +	struct attribute attr; +	ssize_t (*show)(struct efi_runtime_map_entry *entry, char *buf); +}; + +static inline struct map_attribute *to_map_attr(struct attribute *attr) +{ +	return container_of(attr, struct map_attribute, attr); +} + +static ssize_t type_show(struct efi_runtime_map_entry *entry, char *buf) +{ +	return snprintf(buf, PAGE_SIZE, "0x%x\n", entry->md.type); +} + +#define EFI_RUNTIME_FIELD(var) entry->md.var + +#define EFI_RUNTIME_U64_ATTR_SHOW(name) \ +static ssize_t name##_show(struct efi_runtime_map_entry *entry, char *buf) \ +{ \ +	return snprintf(buf, PAGE_SIZE, "0x%llx\n", EFI_RUNTIME_FIELD(name)); \ +} + +EFI_RUNTIME_U64_ATTR_SHOW(phys_addr); +EFI_RUNTIME_U64_ATTR_SHOW(virt_addr); +EFI_RUNTIME_U64_ATTR_SHOW(num_pages); +EFI_RUNTIME_U64_ATTR_SHOW(attribute); + +static inline struct efi_runtime_map_entry *to_map_entry(struct kobject *kobj) +{ +	return container_of(kobj, struct efi_runtime_map_entry, kobj); +} + +static ssize_t map_attr_show(struct kobject *kobj, struct attribute *attr, +			      char *buf) +{ +	struct efi_runtime_map_entry *entry = to_map_entry(kobj); +	struct map_attribute *map_attr = to_map_attr(attr); + +	return map_attr->show(entry, buf); +} + +static struct map_attribute map_type_attr = __ATTR_RO(type); +static struct map_attribute map_phys_addr_attr   = __ATTR_RO(phys_addr); +static struct map_attribute map_virt_addr_attr  = __ATTR_RO(virt_addr); +static struct map_attribute map_num_pages_attr  = __ATTR_RO(num_pages); +static struct map_attribute map_attribute_attr  = __ATTR_RO(attribute); + +/* + * These are default attributes that are added for every memmap entry. + */ +static struct attribute *def_attrs[] = { +	&map_type_attr.attr, +	&map_phys_addr_attr.attr, +	&map_virt_addr_attr.attr, +	&map_num_pages_attr.attr, +	&map_attribute_attr.attr, +	NULL +}; + +static const struct sysfs_ops map_attr_ops = { +	.show = map_attr_show, +}; + +static void map_release(struct kobject *kobj) +{ +	struct efi_runtime_map_entry *entry; + +	entry = to_map_entry(kobj); +	kfree(entry); +} + +static struct kobj_type __refdata map_ktype = { +	.sysfs_ops	= &map_attr_ops, +	.default_attrs	= def_attrs, +	.release	= map_release, +}; + +static struct kset *map_kset; + +static struct efi_runtime_map_entry * +add_sysfs_runtime_map_entry(struct kobject *kobj, int nr) +{ +	int ret; +	struct efi_runtime_map_entry *entry; + +	if (!map_kset) { +		map_kset = kset_create_and_add("runtime-map", NULL, kobj); +		if (!map_kset) +			return ERR_PTR(-ENOMEM); +	} + +	entry = kzalloc(sizeof(*entry), GFP_KERNEL); +	if (!entry) { +		kset_unregister(map_kset); +		return entry; +	} + +	memcpy(&entry->md, efi_runtime_map + nr * efi_memdesc_size, +	       sizeof(efi_memory_desc_t)); + +	kobject_init(&entry->kobj, &map_ktype); +	entry->kobj.kset = map_kset; +	ret = kobject_add(&entry->kobj, NULL, "%d", nr); +	if (ret) { +		kobject_put(&entry->kobj); +		kset_unregister(map_kset); +		return ERR_PTR(ret); +	} + +	return entry; +} + +void efi_runtime_map_setup(void *map, int nr_entries, u32 desc_size) +{ +	efi_runtime_map = map; +	nr_efi_runtime_map = nr_entries; +	efi_memdesc_size = desc_size; +} + +int __init efi_runtime_map_init(struct kobject *efi_kobj) +{ +	int i, j, ret = 0; +	struct efi_runtime_map_entry *entry; + +	if (!efi_runtime_map) +		return 0; + +	map_entries = kzalloc(nr_efi_runtime_map * sizeof(entry), GFP_KERNEL); +	if (!map_entries) { +		ret = -ENOMEM; +		goto out; +	} + +	for (i = 0; i < nr_efi_runtime_map; i++) { +		entry = add_sysfs_runtime_map_entry(efi_kobj, i); +		if (IS_ERR(entry)) { +			ret = PTR_ERR(entry); +			goto out_add_entry; +		} +		*(map_entries + i) = entry; +	} + +	return 0; +out_add_entry: +	for (j = i - 1; j > 0; j--) { +		entry = *(map_entries + j); +		kobject_put(&entry->kobj); +	} +	if (map_kset) +		kset_unregister(map_kset); +out: +	return ret; +} diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c index 391c67b182d..f0a43646a2f 100644 --- a/drivers/firmware/efi/vars.c +++ b/drivers/firmware/efi/vars.c @@ -42,7 +42,7 @@ DECLARE_WORK(efivar_work, NULL);  EXPORT_SYMBOL_GPL(efivar_work);  static bool -validate_device_path(struct efi_variable *var, int match, u8 *buffer, +validate_device_path(efi_char16_t *var_name, int match, u8 *buffer,  		     unsigned long len)  {  	struct efi_generic_dev_path *node; @@ -75,7 +75,7 @@ validate_device_path(struct efi_variable *var, int match, u8 *buffer,  }  static bool -validate_boot_order(struct efi_variable *var, int match, u8 *buffer, +validate_boot_order(efi_char16_t *var_name, int match, u8 *buffer,  		    unsigned long len)  {  	/* An array of 16-bit integers */ @@ -86,18 +86,18 @@ validate_boot_order(struct efi_variable *var, int match, u8 *buffer,  }  static bool -validate_load_option(struct efi_variable *var, int match, u8 *buffer, +validate_load_option(efi_char16_t *var_name, int match, u8 *buffer,  		     unsigned long len)  {  	u16 filepathlength;  	int i, desclength = 0, namelen; -	namelen = ucs2_strnlen(var->VariableName, sizeof(var->VariableName)); +	namelen = ucs2_strnlen(var_name, EFI_VAR_NAME_LEN);  	/* Either "Boot" or "Driver" followed by four digits of hex */  	for (i = match; i < match+4; i++) { -		if (var->VariableName[i] > 127 || -		    hex_to_bin(var->VariableName[i] & 0xff) < 0) +		if (var_name[i] > 127 || +		    hex_to_bin(var_name[i] & 0xff) < 0)  			return true;  	} @@ -132,12 +132,12 @@ validate_load_option(struct efi_variable *var, int match, u8 *buffer,  	/*  	 * And, finally, check the filepath  	 */ -	return validate_device_path(var, match, buffer + desclength + 6, +	return validate_device_path(var_name, match, buffer + desclength + 6,  				    filepathlength);  }  static bool -validate_uint16(struct efi_variable *var, int match, u8 *buffer, +validate_uint16(efi_char16_t *var_name, int match, u8 *buffer,  		unsigned long len)  {  	/* A single 16-bit integer */ @@ -148,7 +148,7 @@ validate_uint16(struct efi_variable *var, int match, u8 *buffer,  }  static bool -validate_ascii_string(struct efi_variable *var, int match, u8 *buffer, +validate_ascii_string(efi_char16_t *var_name, int match, u8 *buffer,  		      unsigned long len)  {  	int i; @@ -166,7 +166,7 @@ validate_ascii_string(struct efi_variable *var, int match, u8 *buffer,  struct variable_validate {  	char *name; -	bool (*validate)(struct efi_variable *var, int match, u8 *data, +	bool (*validate)(efi_char16_t *var_name, int match, u8 *data,  			 unsigned long len);  }; @@ -189,10 +189,10 @@ static const struct variable_validate variable_validate[] = {  };  bool -efivar_validate(struct efi_variable *var, u8 *data, unsigned long len) +efivar_validate(efi_char16_t *var_name, u8 *data, unsigned long len)  {  	int i; -	u16 *unicode_name = var->VariableName; +	u16 *unicode_name = var_name;  	for (i = 0; variable_validate[i].validate != NULL; i++) {  		const char *name = variable_validate[i].name; @@ -208,7 +208,7 @@ efivar_validate(struct efi_variable *var, u8 *data, unsigned long len)  			/* Wildcard in the matching name means we've matched */  			if (c == '*') -				return variable_validate[i].validate(var, +				return variable_validate[i].validate(var_name,  							     match, data, len);  			/* Case sensitive match */ @@ -217,7 +217,7 @@ efivar_validate(struct efi_variable *var, u8 *data, unsigned long len)  			/* Reached the end of the string while matching */  			if (!c) -				return variable_validate[i].validate(var, +				return variable_validate[i].validate(var_name,  							     match, data, len);  		}  	} @@ -683,8 +683,16 @@ struct efivar_entry *efivar_entry_find(efi_char16_t *name, efi_guid_t guid,  	if (!found)  		return NULL; -	if (remove) -		list_del(&entry->list); +	if (remove) { +		if (entry->scanning) { +			/* +			 * The entry will be deleted +			 * after scanning is completed. +			 */ +			entry->deleting = true; +		} else +			list_del(&entry->list); +	}  	return entry;  } @@ -797,7 +805,7 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,  	*set = false; -	if (efivar_validate(&entry->var, data, *size) == false) +	if (efivar_validate(name, data, *size) == false)  		return -EINVAL;  	/*  | 
