Merge tag 'efi-next' of git://git.kernel.org/pub/scm/linux/kernel/git/mfleming/efi into efi/core
Pull EFI updates from Matt Fleming: "* Refactor the EFI memory map code into architecture neutral files and allow drivers to permanently reserve EFI boot services regions on x86, as well as ARM/arm64 - Matt Fleming * Add ARM support for the EFI esrt driver - Ard Biesheuvel * Make the EFI runtime services and efivar API interruptible by swapping spinlocks for semaphores - Sylvain Chouleur * Provide the EFI identity mapping for kexec which allows kexec to work on SGI/UV platforms with requiring the "noefi" kernel command line parameter - Alex Thorlton * Add debugfs node to dump EFI page tables on arm64 - Ard Biesheuvel * Merge the EFI test driver being carried out of tree until now in the FWTS project - Ivan Hu * Expand the list of flags for classifying EFI regions as "RAM" on arm64 so we align with the UEFI spec - Ard Biesheuvel * Optimise out the EFI mixed mode if it's unsupported (CONFIG_X86_32) or disabled (CONFIG_EFI_MIXED=n) and switch the early EFI boot services function table for direct calls, alleviating us from having to maintain the custom function table - Lukas Wunner * Miscellaneous cleanups and fixes" Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
@@ -112,6 +112,23 @@ config EFI_CAPSULE_LOADER
|
||||
|
||||
Most users should say N.
|
||||
|
||||
config EFI_TEST
|
||||
tristate "EFI Runtime Service Tests Support"
|
||||
depends on EFI
|
||||
default n
|
||||
help
|
||||
This driver uses the efi.<service> function pointers directly instead
|
||||
of going through the efivar API, because it is not trying to test the
|
||||
kernel subsystem, just for testing the UEFI runtime service
|
||||
interfaces which are provided by the firmware. This driver is used
|
||||
by the Firmware Test Suite (FWTS) for testing the UEFI runtime
|
||||
interfaces readiness of the firmware.
|
||||
Details for FWTS are available from:
|
||||
<https://wiki.ubuntu.com/FirmwareTestSuite>
|
||||
|
||||
Say Y here to enable the runtime services support via /dev/efi_test.
|
||||
If unsure, say N.
|
||||
|
||||
endmenu
|
||||
|
||||
config UEFI_CPER
|
||||
|
@@ -10,7 +10,7 @@
|
||||
KASAN_SANITIZE_runtime-wrappers.o := n
|
||||
|
||||
obj-$(CONFIG_EFI) += efi.o vars.o reboot.o memattr.o
|
||||
obj-$(CONFIG_EFI) += capsule.o
|
||||
obj-$(CONFIG_EFI) += capsule.o memmap.o
|
||||
obj-$(CONFIG_EFI_VARS) += efivars.o
|
||||
obj-$(CONFIG_EFI_ESRT) += esrt.o
|
||||
obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o
|
||||
@@ -20,6 +20,7 @@ obj-$(CONFIG_EFI_RUNTIME_WRAPPERS) += runtime-wrappers.o
|
||||
obj-$(CONFIG_EFI_STUB) += libstub/
|
||||
obj-$(CONFIG_EFI_FAKE_MEMMAP) += fake_mem.o
|
||||
obj-$(CONFIG_EFI_BOOTLOADER_CONTROL) += efibc.o
|
||||
obj-$(CONFIG_EFI_TEST) += test/
|
||||
|
||||
arm-obj-$(CONFIG_EFI) := arm-init.o arm-runtime.o
|
||||
obj-$(CONFIG_ARM) += $(arm-obj-y)
|
||||
|
@@ -26,9 +26,9 @@
|
||||
|
||||
u64 efi_system_table;
|
||||
|
||||
static int __init is_normal_ram(efi_memory_desc_t *md)
|
||||
static int __init is_memory(efi_memory_desc_t *md)
|
||||
{
|
||||
if (md->attribute & EFI_MEMORY_WB)
|
||||
if (md->attribute & (EFI_MEMORY_WB|EFI_MEMORY_WT|EFI_MEMORY_WC))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
@@ -152,9 +152,9 @@ out:
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true for RAM regions we want to permanently reserve.
|
||||
* Return true for regions that can be used as System RAM.
|
||||
*/
|
||||
static __init int is_reserve_region(efi_memory_desc_t *md)
|
||||
static __init int is_usable_memory(efi_memory_desc_t *md)
|
||||
{
|
||||
switch (md->type) {
|
||||
case EFI_LOADER_CODE:
|
||||
@@ -163,18 +163,22 @@ static __init int is_reserve_region(efi_memory_desc_t *md)
|
||||
case EFI_BOOT_SERVICES_DATA:
|
||||
case EFI_CONVENTIONAL_MEMORY:
|
||||
case EFI_PERSISTENT_MEMORY:
|
||||
return 0;
|
||||
/*
|
||||
* According to the spec, these regions are no longer reserved
|
||||
* after calling ExitBootServices(). However, we can only use
|
||||
* them as System RAM if they can be mapped writeback cacheable.
|
||||
*/
|
||||
return (md->attribute & EFI_MEMORY_WB);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return is_normal_ram(md);
|
||||
return false;
|
||||
}
|
||||
|
||||
static __init void reserve_regions(void)
|
||||
{
|
||||
efi_memory_desc_t *md;
|
||||
u64 paddr, npages, size;
|
||||
int resv;
|
||||
|
||||
if (efi_enabled(EFI_DBG))
|
||||
pr_info("Processing EFI memory map:\n");
|
||||
@@ -191,32 +195,29 @@ static __init void reserve_regions(void)
|
||||
paddr = md->phys_addr;
|
||||
npages = md->num_pages;
|
||||
|
||||
resv = is_reserve_region(md);
|
||||
if (efi_enabled(EFI_DBG)) {
|
||||
char buf[64];
|
||||
|
||||
pr_info(" 0x%012llx-0x%012llx %s%s\n",
|
||||
pr_info(" 0x%012llx-0x%012llx %s\n",
|
||||
paddr, paddr + (npages << EFI_PAGE_SHIFT) - 1,
|
||||
efi_md_typeattr_format(buf, sizeof(buf), md),
|
||||
resv ? "*" : "");
|
||||
efi_md_typeattr_format(buf, sizeof(buf), md));
|
||||
}
|
||||
|
||||
memrange_efi_to_native(&paddr, &npages);
|
||||
size = npages << PAGE_SHIFT;
|
||||
|
||||
if (is_normal_ram(md))
|
||||
if (is_memory(md)) {
|
||||
early_init_dt_add_memory_arch(paddr, size);
|
||||
|
||||
if (resv)
|
||||
memblock_mark_nomap(paddr, size);
|
||||
|
||||
if (!is_usable_memory(md))
|
||||
memblock_mark_nomap(paddr, size);
|
||||
}
|
||||
}
|
||||
|
||||
set_bit(EFI_MEMMAP, &efi.flags);
|
||||
}
|
||||
|
||||
void __init efi_init(void)
|
||||
{
|
||||
struct efi_memory_map_data data;
|
||||
struct efi_fdt_params params;
|
||||
|
||||
/* Grab UEFI information placed in FDT by stub */
|
||||
@@ -225,9 +226,12 @@ void __init efi_init(void)
|
||||
|
||||
efi_system_table = params.system_table;
|
||||
|
||||
efi.memmap.phys_map = params.mmap;
|
||||
efi.memmap.map = early_memremap_ro(params.mmap, params.mmap_size);
|
||||
if (efi.memmap.map == NULL) {
|
||||
data.desc_version = params.desc_ver;
|
||||
data.desc_size = params.desc_size;
|
||||
data.size = params.mmap_size;
|
||||
data.phys_map = params.mmap;
|
||||
|
||||
if (efi_memmap_init_early(&data) < 0) {
|
||||
/*
|
||||
* If we are booting via UEFI, the UEFI memory map is the only
|
||||
* description of memory we have, so there is little point in
|
||||
@@ -235,9 +239,6 @@ void __init efi_init(void)
|
||||
*/
|
||||
panic("Unable to map EFI memory map.\n");
|
||||
}
|
||||
efi.memmap.map_end = efi.memmap.map + params.mmap_size;
|
||||
efi.memmap.desc_size = params.desc_size;
|
||||
efi.memmap.desc_version = params.desc_ver;
|
||||
|
||||
WARN(efi.memmap.desc_version != 1,
|
||||
"Unexpected EFI_MEMORY_DESCRIPTOR version %ld",
|
||||
@@ -248,7 +249,8 @@ void __init efi_init(void)
|
||||
|
||||
reserve_regions();
|
||||
efi_memattr_init();
|
||||
early_memunmap(efi.memmap.map, params.mmap_size);
|
||||
efi_esrt_init();
|
||||
efi_memmap_unmap();
|
||||
|
||||
memblock_reserve(params.mmap & PAGE_MASK,
|
||||
PAGE_ALIGN(params.mmap_size +
|
||||
|
@@ -39,6 +39,26 @@ static struct mm_struct efi_mm = {
|
||||
.mmlist = LIST_HEAD_INIT(efi_mm.mmlist),
|
||||
};
|
||||
|
||||
#ifdef CONFIG_ARM64_PTDUMP
|
||||
#include <asm/ptdump.h>
|
||||
|
||||
static struct ptdump_info efi_ptdump_info = {
|
||||
.mm = &efi_mm,
|
||||
.markers = (struct addr_marker[]){
|
||||
{ 0, "UEFI runtime start" },
|
||||
{ TASK_SIZE_64, "UEFI runtime end" }
|
||||
},
|
||||
.base_addr = 0,
|
||||
};
|
||||
|
||||
static int __init ptdump_init(void)
|
||||
{
|
||||
return ptdump_register(&efi_ptdump_info, "efi_page_tables");
|
||||
}
|
||||
device_initcall(ptdump_init);
|
||||
|
||||
#endif
|
||||
|
||||
static bool __init efi_virtmap_init(void)
|
||||
{
|
||||
efi_memory_desc_t *md;
|
||||
@@ -114,14 +134,12 @@ static int __init arm_enable_runtime_services(void)
|
||||
|
||||
pr_info("Remapping and enabling EFI services.\n");
|
||||
|
||||
mapsize = efi.memmap.map_end - efi.memmap.map;
|
||||
mapsize = efi.memmap.desc_size * efi.memmap.nr_map;
|
||||
|
||||
efi.memmap.map = memremap(efi.memmap.phys_map, mapsize, MEMREMAP_WB);
|
||||
if (!efi.memmap.map) {
|
||||
if (efi_memmap_init_late(efi.memmap.phys_map, mapsize)) {
|
||||
pr_err("Failed to remap EFI memory map\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
efi.memmap.map_end = efi.memmap.map + mapsize;
|
||||
|
||||
if (!efi_virtmap_init()) {
|
||||
pr_err("UEFI virtual mapping missing or invalid -- runtime services will not be available\n");
|
||||
|
@@ -125,16 +125,19 @@ static void efi_pstore_scan_sysfs_enter(struct efivar_entry *pos,
|
||||
* @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,
|
||||
static inline int __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();
|
||||
if (efivar_entry_iter_begin())
|
||||
return -EINTR;
|
||||
} else if (turn_off_scanning)
|
||||
entry->scanning = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,13 +147,18 @@ static inline void __efi_pstore_scan_sysfs_exit(struct efivar_entry *entry,
|
||||
* @head: list head
|
||||
* @stop: a flag checking if scanning will stop
|
||||
*/
|
||||
static void efi_pstore_scan_sysfs_exit(struct efivar_entry *pos,
|
||||
static int 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);
|
||||
int ret = __efi_pstore_scan_sysfs_exit(pos, true);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (stop)
|
||||
__efi_pstore_scan_sysfs_exit(next, &next->list != head);
|
||||
ret = __efi_pstore_scan_sysfs_exit(next, &next->list != head);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,13 +180,17 @@ 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;
|
||||
int ret;
|
||||
|
||||
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);
|
||||
ret = efi_pstore_scan_sysfs_exit(entry, n, head,
|
||||
size < 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (size)
|
||||
break;
|
||||
}
|
||||
@@ -190,7 +202,9 @@ static int efi_pstore_sysfs_entry_iter(void *data, struct efivar_entry **pos)
|
||||
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);
|
||||
ret = efi_pstore_scan_sysfs_exit((*pos), n, head, size < 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (size)
|
||||
break;
|
||||
}
|
||||
@@ -232,7 +246,10 @@ static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
|
||||
if (!*data.buf)
|
||||
return -ENOMEM;
|
||||
|
||||
efivar_entry_iter_begin();
|
||||
if (efivar_entry_iter_begin()) {
|
||||
kfree(*data.buf);
|
||||
return -EINTR;
|
||||
}
|
||||
size = efi_pstore_sysfs_entry_iter(&data,
|
||||
(struct efivar_entry **)&psi->data);
|
||||
efivar_entry_iter_end();
|
||||
@@ -347,7 +364,8 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,
|
||||
edata.time = time;
|
||||
edata.name = efi_name;
|
||||
|
||||
efivar_entry_iter_begin();
|
||||
if (efivar_entry_iter_begin())
|
||||
return -EINTR;
|
||||
found = __efivar_entry_iter(efi_pstore_erase_func, &efivar_sysfs_list, &edata, &entry);
|
||||
|
||||
if (found && !entry->scanning) {
|
||||
|
@@ -27,6 +27,7 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/ucs2_string.h>
|
||||
#include <linux/memblock.h>
|
||||
|
||||
#include <asm/early_ioremap.h>
|
||||
|
||||
@@ -347,56 +348,31 @@ subsys_initcall(efisubsys_init);
|
||||
|
||||
/*
|
||||
* Find the efi memory descriptor for a given physical address. Given a
|
||||
* physicall address, determine if it exists within an EFI Memory Map entry,
|
||||
* physical address, determine if it exists within an EFI Memory Map entry,
|
||||
* and if so, populate the supplied memory descriptor with the appropriate
|
||||
* data.
|
||||
*/
|
||||
int __init efi_mem_desc_lookup(u64 phys_addr, efi_memory_desc_t *out_md)
|
||||
{
|
||||
struct efi_memory_map *map = &efi.memmap;
|
||||
phys_addr_t p, e;
|
||||
efi_memory_desc_t *md;
|
||||
|
||||
if (!efi_enabled(EFI_MEMMAP)) {
|
||||
pr_err_once("EFI_MEMMAP is not enabled.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!map) {
|
||||
pr_err_once("efi.memmap is not set.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!out_md) {
|
||||
pr_err_once("out_md is null.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (WARN_ON_ONCE(!map->phys_map))
|
||||
return -EINVAL;
|
||||
if (WARN_ON_ONCE(map->nr_map == 0) || WARN_ON_ONCE(map->desc_size == 0))
|
||||
return -EINVAL;
|
||||
|
||||
e = map->phys_map + map->nr_map * map->desc_size;
|
||||
for (p = map->phys_map; p < e; p += map->desc_size) {
|
||||
efi_memory_desc_t *md;
|
||||
for_each_efi_memory_desc(md) {
|
||||
u64 size;
|
||||
u64 end;
|
||||
|
||||
/*
|
||||
* If a driver calls this after efi_free_boot_services,
|
||||
* ->map will be NULL, and the target may also not be mapped.
|
||||
* So just always get our own virtual map on the CPU.
|
||||
*
|
||||
*/
|
||||
md = early_memremap(p, sizeof (*md));
|
||||
if (!md) {
|
||||
pr_err_once("early_memremap(%pa, %zu) failed.\n",
|
||||
&p, sizeof (*md));
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (!(md->attribute & EFI_MEMORY_RUNTIME) &&
|
||||
md->type != EFI_BOOT_SERVICES_DATA &&
|
||||
md->type != EFI_RUNTIME_SERVICES_DATA) {
|
||||
early_memunmap(md, sizeof (*md));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -404,11 +380,8 @@ int __init efi_mem_desc_lookup(u64 phys_addr, efi_memory_desc_t *out_md)
|
||||
end = md->phys_addr + size;
|
||||
if (phys_addr >= md->phys_addr && phys_addr < end) {
|
||||
memcpy(out_md, md, sizeof(*out_md));
|
||||
early_memunmap(md, sizeof (*md));
|
||||
return 0;
|
||||
}
|
||||
|
||||
early_memunmap(md, sizeof (*md));
|
||||
}
|
||||
pr_err_once("requested map not found.\n");
|
||||
return -ENOENT;
|
||||
@@ -424,6 +397,35 @@ u64 __init efi_mem_desc_end(efi_memory_desc_t *md)
|
||||
return end;
|
||||
}
|
||||
|
||||
void __init __weak efi_arch_mem_reserve(phys_addr_t addr, u64 size) {}
|
||||
|
||||
/**
|
||||
* efi_mem_reserve - Reserve an EFI memory region
|
||||
* @addr: Physical address to reserve
|
||||
* @size: Size of reservation
|
||||
*
|
||||
* Mark a region as reserved from general kernel allocation and
|
||||
* prevent it being released by efi_free_boot_services().
|
||||
*
|
||||
* This function should be called drivers once they've parsed EFI
|
||||
* configuration tables to figure out where their data lives, e.g.
|
||||
* efi_esrt_init().
|
||||
*/
|
||||
void __init efi_mem_reserve(phys_addr_t addr, u64 size)
|
||||
{
|
||||
if (!memblock_is_region_reserved(addr, size))
|
||||
memblock_reserve(addr, size);
|
||||
|
||||
/*
|
||||
* Some architectures (x86) reserve all boot services ranges
|
||||
* until efi_free_boot_services() because of buggy firmware
|
||||
* implementations. This means the above memblock_reserve() is
|
||||
* superfluous on x86 and instead what it needs to do is
|
||||
* ensure the @start, @size is not freed.
|
||||
*/
|
||||
efi_arch_mem_reserve(addr, size);
|
||||
}
|
||||
|
||||
static __initdata efi_config_table_type_t common_tables[] = {
|
||||
{ACPI_20_TABLE_GUID, "ACPI 2.0", &efi.acpi20},
|
||||
{ACPI_TABLE_GUID, "ACPI", &efi.acpi},
|
||||
@@ -811,6 +813,9 @@ int efi_status_to_err(efi_status_t status)
|
||||
case EFI_NOT_FOUND:
|
||||
err = -ENOENT;
|
||||
break;
|
||||
case EFI_ABORTED:
|
||||
err = -EINTR;
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
}
|
||||
|
@@ -510,7 +510,8 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
|
||||
vendor = del_var->VendorGuid;
|
||||
}
|
||||
|
||||
efivar_entry_iter_begin();
|
||||
if (efivar_entry_iter_begin())
|
||||
return -EINTR;
|
||||
entry = efivar_entry_find(name, vendor, &efivar_sysfs_list, true);
|
||||
if (!entry)
|
||||
err = -EINVAL;
|
||||
@@ -575,7 +576,10 @@ efivar_create_sysfs_entry(struct efivar_entry *new_var)
|
||||
return ret;
|
||||
|
||||
kobject_uevent(&new_var->kobj, KOBJ_ADD);
|
||||
efivar_entry_add(new_var, &efivar_sysfs_list);
|
||||
if (efivar_entry_add(new_var, &efivar_sysfs_list)) {
|
||||
efivar_unregister(new_var);
|
||||
return -EINTR;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -690,7 +694,10 @@ static int efivars_sysfs_callback(efi_char16_t *name, efi_guid_t vendor,
|
||||
|
||||
static int efivar_sysfs_destroy(struct efivar_entry *entry, void *data)
|
||||
{
|
||||
efivar_entry_remove(entry);
|
||||
int err = efivar_entry_remove(entry);
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
efivar_unregister(entry);
|
||||
return 0;
|
||||
}
|
||||
@@ -698,7 +705,14 @@ static int efivar_sysfs_destroy(struct efivar_entry *entry, void *data)
|
||||
static void efivars_sysfs_exit(void)
|
||||
{
|
||||
/* Remove all entries and destroy */
|
||||
__efivar_entry_iter(efivar_sysfs_destroy, &efivar_sysfs_list, NULL, NULL);
|
||||
int err;
|
||||
|
||||
err = __efivar_entry_iter(efivar_sysfs_destroy, &efivar_sysfs_list,
|
||||
NULL, NULL);
|
||||
if (err) {
|
||||
pr_err("efivars: Failed to destroy sysfs entries\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (efivars_new_var)
|
||||
sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var);
|
||||
|
@@ -16,6 +16,7 @@
|
||||
#include <linux/device.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/list.h>
|
||||
@@ -235,7 +236,7 @@ static struct attribute_group esrt_attr_group = {
|
||||
};
|
||||
|
||||
/*
|
||||
* remap the table, copy it to kmalloced pages, and unmap it.
|
||||
* remap the table, validate it, mark it reserved and unmap it.
|
||||
*/
|
||||
void __init efi_esrt_init(void)
|
||||
{
|
||||
@@ -335,7 +336,7 @@ void __init efi_esrt_init(void)
|
||||
|
||||
end = esrt_data + size;
|
||||
pr_info("Reserving ESRT space from %pa to %pa.\n", &esrt_data, &end);
|
||||
memblock_reserve(esrt_data, esrt_data_size);
|
||||
efi_mem_reserve(esrt_data, esrt_data_size);
|
||||
|
||||
pr_debug("esrt-init: loaded.\n");
|
||||
err_memunmap:
|
||||
@@ -382,28 +383,18 @@ static void cleanup_entry_list(void)
|
||||
static int __init esrt_sysfs_init(void)
|
||||
{
|
||||
int error;
|
||||
struct efi_system_resource_table __iomem *ioesrt;
|
||||
|
||||
pr_debug("esrt-sysfs: loading.\n");
|
||||
if (!esrt_data || !esrt_data_size)
|
||||
return -ENOSYS;
|
||||
|
||||
ioesrt = ioremap(esrt_data, esrt_data_size);
|
||||
if (!ioesrt) {
|
||||
pr_err("ioremap(%pa, %zu) failed.\n", &esrt_data,
|
||||
esrt = memremap(esrt_data, esrt_data_size, MEMREMAP_WB);
|
||||
if (!esrt) {
|
||||
pr_err("memremap(%pa, %zu) failed.\n", &esrt_data,
|
||||
esrt_data_size);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
esrt = kmalloc(esrt_data_size, GFP_KERNEL);
|
||||
if (!esrt) {
|
||||
pr_err("kmalloc failed. (wanted %zu bytes)\n", esrt_data_size);
|
||||
iounmap(ioesrt);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
memcpy_fromio(esrt, ioesrt, esrt_data_size);
|
||||
|
||||
esrt_kobj = kobject_create_and_add("esrt", efi_kobj);
|
||||
if (!esrt_kobj) {
|
||||
pr_err("Firmware table registration failed.\n");
|
||||
@@ -429,8 +420,6 @@ static int __init esrt_sysfs_init(void)
|
||||
if (error)
|
||||
goto err_cleanup_list;
|
||||
|
||||
memblock_remove(esrt_data, esrt_data_size);
|
||||
|
||||
pr_debug("esrt-sysfs: loaded.\n");
|
||||
|
||||
return 0;
|
||||
|
@@ -35,17 +35,13 @@
|
||||
|
||||
#define EFI_MAX_FAKEMEM CONFIG_EFI_MAX_FAKE_MEM
|
||||
|
||||
struct fake_mem {
|
||||
struct range range;
|
||||
u64 attribute;
|
||||
};
|
||||
static struct fake_mem fake_mems[EFI_MAX_FAKEMEM];
|
||||
static struct efi_mem_range fake_mems[EFI_MAX_FAKEMEM];
|
||||
static int nr_fake_mem;
|
||||
|
||||
static int __init cmp_fake_mem(const void *x1, const void *x2)
|
||||
{
|
||||
const struct fake_mem *m1 = x1;
|
||||
const struct fake_mem *m2 = x2;
|
||||
const struct efi_mem_range *m1 = x1;
|
||||
const struct efi_mem_range *m2 = x2;
|
||||
|
||||
if (m1->range.start < m2->range.start)
|
||||
return -1;
|
||||
@@ -56,40 +52,21 @@ static int __init cmp_fake_mem(const void *x1, const void *x2)
|
||||
|
||||
void __init efi_fake_memmap(void)
|
||||
{
|
||||
u64 start, end, m_start, m_end, m_attr;
|
||||
int new_nr_map = efi.memmap.nr_map;
|
||||
efi_memory_desc_t *md;
|
||||
phys_addr_t new_memmap_phy;
|
||||
void *new_memmap;
|
||||
void *old, *new;
|
||||
int i;
|
||||
|
||||
if (!nr_fake_mem || !efi_enabled(EFI_MEMMAP))
|
||||
if (!nr_fake_mem)
|
||||
return;
|
||||
|
||||
/* count up the number of EFI memory descriptor */
|
||||
for_each_efi_memory_desc(md) {
|
||||
start = md->phys_addr;
|
||||
end = start + (md->num_pages << EFI_PAGE_SHIFT) - 1;
|
||||
for (i = 0; i < nr_fake_mem; i++) {
|
||||
for_each_efi_memory_desc(md) {
|
||||
struct range *r = &fake_mems[i].range;
|
||||
|
||||
for (i = 0; i < nr_fake_mem; i++) {
|
||||
/* modifying range */
|
||||
m_start = fake_mems[i].range.start;
|
||||
m_end = fake_mems[i].range.end;
|
||||
|
||||
if (m_start <= start) {
|
||||
/* split into 2 parts */
|
||||
if (start < m_end && m_end < end)
|
||||
new_nr_map++;
|
||||
}
|
||||
if (start < m_start && m_start < end) {
|
||||
/* split into 3 parts */
|
||||
if (m_end < end)
|
||||
new_nr_map += 2;
|
||||
/* split into 2 parts */
|
||||
if (end <= m_end)
|
||||
new_nr_map++;
|
||||
}
|
||||
new_nr_map += efi_memmap_split_count(md, r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,85 +84,13 @@ void __init efi_fake_memmap(void)
|
||||
return;
|
||||
}
|
||||
|
||||
for (old = efi.memmap.map, new = new_memmap;
|
||||
old < efi.memmap.map_end;
|
||||
old += efi.memmap.desc_size, new += efi.memmap.desc_size) {
|
||||
|
||||
/* copy original EFI memory descriptor */
|
||||
memcpy(new, old, efi.memmap.desc_size);
|
||||
md = new;
|
||||
start = md->phys_addr;
|
||||
end = md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT) - 1;
|
||||
|
||||
for (i = 0; i < nr_fake_mem; i++) {
|
||||
/* modifying range */
|
||||
m_start = fake_mems[i].range.start;
|
||||
m_end = fake_mems[i].range.end;
|
||||
m_attr = fake_mems[i].attribute;
|
||||
|
||||
if (m_start <= start && end <= m_end)
|
||||
md->attribute |= m_attr;
|
||||
|
||||
if (m_start <= start &&
|
||||
(start < m_end && m_end < end)) {
|
||||
/* first part */
|
||||
md->attribute |= m_attr;
|
||||
md->num_pages = (m_end - md->phys_addr + 1) >>
|
||||
EFI_PAGE_SHIFT;
|
||||
/* latter part */
|
||||
new += efi.memmap.desc_size;
|
||||
memcpy(new, old, efi.memmap.desc_size);
|
||||
md = new;
|
||||
md->phys_addr = m_end + 1;
|
||||
md->num_pages = (end - md->phys_addr + 1) >>
|
||||
EFI_PAGE_SHIFT;
|
||||
}
|
||||
|
||||
if ((start < m_start && m_start < end) && m_end < end) {
|
||||
/* first part */
|
||||
md->num_pages = (m_start - md->phys_addr) >>
|
||||
EFI_PAGE_SHIFT;
|
||||
/* middle part */
|
||||
new += efi.memmap.desc_size;
|
||||
memcpy(new, old, efi.memmap.desc_size);
|
||||
md = new;
|
||||
md->attribute |= m_attr;
|
||||
md->phys_addr = m_start;
|
||||
md->num_pages = (m_end - m_start + 1) >>
|
||||
EFI_PAGE_SHIFT;
|
||||
/* last part */
|
||||
new += efi.memmap.desc_size;
|
||||
memcpy(new, old, efi.memmap.desc_size);
|
||||
md = new;
|
||||
md->phys_addr = m_end + 1;
|
||||
md->num_pages = (end - m_end) >>
|
||||
EFI_PAGE_SHIFT;
|
||||
}
|
||||
|
||||
if ((start < m_start && m_start < end) &&
|
||||
(end <= m_end)) {
|
||||
/* first part */
|
||||
md->num_pages = (m_start - md->phys_addr) >>
|
||||
EFI_PAGE_SHIFT;
|
||||
/* latter part */
|
||||
new += efi.memmap.desc_size;
|
||||
memcpy(new, old, efi.memmap.desc_size);
|
||||
md = new;
|
||||
md->phys_addr = m_start;
|
||||
md->num_pages = (end - md->phys_addr + 1) >>
|
||||
EFI_PAGE_SHIFT;
|
||||
md->attribute |= m_attr;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i = 0; i < nr_fake_mem; i++)
|
||||
efi_memmap_insert(&efi.memmap, new_memmap, &fake_mems[i]);
|
||||
|
||||
/* swap into new EFI memmap */
|
||||
efi_unmap_memmap();
|
||||
efi.memmap.map = new_memmap;
|
||||
efi.memmap.phys_map = new_memmap_phy;
|
||||
efi.memmap.nr_map = new_nr_map;
|
||||
efi.memmap.map_end = efi.memmap.map + efi.memmap.nr_map * efi.memmap.desc_size;
|
||||
set_bit(EFI_MEMMAP, &efi.flags);
|
||||
early_memunmap(new_memmap, efi.memmap.desc_size * new_nr_map);
|
||||
|
||||
efi_memmap_install(new_memmap_phy, new_nr_map);
|
||||
|
||||
/* print new EFI memmap */
|
||||
efi_print_memmap();
|
||||
@@ -223,7 +128,7 @@ static int __init setup_fake_mem(char *p)
|
||||
p++;
|
||||
}
|
||||
|
||||
sort(fake_mems, nr_fake_mem, sizeof(struct fake_mem),
|
||||
sort(fake_mems, nr_fake_mem, sizeof(struct efi_mem_range),
|
||||
cmp_fake_mem, NULL);
|
||||
|
||||
for (i = 0; i < nr_fake_mem; i++)
|
||||
|
292
drivers/firmware/efi/memmap.c
Normal file
292
drivers/firmware/efi/memmap.c
Normal file
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* Common EFI memory map functions.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "efi: " fmt
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/io.h>
|
||||
#include <asm/early_ioremap.h>
|
||||
|
||||
/**
|
||||
* __efi_memmap_init - Common code for mapping the EFI memory map
|
||||
* @data: EFI memory map data
|
||||
* @late: Use early or late mapping function?
|
||||
*
|
||||
* This function takes care of figuring out which function to use to
|
||||
* map the EFI memory map in efi.memmap based on how far into the boot
|
||||
* we are.
|
||||
*
|
||||
* During bootup @late should be %false since we only have access to
|
||||
* the early_memremap*() functions as the vmalloc space isn't setup.
|
||||
* Once the kernel is fully booted we can fallback to the more robust
|
||||
* memremap*() API.
|
||||
*
|
||||
* Returns zero on success, a negative error code on failure.
|
||||
*/
|
||||
static int __init
|
||||
__efi_memmap_init(struct efi_memory_map_data *data, bool late)
|
||||
{
|
||||
struct efi_memory_map map;
|
||||
phys_addr_t phys_map;
|
||||
|
||||
if (efi_enabled(EFI_PARAVIRT))
|
||||
return 0;
|
||||
|
||||
phys_map = data->phys_map;
|
||||
|
||||
if (late)
|
||||
map.map = memremap(phys_map, data->size, MEMREMAP_WB);
|
||||
else
|
||||
map.map = early_memremap(phys_map, data->size);
|
||||
|
||||
if (!map.map) {
|
||||
pr_err("Could not map the memory map!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
map.phys_map = data->phys_map;
|
||||
map.nr_map = data->size / data->desc_size;
|
||||
map.map_end = map.map + data->size;
|
||||
|
||||
map.desc_version = data->desc_version;
|
||||
map.desc_size = data->desc_size;
|
||||
map.late = late;
|
||||
|
||||
set_bit(EFI_MEMMAP, &efi.flags);
|
||||
|
||||
efi.memmap = map;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* efi_memmap_init_early - Map the EFI memory map data structure
|
||||
* @data: EFI memory map data
|
||||
*
|
||||
* Use early_memremap() to map the passed in EFI memory map and assign
|
||||
* it to efi.memmap.
|
||||
*/
|
||||
int __init efi_memmap_init_early(struct efi_memory_map_data *data)
|
||||
{
|
||||
/* Cannot go backwards */
|
||||
WARN_ON(efi.memmap.late);
|
||||
|
||||
return __efi_memmap_init(data, false);
|
||||
}
|
||||
|
||||
void __init efi_memmap_unmap(void)
|
||||
{
|
||||
if (!efi.memmap.late) {
|
||||
unsigned long size;
|
||||
|
||||
size = efi.memmap.desc_size * efi.memmap.nr_map;
|
||||
early_memunmap(efi.memmap.map, size);
|
||||
} else {
|
||||
memunmap(efi.memmap.map);
|
||||
}
|
||||
|
||||
efi.memmap.map = NULL;
|
||||
clear_bit(EFI_MEMMAP, &efi.flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* efi_memmap_init_late - Map efi.memmap with memremap()
|
||||
* @phys_addr: Physical address of the new EFI memory map
|
||||
* @size: Size in bytes of the new EFI memory map
|
||||
*
|
||||
* Setup a mapping of the EFI memory map using ioremap_cache(). This
|
||||
* function should only be called once the vmalloc space has been
|
||||
* setup and is therefore not suitable for calling during early EFI
|
||||
* initialise, e.g. in efi_init(). Additionally, it expects
|
||||
* efi_memmap_init_early() to have already been called.
|
||||
*
|
||||
* The reason there are two EFI memmap initialisation
|
||||
* (efi_memmap_init_early() and this late version) is because the
|
||||
* early EFI memmap should be explicitly unmapped once EFI
|
||||
* initialisation is complete as the fixmap space used to map the EFI
|
||||
* memmap (via early_memremap()) is a scarce resource.
|
||||
*
|
||||
* This late mapping is intended to persist for the duration of
|
||||
* runtime so that things like efi_mem_desc_lookup() and
|
||||
* efi_mem_attributes() always work.
|
||||
*
|
||||
* Returns zero on success, a negative error code on failure.
|
||||
*/
|
||||
int __init efi_memmap_init_late(phys_addr_t addr, unsigned long size)
|
||||
{
|
||||
struct efi_memory_map_data data = {
|
||||
.phys_map = addr,
|
||||
.size = size,
|
||||
};
|
||||
|
||||
/* Did we forget to unmap the early EFI memmap? */
|
||||
WARN_ON(efi.memmap.map);
|
||||
|
||||
/* Were we already called? */
|
||||
WARN_ON(efi.memmap.late);
|
||||
|
||||
/*
|
||||
* It makes no sense to allow callers to register different
|
||||
* values for the following fields. Copy them out of the
|
||||
* existing early EFI memmap.
|
||||
*/
|
||||
data.desc_version = efi.memmap.desc_version;
|
||||
data.desc_size = efi.memmap.desc_size;
|
||||
|
||||
return __efi_memmap_init(&data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* efi_memmap_install - Install a new EFI memory map in efi.memmap
|
||||
* @addr: Physical address of the memory map
|
||||
* @nr_map: Number of entries in the memory map
|
||||
*
|
||||
* Unlike efi_memmap_init_*(), this function does not allow the caller
|
||||
* to switch from early to late mappings. It simply uses the existing
|
||||
* mapping function and installs the new memmap.
|
||||
*
|
||||
* Returns zero on success, a negative error code on failure.
|
||||
*/
|
||||
int __init efi_memmap_install(phys_addr_t addr, unsigned int nr_map)
|
||||
{
|
||||
struct efi_memory_map_data data;
|
||||
|
||||
efi_memmap_unmap();
|
||||
|
||||
data.phys_map = addr;
|
||||
data.size = efi.memmap.desc_size * nr_map;
|
||||
data.desc_version = efi.memmap.desc_version;
|
||||
data.desc_size = efi.memmap.desc_size;
|
||||
|
||||
return __efi_memmap_init(&data, efi.memmap.late);
|
||||
}
|
||||
|
||||
/**
|
||||
* efi_memmap_split_count - Count number of additional EFI memmap entries
|
||||
* @md: EFI memory descriptor to split
|
||||
* @range: Address range (start, end) to split around
|
||||
*
|
||||
* Returns the number of additional EFI memmap entries required to
|
||||
* accomodate @range.
|
||||
*/
|
||||
int __init efi_memmap_split_count(efi_memory_desc_t *md, struct range *range)
|
||||
{
|
||||
u64 m_start, m_end;
|
||||
u64 start, end;
|
||||
int count = 0;
|
||||
|
||||
start = md->phys_addr;
|
||||
end = start + (md->num_pages << EFI_PAGE_SHIFT) - 1;
|
||||
|
||||
/* modifying range */
|
||||
m_start = range->start;
|
||||
m_end = range->end;
|
||||
|
||||
if (m_start <= start) {
|
||||
/* split into 2 parts */
|
||||
if (start < m_end && m_end < end)
|
||||
count++;
|
||||
}
|
||||
|
||||
if (start < m_start && m_start < end) {
|
||||
/* split into 3 parts */
|
||||
if (m_end < end)
|
||||
count += 2;
|
||||
/* split into 2 parts */
|
||||
if (end <= m_end)
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* efi_memmap_insert - Insert a memory region in an EFI memmap
|
||||
* @old_memmap: The existing EFI memory map structure
|
||||
* @buf: Address of buffer to store new map
|
||||
* @mem: Memory map entry to insert
|
||||
*
|
||||
* It is suggested that you call efi_memmap_split_count() first
|
||||
* to see how large @buf needs to be.
|
||||
*/
|
||||
void __init efi_memmap_insert(struct efi_memory_map *old_memmap, void *buf,
|
||||
struct efi_mem_range *mem)
|
||||
{
|
||||
u64 m_start, m_end, m_attr;
|
||||
efi_memory_desc_t *md;
|
||||
u64 start, end;
|
||||
void *old, *new;
|
||||
|
||||
/* modifying range */
|
||||
m_start = mem->range.start;
|
||||
m_end = mem->range.end;
|
||||
m_attr = mem->attribute;
|
||||
|
||||
for (old = old_memmap->map, new = buf;
|
||||
old < old_memmap->map_end;
|
||||
old += old_memmap->desc_size, new += old_memmap->desc_size) {
|
||||
|
||||
/* copy original EFI memory descriptor */
|
||||
memcpy(new, old, old_memmap->desc_size);
|
||||
md = new;
|
||||
start = md->phys_addr;
|
||||
end = md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT) - 1;
|
||||
|
||||
if (m_start <= start && end <= m_end)
|
||||
md->attribute |= m_attr;
|
||||
|
||||
if (m_start <= start &&
|
||||
(start < m_end && m_end < end)) {
|
||||
/* first part */
|
||||
md->attribute |= m_attr;
|
||||
md->num_pages = (m_end - md->phys_addr + 1) >>
|
||||
EFI_PAGE_SHIFT;
|
||||
/* latter part */
|
||||
new += old_memmap->desc_size;
|
||||
memcpy(new, old, old_memmap->desc_size);
|
||||
md = new;
|
||||
md->phys_addr = m_end + 1;
|
||||
md->num_pages = (end - md->phys_addr + 1) >>
|
||||
EFI_PAGE_SHIFT;
|
||||
}
|
||||
|
||||
if ((start < m_start && m_start < end) && m_end < end) {
|
||||
/* first part */
|
||||
md->num_pages = (m_start - md->phys_addr) >>
|
||||
EFI_PAGE_SHIFT;
|
||||
/* middle part */
|
||||
new += old_memmap->desc_size;
|
||||
memcpy(new, old, old_memmap->desc_size);
|
||||
md = new;
|
||||
md->attribute |= m_attr;
|
||||
md->phys_addr = m_start;
|
||||
md->num_pages = (m_end - m_start + 1) >>
|
||||
EFI_PAGE_SHIFT;
|
||||
/* last part */
|
||||
new += old_memmap->desc_size;
|
||||
memcpy(new, old, old_memmap->desc_size);
|
||||
md = new;
|
||||
md->phys_addr = m_end + 1;
|
||||
md->num_pages = (end - m_end) >>
|
||||
EFI_PAGE_SHIFT;
|
||||
}
|
||||
|
||||
if ((start < m_start && m_start < end) &&
|
||||
(end <= m_end)) {
|
||||
/* first part */
|
||||
md->num_pages = (m_start - md->phys_addr) >>
|
||||
EFI_PAGE_SHIFT;
|
||||
/* latter part */
|
||||
new += old_memmap->desc_size;
|
||||
memcpy(new, old, old_memmap->desc_size);
|
||||
md = new;
|
||||
md->phys_addr = m_start;
|
||||
md->num_pages = (end - md->phys_addr + 1) >>
|
||||
EFI_PAGE_SHIFT;
|
||||
md->attribute |= m_attr;
|
||||
}
|
||||
}
|
||||
}
|
@@ -14,10 +14,6 @@
|
||||
|
||||
#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 */
|
||||
@@ -106,7 +102,8 @@ static struct kobj_type __refdata map_ktype = {
|
||||
static struct kset *map_kset;
|
||||
|
||||
static struct efi_runtime_map_entry *
|
||||
add_sysfs_runtime_map_entry(struct kobject *kobj, int nr)
|
||||
add_sysfs_runtime_map_entry(struct kobject *kobj, int nr,
|
||||
efi_memory_desc_t *md)
|
||||
{
|
||||
int ret;
|
||||
struct efi_runtime_map_entry *entry;
|
||||
@@ -124,8 +121,7 @@ add_sysfs_runtime_map_entry(struct kobject *kobj, int nr)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
memcpy(&entry->md, efi_runtime_map + nr * efi_memdesc_size,
|
||||
sizeof(efi_memory_desc_t));
|
||||
memcpy(&entry->md, md, sizeof(efi_memory_desc_t));
|
||||
|
||||
kobject_init(&entry->kobj, &map_ktype);
|
||||
entry->kobj.kset = map_kset;
|
||||
@@ -142,12 +138,12 @@ add_sysfs_runtime_map_entry(struct kobject *kobj, int nr)
|
||||
|
||||
int efi_get_runtime_map_size(void)
|
||||
{
|
||||
return nr_efi_runtime_map * efi_memdesc_size;
|
||||
return efi.memmap.nr_map * efi.memmap.desc_size;
|
||||
}
|
||||
|
||||
int efi_get_runtime_map_desc_size(void)
|
||||
{
|
||||
return efi_memdesc_size;
|
||||
return efi.memmap.desc_size;
|
||||
}
|
||||
|
||||
int efi_runtime_map_copy(void *buf, size_t bufsz)
|
||||
@@ -157,38 +153,33 @@ int efi_runtime_map_copy(void *buf, size_t bufsz)
|
||||
if (sz > bufsz)
|
||||
sz = bufsz;
|
||||
|
||||
memcpy(buf, efi_runtime_map, sz);
|
||||
memcpy(buf, efi.memmap.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;
|
||||
efi_memory_desc_t *md;
|
||||
|
||||
if (!efi_runtime_map)
|
||||
if (!efi_enabled(EFI_MEMMAP))
|
||||
return 0;
|
||||
|
||||
map_entries = kzalloc(nr_efi_runtime_map * sizeof(entry), GFP_KERNEL);
|
||||
map_entries = kzalloc(efi.memmap.nr_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);
|
||||
i = 0;
|
||||
for_each_efi_memory_desc(md) {
|
||||
entry = add_sysfs_runtime_map_entry(efi_kobj, i, md);
|
||||
if (IS_ERR(entry)) {
|
||||
ret = PTR_ERR(entry);
|
||||
goto out_add_entry;
|
||||
}
|
||||
*(map_entries + i) = entry;
|
||||
*(map_entries + i++) = entry;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@@ -14,11 +14,13 @@
|
||||
* This file is released under the GPLv2.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "efi: " fmt
|
||||
|
||||
#include <linux/bug.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/irqflags.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/semaphore.h>
|
||||
#include <linux/stringify.h>
|
||||
#include <asm/efi.h>
|
||||
|
||||
@@ -81,20 +83,21 @@ void efi_call_virt_check_flags(unsigned long flags, const char *call)
|
||||
* +------------------------------------+-------------------------------+
|
||||
*
|
||||
* 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
|
||||
* interrupt context, we need to use a lock 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.
|
||||
* So let's just use a single lock to serialize all Runtime Services calls.
|
||||
*/
|
||||
static DEFINE_SPINLOCK(efi_runtime_lock);
|
||||
static DEFINE_SEMAPHORE(efi_runtime_lock);
|
||||
|
||||
static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc)
|
||||
{
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock(&efi_runtime_lock);
|
||||
if (down_interruptible(&efi_runtime_lock))
|
||||
return EFI_ABORTED;
|
||||
status = efi_call_virt(get_time, tm, tc);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
up(&efi_runtime_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -102,9 +105,10 @@ static efi_status_t virt_efi_set_time(efi_time_t *tm)
|
||||
{
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock(&efi_runtime_lock);
|
||||
if (down_interruptible(&efi_runtime_lock))
|
||||
return EFI_ABORTED;
|
||||
status = efi_call_virt(set_time, tm);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
up(&efi_runtime_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -114,9 +118,10 @@ static efi_status_t virt_efi_get_wakeup_time(efi_bool_t *enabled,
|
||||
{
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock(&efi_runtime_lock);
|
||||
if (down_interruptible(&efi_runtime_lock))
|
||||
return EFI_ABORTED;
|
||||
status = efi_call_virt(get_wakeup_time, enabled, pending, tm);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
up(&efi_runtime_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -124,9 +129,10 @@ static efi_status_t virt_efi_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm)
|
||||
{
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock(&efi_runtime_lock);
|
||||
if (down_interruptible(&efi_runtime_lock))
|
||||
return EFI_ABORTED;
|
||||
status = efi_call_virt(set_wakeup_time, enabled, tm);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
up(&efi_runtime_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -138,10 +144,11 @@ static efi_status_t virt_efi_get_variable(efi_char16_t *name,
|
||||
{
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock(&efi_runtime_lock);
|
||||
if (down_interruptible(&efi_runtime_lock))
|
||||
return EFI_ABORTED;
|
||||
status = efi_call_virt(get_variable, name, vendor, attr, data_size,
|
||||
data);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
up(&efi_runtime_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -151,9 +158,10 @@ static efi_status_t virt_efi_get_next_variable(unsigned long *name_size,
|
||||
{
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock(&efi_runtime_lock);
|
||||
if (down_interruptible(&efi_runtime_lock))
|
||||
return EFI_ABORTED;
|
||||
status = efi_call_virt(get_next_variable, name_size, name, vendor);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
up(&efi_runtime_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -165,10 +173,11 @@ static efi_status_t virt_efi_set_variable(efi_char16_t *name,
|
||||
{
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock(&efi_runtime_lock);
|
||||
if (down_interruptible(&efi_runtime_lock))
|
||||
return EFI_ABORTED;
|
||||
status = efi_call_virt(set_variable, name, vendor, attr, data_size,
|
||||
data);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
up(&efi_runtime_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -179,12 +188,12 @@ virt_efi_set_variable_nonblocking(efi_char16_t *name, efi_guid_t *vendor,
|
||||
{
|
||||
efi_status_t status;
|
||||
|
||||
if (!spin_trylock(&efi_runtime_lock))
|
||||
if (down_trylock(&efi_runtime_lock))
|
||||
return EFI_NOT_READY;
|
||||
|
||||
status = efi_call_virt(set_variable, name, vendor, attr, data_size,
|
||||
data);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
up(&efi_runtime_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -199,10 +208,11 @@ static efi_status_t virt_efi_query_variable_info(u32 attr,
|
||||
if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
|
||||
return EFI_UNSUPPORTED;
|
||||
|
||||
spin_lock(&efi_runtime_lock);
|
||||
if (down_interruptible(&efi_runtime_lock))
|
||||
return EFI_ABORTED;
|
||||
status = efi_call_virt(query_variable_info, attr, storage_space,
|
||||
remaining_space, max_variable_size);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
up(&efi_runtime_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -217,12 +227,12 @@ virt_efi_query_variable_info_nonblocking(u32 attr,
|
||||
if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
|
||||
return EFI_UNSUPPORTED;
|
||||
|
||||
if (!spin_trylock(&efi_runtime_lock))
|
||||
if (down_trylock(&efi_runtime_lock))
|
||||
return EFI_NOT_READY;
|
||||
|
||||
status = efi_call_virt(query_variable_info, attr, storage_space,
|
||||
remaining_space, max_variable_size);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
up(&efi_runtime_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -230,9 +240,10 @@ static efi_status_t virt_efi_get_next_high_mono_count(u32 *count)
|
||||
{
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock(&efi_runtime_lock);
|
||||
if (down_interruptible(&efi_runtime_lock))
|
||||
return EFI_ABORTED;
|
||||
status = efi_call_virt(get_next_high_mono_count, count);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
up(&efi_runtime_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -241,9 +252,13 @@ static void virt_efi_reset_system(int reset_type,
|
||||
unsigned long data_size,
|
||||
efi_char16_t *data)
|
||||
{
|
||||
spin_lock(&efi_runtime_lock);
|
||||
if (down_interruptible(&efi_runtime_lock)) {
|
||||
pr_warn("failed to invoke the reset_system() runtime service:\n"
|
||||
"could not get exclusive access to the firmware\n");
|
||||
return;
|
||||
}
|
||||
__efi_call_virt(reset_system, reset_type, status, data_size, data);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
up(&efi_runtime_lock);
|
||||
}
|
||||
|
||||
static efi_status_t virt_efi_update_capsule(efi_capsule_header_t **capsules,
|
||||
@@ -255,9 +270,10 @@ static efi_status_t virt_efi_update_capsule(efi_capsule_header_t **capsules,
|
||||
if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
|
||||
return EFI_UNSUPPORTED;
|
||||
|
||||
spin_lock(&efi_runtime_lock);
|
||||
if (down_interruptible(&efi_runtime_lock))
|
||||
return EFI_ABORTED;
|
||||
status = efi_call_virt(update_capsule, capsules, count, sg_list);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
up(&efi_runtime_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -271,10 +287,11 @@ static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules,
|
||||
if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
|
||||
return EFI_UNSUPPORTED;
|
||||
|
||||
spin_lock(&efi_runtime_lock);
|
||||
if (down_interruptible(&efi_runtime_lock))
|
||||
return EFI_ABORTED;
|
||||
status = efi_call_virt(query_capsule_caps, capsules, count, max_size,
|
||||
reset_type);
|
||||
spin_unlock(&efi_runtime_lock);
|
||||
up(&efi_runtime_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
1
drivers/firmware/efi/test/Makefile
Normal file
1
drivers/firmware/efi/test/Makefile
Normal file
@@ -0,0 +1 @@
|
||||
obj-$(CONFIG_EFI_TEST) += efi_test.o
|
749
drivers/firmware/efi/test/efi_test.c
Normal file
749
drivers/firmware/efi/test/efi_test.c
Normal file
@@ -0,0 +1,749 @@
|
||||
/*
|
||||
* EFI Test Driver for Runtime Services
|
||||
*
|
||||
* Copyright(C) 2012-2016 Canonical Ltd.
|
||||
*
|
||||
* This driver exports EFI runtime services interfaces into userspace, which
|
||||
* allow to use and test UEFI runtime services provided by firmware.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/version.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include "efi_test.h"
|
||||
|
||||
MODULE_AUTHOR("Ivan Hu <ivan.hu@canonical.com>");
|
||||
MODULE_DESCRIPTION("EFI Test Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/*
|
||||
* Count the bytes in 'str', including the terminating NULL.
|
||||
*
|
||||
* Note this function returns the number of *bytes*, not the number of
|
||||
* ucs2 characters.
|
||||
*/
|
||||
static inline size_t user_ucs2_strsize(efi_char16_t __user *str)
|
||||
{
|
||||
efi_char16_t *s = str, c;
|
||||
size_t len;
|
||||
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
/* Include terminating NULL */
|
||||
len = sizeof(efi_char16_t);
|
||||
|
||||
if (get_user(c, s++)) {
|
||||
/* Can't read userspace memory for size */
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (c != 0) {
|
||||
if (get_user(c, s++)) {
|
||||
/* Can't read userspace memory for size */
|
||||
return 0;
|
||||
}
|
||||
len += sizeof(efi_char16_t);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a buffer and copy a ucs2 string from user space into it.
|
||||
*/
|
||||
static inline int
|
||||
copy_ucs2_from_user_len(efi_char16_t **dst, efi_char16_t __user *src,
|
||||
size_t len)
|
||||
{
|
||||
efi_char16_t *buf;
|
||||
|
||||
if (!src) {
|
||||
*dst = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!access_ok(VERIFY_READ, src, 1))
|
||||
return -EFAULT;
|
||||
|
||||
buf = kmalloc(len, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
*dst = NULL;
|
||||
return -ENOMEM;
|
||||
}
|
||||
*dst = buf;
|
||||
|
||||
if (copy_from_user(*dst, src, len)) {
|
||||
kfree(buf);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Count the bytes in 'str', including the terminating NULL.
|
||||
*
|
||||
* Just a wrap for user_ucs2_strsize
|
||||
*/
|
||||
static inline int
|
||||
get_ucs2_strsize_from_user(efi_char16_t __user *src, size_t *len)
|
||||
{
|
||||
if (!access_ok(VERIFY_READ, src, 1))
|
||||
return -EFAULT;
|
||||
|
||||
*len = user_ucs2_strsize(src);
|
||||
if (*len == 0)
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the required buffer allocation size and copy a ucs2 string
|
||||
* from user space into it.
|
||||
*
|
||||
* This function differs from copy_ucs2_from_user_len() because it
|
||||
* calculates the size of the buffer to allocate by taking the length of
|
||||
* the string 'src'.
|
||||
*
|
||||
* If a non-zero value is returned, the caller MUST NOT access 'dst'.
|
||||
*
|
||||
* It is the caller's responsibility to free 'dst'.
|
||||
*/
|
||||
static inline int
|
||||
copy_ucs2_from_user(efi_char16_t **dst, efi_char16_t __user *src)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
if (!access_ok(VERIFY_READ, src, 1))
|
||||
return -EFAULT;
|
||||
|
||||
len = user_ucs2_strsize(src);
|
||||
if (len == 0)
|
||||
return -EFAULT;
|
||||
return copy_ucs2_from_user_len(dst, src, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy a ucs2 string to a user buffer.
|
||||
*
|
||||
* This function is a simple wrapper around copy_to_user() that does
|
||||
* nothing if 'src' is NULL, which is useful for reducing the amount of
|
||||
* NULL checking the caller has to do.
|
||||
*
|
||||
* 'len' specifies the number of bytes to copy.
|
||||
*/
|
||||
static inline int
|
||||
copy_ucs2_to_user_len(efi_char16_t __user *dst, efi_char16_t *src, size_t len)
|
||||
{
|
||||
if (!src)
|
||||
return 0;
|
||||
|
||||
if (!access_ok(VERIFY_WRITE, dst, 1))
|
||||
return -EFAULT;
|
||||
|
||||
return copy_to_user(dst, src, len);
|
||||
}
|
||||
|
||||
static long efi_runtime_get_variable(unsigned long arg)
|
||||
{
|
||||
struct efi_getvariable __user *getvariable_user;
|
||||
struct efi_getvariable getvariable;
|
||||
unsigned long datasize, prev_datasize, *dz;
|
||||
efi_guid_t vendor_guid, *vd = NULL;
|
||||
efi_status_t status;
|
||||
efi_char16_t *name = NULL;
|
||||
u32 attr, *at;
|
||||
void *data = NULL;
|
||||
int rv = 0;
|
||||
|
||||
getvariable_user = (struct efi_getvariable __user *)arg;
|
||||
|
||||
if (copy_from_user(&getvariable, getvariable_user,
|
||||
sizeof(getvariable)))
|
||||
return -EFAULT;
|
||||
if (getvariable.data_size &&
|
||||
get_user(datasize, getvariable.data_size))
|
||||
return -EFAULT;
|
||||
if (getvariable.vendor_guid) {
|
||||
if (copy_from_user(&vendor_guid, getvariable.vendor_guid,
|
||||
sizeof(vendor_guid)))
|
||||
return -EFAULT;
|
||||
vd = &vendor_guid;
|
||||
}
|
||||
|
||||
if (getvariable.variable_name) {
|
||||
rv = copy_ucs2_from_user(&name, getvariable.variable_name);
|
||||
if (rv)
|
||||
return rv;
|
||||
}
|
||||
|
||||
at = getvariable.attributes ? &attr : NULL;
|
||||
dz = getvariable.data_size ? &datasize : NULL;
|
||||
|
||||
if (getvariable.data_size && getvariable.data) {
|
||||
data = kmalloc(datasize, GFP_KERNEL);
|
||||
if (!data) {
|
||||
kfree(name);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
prev_datasize = datasize;
|
||||
status = efi.get_variable(name, vd, at, dz, data);
|
||||
kfree(name);
|
||||
|
||||
if (put_user(status, getvariable.status)) {
|
||||
rv = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (status != EFI_SUCCESS) {
|
||||
if (status == EFI_BUFFER_TOO_SMALL) {
|
||||
if (dz && put_user(datasize, getvariable.data_size)) {
|
||||
rv = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
rv = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (prev_datasize < datasize) {
|
||||
rv = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
if (copy_to_user(getvariable.data, data, datasize)) {
|
||||
rv = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (at && put_user(attr, getvariable.attributes)) {
|
||||
rv = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (dz && put_user(datasize, getvariable.data_size))
|
||||
rv = -EFAULT;
|
||||
|
||||
out:
|
||||
kfree(data);
|
||||
return rv;
|
||||
|
||||
}
|
||||
|
||||
static long efi_runtime_set_variable(unsigned long arg)
|
||||
{
|
||||
struct efi_setvariable __user *setvariable_user;
|
||||
struct efi_setvariable setvariable;
|
||||
efi_guid_t vendor_guid;
|
||||
efi_status_t status;
|
||||
efi_char16_t *name = NULL;
|
||||
void *data;
|
||||
int rv = 0;
|
||||
|
||||
setvariable_user = (struct efi_setvariable __user *)arg;
|
||||
|
||||
if (copy_from_user(&setvariable, setvariable_user, sizeof(setvariable)))
|
||||
return -EFAULT;
|
||||
if (copy_from_user(&vendor_guid, setvariable.vendor_guid,
|
||||
sizeof(vendor_guid)))
|
||||
return -EFAULT;
|
||||
|
||||
if (setvariable.variable_name) {
|
||||
rv = copy_ucs2_from_user(&name, setvariable.variable_name);
|
||||
if (rv)
|
||||
return rv;
|
||||
}
|
||||
|
||||
data = kmalloc(setvariable.data_size, GFP_KERNEL);
|
||||
if (!data) {
|
||||
kfree(name);
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (copy_from_user(data, setvariable.data, setvariable.data_size)) {
|
||||
rv = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
status = efi.set_variable(name, &vendor_guid,
|
||||
setvariable.attributes,
|
||||
setvariable.data_size, data);
|
||||
|
||||
if (put_user(status, setvariable.status)) {
|
||||
rv = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rv = status == EFI_SUCCESS ? 0 : -EINVAL;
|
||||
|
||||
out:
|
||||
kfree(data);
|
||||
kfree(name);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static long efi_runtime_get_time(unsigned long arg)
|
||||
{
|
||||
struct efi_gettime __user *gettime_user;
|
||||
struct efi_gettime gettime;
|
||||
efi_status_t status;
|
||||
efi_time_cap_t cap;
|
||||
efi_time_t efi_time;
|
||||
|
||||
gettime_user = (struct efi_gettime __user *)arg;
|
||||
if (copy_from_user(&gettime, gettime_user, sizeof(gettime)))
|
||||
return -EFAULT;
|
||||
|
||||
status = efi.get_time(gettime.time ? &efi_time : NULL,
|
||||
gettime.capabilities ? &cap : NULL);
|
||||
|
||||
if (put_user(status, gettime.status))
|
||||
return -EFAULT;
|
||||
|
||||
if (status != EFI_SUCCESS)
|
||||
return -EINVAL;
|
||||
|
||||
if (gettime.capabilities) {
|
||||
efi_time_cap_t __user *cap_local;
|
||||
|
||||
cap_local = (efi_time_cap_t *)gettime.capabilities;
|
||||
if (put_user(cap.resolution, &(cap_local->resolution)) ||
|
||||
put_user(cap.accuracy, &(cap_local->accuracy)) ||
|
||||
put_user(cap.sets_to_zero, &(cap_local->sets_to_zero)))
|
||||
return -EFAULT;
|
||||
}
|
||||
if (gettime.time) {
|
||||
if (copy_to_user(gettime.time, &efi_time, sizeof(efi_time_t)))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long efi_runtime_set_time(unsigned long arg)
|
||||
{
|
||||
struct efi_settime __user *settime_user;
|
||||
struct efi_settime settime;
|
||||
efi_status_t status;
|
||||
efi_time_t efi_time;
|
||||
|
||||
settime_user = (struct efi_settime __user *)arg;
|
||||
if (copy_from_user(&settime, settime_user, sizeof(settime)))
|
||||
return -EFAULT;
|
||||
if (copy_from_user(&efi_time, settime.time,
|
||||
sizeof(efi_time_t)))
|
||||
return -EFAULT;
|
||||
status = efi.set_time(&efi_time);
|
||||
|
||||
if (put_user(status, settime.status))
|
||||
return -EFAULT;
|
||||
|
||||
return status == EFI_SUCCESS ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
static long efi_runtime_get_waketime(unsigned long arg)
|
||||
{
|
||||
struct efi_getwakeuptime __user *getwakeuptime_user;
|
||||
struct efi_getwakeuptime getwakeuptime;
|
||||
efi_bool_t enabled, pending;
|
||||
efi_status_t status;
|
||||
efi_time_t efi_time;
|
||||
|
||||
getwakeuptime_user = (struct efi_getwakeuptime __user *)arg;
|
||||
if (copy_from_user(&getwakeuptime, getwakeuptime_user,
|
||||
sizeof(getwakeuptime)))
|
||||
return -EFAULT;
|
||||
|
||||
status = efi.get_wakeup_time(
|
||||
getwakeuptime.enabled ? (efi_bool_t *)&enabled : NULL,
|
||||
getwakeuptime.pending ? (efi_bool_t *)&pending : NULL,
|
||||
getwakeuptime.time ? &efi_time : NULL);
|
||||
|
||||
if (put_user(status, getwakeuptime.status))
|
||||
return -EFAULT;
|
||||
|
||||
if (status != EFI_SUCCESS)
|
||||
return -EINVAL;
|
||||
|
||||
if (getwakeuptime.enabled && put_user(enabled,
|
||||
getwakeuptime.enabled))
|
||||
return -EFAULT;
|
||||
|
||||
if (getwakeuptime.time) {
|
||||
if (copy_to_user(getwakeuptime.time, &efi_time,
|
||||
sizeof(efi_time_t)))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long efi_runtime_set_waketime(unsigned long arg)
|
||||
{
|
||||
struct efi_setwakeuptime __user *setwakeuptime_user;
|
||||
struct efi_setwakeuptime setwakeuptime;
|
||||
efi_bool_t enabled;
|
||||
efi_status_t status;
|
||||
efi_time_t efi_time;
|
||||
|
||||
setwakeuptime_user = (struct efi_setwakeuptime __user *)arg;
|
||||
|
||||
if (copy_from_user(&setwakeuptime, setwakeuptime_user,
|
||||
sizeof(setwakeuptime)))
|
||||
return -EFAULT;
|
||||
|
||||
enabled = setwakeuptime.enabled;
|
||||
if (setwakeuptime.time) {
|
||||
if (copy_from_user(&efi_time, setwakeuptime.time,
|
||||
sizeof(efi_time_t)))
|
||||
return -EFAULT;
|
||||
|
||||
status = efi.set_wakeup_time(enabled, &efi_time);
|
||||
} else
|
||||
status = efi.set_wakeup_time(enabled, NULL);
|
||||
|
||||
if (put_user(status, setwakeuptime.status))
|
||||
return -EFAULT;
|
||||
|
||||
return status == EFI_SUCCESS ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
static long efi_runtime_get_nextvariablename(unsigned long arg)
|
||||
{
|
||||
struct efi_getnextvariablename __user *getnextvariablename_user;
|
||||
struct efi_getnextvariablename getnextvariablename;
|
||||
unsigned long name_size, prev_name_size = 0, *ns = NULL;
|
||||
efi_status_t status;
|
||||
efi_guid_t *vd = NULL;
|
||||
efi_guid_t vendor_guid;
|
||||
efi_char16_t *name = NULL;
|
||||
int rv;
|
||||
|
||||
getnextvariablename_user = (struct efi_getnextvariablename __user *)arg;
|
||||
|
||||
if (copy_from_user(&getnextvariablename, getnextvariablename_user,
|
||||
sizeof(getnextvariablename)))
|
||||
return -EFAULT;
|
||||
|
||||
if (getnextvariablename.variable_name_size) {
|
||||
if (get_user(name_size, getnextvariablename.variable_name_size))
|
||||
return -EFAULT;
|
||||
ns = &name_size;
|
||||
prev_name_size = name_size;
|
||||
}
|
||||
|
||||
if (getnextvariablename.vendor_guid) {
|
||||
if (copy_from_user(&vendor_guid,
|
||||
getnextvariablename.vendor_guid,
|
||||
sizeof(vendor_guid)))
|
||||
return -EFAULT;
|
||||
vd = &vendor_guid;
|
||||
}
|
||||
|
||||
if (getnextvariablename.variable_name) {
|
||||
size_t name_string_size = 0;
|
||||
|
||||
rv = get_ucs2_strsize_from_user(
|
||||
getnextvariablename.variable_name,
|
||||
&name_string_size);
|
||||
if (rv)
|
||||
return rv;
|
||||
/*
|
||||
* The name_size may be smaller than the real buffer size where
|
||||
* variable name located in some use cases. The most typical
|
||||
* case is passing a 0 to get the required buffer size for the
|
||||
* 1st time call. So we need to copy the content from user
|
||||
* space for at least the string size of variable name, or else
|
||||
* the name passed to UEFI may not be terminated as we expected.
|
||||
*/
|
||||
rv = copy_ucs2_from_user_len(&name,
|
||||
getnextvariablename.variable_name,
|
||||
prev_name_size > name_string_size ?
|
||||
prev_name_size : name_string_size);
|
||||
if (rv)
|
||||
return rv;
|
||||
}
|
||||
|
||||
status = efi.get_next_variable(ns, name, vd);
|
||||
|
||||
if (put_user(status, getnextvariablename.status)) {
|
||||
rv = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (status != EFI_SUCCESS) {
|
||||
if (status == EFI_BUFFER_TOO_SMALL) {
|
||||
if (ns && put_user(*ns,
|
||||
getnextvariablename.variable_name_size)) {
|
||||
rv = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
rv = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
if (copy_ucs2_to_user_len(getnextvariablename.variable_name,
|
||||
name, prev_name_size)) {
|
||||
rv = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (ns) {
|
||||
if (put_user(*ns, getnextvariablename.variable_name_size)) {
|
||||
rv = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (vd) {
|
||||
if (copy_to_user(getnextvariablename.vendor_guid, vd,
|
||||
sizeof(efi_guid_t)))
|
||||
rv = -EFAULT;
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(name);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static long efi_runtime_get_nexthighmonocount(unsigned long arg)
|
||||
{
|
||||
struct efi_getnexthighmonotoniccount __user *getnexthighmonocount_user;
|
||||
struct efi_getnexthighmonotoniccount getnexthighmonocount;
|
||||
efi_status_t status;
|
||||
u32 count;
|
||||
|
||||
getnexthighmonocount_user = (struct
|
||||
efi_getnexthighmonotoniccount __user *)arg;
|
||||
|
||||
if (copy_from_user(&getnexthighmonocount,
|
||||
getnexthighmonocount_user,
|
||||
sizeof(getnexthighmonocount)))
|
||||
return -EFAULT;
|
||||
|
||||
status = efi.get_next_high_mono_count(
|
||||
getnexthighmonocount.high_count ? &count : NULL);
|
||||
|
||||
if (put_user(status, getnexthighmonocount.status))
|
||||
return -EFAULT;
|
||||
|
||||
if (status != EFI_SUCCESS)
|
||||
return -EINVAL;
|
||||
|
||||
if (getnexthighmonocount.high_count &&
|
||||
put_user(count, getnexthighmonocount.high_count))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long efi_runtime_query_variableinfo(unsigned long arg)
|
||||
{
|
||||
struct efi_queryvariableinfo __user *queryvariableinfo_user;
|
||||
struct efi_queryvariableinfo queryvariableinfo;
|
||||
efi_status_t status;
|
||||
u64 max_storage, remaining, max_size;
|
||||
|
||||
queryvariableinfo_user = (struct efi_queryvariableinfo __user *)arg;
|
||||
|
||||
if (copy_from_user(&queryvariableinfo, queryvariableinfo_user,
|
||||
sizeof(queryvariableinfo)))
|
||||
return -EFAULT;
|
||||
|
||||
status = efi.query_variable_info(queryvariableinfo.attributes,
|
||||
&max_storage, &remaining, &max_size);
|
||||
|
||||
if (put_user(status, queryvariableinfo.status))
|
||||
return -EFAULT;
|
||||
|
||||
if (status != EFI_SUCCESS)
|
||||
return -EINVAL;
|
||||
|
||||
if (put_user(max_storage,
|
||||
queryvariableinfo.maximum_variable_storage_size))
|
||||
return -EFAULT;
|
||||
|
||||
if (put_user(remaining,
|
||||
queryvariableinfo.remaining_variable_storage_size))
|
||||
return -EFAULT;
|
||||
|
||||
if (put_user(max_size, queryvariableinfo.maximum_variable_size))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long efi_runtime_query_capsulecaps(unsigned long arg)
|
||||
{
|
||||
struct efi_querycapsulecapabilities __user *qcaps_user;
|
||||
struct efi_querycapsulecapabilities qcaps;
|
||||
efi_capsule_header_t *capsules;
|
||||
efi_status_t status;
|
||||
u64 max_size;
|
||||
int i, reset_type;
|
||||
int rv = 0;
|
||||
|
||||
qcaps_user = (struct efi_querycapsulecapabilities __user *)arg;
|
||||
|
||||
if (copy_from_user(&qcaps, qcaps_user, sizeof(qcaps)))
|
||||
return -EFAULT;
|
||||
|
||||
capsules = kcalloc(qcaps.capsule_count + 1,
|
||||
sizeof(efi_capsule_header_t), GFP_KERNEL);
|
||||
if (!capsules)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < qcaps.capsule_count; i++) {
|
||||
efi_capsule_header_t *c;
|
||||
/*
|
||||
* We cannot dereference qcaps.capsule_header_array directly to
|
||||
* obtain the address of the capsule as it resides in the
|
||||
* user space
|
||||
*/
|
||||
if (get_user(c, qcaps.capsule_header_array + i)) {
|
||||
rv = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
if (copy_from_user(&capsules[i], c,
|
||||
sizeof(efi_capsule_header_t))) {
|
||||
rv = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
qcaps.capsule_header_array = &capsules;
|
||||
|
||||
status = efi.query_capsule_caps((efi_capsule_header_t **)
|
||||
qcaps.capsule_header_array,
|
||||
qcaps.capsule_count,
|
||||
&max_size, &reset_type);
|
||||
|
||||
if (put_user(status, qcaps.status)) {
|
||||
rv = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (status != EFI_SUCCESS) {
|
||||
rv = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (put_user(max_size, qcaps.maximum_capsule_size)) {
|
||||
rv = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (put_user(reset_type, qcaps.reset_type))
|
||||
rv = -EFAULT;
|
||||
|
||||
out:
|
||||
kfree(capsules);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static long efi_test_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case EFI_RUNTIME_GET_VARIABLE:
|
||||
return efi_runtime_get_variable(arg);
|
||||
|
||||
case EFI_RUNTIME_SET_VARIABLE:
|
||||
return efi_runtime_set_variable(arg);
|
||||
|
||||
case EFI_RUNTIME_GET_TIME:
|
||||
return efi_runtime_get_time(arg);
|
||||
|
||||
case EFI_RUNTIME_SET_TIME:
|
||||
return efi_runtime_set_time(arg);
|
||||
|
||||
case EFI_RUNTIME_GET_WAKETIME:
|
||||
return efi_runtime_get_waketime(arg);
|
||||
|
||||
case EFI_RUNTIME_SET_WAKETIME:
|
||||
return efi_runtime_set_waketime(arg);
|
||||
|
||||
case EFI_RUNTIME_GET_NEXTVARIABLENAME:
|
||||
return efi_runtime_get_nextvariablename(arg);
|
||||
|
||||
case EFI_RUNTIME_GET_NEXTHIGHMONOTONICCOUNT:
|
||||
return efi_runtime_get_nexthighmonocount(arg);
|
||||
|
||||
case EFI_RUNTIME_QUERY_VARIABLEINFO:
|
||||
return efi_runtime_query_variableinfo(arg);
|
||||
|
||||
case EFI_RUNTIME_QUERY_CAPSULECAPABILITIES:
|
||||
return efi_runtime_query_capsulecaps(arg);
|
||||
}
|
||||
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
static int efi_test_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
/*
|
||||
* nothing special to do here
|
||||
* We do accept multiple open files at the same time as we
|
||||
* synchronize on the per call operation.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int efi_test_close(struct inode *inode, struct file *file)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The various file operations we support.
|
||||
*/
|
||||
static const struct file_operations efi_test_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = efi_test_ioctl,
|
||||
.open = efi_test_open,
|
||||
.release = efi_test_close,
|
||||
.llseek = no_llseek,
|
||||
};
|
||||
|
||||
static struct miscdevice efi_test_dev = {
|
||||
MISC_DYNAMIC_MINOR,
|
||||
"efi_test",
|
||||
&efi_test_fops
|
||||
};
|
||||
|
||||
static int __init efi_test_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = misc_register(&efi_test_dev);
|
||||
if (ret) {
|
||||
pr_err("efi_test: can't misc_register on minor=%d\n",
|
||||
MISC_DYNAMIC_MINOR);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit efi_test_exit(void)
|
||||
{
|
||||
misc_deregister(&efi_test_dev);
|
||||
}
|
||||
|
||||
module_init(efi_test_init);
|
||||
module_exit(efi_test_exit);
|
110
drivers/firmware/efi/test/efi_test.h
Normal file
110
drivers/firmware/efi/test/efi_test.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* EFI Test driver Header
|
||||
*
|
||||
* Copyright(C) 2012-2016 Canonical Ltd.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DRIVERS_FIRMWARE_EFI_TEST_H_
|
||||
#define _DRIVERS_FIRMWARE_EFI_TEST_H_
|
||||
|
||||
#include <linux/efi.h>
|
||||
|
||||
struct efi_getvariable {
|
||||
efi_char16_t *variable_name;
|
||||
efi_guid_t *vendor_guid;
|
||||
u32 *attributes;
|
||||
unsigned long *data_size;
|
||||
void *data;
|
||||
efi_status_t *status;
|
||||
} __packed;
|
||||
|
||||
struct efi_setvariable {
|
||||
efi_char16_t *variable_name;
|
||||
efi_guid_t *vendor_guid;
|
||||
u32 attributes;
|
||||
unsigned long data_size;
|
||||
void *data;
|
||||
efi_status_t *status;
|
||||
} __packed;
|
||||
|
||||
struct efi_getnextvariablename {
|
||||
unsigned long *variable_name_size;
|
||||
efi_char16_t *variable_name;
|
||||
efi_guid_t *vendor_guid;
|
||||
efi_status_t *status;
|
||||
} __packed;
|
||||
|
||||
struct efi_queryvariableinfo {
|
||||
u32 attributes;
|
||||
u64 *maximum_variable_storage_size;
|
||||
u64 *remaining_variable_storage_size;
|
||||
u64 *maximum_variable_size;
|
||||
efi_status_t *status;
|
||||
} __packed;
|
||||
|
||||
struct efi_gettime {
|
||||
efi_time_t *time;
|
||||
efi_time_cap_t *capabilities;
|
||||
efi_status_t *status;
|
||||
} __packed;
|
||||
|
||||
struct efi_settime {
|
||||
efi_time_t *time;
|
||||
efi_status_t *status;
|
||||
} __packed;
|
||||
|
||||
struct efi_getwakeuptime {
|
||||
efi_bool_t *enabled;
|
||||
efi_bool_t *pending;
|
||||
efi_time_t *time;
|
||||
efi_status_t *status;
|
||||
} __packed;
|
||||
|
||||
struct efi_setwakeuptime {
|
||||
efi_bool_t enabled;
|
||||
efi_time_t *time;
|
||||
efi_status_t *status;
|
||||
} __packed;
|
||||
|
||||
struct efi_getnexthighmonotoniccount {
|
||||
u32 *high_count;
|
||||
efi_status_t *status;
|
||||
} __packed;
|
||||
|
||||
struct efi_querycapsulecapabilities {
|
||||
efi_capsule_header_t **capsule_header_array;
|
||||
unsigned long capsule_count;
|
||||
u64 *maximum_capsule_size;
|
||||
int *reset_type;
|
||||
efi_status_t *status;
|
||||
} __packed;
|
||||
|
||||
#define EFI_RUNTIME_GET_VARIABLE \
|
||||
_IOWR('p', 0x01, struct efi_getvariable)
|
||||
#define EFI_RUNTIME_SET_VARIABLE \
|
||||
_IOW('p', 0x02, struct efi_setvariable)
|
||||
|
||||
#define EFI_RUNTIME_GET_TIME \
|
||||
_IOR('p', 0x03, struct efi_gettime)
|
||||
#define EFI_RUNTIME_SET_TIME \
|
||||
_IOW('p', 0x04, struct efi_settime)
|
||||
|
||||
#define EFI_RUNTIME_GET_WAKETIME \
|
||||
_IOR('p', 0x05, struct efi_getwakeuptime)
|
||||
#define EFI_RUNTIME_SET_WAKETIME \
|
||||
_IOW('p', 0x06, struct efi_setwakeuptime)
|
||||
|
||||
#define EFI_RUNTIME_GET_NEXTVARIABLENAME \
|
||||
_IOWR('p', 0x07, struct efi_getnextvariablename)
|
||||
|
||||
#define EFI_RUNTIME_QUERY_VARIABLEINFO \
|
||||
_IOR('p', 0x08, struct efi_queryvariableinfo)
|
||||
|
||||
#define EFI_RUNTIME_GET_NEXTHIGHMONOTONICCOUNT \
|
||||
_IOR('p', 0x09, struct efi_getnexthighmonotoniccount)
|
||||
|
||||
#define EFI_RUNTIME_QUERY_CAPSULECAPABILITIES \
|
||||
_IOR('p', 0x0A, struct efi_querycapsulecapabilities)
|
||||
|
||||
#endif /* _DRIVERS_FIRMWARE_EFI_TEST_H_ */
|
@@ -37,6 +37,14 @@
|
||||
/* Private pointer to registered efivars */
|
||||
static struct efivars *__efivars;
|
||||
|
||||
/*
|
||||
* efivars_lock protects three things:
|
||||
* 1) efivarfs_list and efivars_sysfs_list
|
||||
* 2) ->ops calls
|
||||
* 3) (un)registration of __efivars
|
||||
*/
|
||||
static DEFINE_SEMAPHORE(efivars_lock);
|
||||
|
||||
static bool efivar_wq_enabled = true;
|
||||
DECLARE_WORK(efivar_work, NULL);
|
||||
EXPORT_SYMBOL_GPL(efivar_work);
|
||||
@@ -434,7 +442,10 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
spin_lock_irq(&__efivars->lock);
|
||||
if (down_interruptible(&efivars_lock)) {
|
||||
err = -EINTR;
|
||||
goto free;
|
||||
}
|
||||
|
||||
/*
|
||||
* Per EFI spec, the maximum storage allocated for both
|
||||
@@ -450,7 +461,7 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
|
||||
switch (status) {
|
||||
case EFI_SUCCESS:
|
||||
if (duplicates)
|
||||
spin_unlock_irq(&__efivars->lock);
|
||||
up(&efivars_lock);
|
||||
|
||||
variable_name_size = var_name_strnsize(variable_name,
|
||||
variable_name_size);
|
||||
@@ -476,8 +487,12 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
|
||||
status = EFI_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (duplicates)
|
||||
spin_lock_irq(&__efivars->lock);
|
||||
if (duplicates) {
|
||||
if (down_interruptible(&efivars_lock)) {
|
||||
err = -EINTR;
|
||||
goto free;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case EFI_NOT_FOUND:
|
||||
@@ -491,8 +506,8 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
|
||||
|
||||
} while (status != EFI_NOT_FOUND);
|
||||
|
||||
spin_unlock_irq(&__efivars->lock);
|
||||
|
||||
up(&efivars_lock);
|
||||
free:
|
||||
kfree(variable_name);
|
||||
|
||||
return err;
|
||||
@@ -503,24 +518,34 @@ EXPORT_SYMBOL_GPL(efivar_init);
|
||||
* efivar_entry_add - add entry to variable list
|
||||
* @entry: entry to add to list
|
||||
* @head: list head
|
||||
*
|
||||
* Returns 0 on success, or a kernel error code on failure.
|
||||
*/
|
||||
void efivar_entry_add(struct efivar_entry *entry, struct list_head *head)
|
||||
int efivar_entry_add(struct efivar_entry *entry, struct list_head *head)
|
||||
{
|
||||
spin_lock_irq(&__efivars->lock);
|
||||
if (down_interruptible(&efivars_lock))
|
||||
return -EINTR;
|
||||
list_add(&entry->list, head);
|
||||
spin_unlock_irq(&__efivars->lock);
|
||||
up(&efivars_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(efivar_entry_add);
|
||||
|
||||
/**
|
||||
* efivar_entry_remove - remove entry from variable list
|
||||
* @entry: entry to remove from list
|
||||
*
|
||||
* Returns 0 on success, or a kernel error code on failure.
|
||||
*/
|
||||
void efivar_entry_remove(struct efivar_entry *entry)
|
||||
int efivar_entry_remove(struct efivar_entry *entry)
|
||||
{
|
||||
spin_lock_irq(&__efivars->lock);
|
||||
if (down_interruptible(&efivars_lock))
|
||||
return -EINTR;
|
||||
list_del(&entry->list);
|
||||
spin_unlock_irq(&__efivars->lock);
|
||||
up(&efivars_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(efivar_entry_remove);
|
||||
|
||||
@@ -537,10 +562,8 @@ EXPORT_SYMBOL_GPL(efivar_entry_remove);
|
||||
*/
|
||||
static void efivar_entry_list_del_unlock(struct efivar_entry *entry)
|
||||
{
|
||||
lockdep_assert_held(&__efivars->lock);
|
||||
|
||||
list_del(&entry->list);
|
||||
spin_unlock_irq(&__efivars->lock);
|
||||
up(&efivars_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -563,8 +586,6 @@ int __efivar_entry_delete(struct efivar_entry *entry)
|
||||
const struct efivar_operations *ops = __efivars->ops;
|
||||
efi_status_t status;
|
||||
|
||||
lockdep_assert_held(&__efivars->lock);
|
||||
|
||||
status = ops->set_variable(entry->var.VariableName,
|
||||
&entry->var.VendorGuid,
|
||||
0, 0, NULL);
|
||||
@@ -581,20 +602,22 @@ EXPORT_SYMBOL_GPL(__efivar_entry_delete);
|
||||
* variable list. It is the caller's responsibility to free @entry
|
||||
* once we return.
|
||||
*
|
||||
* Returns 0 on success, or a converted EFI status code if
|
||||
* set_variable() fails.
|
||||
* Returns 0 on success, -EINTR if we can't grab the semaphore,
|
||||
* converted EFI status code if set_variable() fails.
|
||||
*/
|
||||
int efivar_entry_delete(struct efivar_entry *entry)
|
||||
{
|
||||
const struct efivar_operations *ops = __efivars->ops;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irq(&__efivars->lock);
|
||||
if (down_interruptible(&efivars_lock))
|
||||
return -EINTR;
|
||||
|
||||
status = ops->set_variable(entry->var.VariableName,
|
||||
&entry->var.VendorGuid,
|
||||
0, 0, NULL);
|
||||
if (!(status == EFI_SUCCESS || status == EFI_NOT_FOUND)) {
|
||||
spin_unlock_irq(&__efivars->lock);
|
||||
up(&efivars_lock);
|
||||
return efi_status_to_err(status);
|
||||
}
|
||||
|
||||
@@ -620,9 +643,9 @@ EXPORT_SYMBOL_GPL(efivar_entry_delete);
|
||||
* If @head is not NULL a lookup is performed to determine whether
|
||||
* the entry is already on the list.
|
||||
*
|
||||
* Returns 0 on success, -EEXIST if a lookup is performed and the entry
|
||||
* already exists on the list, or a converted EFI status code if
|
||||
* set_variable() fails.
|
||||
* Returns 0 on success, -EINTR if we can't grab the semaphore,
|
||||
* -EEXIST if a lookup is performed and the entry already exists on
|
||||
* the list, or a converted EFI status code if set_variable() fails.
|
||||
*/
|
||||
int efivar_entry_set(struct efivar_entry *entry, u32 attributes,
|
||||
unsigned long size, void *data, struct list_head *head)
|
||||
@@ -632,10 +655,10 @@ int efivar_entry_set(struct efivar_entry *entry, u32 attributes,
|
||||
efi_char16_t *name = entry->var.VariableName;
|
||||
efi_guid_t vendor = entry->var.VendorGuid;
|
||||
|
||||
spin_lock_irq(&__efivars->lock);
|
||||
|
||||
if (down_interruptible(&efivars_lock))
|
||||
return -EINTR;
|
||||
if (head && efivar_entry_find(name, vendor, head, false)) {
|
||||
spin_unlock_irq(&__efivars->lock);
|
||||
up(&efivars_lock);
|
||||
return -EEXIST;
|
||||
}
|
||||
|
||||
@@ -644,7 +667,7 @@ int efivar_entry_set(struct efivar_entry *entry, u32 attributes,
|
||||
status = ops->set_variable(name, &vendor,
|
||||
attributes, size, data);
|
||||
|
||||
spin_unlock_irq(&__efivars->lock);
|
||||
up(&efivars_lock);
|
||||
|
||||
return efi_status_to_err(status);
|
||||
|
||||
@@ -658,30 +681,29 @@ EXPORT_SYMBOL_GPL(efivar_entry_set);
|
||||
* from crash/panic handlers.
|
||||
*
|
||||
* Crucially, this function will not block if it cannot acquire
|
||||
* __efivars->lock. Instead, it returns -EBUSY.
|
||||
* efivars_lock. Instead, it returns -EBUSY.
|
||||
*/
|
||||
static int
|
||||
efivar_entry_set_nonblocking(efi_char16_t *name, efi_guid_t vendor,
|
||||
u32 attributes, unsigned long size, void *data)
|
||||
{
|
||||
const struct efivar_operations *ops = __efivars->ops;
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
if (!spin_trylock_irqsave(&__efivars->lock, flags))
|
||||
if (down_trylock(&efivars_lock))
|
||||
return -EBUSY;
|
||||
|
||||
status = check_var_size_nonblocking(attributes,
|
||||
size + ucs2_strsize(name, 1024));
|
||||
if (status != EFI_SUCCESS) {
|
||||
spin_unlock_irqrestore(&__efivars->lock, flags);
|
||||
up(&efivars_lock);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
status = ops->set_variable_nonblocking(name, &vendor, attributes,
|
||||
size, data);
|
||||
|
||||
spin_unlock_irqrestore(&__efivars->lock, flags);
|
||||
up(&efivars_lock);
|
||||
return efi_status_to_err(status);
|
||||
}
|
||||
|
||||
@@ -706,7 +728,6 @@ int efivar_entry_set_safe(efi_char16_t *name, efi_guid_t vendor, u32 attributes,
|
||||
bool block, unsigned long size, void *data)
|
||||
{
|
||||
const struct efivar_operations *ops = __efivars->ops;
|
||||
unsigned long flags;
|
||||
efi_status_t status;
|
||||
|
||||
if (!ops->query_variable_store)
|
||||
@@ -727,21 +748,22 @@ int efivar_entry_set_safe(efi_char16_t *name, efi_guid_t vendor, u32 attributes,
|
||||
size, data);
|
||||
|
||||
if (!block) {
|
||||
if (!spin_trylock_irqsave(&__efivars->lock, flags))
|
||||
if (down_trylock(&efivars_lock))
|
||||
return -EBUSY;
|
||||
} else {
|
||||
spin_lock_irqsave(&__efivars->lock, flags);
|
||||
if (down_interruptible(&efivars_lock))
|
||||
return -EINTR;
|
||||
}
|
||||
|
||||
status = check_var_size(attributes, size + ucs2_strsize(name, 1024));
|
||||
if (status != EFI_SUCCESS) {
|
||||
spin_unlock_irqrestore(&__efivars->lock, flags);
|
||||
up(&efivars_lock);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
status = ops->set_variable(name, &vendor, attributes, size, data);
|
||||
|
||||
spin_unlock_irqrestore(&__efivars->lock, flags);
|
||||
up(&efivars_lock);
|
||||
|
||||
return efi_status_to_err(status);
|
||||
}
|
||||
@@ -771,8 +793,6 @@ struct efivar_entry *efivar_entry_find(efi_char16_t *name, efi_guid_t guid,
|
||||
int strsize1, strsize2;
|
||||
bool found = false;
|
||||
|
||||
lockdep_assert_held(&__efivars->lock);
|
||||
|
||||
list_for_each_entry_safe(entry, n, head, list) {
|
||||
strsize1 = ucs2_strsize(name, 1024);
|
||||
strsize2 = ucs2_strsize(entry->var.VariableName, 1024);
|
||||
@@ -814,10 +834,11 @@ int efivar_entry_size(struct efivar_entry *entry, unsigned long *size)
|
||||
|
||||
*size = 0;
|
||||
|
||||
spin_lock_irq(&__efivars->lock);
|
||||
if (down_interruptible(&efivars_lock))
|
||||
return -EINTR;
|
||||
status = ops->get_variable(entry->var.VariableName,
|
||||
&entry->var.VendorGuid, NULL, size, NULL);
|
||||
spin_unlock_irq(&__efivars->lock);
|
||||
up(&efivars_lock);
|
||||
|
||||
if (status != EFI_BUFFER_TOO_SMALL)
|
||||
return efi_status_to_err(status);
|
||||
@@ -843,8 +864,6 @@ int __efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
|
||||
const struct efivar_operations *ops = __efivars->ops;
|
||||
efi_status_t status;
|
||||
|
||||
lockdep_assert_held(&__efivars->lock);
|
||||
|
||||
status = ops->get_variable(entry->var.VariableName,
|
||||
&entry->var.VendorGuid,
|
||||
attributes, size, data);
|
||||
@@ -866,11 +885,12 @@ int efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
|
||||
const struct efivar_operations *ops = __efivars->ops;
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock_irq(&__efivars->lock);
|
||||
if (down_interruptible(&efivars_lock))
|
||||
return -EINTR;
|
||||
status = ops->get_variable(entry->var.VariableName,
|
||||
&entry->var.VendorGuid,
|
||||
attributes, size, data);
|
||||
spin_unlock_irq(&__efivars->lock);
|
||||
up(&efivars_lock);
|
||||
|
||||
return efi_status_to_err(status);
|
||||
}
|
||||
@@ -917,7 +937,8 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
|
||||
* set_variable call, and removal of the variable from the efivars
|
||||
* list (in the case of an authenticated delete).
|
||||
*/
|
||||
spin_lock_irq(&__efivars->lock);
|
||||
if (down_interruptible(&efivars_lock))
|
||||
return -EINTR;
|
||||
|
||||
/*
|
||||
* Ensure that the available space hasn't shrunk below the safe level
|
||||
@@ -957,7 +978,7 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
|
||||
if (status == EFI_NOT_FOUND)
|
||||
efivar_entry_list_del_unlock(entry);
|
||||
else
|
||||
spin_unlock_irq(&__efivars->lock);
|
||||
up(&efivars_lock);
|
||||
|
||||
if (status && status != EFI_BUFFER_TOO_SMALL)
|
||||
return efi_status_to_err(status);
|
||||
@@ -965,7 +986,7 @@ int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
|
||||
return 0;
|
||||
|
||||
out:
|
||||
spin_unlock_irq(&__efivars->lock);
|
||||
up(&efivars_lock);
|
||||
return err;
|
||||
|
||||
}
|
||||
@@ -978,9 +999,9 @@ EXPORT_SYMBOL_GPL(efivar_entry_set_get_size);
|
||||
* efivar_entry_iter_end() is called. This function is usually used in
|
||||
* conjunction with __efivar_entry_iter() or efivar_entry_iter().
|
||||
*/
|
||||
void efivar_entry_iter_begin(void)
|
||||
int efivar_entry_iter_begin(void)
|
||||
{
|
||||
spin_lock_irq(&__efivars->lock);
|
||||
return down_interruptible(&efivars_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(efivar_entry_iter_begin);
|
||||
|
||||
@@ -991,7 +1012,7 @@ EXPORT_SYMBOL_GPL(efivar_entry_iter_begin);
|
||||
*/
|
||||
void efivar_entry_iter_end(void)
|
||||
{
|
||||
spin_unlock_irq(&__efivars->lock);
|
||||
up(&efivars_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(efivar_entry_iter_end);
|
||||
|
||||
@@ -1067,7 +1088,9 @@ int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
efivar_entry_iter_begin();
|
||||
err = efivar_entry_iter_begin();
|
||||
if (err)
|
||||
return err;
|
||||
err = __efivar_entry_iter(func, head, data, NULL);
|
||||
efivar_entry_iter_end();
|
||||
|
||||
@@ -1112,12 +1135,18 @@ int efivars_register(struct efivars *efivars,
|
||||
const struct efivar_operations *ops,
|
||||
struct kobject *kobject)
|
||||
{
|
||||
spin_lock_init(&efivars->lock);
|
||||
if (down_interruptible(&efivars_lock))
|
||||
return -EINTR;
|
||||
|
||||
efivars->ops = ops;
|
||||
efivars->kobject = kobject;
|
||||
|
||||
__efivars = efivars;
|
||||
|
||||
pr_info("Registered efivars operations\n");
|
||||
|
||||
up(&efivars_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(efivars_register);
|
||||
@@ -1133,6 +1162,9 @@ int efivars_unregister(struct efivars *efivars)
|
||||
{
|
||||
int rv;
|
||||
|
||||
if (down_interruptible(&efivars_lock))
|
||||
return -EINTR;
|
||||
|
||||
if (!__efivars) {
|
||||
printk(KERN_ERR "efivars not registered\n");
|
||||
rv = -EINVAL;
|
||||
@@ -1144,10 +1176,12 @@ int efivars_unregister(struct efivars *efivars)
|
||||
goto out;
|
||||
}
|
||||
|
||||
pr_info("Unregistered efivars operations\n");
|
||||
__efivars = NULL;
|
||||
|
||||
rv = 0;
|
||||
out:
|
||||
up(&efivars_lock);
|
||||
return rv;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(efivars_unregister);
|
||||
|
Reference in New Issue
Block a user