mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 01:08:03 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
66
drivers/firmware/efi/Kconfig
Normal file
66
drivers/firmware/efi/Kconfig
Normal file
|
@ -0,0 +1,66 @@
|
|||
menu "EFI (Extensible Firmware Interface) Support"
|
||||
depends on EFI
|
||||
|
||||
config EFI_VARS
|
||||
tristate "EFI Variable Support via sysfs"
|
||||
depends on EFI
|
||||
default n
|
||||
help
|
||||
If you say Y here, you are able to get EFI (Extensible Firmware
|
||||
Interface) variable information via sysfs. You may read,
|
||||
write, create, and destroy EFI variables through this interface.
|
||||
|
||||
Note that using this driver in concert with efibootmgr requires
|
||||
at least test release version 0.5.0-test3 or later, which is
|
||||
available from Matt Domsch's website located at:
|
||||
<http://linux.dell.com/efibootmgr/testing/efibootmgr-0.5.0-test3.tar.gz>
|
||||
|
||||
Subsequent efibootmgr releases may be found at:
|
||||
<http://linux.dell.com/efibootmgr>
|
||||
|
||||
config EFI_VARS_PSTORE
|
||||
tristate "Register efivars backend for pstore"
|
||||
depends on EFI_VARS && PSTORE
|
||||
default y
|
||||
help
|
||||
Say Y here to enable use efivars as a backend to pstore. This
|
||||
will allow writing console messages, crash dumps, or anything
|
||||
else supported by pstore to EFI variables.
|
||||
|
||||
config EFI_VARS_PSTORE_DEFAULT_DISABLE
|
||||
bool "Disable using efivars as a pstore backend by default"
|
||||
depends on EFI_VARS_PSTORE
|
||||
default n
|
||||
help
|
||||
Saying Y here will disable the use of efivars as a storage
|
||||
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.
|
||||
|
||||
config EFI_RUNTIME_WRAPPERS
|
||||
bool
|
||||
|
||||
config EFI_ARMSTUB
|
||||
bool
|
||||
|
||||
endmenu
|
||||
|
||||
config UEFI_CPER
|
||||
bool
|
10
drivers/firmware/efi/Makefile
Normal file
10
drivers/firmware/efi/Makefile
Normal file
|
@ -0,0 +1,10 @@
|
|||
#
|
||||
# Makefile for linux kernel
|
||||
#
|
||||
obj-$(CONFIG_EFI) += efi.o vars.o reboot.o
|
||||
obj-$(CONFIG_EFI_VARS) += efivars.o
|
||||
obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o
|
||||
obj-$(CONFIG_UEFI_CPER) += cper.o
|
||||
obj-$(CONFIG_EFI_RUNTIME_MAP) += runtime-map.o
|
||||
obj-$(CONFIG_EFI_RUNTIME_WRAPPERS) += runtime-wrappers.o
|
||||
obj-$(CONFIG_EFI_ARM_STUB) += libstub/
|
492
drivers/firmware/efi/cper.c
Normal file
492
drivers/firmware/efi/cper.c
Normal file
|
@ -0,0 +1,492 @@
|
|||
/*
|
||||
* 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 " "
|
||||
|
||||
static char rcd_decode_str[CPER_REC_LEN];
|
||||
|
||||
/*
|
||||
* 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 * const severity_strs[] = {
|
||||
"recoverable",
|
||||
"fatal",
|
||||
"corrected",
|
||||
"info",
|
||||
};
|
||||
|
||||
const char *cper_severity_str(unsigned int severity)
|
||||
{
|
||||
return severity < ARRAY_SIZE(severity_strs) ?
|
||||
severity_strs[severity] : "unknown";
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cper_severity_str);
|
||||
|
||||
/*
|
||||
* 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 proc_type_strs[] = {
|
||||
"IA32/X64",
|
||||
"IA64",
|
||||
};
|
||||
|
||||
static const char * const proc_isa_strs[] = {
|
||||
"IA32",
|
||||
"IA64",
|
||||
"X64",
|
||||
};
|
||||
|
||||
static const char * const proc_error_type_strs[] = {
|
||||
"cache error",
|
||||
"TLB error",
|
||||
"bus error",
|
||||
"micro-architectural error",
|
||||
};
|
||||
|
||||
static const char * const proc_op_strs[] = {
|
||||
"unknown or generic",
|
||||
"data read",
|
||||
"data write",
|
||||
"instruction execution",
|
||||
};
|
||||
|
||||
static const char * const 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(proc_type_strs) ?
|
||||
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(proc_isa_strs) ?
|
||||
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,
|
||||
proc_error_type_strs,
|
||||
ARRAY_SIZE(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(proc_op_strs) ?
|
||||
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, proc_flag_strs,
|
||||
ARRAY_SIZE(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 * const 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",
|
||||
};
|
||||
|
||||
const char *cper_mem_err_type_str(unsigned int etype)
|
||||
{
|
||||
return etype < ARRAY_SIZE(mem_err_type_strs) ?
|
||||
mem_err_type_strs[etype] : "unknown";
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cper_mem_err_type_str);
|
||||
|
||||
static int cper_mem_err_location(struct cper_mem_err_compact *mem, char *msg)
|
||||
{
|
||||
u32 len, n;
|
||||
|
||||
if (!msg)
|
||||
return 0;
|
||||
|
||||
n = 0;
|
||||
len = CPER_REC_LEN - 1;
|
||||
if (mem->validation_bits & CPER_MEM_VALID_NODE)
|
||||
n += scnprintf(msg + n, len - n, "node: %d ", mem->node);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_CARD)
|
||||
n += scnprintf(msg + n, len - n, "card: %d ", mem->card);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_MODULE)
|
||||
n += scnprintf(msg + n, len - n, "module: %d ", mem->module);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_RANK_NUMBER)
|
||||
n += scnprintf(msg + n, len - n, "rank: %d ", mem->rank);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_BANK)
|
||||
n += scnprintf(msg + n, len - n, "bank: %d ", mem->bank);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_DEVICE)
|
||||
n += scnprintf(msg + n, len - n, "device: %d ", mem->device);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_ROW)
|
||||
n += scnprintf(msg + n, len - n, "row: %d ", mem->row);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_COLUMN)
|
||||
n += scnprintf(msg + n, len - n, "column: %d ", mem->column);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_BIT_POSITION)
|
||||
n += scnprintf(msg + n, len - n, "bit_position: %d ",
|
||||
mem->bit_pos);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_REQUESTOR_ID)
|
||||
n += scnprintf(msg + n, len - n, "requestor_id: 0x%016llx ",
|
||||
mem->requestor_id);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_RESPONDER_ID)
|
||||
n += scnprintf(msg + n, len - n, "responder_id: 0x%016llx ",
|
||||
mem->responder_id);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_TARGET_ID)
|
||||
scnprintf(msg + n, len - n, "target_id: 0x%016llx ",
|
||||
mem->target_id);
|
||||
|
||||
msg[n] = '\0';
|
||||
return n;
|
||||
}
|
||||
|
||||
static int cper_dimm_err_location(struct cper_mem_err_compact *mem, char *msg)
|
||||
{
|
||||
u32 len, n;
|
||||
const char *bank = NULL, *device = NULL;
|
||||
|
||||
if (!msg || !(mem->validation_bits & CPER_MEM_VALID_MODULE_HANDLE))
|
||||
return 0;
|
||||
|
||||
n = 0;
|
||||
len = CPER_REC_LEN - 1;
|
||||
dmi_memdev_name(mem->mem_dev_handle, &bank, &device);
|
||||
if (bank && device)
|
||||
n = snprintf(msg, len, "DIMM location: %s %s ", bank, device);
|
||||
else
|
||||
n = snprintf(msg, len,
|
||||
"DIMM location: not present. DMI handle: 0x%.4x ",
|
||||
mem->mem_dev_handle);
|
||||
|
||||
msg[n] = '\0';
|
||||
return n;
|
||||
}
|
||||
|
||||
void cper_mem_err_pack(const struct cper_sec_mem_err *mem,
|
||||
struct cper_mem_err_compact *cmem)
|
||||
{
|
||||
cmem->validation_bits = mem->validation_bits;
|
||||
cmem->node = mem->node;
|
||||
cmem->card = mem->card;
|
||||
cmem->module = mem->module;
|
||||
cmem->bank = mem->bank;
|
||||
cmem->device = mem->device;
|
||||
cmem->row = mem->row;
|
||||
cmem->column = mem->column;
|
||||
cmem->bit_pos = mem->bit_pos;
|
||||
cmem->requestor_id = mem->requestor_id;
|
||||
cmem->responder_id = mem->responder_id;
|
||||
cmem->target_id = mem->target_id;
|
||||
cmem->rank = mem->rank;
|
||||
cmem->mem_array_handle = mem->mem_array_handle;
|
||||
cmem->mem_dev_handle = mem->mem_dev_handle;
|
||||
}
|
||||
|
||||
const char *cper_mem_err_unpack(struct trace_seq *p,
|
||||
struct cper_mem_err_compact *cmem)
|
||||
{
|
||||
const char *ret = p->buffer + p->len;
|
||||
|
||||
if (cper_mem_err_location(cmem, rcd_decode_str))
|
||||
trace_seq_printf(p, "%s", rcd_decode_str);
|
||||
if (cper_dimm_err_location(cmem, rcd_decode_str))
|
||||
trace_seq_printf(p, "%s", rcd_decode_str);
|
||||
trace_seq_putc(p, '\0');
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cper_print_mem(const char *pfx, const struct cper_sec_mem_err *mem)
|
||||
{
|
||||
struct cper_mem_err_compact cmem;
|
||||
|
||||
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);
|
||||
cper_mem_err_pack(mem, &cmem);
|
||||
if (cper_mem_err_location(&cmem, rcd_decode_str))
|
||||
printk("%s%s\n", pfx, rcd_decode_str);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_ERROR_TYPE) {
|
||||
u8 etype = mem->error_type;
|
||||
printk("%s""error_type: %d, %s\n", pfx, etype,
|
||||
cper_mem_err_type_str(etype));
|
||||
}
|
||||
if (cper_dimm_err_location(&cmem, rcd_decode_str))
|
||||
printk("%s%s\n", pfx, rcd_decode_str);
|
||||
}
|
||||
|
||||
static const char * const 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_hest_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(pcie_port_type_strs) ?
|
||||
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_hest_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_hest_generic_status *estatus)
|
||||
{
|
||||
struct acpi_hest_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_hest_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_hest_generic_status *estatus)
|
||||
{
|
||||
if (estatus->data_length &&
|
||||
estatus->data_length < sizeof(struct acpi_hest_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_hest_generic_status *estatus)
|
||||
{
|
||||
struct acpi_hest_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_hest_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);
|
402
drivers/firmware/efi/efi-pstore.c
Normal file
402
drivers/firmware/efi/efi-pstore.c
Normal file
|
@ -0,0 +1,402 @@
|
|||
#include <linux/efi.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pstore.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/ucs2_string.h>
|
||||
|
||||
#define DUMP_NAME_LEN 52
|
||||
|
||||
static bool efivars_pstore_disable =
|
||||
IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE);
|
||||
|
||||
module_param_named(pstore_disable, efivars_pstore_disable, bool, 0644);
|
||||
|
||||
#define PSTORE_EFI_ATTRIBUTES \
|
||||
(EFI_VARIABLE_NON_VOLATILE | \
|
||||
EFI_VARIABLE_BOOTSERVICE_ACCESS | \
|
||||
EFI_VARIABLE_RUNTIME_ACCESS)
|
||||
|
||||
static int efi_pstore_open(struct pstore_info *psi)
|
||||
{
|
||||
psi->data = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int efi_pstore_close(struct pstore_info *psi)
|
||||
{
|
||||
psi->data = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct pstore_read_data {
|
||||
u64 *id;
|
||||
enum pstore_type_id *type;
|
||||
int *count;
|
||||
struct timespec *timespec;
|
||||
bool *compressed;
|
||||
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;
|
||||
struct pstore_read_data *cb_data = data;
|
||||
char name[DUMP_NAME_LEN], data_type;
|
||||
int i;
|
||||
int cnt;
|
||||
unsigned int part;
|
||||
unsigned long time, size;
|
||||
|
||||
if (efi_guidcmp(entry->var.VendorGuid, vendor))
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < DUMP_NAME_LEN; i++)
|
||||
name[i] = entry->var.VariableName[i];
|
||||
|
||||
if (sscanf(name, "dump-type%u-%u-%d-%lu-%c",
|
||||
cb_data->type, &part, &cnt, &time, &data_type) == 5) {
|
||||
*cb_data->id = generic_id(time, part, cnt);
|
||||
*cb_data->count = cnt;
|
||||
cb_data->timespec->tv_sec = time;
|
||||
cb_data->timespec->tv_nsec = 0;
|
||||
if (data_type == 'C')
|
||||
*cb_data->compressed = true;
|
||||
else
|
||||
*cb_data->compressed = false;
|
||||
} else if (sscanf(name, "dump-type%u-%u-%d-%lu",
|
||||
cb_data->type, &part, &cnt, &time) == 4) {
|
||||
*cb_data->id = generic_id(time, part, cnt);
|
||||
*cb_data->count = cnt;
|
||||
cb_data->timespec->tv_sec = time;
|
||||
cb_data->timespec->tv_nsec = 0;
|
||||
*cb_data->compressed = false;
|
||||
} else if (sscanf(name, "dump-type%u-%u-%lu",
|
||||
cb_data->type, &part, &time) == 3) {
|
||||
/*
|
||||
* Check if an old format,
|
||||
* which doesn't support holding
|
||||
* multiple logs, remains.
|
||||
*/
|
||||
*cb_data->id = generic_id(time, part, 0);
|
||||
*cb_data->count = 0;
|
||||
cb_data->timespec->tv_sec = time;
|
||||
cb_data->timespec->tv_nsec = 0;
|
||||
*cb_data->compressed = false;
|
||||
} else
|
||||
return 0;
|
||||
|
||||
entry->var.DataSize = 1024;
|
||||
__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));
|
||||
|
||||
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;
|
||||
data.count = count;
|
||||
data.timespec = timespec;
|
||||
data.compressed = compressed;
|
||||
data.buf = buf;
|
||||
|
||||
*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,
|
||||
enum kmsg_dump_reason reason, u64 *id,
|
||||
unsigned int part, int count, bool compressed, size_t size,
|
||||
struct pstore_info *psi)
|
||||
{
|
||||
char name[DUMP_NAME_LEN];
|
||||
efi_char16_t efi_name[DUMP_NAME_LEN];
|
||||
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
|
||||
int i, ret = 0;
|
||||
|
||||
sprintf(name, "dump-type%u-%u-%d-%lu-%c", type, part, count,
|
||||
get_seconds(), compressed ? 'C' : 'D');
|
||||
|
||||
for (i = 0; i < DUMP_NAME_LEN; i++)
|
||||
efi_name[i] = name[i];
|
||||
|
||||
efivar_entry_set_safe(efi_name, vendor, PSTORE_EFI_ATTRIBUTES,
|
||||
!pstore_cannot_block_path(reason),
|
||||
size, psi->buf);
|
||||
|
||||
if (reason == KMSG_DUMP_OOPS)
|
||||
efivar_run_worker();
|
||||
|
||||
*id = part;
|
||||
return ret;
|
||||
};
|
||||
|
||||
struct pstore_erase_data {
|
||||
u64 id;
|
||||
enum pstore_type_id type;
|
||||
int count;
|
||||
struct timespec time;
|
||||
efi_char16_t *name;
|
||||
};
|
||||
|
||||
/*
|
||||
* Clean up an entry with the same name
|
||||
*/
|
||||
static int efi_pstore_erase_func(struct efivar_entry *entry, void *data)
|
||||
{
|
||||
struct pstore_erase_data *ed = data;
|
||||
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
|
||||
efi_char16_t efi_name_old[DUMP_NAME_LEN];
|
||||
efi_char16_t *efi_name = ed->name;
|
||||
unsigned long ucs2_len = ucs2_strlen(ed->name);
|
||||
char name_old[DUMP_NAME_LEN];
|
||||
int i;
|
||||
|
||||
if (efi_guidcmp(entry->var.VendorGuid, vendor))
|
||||
return 0;
|
||||
|
||||
if (ucs2_strncmp(entry->var.VariableName,
|
||||
efi_name, (size_t)ucs2_len)) {
|
||||
/*
|
||||
* Check if an old format, which doesn't support
|
||||
* holding multiple logs, remains.
|
||||
*/
|
||||
sprintf(name_old, "dump-type%u-%u-%lu", ed->type,
|
||||
(unsigned int)ed->id, ed->time.tv_sec);
|
||||
|
||||
for (i = 0; i < DUMP_NAME_LEN; i++)
|
||||
efi_name_old[i] = name_old[i];
|
||||
|
||||
if (ucs2_strncmp(entry->var.VariableName, efi_name_old,
|
||||
ucs2_strlen(efi_name_old)))
|
||||
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);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,
|
||||
struct timespec time, struct pstore_info *psi)
|
||||
{
|
||||
struct pstore_erase_data edata;
|
||||
struct efivar_entry *entry = NULL;
|
||||
char name[DUMP_NAME_LEN];
|
||||
efi_char16_t efi_name[DUMP_NAME_LEN];
|
||||
int found, i;
|
||||
unsigned int part;
|
||||
|
||||
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 = part;
|
||||
edata.type = type;
|
||||
edata.count = count;
|
||||
edata.time = time;
|
||||
edata.name = efi_name;
|
||||
|
||||
efivar_entry_iter_begin();
|
||||
found = __efivar_entry_iter(efi_pstore_erase_func, &efivar_sysfs_list, &edata, &entry);
|
||||
|
||||
if (found && !entry->scanning) {
|
||||
efivar_entry_iter_end();
|
||||
efivar_unregister(entry);
|
||||
} else
|
||||
efivar_entry_iter_end();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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,
|
||||
.write = efi_pstore_write,
|
||||
.erase = efi_pstore_erase,
|
||||
};
|
||||
|
||||
static __init int efivars_pstore_init(void)
|
||||
{
|
||||
if (!efi_enabled(EFI_RUNTIME_SERVICES))
|
||||
return 0;
|
||||
|
||||
if (!efivars_kobject())
|
||||
return 0;
|
||||
|
||||
if (efivars_pstore_disable)
|
||||
return 0;
|
||||
|
||||
efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL);
|
||||
if (!efi_pstore_info.buf)
|
||||
return -ENOMEM;
|
||||
|
||||
efi_pstore_info.bufsize = 1024;
|
||||
spin_lock_init(&efi_pstore_info.buf_lock);
|
||||
|
||||
if (pstore_register(&efi_pstore_info)) {
|
||||
kfree(efi_pstore_info.buf);
|
||||
efi_pstore_info.buf = NULL;
|
||||
efi_pstore_info.bufsize = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __exit void efivars_pstore_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
module_init(efivars_pstore_init);
|
||||
module_exit(efivars_pstore_exit);
|
||||
|
||||
MODULE_DESCRIPTION("EFI variable backend for pstore");
|
||||
MODULE_LICENSE("GPL");
|
504
drivers/firmware/efi/efi.c
Normal file
504
drivers/firmware/efi/efi.c
Normal file
|
@ -0,0 +1,504 @@
|
|||
/*
|
||||
* efi.c - EFI subsystem
|
||||
*
|
||||
* Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com>
|
||||
* Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com>
|
||||
* Copyright (C) 2013 Tom Gundersen <teg@jklm.no>
|
||||
*
|
||||
* This code registers /sys/firmware/efi{,/efivars} when EFI is supported,
|
||||
* allowing the efivarfs to be mounted or the efivars module to be loaded.
|
||||
* The existance of /sys/firmware/efi may also be used by userspace to
|
||||
* determine that the system supports EFI.
|
||||
*
|
||||
* 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>
|
||||
#include <linux/platform_device.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 bool disable_runtime;
|
||||
static int __init setup_noefi(char *arg)
|
||||
{
|
||||
disable_runtime = true;
|
||||
return 0;
|
||||
}
|
||||
early_param("noefi", setup_noefi);
|
||||
|
||||
bool efi_runtime_disabled(void)
|
||||
{
|
||||
return disable_runtime;
|
||||
}
|
||||
|
||||
static int __init parse_efi_cmdline(char *str)
|
||||
{
|
||||
if (parse_option_str(str, "noruntime"))
|
||||
disable_runtime = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
early_param("efi", parse_efi_cmdline);
|
||||
|
||||
static struct kobject *efi_kobj;
|
||||
static struct kobject *efivars_kobj;
|
||||
|
||||
/*
|
||||
* Let's not leave out systab information that snuck into
|
||||
* the efivars driver
|
||||
*/
|
||||
static ssize_t systab_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
char *str = buf;
|
||||
|
||||
if (!kobj || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
if (efi.mps != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "MPS=0x%lx\n", efi.mps);
|
||||
if (efi.acpi20 != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "ACPI20=0x%lx\n", efi.acpi20);
|
||||
if (efi.acpi != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "ACPI=0x%lx\n", efi.acpi);
|
||||
if (efi.smbios != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "SMBIOS=0x%lx\n", efi.smbios);
|
||||
if (efi.hcdp != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "HCDP=0x%lx\n", efi.hcdp);
|
||||
if (efi.boot_info != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "BOOTINFO=0x%lx\n", efi.boot_info);
|
||||
if (efi.uga != EFI_INVALID_TABLE_ADDR)
|
||||
str += sprintf(str, "UGA=0x%lx\n", efi.uga);
|
||||
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
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,
|
||||
&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)
|
||||
{
|
||||
if (attr == &efi_attr_fw_vendor.attr) {
|
||||
if (efi_enabled(EFI_PARAVIRT) ||
|
||||
efi.fw_vendor == EFI_INVALID_TABLE_ADDR)
|
||||
return 0;
|
||||
} else if (attr == &efi_attr_runtime.attr) {
|
||||
if (efi.runtime == EFI_INVALID_TABLE_ADDR)
|
||||
return 0;
|
||||
} else if (attr == &efi_attr_config_table.attr) {
|
||||
if (efi.config_table == EFI_INVALID_TABLE_ADDR)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return attr->mode;
|
||||
}
|
||||
|
||||
static struct attribute_group efi_subsys_attr_group = {
|
||||
.attrs = efi_subsys_attrs,
|
||||
.is_visible = efi_attr_is_visible,
|
||||
};
|
||||
|
||||
static struct efivars generic_efivars;
|
||||
static struct efivar_operations generic_ops;
|
||||
|
||||
static int generic_ops_register(void)
|
||||
{
|
||||
generic_ops.get_variable = efi.get_variable;
|
||||
generic_ops.set_variable = efi.set_variable;
|
||||
generic_ops.get_next_variable = efi.get_next_variable;
|
||||
generic_ops.query_variable_store = efi_query_variable_store;
|
||||
|
||||
return efivars_register(&generic_efivars, &generic_ops, efi_kobj);
|
||||
}
|
||||
|
||||
static void generic_ops_unregister(void)
|
||||
{
|
||||
efivars_unregister(&generic_efivars);
|
||||
}
|
||||
|
||||
/*
|
||||
* We register the efi subsystem with the firmware subsystem and the
|
||||
* efivars subsystem with the efi subsystem, if the system was booted with
|
||||
* EFI.
|
||||
*/
|
||||
static int __init efisubsys_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (!efi_enabled(EFI_BOOT))
|
||||
return 0;
|
||||
|
||||
/* We register the efi directory at /sys/firmware/efi */
|
||||
efi_kobj = kobject_create_and_add("efi", firmware_kobj);
|
||||
if (!efi_kobj) {
|
||||
pr_err("efi: Firmware registration failed.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
error = generic_ops_register();
|
||||
if (error)
|
||||
goto err_put;
|
||||
|
||||
error = sysfs_create_group(efi_kobj, &efi_subsys_attr_group);
|
||||
if (error) {
|
||||
pr_err("efi: Sysfs attribute export failed with error %d.\n",
|
||||
error);
|
||||
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) {
|
||||
pr_err("efivars: Subsystem registration failed.\n");
|
||||
error = -ENOMEM;
|
||||
goto err_remove_group;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_remove_group:
|
||||
sysfs_remove_group(efi_kobj, &efi_subsys_attr_group);
|
||||
err_unregister:
|
||||
generic_ops_unregister();
|
||||
err_put:
|
||||
kobject_put(efi_kobj);
|
||||
return error;
|
||||
}
|
||||
|
||||
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_memunmap(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_memunmap(config_tables, efi.systab->nr_tables * sz);
|
||||
|
||||
set_bit(EFI_CONFIG_TABLES, &efi.flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EFI_VARS_MODULE
|
||||
static int __init efi_load_efivars(void)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
|
||||
if (!efi_enabled(EFI_RUNTIME_SERVICES))
|
||||
return 0;
|
||||
|
||||
pdev = platform_device_register_simple("efivars", 0, NULL, 0);
|
||||
return IS_ERR(pdev) ? PTR_ERR(pdev) : 0;
|
||||
}
|
||||
device_initcall(efi_load_efivars);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_EFI_PARAMS_FROM_FDT
|
||||
|
||||
#define UEFI_PARAM(name, prop, field) \
|
||||
{ \
|
||||
{ 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 */
|
||||
|
||||
static __initdata char memory_type_name[][20] = {
|
||||
"Reserved",
|
||||
"Loader Code",
|
||||
"Loader Data",
|
||||
"Boot Code",
|
||||
"Boot Data",
|
||||
"Runtime Code",
|
||||
"Runtime Data",
|
||||
"Conventional Memory",
|
||||
"Unusable Memory",
|
||||
"ACPI Reclaim Memory",
|
||||
"ACPI Memory NVS",
|
||||
"Memory Mapped I/O",
|
||||
"MMIO Port Space",
|
||||
"PAL Code"
|
||||
};
|
||||
|
||||
char * __init efi_md_typeattr_format(char *buf, size_t size,
|
||||
const efi_memory_desc_t *md)
|
||||
{
|
||||
char *pos;
|
||||
int type_len;
|
||||
u64 attr;
|
||||
|
||||
pos = buf;
|
||||
if (md->type >= ARRAY_SIZE(memory_type_name))
|
||||
type_len = snprintf(pos, size, "[type=%u", md->type);
|
||||
else
|
||||
type_len = snprintf(pos, size, "[%-*s",
|
||||
(int)(sizeof(memory_type_name[0]) - 1),
|
||||
memory_type_name[md->type]);
|
||||
if (type_len >= size)
|
||||
return buf;
|
||||
|
||||
pos += type_len;
|
||||
size -= type_len;
|
||||
|
||||
attr = md->attribute;
|
||||
if (attr & ~(EFI_MEMORY_UC | EFI_MEMORY_WC | EFI_MEMORY_WT |
|
||||
EFI_MEMORY_WB | EFI_MEMORY_UCE | EFI_MEMORY_WP |
|
||||
EFI_MEMORY_RP | EFI_MEMORY_XP | EFI_MEMORY_RUNTIME))
|
||||
snprintf(pos, size, "|attr=0x%016llx]",
|
||||
(unsigned long long)attr);
|
||||
else
|
||||
snprintf(pos, size, "|%3s|%2s|%2s|%2s|%3s|%2s|%2s|%2s|%2s]",
|
||||
attr & EFI_MEMORY_RUNTIME ? "RUN" : "",
|
||||
attr & EFI_MEMORY_XP ? "XP" : "",
|
||||
attr & EFI_MEMORY_RP ? "RP" : "",
|
||||
attr & EFI_MEMORY_WP ? "WP" : "",
|
||||
attr & EFI_MEMORY_UCE ? "UCE" : "",
|
||||
attr & EFI_MEMORY_WB ? "WB" : "",
|
||||
attr & EFI_MEMORY_WT ? "WT" : "",
|
||||
attr & EFI_MEMORY_WC ? "WC" : "",
|
||||
attr & EFI_MEMORY_UC ? "UC" : "");
|
||||
return buf;
|
||||
}
|
755
drivers/firmware/efi/efivars.c
Normal file
755
drivers/firmware/efi/efivars.c
Normal file
|
@ -0,0 +1,755 @@
|
|||
/*
|
||||
* Originally from efivars.c,
|
||||
*
|
||||
* Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com>
|
||||
* Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com>
|
||||
*
|
||||
* This code takes all variables accessible from EFI runtime and
|
||||
* exports them via sysfs
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Changelog:
|
||||
*
|
||||
* 17 May 2004 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* remove check for efi_enabled in exit
|
||||
* add MODULE_VERSION
|
||||
*
|
||||
* 26 Apr 2004 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* minor bug fixes
|
||||
*
|
||||
* 21 Apr 2004 - Matt Tolentino <matthew.e.tolentino@intel.com)
|
||||
* converted driver to export variable information via sysfs
|
||||
* and moved to drivers/firmware directory
|
||||
* bumped revision number to v0.07 to reflect conversion & move
|
||||
*
|
||||
* 10 Dec 2002 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* fix locking per Peter Chubb's findings
|
||||
*
|
||||
* 25 Mar 2002 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* move uuid_unparse() to include/asm-ia64/efi.h:efi_guid_unparse()
|
||||
*
|
||||
* 12 Feb 2002 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* use list_for_each_safe when deleting vars.
|
||||
* remove ifdef CONFIG_SMP around include <linux/smp.h>
|
||||
* v0.04 release to linux-ia64@linuxia64.org
|
||||
*
|
||||
* 20 April 2001 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* Moved vars from /proc/efi to /proc/efi/vars, and made
|
||||
* efi.c own the /proc/efi directory.
|
||||
* v0.03 release to linux-ia64@linuxia64.org
|
||||
*
|
||||
* 26 March 2001 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* At the request of Stephane, moved ownership of /proc/efi
|
||||
* to efi.c, and now efivars lives under /proc/efi/vars.
|
||||
*
|
||||
* 12 March 2001 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* Feedback received from Stephane Eranian incorporated.
|
||||
* efivar_write() checks copy_from_user() return value.
|
||||
* efivar_read/write() returns proper errno.
|
||||
* v0.02 release to linux-ia64@linuxia64.org
|
||||
*
|
||||
* 26 February 2001 - Matt Domsch <Matt_Domsch@dell.com>
|
||||
* v0.01 release to linux-ia64@linuxia64.org
|
||||
*/
|
||||
|
||||
#include <linux/efi.h>
|
||||
#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"
|
||||
|
||||
MODULE_AUTHOR("Matt Domsch <Matt_Domsch@Dell.com>");
|
||||
MODULE_DESCRIPTION("sysfs interface to EFI Variables");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(EFIVARS_VERSION);
|
||||
MODULE_ALIAS("platform:efivars");
|
||||
|
||||
LIST_HEAD(efivar_sysfs_list);
|
||||
EXPORT_SYMBOL_GPL(efivar_sysfs_list);
|
||||
|
||||
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);
|
||||
ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count);
|
||||
};
|
||||
|
||||
#define EFIVAR_ATTR(_name, _mode, _show, _store) \
|
||||
struct efivar_attribute efivar_attr_##_name = { \
|
||||
.attr = {.name = __stringify(_name), .mode = _mode}, \
|
||||
.show = _show, \
|
||||
.store = _store, \
|
||||
};
|
||||
|
||||
#define to_efivar_attr(_attr) container_of(_attr, struct efivar_attribute, attr)
|
||||
#define to_efivar_entry(obj) container_of(obj, struct efivar_entry, kobj)
|
||||
|
||||
/*
|
||||
* Prototype for sysfs creation function
|
||||
*/
|
||||
static int
|
||||
efivar_create_sysfs_entry(struct efivar_entry *new_var);
|
||||
|
||||
static ssize_t
|
||||
efivar_guid_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
char *str = buf;
|
||||
|
||||
if (!entry || !buf)
|
||||
return 0;
|
||||
|
||||
efi_guid_unparse(&var->VendorGuid, str);
|
||||
str += strlen(str);
|
||||
str += sprintf(str, "\n");
|
||||
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_attr_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
char *str = buf;
|
||||
|
||||
if (!entry || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
var->DataSize = 1024;
|
||||
if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data))
|
||||
return -EIO;
|
||||
|
||||
if (var->Attributes & EFI_VARIABLE_NON_VOLATILE)
|
||||
str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n");
|
||||
if (var->Attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS)
|
||||
str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n");
|
||||
if (var->Attributes & EFI_VARIABLE_RUNTIME_ACCESS)
|
||||
str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n");
|
||||
if (var->Attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD)
|
||||
str += sprintf(str, "EFI_VARIABLE_HARDWARE_ERROR_RECORD\n");
|
||||
if (var->Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS)
|
||||
str += sprintf(str,
|
||||
"EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS\n");
|
||||
if (var->Attributes &
|
||||
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)
|
||||
str += sprintf(str,
|
||||
"EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS\n");
|
||||
if (var->Attributes & EFI_VARIABLE_APPEND_WRITE)
|
||||
str += sprintf(str, "EFI_VARIABLE_APPEND_WRITE\n");
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_size_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
char *str = buf;
|
||||
|
||||
if (!entry || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
var->DataSize = 1024;
|
||||
if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data))
|
||||
return -EIO;
|
||||
|
||||
str += sprintf(str, "0x%lx\n", var->DataSize);
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_data_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
|
||||
if (!entry || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
var->DataSize = 1024;
|
||||
if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data))
|
||||
return -EIO;
|
||||
|
||||
memcpy(buf, var->Data, var->DataSize);
|
||||
return var->DataSize;
|
||||
}
|
||||
|
||||
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(name, var->VariableName, sizeof(var->VariableName)) ||
|
||||
efi_guidcmp(vendor, var->VendorGuid)) {
|
||||
printk(KERN_ERR "efivars: Cannot edit the wrong variable!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((size <= 0) || (attributes == 0)){
|
||||
printk(KERN_ERR "efivars: DataSize & Attributes must be valid!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((attributes & ~EFI_VARIABLE_MASK) != 0 ||
|
||||
efivar_validate(name, data, size) == false) {
|
||||
printk(KERN_ERR "efivars: Malformed variable content\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
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, attributes, size, data, NULL);
|
||||
if (err) {
|
||||
printk(KERN_WARNING "efivars: set_variable() failed: status=%d\n", err);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
var->DataSize = 1024;
|
||||
if (efivar_entry_get(entry, &entry->var.Attributes,
|
||||
&entry->var.DataSize, entry->var.Data))
|
||||
return -EIO;
|
||||
|
||||
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 size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generic read/write functions that call the specific functions of
|
||||
* the attributes...
|
||||
*/
|
||||
static ssize_t efivar_attr_show(struct kobject *kobj, struct attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct efivar_entry *var = to_efivar_entry(kobj);
|
||||
struct efivar_attribute *efivar_attr = to_efivar_attr(attr);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
if (efivar_attr->show) {
|
||||
ret = efivar_attr->show(var, buf);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t efivar_attr_store(struct kobject *kobj, struct attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct efivar_entry *var = to_efivar_entry(kobj);
|
||||
struct efivar_attribute *efivar_attr = to_efivar_attr(attr);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
if (efivar_attr->store)
|
||||
ret = efivar_attr->store(var, buf, count);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct sysfs_ops efivar_attr_ops = {
|
||||
.show = efivar_attr_show,
|
||||
.store = efivar_attr_store,
|
||||
};
|
||||
|
||||
static void efivar_release(struct kobject *kobj)
|
||||
{
|
||||
struct efivar_entry *var = container_of(kobj, struct efivar_entry, kobj);
|
||||
kfree(var);
|
||||
}
|
||||
|
||||
static EFIVAR_ATTR(guid, 0400, efivar_guid_read, NULL);
|
||||
static EFIVAR_ATTR(attributes, 0400, efivar_attr_read, NULL);
|
||||
static EFIVAR_ATTR(size, 0400, efivar_size_read, NULL);
|
||||
static EFIVAR_ATTR(data, 0400, efivar_data_read, NULL);
|
||||
static EFIVAR_ATTR(raw_var, 0600, efivar_show_raw, efivar_store_raw);
|
||||
|
||||
static struct attribute *def_attrs[] = {
|
||||
&efivar_attr_guid.attr,
|
||||
&efivar_attr_size.attr,
|
||||
&efivar_attr_attributes.attr,
|
||||
&efivar_attr_data.attr,
|
||||
&efivar_attr_raw_var.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct kobj_type efivar_ktype = {
|
||||
.release = efivar_release,
|
||||
.sysfs_ops = &efivar_attr_ops,
|
||||
.default_attrs = def_attrs,
|
||||
};
|
||||
|
||||
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 (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;
|
||||
}
|
||||
|
||||
new_entry = kzalloc(sizeof(*new_entry), GFP_KERNEL);
|
||||
if (!new_entry)
|
||||
return -ENOMEM;
|
||||
|
||||
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, attributes, size,
|
||||
data, &efivar_sysfs_list);
|
||||
if (err) {
|
||||
if (err == -EEXIST)
|
||||
err = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (efivar_create_sysfs_entry(new_entry)) {
|
||||
printk(KERN_WARNING "efivars: failed to create sysfs entry.\n");
|
||||
kfree(new_entry);
|
||||
}
|
||||
return count;
|
||||
|
||||
out:
|
||||
kfree(new_entry);
|
||||
return err;
|
||||
}
|
||||
|
||||
static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
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(name, vendor, &efivar_sysfs_list, true);
|
||||
if (!entry)
|
||||
err = -EINVAL;
|
||||
else if (__efivar_entry_delete(entry))
|
||||
err = -EIO;
|
||||
|
||||
if (err) {
|
||||
efivar_entry_iter_end();
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!entry->scanning) {
|
||||
efivar_entry_iter_end();
|
||||
efivar_unregister(entry);
|
||||
} else
|
||||
efivar_entry_iter_end();
|
||||
|
||||
/* It's dead Jim.... */
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* efivar_create_sysfs_entry - create a new entry in sysfs
|
||||
* @new_var: efivar entry to create
|
||||
*
|
||||
* Returns 1 on failure, 0 on success
|
||||
*/
|
||||
static int
|
||||
efivar_create_sysfs_entry(struct efivar_entry *new_var)
|
||||
{
|
||||
int i, short_name_size;
|
||||
char *short_name;
|
||||
unsigned long variable_name_size;
|
||||
efi_char16_t *variable_name;
|
||||
|
||||
variable_name = new_var->var.VariableName;
|
||||
variable_name_size = ucs2_strlen(variable_name) * sizeof(efi_char16_t);
|
||||
|
||||
/*
|
||||
* Length of the variable bytes in ASCII, plus the '-' separator,
|
||||
* plus the GUID, plus trailing NUL
|
||||
*/
|
||||
short_name_size = variable_name_size / sizeof(efi_char16_t)
|
||||
+ 1 + EFI_VARIABLE_GUID_LEN + 1;
|
||||
|
||||
short_name = kzalloc(short_name_size, GFP_KERNEL);
|
||||
|
||||
if (!short_name)
|
||||
return 1;
|
||||
|
||||
/* Convert Unicode to normal chars (assume top bits are 0),
|
||||
ala UTF-8 */
|
||||
for (i=0; i < (int)(variable_name_size / sizeof(efi_char16_t)); i++) {
|
||||
short_name[i] = variable_name[i] & 0xFF;
|
||||
}
|
||||
/* This is ugly, but necessary to separate one vendor's
|
||||
private variables from another's. */
|
||||
|
||||
*(short_name + strlen(short_name)) = '-';
|
||||
efi_guid_unparse(&new_var->var.VendorGuid,
|
||||
short_name + strlen(short_name));
|
||||
|
||||
new_var->kobj.kset = efivars_kset;
|
||||
|
||||
i = kobject_init_and_add(&new_var->kobj, &efivar_ktype,
|
||||
NULL, "%s", short_name);
|
||||
kfree(short_name);
|
||||
if (i)
|
||||
return 1;
|
||||
|
||||
kobject_uevent(&new_var->kobj, KOBJ_ADD);
|
||||
efivar_entry_add(new_var, &efivar_sysfs_list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
create_efivars_bin_attributes(void)
|
||||
{
|
||||
struct bin_attribute *attr;
|
||||
int error;
|
||||
|
||||
/* new_var */
|
||||
attr = kzalloc(sizeof(*attr), GFP_KERNEL);
|
||||
if (!attr)
|
||||
return -ENOMEM;
|
||||
|
||||
attr->attr.name = "new_var";
|
||||
attr->attr.mode = 0200;
|
||||
attr->write = efivar_create;
|
||||
efivars_new_var = attr;
|
||||
|
||||
/* del_var */
|
||||
attr = kzalloc(sizeof(*attr), GFP_KERNEL);
|
||||
if (!attr) {
|
||||
error = -ENOMEM;
|
||||
goto out_free;
|
||||
}
|
||||
attr->attr.name = "del_var";
|
||||
attr->attr.mode = 0200;
|
||||
attr->write = efivar_delete;
|
||||
efivars_del_var = attr;
|
||||
|
||||
sysfs_bin_attr_init(efivars_new_var);
|
||||
sysfs_bin_attr_init(efivars_del_var);
|
||||
|
||||
/* Register */
|
||||
error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_new_var);
|
||||
if (error) {
|
||||
printk(KERN_ERR "efivars: unable to create new_var sysfs file"
|
||||
" due to error %d\n", error);
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_del_var);
|
||||
if (error) {
|
||||
printk(KERN_ERR "efivars: unable to create del_var sysfs file"
|
||||
" due to error %d\n", error);
|
||||
sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var);
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
return 0;
|
||||
out_free:
|
||||
kfree(efivars_del_var);
|
||||
efivars_del_var = NULL;
|
||||
kfree(efivars_new_var);
|
||||
efivars_new_var = NULL;
|
||||
return error;
|
||||
}
|
||||
|
||||
static int efivar_update_sysfs_entry(efi_char16_t *name, efi_guid_t vendor,
|
||||
unsigned long name_size, void *data)
|
||||
{
|
||||
struct efivar_entry *entry = data;
|
||||
|
||||
if (efivar_entry_find(name, vendor, &efivar_sysfs_list, false))
|
||||
return 0;
|
||||
|
||||
memcpy(entry->var.VariableName, name, name_size);
|
||||
memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void efivar_update_sysfs_entries(struct work_struct *work)
|
||||
{
|
||||
struct efivar_entry *entry;
|
||||
int err;
|
||||
|
||||
/* Add new sysfs entries */
|
||||
while (1) {
|
||||
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
||||
if (!entry)
|
||||
return;
|
||||
|
||||
err = efivar_init(efivar_update_sysfs_entry, entry,
|
||||
true, false, &efivar_sysfs_list);
|
||||
if (!err)
|
||||
break;
|
||||
|
||||
efivar_create_sysfs_entry(entry);
|
||||
}
|
||||
|
||||
kfree(entry);
|
||||
}
|
||||
|
||||
static int efivars_sysfs_callback(efi_char16_t *name, efi_guid_t vendor,
|
||||
unsigned long name_size, void *data)
|
||||
{
|
||||
struct efivar_entry *entry;
|
||||
|
||||
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
||||
if (!entry)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(entry->var.VariableName, name, name_size);
|
||||
memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
|
||||
|
||||
efivar_create_sysfs_entry(entry);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int efivar_sysfs_destroy(struct efivar_entry *entry, void *data)
|
||||
{
|
||||
efivar_entry_remove(entry);
|
||||
efivar_unregister(entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void efivars_sysfs_exit(void)
|
||||
{
|
||||
/* Remove all entries and destroy */
|
||||
__efivar_entry_iter(efivar_sysfs_destroy, &efivar_sysfs_list, NULL, NULL);
|
||||
|
||||
if (efivars_new_var)
|
||||
sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var);
|
||||
if (efivars_del_var)
|
||||
sysfs_remove_bin_file(&efivars_kset->kobj, efivars_del_var);
|
||||
kfree(efivars_new_var);
|
||||
kfree(efivars_del_var);
|
||||
kset_unregister(efivars_kset);
|
||||
}
|
||||
|
||||
int efivars_sysfs_init(void)
|
||||
{
|
||||
struct kobject *parent_kobj = efivars_kobject();
|
||||
int error = 0;
|
||||
|
||||
if (!efi_enabled(EFI_RUNTIME_SERVICES))
|
||||
return -ENODEV;
|
||||
|
||||
/* No efivars has been registered yet */
|
||||
if (!parent_kobj)
|
||||
return 0;
|
||||
|
||||
printk(KERN_INFO "EFI Variables Facility v%s %s\n", EFIVARS_VERSION,
|
||||
EFIVARS_DATE);
|
||||
|
||||
efivars_kset = kset_create_and_add("vars", NULL, parent_kobj);
|
||||
if (!efivars_kset) {
|
||||
printk(KERN_ERR "efivars: Subsystem registration failed.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
efivar_init(efivars_sysfs_callback, NULL, false,
|
||||
true, &efivar_sysfs_list);
|
||||
|
||||
error = create_efivars_bin_attributes();
|
||||
if (error) {
|
||||
efivars_sysfs_exit();
|
||||
return error;
|
||||
}
|
||||
|
||||
INIT_WORK(&efivar_work, efivar_update_sysfs_entries);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(efivars_sysfs_init);
|
||||
|
||||
module_init(efivars_sysfs_init);
|
||||
module_exit(efivars_sysfs_exit);
|
26
drivers/firmware/efi/libstub/Makefile
Normal file
26
drivers/firmware/efi/libstub/Makefile
Normal file
|
@ -0,0 +1,26 @@
|
|||
#
|
||||
# The stub may be linked into the kernel proper or into a separate boot binary,
|
||||
# but in either case, it executes before the kernel does (with MMU disabled) so
|
||||
# things like ftrace and stack-protector are likely to cause trouble if left
|
||||
# enabled, even if doing so doesn't break the build.
|
||||
#
|
||||
cflags-$(CONFIG_X86_32) := -march=i386
|
||||
cflags-$(CONFIG_X86_64) := -mcmodel=small
|
||||
cflags-$(CONFIG_X86) += -m$(BITS) -D__KERNEL__ $(LINUX_INCLUDE) -O2 \
|
||||
-fPIC -fno-strict-aliasing -mno-red-zone \
|
||||
-mno-mmx -mno-sse -DDISABLE_BRANCH_PROFILING
|
||||
|
||||
cflags-$(CONFIG_ARM64) := $(subst -pg,,$(KBUILD_CFLAGS))
|
||||
cflags-$(CONFIG_ARM) := $(subst -pg,,$(KBUILD_CFLAGS)) \
|
||||
-fno-builtin -fpic -mno-single-pic-base
|
||||
|
||||
KBUILD_CFLAGS := $(cflags-y) \
|
||||
$(call cc-option,-ffreestanding) \
|
||||
$(call cc-option,-fno-stack-protector)
|
||||
|
||||
GCOV_PROFILE := n
|
||||
|
||||
lib-y := efi-stub-helper.o
|
||||
lib-$(CONFIG_EFI_ARMSTUB) += arm-stub.o fdt.o
|
||||
|
||||
CFLAGS_fdt.o += -I$(srctree)/scripts/dtc/libfdt/
|
288
drivers/firmware/efi/libstub/arm-stub.c
Normal file
288
drivers/firmware/efi/libstub/arm-stub.c
Normal file
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* EFI stub implementation that is shared by arm and arm64 architectures.
|
||||
* This should be #included by the EFI stub implementation files.
|
||||
*
|
||||
* Copyright (C) 2013,2014 Linaro Limited
|
||||
* Roy Franz <roy.franz@linaro.org
|
||||
* Copyright (C) 2013 Red Hat, Inc.
|
||||
* Mark Salter <msalter@redhat.com>
|
||||
*
|
||||
* This file is part of the Linux kernel, and is made available under the
|
||||
* terms of the GNU General Public License version 2.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/efi.h>
|
||||
#include <asm/efi.h>
|
||||
|
||||
#include "efistub.h"
|
||||
|
||||
static int __init efi_secureboot_enabled(efi_system_table_t *sys_table_arg)
|
||||
{
|
||||
static efi_guid_t const var_guid __initconst = EFI_GLOBAL_VARIABLE_GUID;
|
||||
static efi_char16_t const var_name[] __initconst = {
|
||||
'S', 'e', 'c', 'u', 'r', 'e', 'B', 'o', 'o', 't', 0 };
|
||||
|
||||
efi_get_variable_t *f_getvar = sys_table_arg->runtime->get_variable;
|
||||
unsigned long size = sizeof(u8);
|
||||
efi_status_t status;
|
||||
u8 val;
|
||||
|
||||
status = f_getvar((efi_char16_t *)var_name, (efi_guid_t *)&var_guid,
|
||||
NULL, &size, &val);
|
||||
|
||||
switch (status) {
|
||||
case EFI_SUCCESS:
|
||||
return val;
|
||||
case EFI_NOT_FOUND:
|
||||
return 0;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg,
|
||||
void *__image, void **__fh)
|
||||
{
|
||||
efi_file_io_interface_t *io;
|
||||
efi_loaded_image_t *image = __image;
|
||||
efi_file_handle_t *fh;
|
||||
efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
|
||||
efi_status_t status;
|
||||
void *handle = (void *)(unsigned long)image->device_handle;
|
||||
|
||||
status = sys_table_arg->boottime->handle_protocol(handle,
|
||||
&fs_proto, (void **)&io);
|
||||
if (status != EFI_SUCCESS) {
|
||||
efi_printk(sys_table_arg, "Failed to handle fs_proto\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
status = io->open_volume(io, &fh);
|
||||
if (status != EFI_SUCCESS)
|
||||
efi_printk(sys_table_arg, "Failed to open volume\n");
|
||||
|
||||
*__fh = fh;
|
||||
return status;
|
||||
}
|
||||
|
||||
efi_status_t efi_file_close(void *handle)
|
||||
{
|
||||
efi_file_handle_t *fh = handle;
|
||||
|
||||
return fh->close(handle);
|
||||
}
|
||||
|
||||
efi_status_t
|
||||
efi_file_read(void *handle, unsigned long *size, void *addr)
|
||||
{
|
||||
efi_file_handle_t *fh = handle;
|
||||
|
||||
return fh->read(handle, size, addr);
|
||||
}
|
||||
|
||||
|
||||
efi_status_t
|
||||
efi_file_size(efi_system_table_t *sys_table_arg, void *__fh,
|
||||
efi_char16_t *filename_16, void **handle, u64 *file_sz)
|
||||
{
|
||||
efi_file_handle_t *h, *fh = __fh;
|
||||
efi_file_info_t *info;
|
||||
efi_status_t status;
|
||||
efi_guid_t info_guid = EFI_FILE_INFO_ID;
|
||||
unsigned long info_sz;
|
||||
|
||||
status = fh->open(fh, &h, filename_16, EFI_FILE_MODE_READ, (u64)0);
|
||||
if (status != EFI_SUCCESS) {
|
||||
efi_printk(sys_table_arg, "Failed to open file: ");
|
||||
efi_char16_printk(sys_table_arg, filename_16);
|
||||
efi_printk(sys_table_arg, "\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
*handle = h;
|
||||
|
||||
info_sz = 0;
|
||||
status = h->get_info(h, &info_guid, &info_sz, NULL);
|
||||
if (status != EFI_BUFFER_TOO_SMALL) {
|
||||
efi_printk(sys_table_arg, "Failed to get file info size\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
grow:
|
||||
status = sys_table_arg->boottime->allocate_pool(EFI_LOADER_DATA,
|
||||
info_sz, (void **)&info);
|
||||
if (status != EFI_SUCCESS) {
|
||||
efi_printk(sys_table_arg, "Failed to alloc mem for file info\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
status = h->get_info(h, &info_guid, &info_sz,
|
||||
info);
|
||||
if (status == EFI_BUFFER_TOO_SMALL) {
|
||||
sys_table_arg->boottime->free_pool(info);
|
||||
goto grow;
|
||||
}
|
||||
|
||||
*file_sz = info->file_size;
|
||||
sys_table_arg->boottime->free_pool(info);
|
||||
|
||||
if (status != EFI_SUCCESS)
|
||||
efi_printk(sys_table_arg, "Failed to get initrd info\n");
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void efi_char16_printk(efi_system_table_t *sys_table_arg,
|
||||
efi_char16_t *str)
|
||||
{
|
||||
struct efi_simple_text_output_protocol *out;
|
||||
|
||||
out = (struct efi_simple_text_output_protocol *)sys_table_arg->con_out;
|
||||
out->output_string(out, str);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This function handles the architcture specific differences between arm and
|
||||
* arm64 regarding where the kernel image must be loaded and any memory that
|
||||
* must be reserved. On failure it is required to free all
|
||||
* all allocations it has made.
|
||||
*/
|
||||
efi_status_t handle_kernel_image(efi_system_table_t *sys_table,
|
||||
unsigned long *image_addr,
|
||||
unsigned long *image_size,
|
||||
unsigned long *reserve_addr,
|
||||
unsigned long *reserve_size,
|
||||
unsigned long dram_base,
|
||||
efi_loaded_image_t *image);
|
||||
/*
|
||||
* EFI entry point for the arm/arm64 EFI stubs. This is the entrypoint
|
||||
* that is described in the PE/COFF header. Most of the code is the same
|
||||
* for both archictectures, with the arch-specific code provided in the
|
||||
* handle_kernel_image() function.
|
||||
*/
|
||||
unsigned long __init efi_entry(void *handle, efi_system_table_t *sys_table,
|
||||
unsigned long *image_addr)
|
||||
{
|
||||
efi_loaded_image_t *image;
|
||||
efi_status_t status;
|
||||
unsigned long image_size = 0;
|
||||
unsigned long dram_base;
|
||||
/* addr/point and size pairs for memory management*/
|
||||
unsigned long initrd_addr;
|
||||
u64 initrd_size = 0;
|
||||
unsigned long fdt_addr = 0; /* Original DTB */
|
||||
u64 fdt_size = 0; /* We don't get size from configuration table */
|
||||
char *cmdline_ptr = NULL;
|
||||
int cmdline_size = 0;
|
||||
unsigned long new_fdt_addr;
|
||||
efi_guid_t loaded_image_proto = LOADED_IMAGE_PROTOCOL_GUID;
|
||||
unsigned long reserve_addr = 0;
|
||||
unsigned long reserve_size = 0;
|
||||
|
||||
/* Check if we were booted by the EFI firmware */
|
||||
if (sys_table->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE)
|
||||
goto fail;
|
||||
|
||||
pr_efi(sys_table, "Booting Linux Kernel...\n");
|
||||
|
||||
/*
|
||||
* Get a handle to the loaded image protocol. This is used to get
|
||||
* information about the running image, such as size and the command
|
||||
* line.
|
||||
*/
|
||||
status = sys_table->boottime->handle_protocol(handle,
|
||||
&loaded_image_proto, (void *)&image);
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table, "Failed to get loaded image protocol\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dram_base = get_dram_base(sys_table);
|
||||
if (dram_base == EFI_ERROR) {
|
||||
pr_efi_err(sys_table, "Failed to find DRAM base\n");
|
||||
goto fail;
|
||||
}
|
||||
status = handle_kernel_image(sys_table, image_addr, &image_size,
|
||||
&reserve_addr,
|
||||
&reserve_size,
|
||||
dram_base, image);
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table, "Failed to relocate kernel\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the command line from EFI, using the LOADED_IMAGE
|
||||
* protocol. We are going to copy the command line into the
|
||||
* device tree, so this can be allocated anywhere.
|
||||
*/
|
||||
cmdline_ptr = efi_convert_cmdline(sys_table, image, &cmdline_size);
|
||||
if (!cmdline_ptr) {
|
||||
pr_efi_err(sys_table, "getting command line via LOADED_IMAGE_PROTOCOL\n");
|
||||
goto fail_free_image;
|
||||
}
|
||||
|
||||
status = efi_parse_options(cmdline_ptr);
|
||||
if (status != EFI_SUCCESS)
|
||||
pr_efi_err(sys_table, "Failed to parse EFI cmdline options\n");
|
||||
|
||||
/*
|
||||
* Unauthenticated device tree data is a security hazard, so
|
||||
* ignore 'dtb=' unless UEFI Secure Boot is disabled.
|
||||
*/
|
||||
if (efi_secureboot_enabled(sys_table)) {
|
||||
pr_efi(sys_table, "UEFI Secure Boot is enabled.\n");
|
||||
} else {
|
||||
status = handle_cmdline_files(sys_table, image, cmdline_ptr,
|
||||
"dtb=",
|
||||
~0UL, (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;
|
||||
}
|
690
drivers/firmware/efi/libstub/efi-stub-helper.c
Normal file
690
drivers/firmware/efi/libstub/efi-stub-helper.c
Normal file
|
@ -0,0 +1,690 @@
|
|||
/*
|
||||
* Helper functions used by the EFI stub on multiple
|
||||
* architectures. This should be #included by the EFI stub
|
||||
* implementation files.
|
||||
*
|
||||
* Copyright 2011 Intel Corporation; author Matt Fleming
|
||||
*
|
||||
* This file is part of the Linux kernel, and is made available
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/efi.h>
|
||||
#include <asm/efi.h>
|
||||
|
||||
#include "efistub.h"
|
||||
|
||||
/*
|
||||
* Some firmware implementations have problems reading files in one go.
|
||||
* A read chunk size of 1MB seems to work for most platforms.
|
||||
*
|
||||
* Unfortunately, reading files in chunks triggers *other* bugs on some
|
||||
* platforms, so we provide a way to disable this workaround, which can
|
||||
* be done by passing "efi=nochunk" on the EFI boot stub command line.
|
||||
*
|
||||
* If you experience issues with initrd images being corrupt it's worth
|
||||
* trying efi=nochunk, but chunking is enabled by default because there
|
||||
* are far more machines that require the workaround than those that
|
||||
* break with it enabled.
|
||||
*/
|
||||
#define EFI_READ_CHUNK_SIZE (1024 * 1024)
|
||||
|
||||
static unsigned long __chunk_size = EFI_READ_CHUNK_SIZE;
|
||||
|
||||
struct file_info {
|
||||
efi_file_handle_t *handle;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
void efi_printk(efi_system_table_t *sys_table_arg, char *str)
|
||||
{
|
||||
char *s8;
|
||||
|
||||
for (s8 = str; *s8; s8++) {
|
||||
efi_char16_t ch[2] = { 0 };
|
||||
|
||||
ch[0] = *s8;
|
||||
if (*s8 == '\n') {
|
||||
efi_char16_t nl[2] = { '\r', 0 };
|
||||
efi_char16_printk(sys_table_arg, nl);
|
||||
}
|
||||
|
||||
efi_char16_printk(sys_table_arg, ch);
|
||||
}
|
||||
}
|
||||
|
||||
efi_status_t efi_get_memory_map(efi_system_table_t *sys_table_arg,
|
||||
efi_memory_desc_t **map,
|
||||
unsigned long *map_size,
|
||||
unsigned long *desc_size,
|
||||
u32 *desc_ver,
|
||||
unsigned long *key_ptr)
|
||||
{
|
||||
efi_memory_desc_t *m = NULL;
|
||||
efi_status_t status;
|
||||
unsigned long key;
|
||||
u32 desc_version;
|
||||
|
||||
*map_size = sizeof(*m) * 32;
|
||||
again:
|
||||
/*
|
||||
* Add an additional efi_memory_desc_t because we're doing an
|
||||
* allocation which may be in a new descriptor region.
|
||||
*/
|
||||
*map_size += sizeof(*m);
|
||||
status = efi_call_early(allocate_pool, EFI_LOADER_DATA,
|
||||
*map_size, (void **)&m);
|
||||
if (status != EFI_SUCCESS)
|
||||
goto fail;
|
||||
|
||||
*desc_size = 0;
|
||||
key = 0;
|
||||
status = efi_call_early(get_memory_map, map_size, m,
|
||||
&key, desc_size, &desc_version);
|
||||
if (status == EFI_BUFFER_TOO_SMALL) {
|
||||
efi_call_early(free_pool, m);
|
||||
goto again;
|
||||
}
|
||||
|
||||
if (status != EFI_SUCCESS)
|
||||
efi_call_early(free_pool, m);
|
||||
|
||||
if (key_ptr && status == EFI_SUCCESS)
|
||||
*key_ptr = key;
|
||||
if (desc_ver && status == EFI_SUCCESS)
|
||||
*desc_ver = desc_version;
|
||||
|
||||
fail:
|
||||
*map = m;
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
unsigned long __init get_dram_base(efi_system_table_t *sys_table_arg)
|
||||
{
|
||||
efi_status_t status;
|
||||
unsigned long map_size;
|
||||
unsigned long membase = EFI_ERROR;
|
||||
struct efi_memory_map map;
|
||||
efi_memory_desc_t *md;
|
||||
|
||||
status = efi_get_memory_map(sys_table_arg, (efi_memory_desc_t **)&map.map,
|
||||
&map_size, &map.desc_size, NULL, NULL);
|
||||
if (status != EFI_SUCCESS)
|
||||
return membase;
|
||||
|
||||
map.map_end = map.map + map_size;
|
||||
|
||||
for_each_efi_memory_desc(&map, md)
|
||||
if (md->attribute & EFI_MEMORY_WB)
|
||||
if (membase > md->phys_addr)
|
||||
membase = md->phys_addr;
|
||||
|
||||
efi_call_early(free_pool, map.map);
|
||||
|
||||
return membase;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate at the highest possible address that is not above 'max'.
|
||||
*/
|
||||
efi_status_t efi_high_alloc(efi_system_table_t *sys_table_arg,
|
||||
unsigned long size, unsigned long align,
|
||||
unsigned long *addr, unsigned long max)
|
||||
{
|
||||
unsigned long map_size, desc_size;
|
||||
efi_memory_desc_t *map;
|
||||
efi_status_t status;
|
||||
unsigned long nr_pages;
|
||||
u64 max_addr = 0;
|
||||
int i;
|
||||
|
||||
status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size,
|
||||
NULL, NULL);
|
||||
if (status != EFI_SUCCESS)
|
||||
goto fail;
|
||||
|
||||
/*
|
||||
* Enforce minimum alignment that EFI requires when requesting
|
||||
* a specific address. We are doing page-based allocations,
|
||||
* so we must be aligned to a page.
|
||||
*/
|
||||
if (align < EFI_PAGE_SIZE)
|
||||
align = EFI_PAGE_SIZE;
|
||||
|
||||
nr_pages = round_up(size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
|
||||
again:
|
||||
for (i = 0; i < map_size / desc_size; i++) {
|
||||
efi_memory_desc_t *desc;
|
||||
unsigned long m = (unsigned long)map;
|
||||
u64 start, end;
|
||||
|
||||
desc = (efi_memory_desc_t *)(m + (i * desc_size));
|
||||
if (desc->type != EFI_CONVENTIONAL_MEMORY)
|
||||
continue;
|
||||
|
||||
if (desc->num_pages < nr_pages)
|
||||
continue;
|
||||
|
||||
start = desc->phys_addr;
|
||||
end = start + desc->num_pages * (1UL << EFI_PAGE_SHIFT);
|
||||
|
||||
if (end > max)
|
||||
end = max;
|
||||
|
||||
if ((start + size) > end)
|
||||
continue;
|
||||
|
||||
if (round_down(end - size, align) < start)
|
||||
continue;
|
||||
|
||||
start = round_down(end - size, align);
|
||||
|
||||
/*
|
||||
* Don't allocate at 0x0. It will confuse code that
|
||||
* checks pointers against NULL.
|
||||
*/
|
||||
if (start == 0x0)
|
||||
continue;
|
||||
|
||||
if (start > max_addr)
|
||||
max_addr = start;
|
||||
}
|
||||
|
||||
if (!max_addr)
|
||||
status = EFI_NOT_FOUND;
|
||||
else {
|
||||
status = efi_call_early(allocate_pages,
|
||||
EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
|
||||
nr_pages, &max_addr);
|
||||
if (status != EFI_SUCCESS) {
|
||||
max = max_addr;
|
||||
max_addr = 0;
|
||||
goto again;
|
||||
}
|
||||
|
||||
*addr = max_addr;
|
||||
}
|
||||
|
||||
efi_call_early(free_pool, map);
|
||||
fail:
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate at the lowest possible address.
|
||||
*/
|
||||
efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg,
|
||||
unsigned long size, unsigned long align,
|
||||
unsigned long *addr)
|
||||
{
|
||||
unsigned long map_size, desc_size;
|
||||
efi_memory_desc_t *map;
|
||||
efi_status_t status;
|
||||
unsigned long nr_pages;
|
||||
int i;
|
||||
|
||||
status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size,
|
||||
NULL, NULL);
|
||||
if (status != EFI_SUCCESS)
|
||||
goto fail;
|
||||
|
||||
/*
|
||||
* Enforce minimum alignment that EFI requires when requesting
|
||||
* a specific address. We are doing page-based allocations,
|
||||
* so we must be aligned to a page.
|
||||
*/
|
||||
if (align < EFI_PAGE_SIZE)
|
||||
align = EFI_PAGE_SIZE;
|
||||
|
||||
nr_pages = round_up(size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
|
||||
for (i = 0; i < map_size / desc_size; i++) {
|
||||
efi_memory_desc_t *desc;
|
||||
unsigned long m = (unsigned long)map;
|
||||
u64 start, end;
|
||||
|
||||
desc = (efi_memory_desc_t *)(m + (i * desc_size));
|
||||
|
||||
if (desc->type != EFI_CONVENTIONAL_MEMORY)
|
||||
continue;
|
||||
|
||||
if (desc->num_pages < nr_pages)
|
||||
continue;
|
||||
|
||||
start = desc->phys_addr;
|
||||
end = start + desc->num_pages * (1UL << EFI_PAGE_SHIFT);
|
||||
|
||||
/*
|
||||
* Don't allocate at 0x0. It will confuse code that
|
||||
* checks pointers against NULL. Skip the first 8
|
||||
* bytes so we start at a nice even number.
|
||||
*/
|
||||
if (start == 0x0)
|
||||
start += 8;
|
||||
|
||||
start = round_up(start, align);
|
||||
if ((start + size) > end)
|
||||
continue;
|
||||
|
||||
status = efi_call_early(allocate_pages,
|
||||
EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
|
||||
nr_pages, &start);
|
||||
if (status == EFI_SUCCESS) {
|
||||
*addr = start;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == map_size / desc_size)
|
||||
status = EFI_NOT_FOUND;
|
||||
|
||||
efi_call_early(free_pool, map);
|
||||
fail:
|
||||
return status;
|
||||
}
|
||||
|
||||
void efi_free(efi_system_table_t *sys_table_arg, unsigned long size,
|
||||
unsigned long addr)
|
||||
{
|
||||
unsigned long nr_pages;
|
||||
|
||||
if (!size)
|
||||
return;
|
||||
|
||||
nr_pages = round_up(size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
|
||||
efi_call_early(free_pages, addr, nr_pages);
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the ASCII string 'cmdline' for EFI options, denoted by the efi=
|
||||
* option, e.g. efi=nochunk.
|
||||
*
|
||||
* It should be noted that efi= is parsed in two very different
|
||||
* environments, first in the early boot environment of the EFI boot
|
||||
* stub, and subsequently during the kernel boot.
|
||||
*/
|
||||
efi_status_t efi_parse_options(char *cmdline)
|
||||
{
|
||||
char *str;
|
||||
|
||||
/*
|
||||
* If no EFI parameters were specified on the cmdline we've got
|
||||
* nothing to do.
|
||||
*/
|
||||
str = strstr(cmdline, "efi=");
|
||||
if (!str)
|
||||
return EFI_SUCCESS;
|
||||
|
||||
/* Skip ahead to first argument */
|
||||
str += strlen("efi=");
|
||||
|
||||
/*
|
||||
* Remember, because efi= is also used by the kernel we need to
|
||||
* skip over arguments we don't understand.
|
||||
*/
|
||||
while (*str) {
|
||||
if (!strncmp(str, "nochunk", 7)) {
|
||||
str += strlen("nochunk");
|
||||
__chunk_size = -1UL;
|
||||
}
|
||||
|
||||
/* Group words together, delimited by "," */
|
||||
while (*str && *str != ',')
|
||||
str++;
|
||||
|
||||
if (*str == ',')
|
||||
str++;
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the cmdline for a LILO-style file= arguments.
|
||||
*
|
||||
* We only support loading a file from the same filesystem as
|
||||
* the kernel image.
|
||||
*/
|
||||
efi_status_t handle_cmdline_files(efi_system_table_t *sys_table_arg,
|
||||
efi_loaded_image_t *image,
|
||||
char *cmd_line, char *option_string,
|
||||
unsigned long max_addr,
|
||||
unsigned long *load_addr,
|
||||
unsigned long *load_size)
|
||||
{
|
||||
struct file_info *files;
|
||||
unsigned long file_addr;
|
||||
u64 file_size_total;
|
||||
efi_file_handle_t *fh = NULL;
|
||||
efi_status_t status;
|
||||
int nr_files;
|
||||
char *str;
|
||||
int i, j, k;
|
||||
|
||||
file_addr = 0;
|
||||
file_size_total = 0;
|
||||
|
||||
str = cmd_line;
|
||||
|
||||
j = 0; /* See close_handles */
|
||||
|
||||
if (!load_addr || !load_size)
|
||||
return EFI_INVALID_PARAMETER;
|
||||
|
||||
*load_addr = 0;
|
||||
*load_size = 0;
|
||||
|
||||
if (!str || !*str)
|
||||
return EFI_SUCCESS;
|
||||
|
||||
for (nr_files = 0; *str; nr_files++) {
|
||||
str = strstr(str, option_string);
|
||||
if (!str)
|
||||
break;
|
||||
|
||||
str += strlen(option_string);
|
||||
|
||||
/* Skip any leading slashes */
|
||||
while (*str == '/' || *str == '\\')
|
||||
str++;
|
||||
|
||||
while (*str && *str != ' ' && *str != '\n')
|
||||
str++;
|
||||
}
|
||||
|
||||
if (!nr_files)
|
||||
return EFI_SUCCESS;
|
||||
|
||||
status = efi_call_early(allocate_pool, EFI_LOADER_DATA,
|
||||
nr_files * sizeof(*files), (void **)&files);
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table_arg, "Failed to alloc mem for file handle list\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
str = cmd_line;
|
||||
for (i = 0; i < nr_files; i++) {
|
||||
struct file_info *file;
|
||||
efi_char16_t filename_16[256];
|
||||
efi_char16_t *p;
|
||||
|
||||
str = strstr(str, option_string);
|
||||
if (!str)
|
||||
break;
|
||||
|
||||
str += strlen(option_string);
|
||||
|
||||
file = &files[i];
|
||||
p = filename_16;
|
||||
|
||||
/* Skip any leading slashes */
|
||||
while (*str == '/' || *str == '\\')
|
||||
str++;
|
||||
|
||||
while (*str && *str != ' ' && *str != '\n') {
|
||||
if ((u8 *)p >= (u8 *)filename_16 + sizeof(filename_16))
|
||||
break;
|
||||
|
||||
if (*str == '/') {
|
||||
*p++ = '\\';
|
||||
str++;
|
||||
} else {
|
||||
*p++ = *str++;
|
||||
}
|
||||
}
|
||||
|
||||
*p = '\0';
|
||||
|
||||
/* Only open the volume once. */
|
||||
if (!i) {
|
||||
status = efi_open_volume(sys_table_arg, image,
|
||||
(void **)&fh);
|
||||
if (status != EFI_SUCCESS)
|
||||
goto free_files;
|
||||
}
|
||||
|
||||
status = efi_file_size(sys_table_arg, fh, filename_16,
|
||||
(void **)&file->handle, &file->size);
|
||||
if (status != EFI_SUCCESS)
|
||||
goto close_handles;
|
||||
|
||||
file_size_total += file->size;
|
||||
}
|
||||
|
||||
if (file_size_total) {
|
||||
unsigned long addr;
|
||||
|
||||
/*
|
||||
* Multiple files need to be at consecutive addresses in memory,
|
||||
* so allocate enough memory for all the files. This is used
|
||||
* for loading multiple files.
|
||||
*/
|
||||
status = efi_high_alloc(sys_table_arg, file_size_total, 0x1000,
|
||||
&file_addr, max_addr);
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table_arg, "Failed to alloc highmem for files\n");
|
||||
goto close_handles;
|
||||
}
|
||||
|
||||
/* We've run out of free low memory. */
|
||||
if (file_addr > max_addr) {
|
||||
pr_efi_err(sys_table_arg, "We've run out of free low memory\n");
|
||||
status = EFI_INVALID_PARAMETER;
|
||||
goto free_file_total;
|
||||
}
|
||||
|
||||
addr = file_addr;
|
||||
for (j = 0; j < nr_files; j++) {
|
||||
unsigned long size;
|
||||
|
||||
size = files[j].size;
|
||||
while (size) {
|
||||
unsigned long chunksize;
|
||||
if (size > __chunk_size)
|
||||
chunksize = __chunk_size;
|
||||
else
|
||||
chunksize = size;
|
||||
|
||||
status = efi_file_read(files[j].handle,
|
||||
&chunksize,
|
||||
(void *)addr);
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table_arg, "Failed to read file\n");
|
||||
goto free_file_total;
|
||||
}
|
||||
addr += chunksize;
|
||||
size -= chunksize;
|
||||
}
|
||||
|
||||
efi_file_close(files[j].handle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
efi_call_early(free_pool, files);
|
||||
|
||||
*load_addr = file_addr;
|
||||
*load_size = file_size_total;
|
||||
|
||||
return status;
|
||||
|
||||
free_file_total:
|
||||
efi_free(sys_table_arg, file_size_total, file_addr);
|
||||
|
||||
close_handles:
|
||||
for (k = j; k < i; k++)
|
||||
efi_file_close(files[k].handle);
|
||||
free_files:
|
||||
efi_call_early(free_pool, files);
|
||||
fail:
|
||||
*load_addr = 0;
|
||||
*load_size = 0;
|
||||
|
||||
return status;
|
||||
}
|
||||
/*
|
||||
* Relocate a kernel image, either compressed or uncompressed.
|
||||
* In the ARM64 case, all kernel images are currently
|
||||
* uncompressed, and as such when we relocate it we need to
|
||||
* allocate additional space for the BSS segment. Any low
|
||||
* memory that this function should avoid needs to be
|
||||
* unavailable in the EFI memory map, as if the preferred
|
||||
* address is not available the lowest available address will
|
||||
* be used.
|
||||
*/
|
||||
efi_status_t efi_relocate_kernel(efi_system_table_t *sys_table_arg,
|
||||
unsigned long *image_addr,
|
||||
unsigned long image_size,
|
||||
unsigned long alloc_size,
|
||||
unsigned long preferred_addr,
|
||||
unsigned long alignment)
|
||||
{
|
||||
unsigned long cur_image_addr;
|
||||
unsigned long new_addr = 0;
|
||||
efi_status_t status;
|
||||
unsigned long nr_pages;
|
||||
efi_physical_addr_t efi_addr = preferred_addr;
|
||||
|
||||
if (!image_addr || !image_size || !alloc_size)
|
||||
return EFI_INVALID_PARAMETER;
|
||||
if (alloc_size < image_size)
|
||||
return EFI_INVALID_PARAMETER;
|
||||
|
||||
cur_image_addr = *image_addr;
|
||||
|
||||
/*
|
||||
* The EFI firmware loader could have placed the kernel image
|
||||
* anywhere in memory, but the kernel has restrictions on the
|
||||
* max physical address it can run at. Some architectures
|
||||
* also have a prefered address, so first try to relocate
|
||||
* to the preferred address. If that fails, allocate as low
|
||||
* as possible while respecting the required alignment.
|
||||
*/
|
||||
nr_pages = round_up(alloc_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
|
||||
status = efi_call_early(allocate_pages,
|
||||
EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
|
||||
nr_pages, &efi_addr);
|
||||
new_addr = efi_addr;
|
||||
/*
|
||||
* If preferred address allocation failed allocate as low as
|
||||
* possible.
|
||||
*/
|
||||
if (status != EFI_SUCCESS) {
|
||||
status = efi_low_alloc(sys_table_arg, alloc_size, alignment,
|
||||
&new_addr);
|
||||
}
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table_arg, "Failed to allocate usable memory for kernel.\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* We know source/dest won't overlap since both memory ranges
|
||||
* have been allocated by UEFI, so we can safely use memcpy.
|
||||
*/
|
||||
memcpy((void *)new_addr, (void *)cur_image_addr, image_size);
|
||||
|
||||
/* Return the new address of the relocated image. */
|
||||
*image_addr = new_addr;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the number of UTF-8 bytes corresponding to an UTF-16 character.
|
||||
* This overestimates for surrogates, but that is okay.
|
||||
*/
|
||||
static int efi_utf8_bytes(u16 c)
|
||||
{
|
||||
return 1 + (c >= 0x80) + (c >= 0x800);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an UTF-16 string, not necessarily null terminated, to UTF-8.
|
||||
*/
|
||||
static u8 *efi_utf16_to_utf8(u8 *dst, const u16 *src, int n)
|
||||
{
|
||||
unsigned int c;
|
||||
|
||||
while (n--) {
|
||||
c = *src++;
|
||||
if (n && c >= 0xd800 && c <= 0xdbff &&
|
||||
*src >= 0xdc00 && *src <= 0xdfff) {
|
||||
c = 0x10000 + ((c & 0x3ff) << 10) + (*src & 0x3ff);
|
||||
src++;
|
||||
n--;
|
||||
}
|
||||
if (c >= 0xd800 && c <= 0xdfff)
|
||||
c = 0xfffd; /* Unmatched surrogate */
|
||||
if (c < 0x80) {
|
||||
*dst++ = c;
|
||||
continue;
|
||||
}
|
||||
if (c < 0x800) {
|
||||
*dst++ = 0xc0 + (c >> 6);
|
||||
goto t1;
|
||||
}
|
||||
if (c < 0x10000) {
|
||||
*dst++ = 0xe0 + (c >> 12);
|
||||
goto t2;
|
||||
}
|
||||
*dst++ = 0xf0 + (c >> 18);
|
||||
*dst++ = 0x80 + ((c >> 12) & 0x3f);
|
||||
t2:
|
||||
*dst++ = 0x80 + ((c >> 6) & 0x3f);
|
||||
t1:
|
||||
*dst++ = 0x80 + (c & 0x3f);
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert the unicode UEFI command line to ASCII to pass to kernel.
|
||||
* Size of memory allocated return in *cmd_line_len.
|
||||
* Returns NULL on error.
|
||||
*/
|
||||
char *efi_convert_cmdline(efi_system_table_t *sys_table_arg,
|
||||
efi_loaded_image_t *image,
|
||||
int *cmd_line_len)
|
||||
{
|
||||
const u16 *s2;
|
||||
u8 *s1 = NULL;
|
||||
unsigned long cmdline_addr = 0;
|
||||
int load_options_chars = image->load_options_size / 2; /* UTF-16 */
|
||||
const u16 *options = image->load_options;
|
||||
int options_bytes = 0; /* UTF-8 bytes */
|
||||
int options_chars = 0; /* UTF-16 chars */
|
||||
efi_status_t status;
|
||||
u16 zero = 0;
|
||||
|
||||
if (options) {
|
||||
s2 = options;
|
||||
while (*s2 && *s2 != '\n'
|
||||
&& options_chars < load_options_chars) {
|
||||
options_bytes += efi_utf8_bytes(*s2++);
|
||||
options_chars++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!options_chars) {
|
||||
/* No command line options, so return empty string*/
|
||||
options = &zero;
|
||||
}
|
||||
|
||||
options_bytes++; /* NUL termination */
|
||||
|
||||
status = efi_low_alloc(sys_table_arg, options_bytes, 0, &cmdline_addr);
|
||||
if (status != EFI_SUCCESS)
|
||||
return NULL;
|
||||
|
||||
s1 = (u8 *)cmdline_addr;
|
||||
s2 = (const u16 *)options;
|
||||
|
||||
s1 = efi_utf16_to_utf8(s1, s2, options_chars);
|
||||
*s1 = '\0';
|
||||
|
||||
*cmd_line_len = options_bytes;
|
||||
return (char *)cmdline_addr;
|
||||
}
|
42
drivers/firmware/efi/libstub/efistub.h
Normal file
42
drivers/firmware/efi/libstub/efistub.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
|
||||
#ifndef _DRIVERS_FIRMWARE_EFI_EFISTUB_H
|
||||
#define _DRIVERS_FIRMWARE_EFI_EFISTUB_H
|
||||
|
||||
/* error code which can't be mistaken for valid address */
|
||||
#define EFI_ERROR (~0UL)
|
||||
|
||||
void efi_char16_printk(efi_system_table_t *, efi_char16_t *);
|
||||
|
||||
efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg, void *__image,
|
||||
void **__fh);
|
||||
|
||||
efi_status_t efi_file_size(efi_system_table_t *sys_table_arg, void *__fh,
|
||||
efi_char16_t *filename_16, void **handle,
|
||||
u64 *file_sz);
|
||||
|
||||
efi_status_t efi_file_read(void *handle, unsigned long *size, void *addr);
|
||||
|
||||
efi_status_t efi_file_close(void *handle);
|
||||
|
||||
unsigned long get_dram_base(efi_system_table_t *sys_table_arg);
|
||||
|
||||
efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt,
|
||||
unsigned long orig_fdt_size,
|
||||
void *fdt, int new_fdt_size, char *cmdline_ptr,
|
||||
u64 initrd_addr, u64 initrd_size,
|
||||
efi_memory_desc_t *memory_map,
|
||||
unsigned long map_size, unsigned long desc_size,
|
||||
u32 desc_ver);
|
||||
|
||||
efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
|
||||
void *handle,
|
||||
unsigned long *new_fdt_addr,
|
||||
unsigned long max_addr,
|
||||
u64 initrd_addr, u64 initrd_size,
|
||||
char *cmdline_ptr,
|
||||
unsigned long fdt_addr,
|
||||
unsigned long fdt_size);
|
||||
|
||||
void *get_fdt(efi_system_table_t *sys_table);
|
||||
|
||||
#endif
|
287
drivers/firmware/efi/libstub/fdt.c
Normal file
287
drivers/firmware/efi/libstub/fdt.c
Normal file
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
* FDT related Helper functions used by the EFI stub on multiple
|
||||
* architectures. This should be #included by the EFI stub
|
||||
* implementation files.
|
||||
*
|
||||
* Copyright 2013 Linaro Limited; author Roy Franz
|
||||
*
|
||||
* This file is part of the Linux kernel, and is made available
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/efi.h>
|
||||
#include <linux/libfdt.h>
|
||||
#include <asm/efi.h>
|
||||
|
||||
efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt,
|
||||
unsigned long orig_fdt_size,
|
||||
void *fdt, int new_fdt_size, char *cmdline_ptr,
|
||||
u64 initrd_addr, u64 initrd_size,
|
||||
efi_memory_desc_t *memory_map,
|
||||
unsigned long map_size, unsigned long desc_size,
|
||||
u32 desc_ver)
|
||||
{
|
||||
int node, prev, num_rsv;
|
||||
int status;
|
||||
u32 fdt_val32;
|
||||
u64 fdt_val64;
|
||||
|
||||
/* Do some checks on provided FDT, if it exists*/
|
||||
if (orig_fdt) {
|
||||
if (fdt_check_header(orig_fdt)) {
|
||||
pr_efi_err(sys_table, "Device Tree header not valid!\n");
|
||||
return EFI_LOAD_ERROR;
|
||||
}
|
||||
/*
|
||||
* We don't get the size of the FDT if we get if from a
|
||||
* configuration table.
|
||||
*/
|
||||
if (orig_fdt_size && fdt_totalsize(orig_fdt) > orig_fdt_size) {
|
||||
pr_efi_err(sys_table, "Truncated device tree! foo!\n");
|
||||
return EFI_LOAD_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (orig_fdt)
|
||||
status = fdt_open_into(orig_fdt, fdt, new_fdt_size);
|
||||
else
|
||||
status = fdt_create_empty_tree(fdt, new_fdt_size);
|
||||
|
||||
if (status != 0)
|
||||
goto fdt_set_fail;
|
||||
|
||||
/*
|
||||
* Delete any memory nodes present. We must delete nodes which
|
||||
* early_init_dt_scan_memory may try to use.
|
||||
*/
|
||||
prev = 0;
|
||||
for (;;) {
|
||||
const char *type;
|
||||
int len;
|
||||
|
||||
node = fdt_next_node(fdt, prev, NULL);
|
||||
if (node < 0)
|
||||
break;
|
||||
|
||||
type = fdt_getprop(fdt, node, "device_type", &len);
|
||||
if (type && strncmp(type, "memory", len) == 0) {
|
||||
fdt_del_node(fdt, node);
|
||||
continue;
|
||||
}
|
||||
|
||||
prev = node;
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete all memory reserve map entries. When booting via UEFI,
|
||||
* kernel will use the UEFI memory map to find reserved regions.
|
||||
*/
|
||||
num_rsv = fdt_num_mem_rsv(fdt);
|
||||
while (num_rsv-- > 0)
|
||||
fdt_del_mem_rsv(fdt, num_rsv);
|
||||
|
||||
node = fdt_subnode_offset(fdt, 0, "chosen");
|
||||
if (node < 0) {
|
||||
node = fdt_add_subnode(fdt, 0, "chosen");
|
||||
if (node < 0) {
|
||||
status = node; /* node is error code when negative */
|
||||
goto fdt_set_fail;
|
||||
}
|
||||
}
|
||||
|
||||
if ((cmdline_ptr != NULL) && (strlen(cmdline_ptr) > 0)) {
|
||||
status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr,
|
||||
strlen(cmdline_ptr) + 1);
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
}
|
||||
|
||||
/* Set initrd address/end in device tree, if present */
|
||||
if (initrd_size != 0) {
|
||||
u64 initrd_image_end;
|
||||
u64 initrd_image_start = cpu_to_fdt64(initrd_addr);
|
||||
|
||||
status = fdt_setprop(fdt, node, "linux,initrd-start",
|
||||
&initrd_image_start, sizeof(u64));
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
initrd_image_end = cpu_to_fdt64(initrd_addr + initrd_size);
|
||||
status = fdt_setprop(fdt, node, "linux,initrd-end",
|
||||
&initrd_image_end, sizeof(u64));
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
}
|
||||
|
||||
/* Add FDT entries for EFI runtime services in chosen node. */
|
||||
node = fdt_subnode_offset(fdt, 0, "chosen");
|
||||
fdt_val64 = cpu_to_fdt64((u64)(unsigned long)sys_table);
|
||||
status = fdt_setprop(fdt, node, "linux,uefi-system-table",
|
||||
&fdt_val64, sizeof(fdt_val64));
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
|
||||
fdt_val64 = cpu_to_fdt64((u64)(unsigned long)memory_map);
|
||||
status = fdt_setprop(fdt, node, "linux,uefi-mmap-start",
|
||||
&fdt_val64, sizeof(fdt_val64));
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
|
||||
fdt_val32 = cpu_to_fdt32(map_size);
|
||||
status = fdt_setprop(fdt, node, "linux,uefi-mmap-size",
|
||||
&fdt_val32, sizeof(fdt_val32));
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
|
||||
fdt_val32 = cpu_to_fdt32(desc_size);
|
||||
status = fdt_setprop(fdt, node, "linux,uefi-mmap-desc-size",
|
||||
&fdt_val32, sizeof(fdt_val32));
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
|
||||
fdt_val32 = cpu_to_fdt32(desc_ver);
|
||||
status = fdt_setprop(fdt, node, "linux,uefi-mmap-desc-ver",
|
||||
&fdt_val32, sizeof(fdt_val32));
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
|
||||
/*
|
||||
* Add kernel version banner so stub/kernel match can be
|
||||
* verified.
|
||||
*/
|
||||
status = fdt_setprop_string(fdt, node, "linux,uefi-stub-kern-ver",
|
||||
linux_banner);
|
||||
if (status)
|
||||
goto fdt_set_fail;
|
||||
|
||||
return EFI_SUCCESS;
|
||||
|
||||
fdt_set_fail:
|
||||
if (status == -FDT_ERR_NOSPACE)
|
||||
return EFI_BUFFER_TOO_SMALL;
|
||||
|
||||
return EFI_LOAD_ERROR;
|
||||
}
|
||||
|
||||
#ifndef EFI_FDT_ALIGN
|
||||
#define EFI_FDT_ALIGN EFI_PAGE_SIZE
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Allocate memory for a new FDT, then add EFI, commandline, and
|
||||
* initrd related fields to the FDT. This routine increases the
|
||||
* FDT allocation size until the allocated memory is large
|
||||
* enough. EFI allocations are in EFI_PAGE_SIZE granules,
|
||||
* which are fixed at 4K bytes, so in most cases the first
|
||||
* allocation should succeed.
|
||||
* EFI boot services are exited at the end of this function.
|
||||
* There must be no allocations between the get_memory_map()
|
||||
* call and the exit_boot_services() call, so the exiting of
|
||||
* boot services is very tightly tied to the creation of the FDT
|
||||
* with the final memory map in it.
|
||||
*/
|
||||
|
||||
efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
|
||||
void *handle,
|
||||
unsigned long *new_fdt_addr,
|
||||
unsigned long max_addr,
|
||||
u64 initrd_addr, u64 initrd_size,
|
||||
char *cmdline_ptr,
|
||||
unsigned long fdt_addr,
|
||||
unsigned long fdt_size)
|
||||
{
|
||||
unsigned long map_size, desc_size;
|
||||
u32 desc_ver;
|
||||
unsigned long mmap_key;
|
||||
efi_memory_desc_t *memory_map;
|
||||
unsigned long new_fdt_size;
|
||||
efi_status_t status;
|
||||
|
||||
/*
|
||||
* Estimate size of new FDT, and allocate memory for it. We
|
||||
* will allocate a bigger buffer if this ends up being too
|
||||
* small, so a rough guess is OK here.
|
||||
*/
|
||||
new_fdt_size = fdt_size + EFI_PAGE_SIZE;
|
||||
while (1) {
|
||||
status = efi_high_alloc(sys_table, new_fdt_size, EFI_FDT_ALIGN,
|
||||
new_fdt_addr, max_addr);
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table, "Unable to allocate memory for new device tree.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now that we have done our final memory allocation (and free)
|
||||
* we can get the memory map key needed for
|
||||
* exit_boot_services().
|
||||
*/
|
||||
status = efi_get_memory_map(sys_table, &memory_map, &map_size,
|
||||
&desc_size, &desc_ver, &mmap_key);
|
||||
if (status != EFI_SUCCESS)
|
||||
goto fail_free_new_fdt;
|
||||
|
||||
status = update_fdt(sys_table,
|
||||
(void *)fdt_addr, fdt_size,
|
||||
(void *)*new_fdt_addr, new_fdt_size,
|
||||
cmdline_ptr, initrd_addr, initrd_size,
|
||||
memory_map, map_size, desc_size, desc_ver);
|
||||
|
||||
/* Succeeding the first time is the expected case. */
|
||||
if (status == EFI_SUCCESS)
|
||||
break;
|
||||
|
||||
if (status == EFI_BUFFER_TOO_SMALL) {
|
||||
/*
|
||||
* We need to allocate more space for the new
|
||||
* device tree, so free existing buffer that is
|
||||
* too small. Also free memory map, as we will need
|
||||
* to get new one that reflects the free/alloc we do
|
||||
* on the device tree buffer.
|
||||
*/
|
||||
efi_free(sys_table, new_fdt_size, *new_fdt_addr);
|
||||
sys_table->boottime->free_pool(memory_map);
|
||||
new_fdt_size += EFI_PAGE_SIZE;
|
||||
} else {
|
||||
pr_efi_err(sys_table, "Unable to constuct new device tree.\n");
|
||||
goto fail_free_mmap;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now we are ready to exit_boot_services.*/
|
||||
status = sys_table->boottime->exit_boot_services(handle, mmap_key);
|
||||
|
||||
|
||||
if (status == EFI_SUCCESS)
|
||||
return status;
|
||||
|
||||
pr_efi_err(sys_table, "Exit boot services failed.\n");
|
||||
|
||||
fail_free_mmap:
|
||||
sys_table->boottime->free_pool(memory_map);
|
||||
|
||||
fail_free_new_fdt:
|
||||
efi_free(sys_table, new_fdt_size, *new_fdt_addr);
|
||||
|
||||
fail:
|
||||
return EFI_LOAD_ERROR;
|
||||
}
|
||||
|
||||
void *get_fdt(efi_system_table_t *sys_table)
|
||||
{
|
||||
efi_guid_t fdt_guid = DEVICE_TREE_GUID;
|
||||
efi_config_table_t *tables;
|
||||
void *fdt;
|
||||
int i;
|
||||
|
||||
tables = (efi_config_table_t *) sys_table->tables;
|
||||
fdt = NULL;
|
||||
|
||||
for (i = 0; i < sys_table->nr_tables; i++)
|
||||
if (efi_guidcmp(tables[i].guid, fdt_guid) == 0) {
|
||||
fdt = (void *) tables[i].table;
|
||||
break;
|
||||
}
|
||||
|
||||
return fdt;
|
||||
}
|
56
drivers/firmware/efi/reboot.c
Normal file
56
drivers/firmware/efi/reboot.c
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (C) 2014 Intel Corporation; author Matt Fleming
|
||||
* Copyright (c) 2014 Red Hat, Inc., Mark Salter <msalter@redhat.com>
|
||||
*/
|
||||
#include <linux/efi.h>
|
||||
#include <linux/reboot.h>
|
||||
|
||||
int efi_reboot_quirk_mode = -1;
|
||||
|
||||
void efi_reboot(enum reboot_mode reboot_mode, const char *__unused)
|
||||
{
|
||||
int efi_mode;
|
||||
|
||||
if (!efi_enabled(EFI_RUNTIME_SERVICES))
|
||||
return;
|
||||
|
||||
switch (reboot_mode) {
|
||||
case REBOOT_WARM:
|
||||
case REBOOT_SOFT:
|
||||
efi_mode = EFI_RESET_WARM;
|
||||
break;
|
||||
default:
|
||||
efi_mode = EFI_RESET_COLD;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* If a quirk forced an EFI reset mode, always use that.
|
||||
*/
|
||||
if (efi_reboot_quirk_mode != -1)
|
||||
efi_mode = efi_reboot_quirk_mode;
|
||||
|
||||
efi.reset_system(efi_mode, EFI_SUCCESS, 0, NULL);
|
||||
}
|
||||
|
||||
bool __weak efi_poweroff_required(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static void efi_power_off(void)
|
||||
{
|
||||
efi.reset_system(EFI_RESET_SHUTDOWN, EFI_SUCCESS, 0, NULL);
|
||||
}
|
||||
|
||||
static int __init efi_shutdown_init(void)
|
||||
{
|
||||
if (!efi_enabled(EFI_RUNTIME_SERVICES))
|
||||
return -ENODEV;
|
||||
|
||||
if (efi_poweroff_required())
|
||||
pm_power_off = efi_power_off;
|
||||
|
||||
return 0;
|
||||
}
|
||||
late_initcall(efi_shutdown_init);
|
202
drivers/firmware/efi/runtime-map.c
Normal file
202
drivers/firmware/efi/runtime-map.c
Normal file
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
int efi_get_runtime_map_size(void)
|
||||
{
|
||||
return nr_efi_runtime_map * efi_memdesc_size;
|
||||
}
|
||||
|
||||
int efi_get_runtime_map_desc_size(void)
|
||||
{
|
||||
return efi_memdesc_size;
|
||||
}
|
||||
|
||||
int efi_runtime_map_copy(void *buf, size_t bufsz)
|
||||
{
|
||||
size_t sz = efi_get_runtime_map_size();
|
||||
|
||||
if (sz > bufsz)
|
||||
sz = bufsz;
|
||||
|
||||
memcpy(buf, efi_runtime_map, sz);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
305
drivers/firmware/efi/runtime-wrappers.c
Normal file
305
drivers/firmware/efi/runtime-wrappers.c
Normal file
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* runtime-wrappers.c - Runtime Services function call wrappers
|
||||
*
|
||||
* Copyright (C) 2014 Linaro Ltd. <ard.biesheuvel@linaro.org>
|
||||
*
|
||||
* Split off from arch/x86/platform/efi/efi.c
|
||||
*
|
||||
* Copyright (C) 1999 VA Linux Systems
|
||||
* Copyright (C) 1999 Walt Drummond <drummond@valinux.com>
|
||||
* Copyright (C) 1999-2002 Hewlett-Packard Co.
|
||||
* Copyright (C) 2005-2008 Intel Co.
|
||||
* Copyright (C) 2013 SuSE Labs
|
||||
*
|
||||
* This file is released under the GPLv2.
|
||||
*/
|
||||
|
||||
#include <linux/bug.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <asm/efi.h>
|
||||
|
||||
/*
|
||||
* According to section 7.1 of the UEFI spec, Runtime Services are not fully
|
||||
* reentrant, and there are particular combinations of calls that need to be
|
||||
* serialized. (source: UEFI Specification v2.4A)
|
||||
*
|
||||
* Table 31. Rules for Reentry Into Runtime Services
|
||||
* +------------------------------------+-------------------------------+
|
||||
* | If previous call is busy in | Forbidden to call |
|
||||
* +------------------------------------+-------------------------------+
|
||||
* | Any | SetVirtualAddressMap() |
|
||||
* +------------------------------------+-------------------------------+
|
||||
* | ConvertPointer() | ConvertPointer() |
|
||||
* +------------------------------------+-------------------------------+
|
||||
* | SetVariable() | ResetSystem() |
|
||||
* | UpdateCapsule() | |
|
||||
* | SetTime() | |
|
||||
* | SetWakeupTime() | |
|
||||
* | GetNextHighMonotonicCount() | |
|
||||
* +------------------------------------+-------------------------------+
|
||||
* | GetVariable() | GetVariable() |
|
||||
* | GetNextVariableName() | GetNextVariableName() |
|
||||
* | SetVariable() | SetVariable() |
|
||||
* | QueryVariableInfo() | QueryVariableInfo() |
|
||||
* | UpdateCapsule() | UpdateCapsule() |
|
||||
* | QueryCapsuleCapabilities() | QueryCapsuleCapabilities() |
|
||||
* | GetNextHighMonotonicCount() | GetNextHighMonotonicCount() |
|
||||
* +------------------------------------+-------------------------------+
|
||||
* | GetTime() | GetTime() |
|
||||
* | SetTime() | SetTime() |
|
||||
* | GetWakeupTime() | GetWakeupTime() |
|
||||
* | SetWakeupTime() | SetWakeupTime() |
|
||||
* +------------------------------------+-------------------------------+
|
||||
*
|
||||
* Due to the fact that the EFI pstore may write to the variable store in
|
||||
* interrupt context, we need to use a spinlock for at least the groups that
|
||||
* contain SetVariable() and QueryVariableInfo(). That leaves little else, as
|
||||
* none of the remaining functions are actually ever called at runtime.
|
||||
* So let's just use a single spinlock to serialize all Runtime Services calls.
|
||||
*/
|
||||
static DEFINE_SPINLOCK(efi_runtime_lock);
|
||||
|
||||
/*
|
||||
* Some runtime services calls can be reentrant under NMI, even if the table
|
||||
* above says they are not. (source: UEFI Specification v2.4A)
|
||||
*
|
||||
* Table 32. Functions that may be called after Machine Check, INIT and NMI
|
||||
* +----------------------------+------------------------------------------+
|
||||
* | Function | Called after Machine Check, INIT and NMI |
|
||||
* +----------------------------+------------------------------------------+
|
||||
* | GetTime() | Yes, even if previously busy. |
|
||||
* | GetVariable() | Yes, even if previously busy |
|
||||
* | GetNextVariableName() | Yes, even if previously busy |
|
||||
* | QueryVariableInfo() | Yes, even if previously busy |
|
||||
* | SetVariable() | Yes, even if previously busy |
|
||||
* | UpdateCapsule() | Yes, even if previously busy |
|
||||
* | QueryCapsuleCapabilities() | Yes, even if previously busy |
|
||||
* | ResetSystem() | Yes, even if previously busy |
|
||||
* +----------------------------+------------------------------------------+
|
||||
*
|
||||
* In order to prevent deadlocks under NMI, the wrappers for these functions
|
||||
* may only grab the efi_runtime_lock or rtc_lock spinlocks if !efi_in_nmi().
|
||||
* However, not all of the services listed are reachable through NMI code paths,
|
||||
* so the the special handling as suggested by the UEFI spec is only implemented
|
||||
* for QueryVariableInfo() and SetVariable(), as these can be reached in NMI
|
||||
* context through efi_pstore_write().
|
||||
*/
|
||||
|
||||
/*
|
||||
* As per commit ef68c8f87ed1 ("x86: Serialize EFI time accesses on rtc_lock"),
|
||||
* the EFI specification requires that callers of the time related runtime
|
||||
* functions serialize with other CMOS accesses in the kernel, as the EFI time
|
||||
* functions may choose to also use the legacy CMOS RTC.
|
||||
*/
|
||||
__weak DEFINE_SPINLOCK(rtc_lock);
|
||||
|
||||
static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&rtc_lock, flags);
|
||||
spin_lock(&efi_runtime_lock);
|
||||
status = efi_call_virt(get_time, tm, tc);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
spin_unlock_irqrestore(&rtc_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_set_time(efi_time_t *tm)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&rtc_lock, flags);
|
||||
spin_lock(&efi_runtime_lock);
|
||||
status = efi_call_virt(set_time, tm);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
spin_unlock_irqrestore(&rtc_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_get_wakeup_time(efi_bool_t *enabled,
|
||||
efi_bool_t *pending,
|
||||
efi_time_t *tm)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&rtc_lock, flags);
|
||||
spin_lock(&efi_runtime_lock);
|
||||
status = efi_call_virt(get_wakeup_time, enabled, pending, tm);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
spin_unlock_irqrestore(&rtc_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&rtc_lock, flags);
|
||||
spin_lock(&efi_runtime_lock);
|
||||
status = efi_call_virt(set_wakeup_time, enabled, tm);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
spin_unlock_irqrestore(&rtc_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_get_variable(efi_char16_t *name,
|
||||
efi_guid_t *vendor,
|
||||
u32 *attr,
|
||||
unsigned long *data_size,
|
||||
void *data)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
status = efi_call_virt(get_variable, name, vendor, attr, data_size,
|
||||
data);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_get_next_variable(unsigned long *name_size,
|
||||
efi_char16_t *name,
|
||||
efi_guid_t *vendor)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
status = efi_call_virt(get_next_variable, name_size, name, vendor);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_set_variable(efi_char16_t *name,
|
||||
efi_guid_t *vendor,
|
||||
u32 attr,
|
||||
unsigned long data_size,
|
||||
void *data)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
status = efi_call_virt(set_variable, name, vendor, attr, data_size,
|
||||
data);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t
|
||||
virt_efi_set_variable_nonblocking(efi_char16_t *name, efi_guid_t *vendor,
|
||||
u32 attr, unsigned long data_size,
|
||||
void *data)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
if (!spin_trylock_irqsave(&efi_runtime_lock, flags))
|
||||
return EFI_NOT_READY;
|
||||
|
||||
status = efi_call_virt(set_variable, name, vendor, attr, data_size,
|
||||
data);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
static efi_status_t virt_efi_query_variable_info(u32 attr,
|
||||
u64 *storage_space,
|
||||
u64 *remaining_space,
|
||||
u64 *max_variable_size)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
|
||||
return EFI_UNSUPPORTED;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
status = efi_call_virt(query_variable_info, attr, storage_space,
|
||||
remaining_space, max_variable_size);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_get_next_high_mono_count(u32 *count)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
status = efi_call_virt(get_next_high_mono_count, count);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void virt_efi_reset_system(int reset_type,
|
||||
efi_status_t status,
|
||||
unsigned long data_size,
|
||||
efi_char16_t *data)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
__efi_call_virt(reset_system, reset_type, status, data_size, data);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_update_capsule(efi_capsule_header_t **capsules,
|
||||
unsigned long count,
|
||||
unsigned long sg_list)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
|
||||
return EFI_UNSUPPORTED;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
status = efi_call_virt(update_capsule, capsules, count, sg_list);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules,
|
||||
unsigned long count,
|
||||
u64 *max_size,
|
||||
int *reset_type)
|
||||
{
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
|
||||
return EFI_UNSUPPORTED;
|
||||
|
||||
spin_lock_irqsave(&efi_runtime_lock, flags);
|
||||
status = efi_call_virt(query_capsule_caps, capsules, count, max_size,
|
||||
reset_type);
|
||||
spin_unlock_irqrestore(&efi_runtime_lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
void efi_native_runtime_setup(void)
|
||||
{
|
||||
efi.get_time = virt_efi_get_time;
|
||||
efi.set_time = virt_efi_set_time;
|
||||
efi.get_wakeup_time = virt_efi_get_wakeup_time;
|
||||
efi.set_wakeup_time = virt_efi_set_wakeup_time;
|
||||
efi.get_variable = virt_efi_get_variable;
|
||||
efi.get_next_variable = virt_efi_get_next_variable;
|
||||
efi.set_variable = virt_efi_set_variable;
|
||||
efi.set_variable_nonblocking = virt_efi_set_variable_nonblocking;
|
||||
efi.get_next_high_mono_count = virt_efi_get_next_high_mono_count;
|
||||
efi.reset_system = virt_efi_reset_system;
|
||||
efi.query_variable_info = virt_efi_query_variable_info;
|
||||
efi.update_capsule = virt_efi_update_capsule;
|
||||
efi.query_capsule_caps = virt_efi_query_capsule_caps;
|
||||
}
|
1096
drivers/firmware/efi/vars.c
Normal file
1096
drivers/firmware/efi/vars.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue