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:
Ingo Molnar
2016-09-13 20:21:55 +02:00
31 changed files with 1767 additions and 547 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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 +

View File

@@ -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");

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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++)

View 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;
}
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -0,0 +1 @@
obj-$(CONFIG_EFI_TEST) += efi_test.o

View 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);

View 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_ */

View File

@@ -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);