efi: Export EFI runtime memory mapping to sysfs

kexec kernel will need exactly same mapping for EFI runtime memory
ranges. Thus here export the runtime ranges mapping to sysfs,
kexec-tools will assemble them and pass to 2nd kernel via setup_data.

Introducing a new directory /sys/firmware/efi/runtime-map just like
/sys/firmware/memmap. Containing below attribute in each file of that
directory:

attribute  num_pages  phys_addr  type  virt_addr

Signed-off-by: Dave Young <dyoung@redhat.com>
Tested-by: Toshi Kani <toshi.kani@hp.com>
Signed-off-by: Matt Fleming <matt.fleming@intel.com>
This commit is contained in:
Dave Young 2013-12-20 18:02:18 +08:00 committed by Matt Fleming
parent a0998eb15a
commit 926172d460
7 changed files with 287 additions and 3 deletions

View File

@ -0,0 +1,34 @@
What: /sys/firmware/efi/runtime-map/
Date: December 2013
Contact: Dave Young <dyoung@redhat.com>
Description: Switching efi runtime services to virtual mode requires
that all efi memory ranges which have the runtime attribute
bit set to be mapped to virtual addresses.
The efi runtime services can only be switched to virtual
mode once without rebooting. The kexec kernel must maintain
the same physical to virtual address mappings as the first
kernel. The mappings are exported to sysfs so userspace tools
can reassemble them and pass them into the kexec kernel.
/sys/firmware/efi/runtime-map/ is the directory the kernel
exports that information in.
subdirectories are named with the number of the memory range:
/sys/firmware/efi/runtime-map/0
/sys/firmware/efi/runtime-map/1
/sys/firmware/efi/runtime-map/2
/sys/firmware/efi/runtime-map/3
...
Each subdirectory contains five files:
attribute : The attributes of the memory range.
num_pages : The size of the memory range in pages.
phys_addr : The physical address of the memory range.
type : The type of the memory range.
virt_addr : The virtual address of the memory range.
Above values are all hexadecimal numbers with the '0x' prefix.
Users: Kexec

View File

@ -76,6 +76,9 @@ static __initdata efi_config_table_type_t arch_tables[] = {
{NULL_GUID, NULL, NULL},
};
static void *efi_runtime_map;
static int nr_efi_runtime_map;
/*
* Returns 1 if 'facility' is enabled, 0 otherwise.
*/
@ -824,6 +827,39 @@ static void __init get_systab_virt_addr(efi_memory_desc_t *md)
}
}
static int __init save_runtime_map(void)
{
efi_memory_desc_t *md;
void *tmp, *p, *q = NULL;
int count = 0;
for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {
md = p;
if (!(md->attribute & EFI_MEMORY_RUNTIME) ||
(md->type == EFI_BOOT_SERVICES_CODE) ||
(md->type == EFI_BOOT_SERVICES_DATA))
continue;
tmp = krealloc(q, (count + 1) * memmap.desc_size, GFP_KERNEL);
if (!tmp)
goto out;
q = tmp;
memcpy(q + count * memmap.desc_size, md, memmap.desc_size);
count++;
}
efi_runtime_map = q;
nr_efi_runtime_map = count;
efi_runtime_map_setup(efi_runtime_map, nr_efi_runtime_map,
boot_params.efi_info.efi_memdesc_size);
return 0;
out:
kfree(q);
return -ENOMEM;
}
/*
* Map efi memory ranges for runtime serivce and update new_memmap with virtual
* addresses.
@ -849,7 +885,7 @@ static void * __init efi_map_regions(int *count)
tmp = krealloc(new_memmap, (*count + 1) * memmap.desc_size,
GFP_KERNEL);
if (!tmp)
goto out_krealloc;
goto out;
new_memmap = tmp;
memcpy(new_memmap + (*count * memmap.desc_size), md,
memmap.desc_size);
@ -857,7 +893,7 @@ static void * __init efi_map_regions(int *count)
}
return new_memmap;
out_krealloc:
out:
kfree(new_memmap);
return NULL;
}
@ -883,7 +919,7 @@ void __init efi_enter_virtual_mode(void)
{
efi_status_t status;
void *new_memmap = NULL;
int count = 0;
int err, count = 0;
efi.systab = NULL;
@ -904,6 +940,10 @@ void __init efi_enter_virtual_mode(void)
return;
}
err = save_runtime_map();
if (err)
pr_err("Error saving runtime map, efi runtime on kexec non-functional!!\n");
BUG_ON(!efi.systab);
efi_setup_page_tables();

View File

@ -39,4 +39,15 @@ config EFI_VARS_PSTORE_DEFAULT_DISABLE
config UEFI_CPER
def_bool n
config EFI_RUNTIME_MAP
bool "Export efi runtime maps to sysfs"
depends on X86 && EFI && KEXEC
default y
help
Export efi runtime memory maps to /sys/firmware/efi/runtime-map.
That memory map is used for example by kexec to set up efi virtual
mapping the 2nd kernel, but can also be used for debugging purposes.
See also Documentation/ABI/testing/sysfs-firmware-efi-runtime-map.
endmenu

View File

@ -5,3 +5,4 @@ obj-y += efi.o vars.o
obj-$(CONFIG_EFI_VARS) += efivars.o
obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o
obj-$(CONFIG_UEFI_CPER) += cper.o
obj-$(CONFIG_EFI_RUNTIME_MAP) += runtime-map.o

View File

@ -167,6 +167,10 @@ static int __init efisubsys_init(void)
goto err_unregister;
}
error = efi_runtime_map_init(efi_kobj);
if (error)
goto err_remove_group;
/* and the standard mountpoint for efivarfs */
efivars_kobj = kobject_create_and_add("efivars", efi_kobj);
if (!efivars_kobj) {

View File

@ -0,0 +1,181 @@
/*
* linux/drivers/efi/runtime-map.c
* Copyright (C) 2013 Red Hat, Inc., Dave Young <dyoung@redhat.com>
*
* This file is released under the GPLv2.
*/
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/efi.h>
#include <linux/slab.h>
#include <asm/setup.h>
static void *efi_runtime_map;
static int nr_efi_runtime_map;
static u32 efi_memdesc_size;
struct efi_runtime_map_entry {
efi_memory_desc_t md;
struct kobject kobj; /* kobject for each entry */
};
static struct efi_runtime_map_entry **map_entries;
struct map_attribute {
struct attribute attr;
ssize_t (*show)(struct efi_runtime_map_entry *entry, char *buf);
};
static inline struct map_attribute *to_map_attr(struct attribute *attr)
{
return container_of(attr, struct map_attribute, attr);
}
static ssize_t type_show(struct efi_runtime_map_entry *entry, char *buf)
{
return snprintf(buf, PAGE_SIZE, "0x%x\n", entry->md.type);
}
#define EFI_RUNTIME_FIELD(var) entry->md.var
#define EFI_RUNTIME_U64_ATTR_SHOW(name) \
static ssize_t name##_show(struct efi_runtime_map_entry *entry, char *buf) \
{ \
return snprintf(buf, PAGE_SIZE, "0x%llx\n", EFI_RUNTIME_FIELD(name)); \
}
EFI_RUNTIME_U64_ATTR_SHOW(phys_addr);
EFI_RUNTIME_U64_ATTR_SHOW(virt_addr);
EFI_RUNTIME_U64_ATTR_SHOW(num_pages);
EFI_RUNTIME_U64_ATTR_SHOW(attribute);
static inline struct efi_runtime_map_entry *to_map_entry(struct kobject *kobj)
{
return container_of(kobj, struct efi_runtime_map_entry, kobj);
}
static ssize_t map_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct efi_runtime_map_entry *entry = to_map_entry(kobj);
struct map_attribute *map_attr = to_map_attr(attr);
return map_attr->show(entry, buf);
}
static struct map_attribute map_type_attr = __ATTR_RO(type);
static struct map_attribute map_phys_addr_attr = __ATTR_RO(phys_addr);
static struct map_attribute map_virt_addr_attr = __ATTR_RO(virt_addr);
static struct map_attribute map_num_pages_attr = __ATTR_RO(num_pages);
static struct map_attribute map_attribute_attr = __ATTR_RO(attribute);
/*
* These are default attributes that are added for every memmap entry.
*/
static struct attribute *def_attrs[] = {
&map_type_attr.attr,
&map_phys_addr_attr.attr,
&map_virt_addr_attr.attr,
&map_num_pages_attr.attr,
&map_attribute_attr.attr,
NULL
};
static const struct sysfs_ops map_attr_ops = {
.show = map_attr_show,
};
static void map_release(struct kobject *kobj)
{
struct efi_runtime_map_entry *entry;
entry = to_map_entry(kobj);
kfree(entry);
}
static struct kobj_type __refdata map_ktype = {
.sysfs_ops = &map_attr_ops,
.default_attrs = def_attrs,
.release = map_release,
};
static struct kset *map_kset;
static struct efi_runtime_map_entry *
add_sysfs_runtime_map_entry(struct kobject *kobj, int nr)
{
int ret;
struct efi_runtime_map_entry *entry;
if (!map_kset) {
map_kset = kset_create_and_add("runtime-map", NULL, kobj);
if (!map_kset)
return ERR_PTR(-ENOMEM);
}
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry) {
kset_unregister(map_kset);
return entry;
}
memcpy(&entry->md, efi_runtime_map + nr * efi_memdesc_size,
sizeof(efi_memory_desc_t));
kobject_init(&entry->kobj, &map_ktype);
entry->kobj.kset = map_kset;
ret = kobject_add(&entry->kobj, NULL, "%d", nr);
if (ret) {
kobject_put(&entry->kobj);
kset_unregister(map_kset);
return ERR_PTR(ret);
}
return entry;
}
void efi_runtime_map_setup(void *map, int nr_entries, u32 desc_size)
{
efi_runtime_map = map;
nr_efi_runtime_map = nr_entries;
efi_memdesc_size = desc_size;
}
int __init efi_runtime_map_init(struct kobject *efi_kobj)
{
int i, j, ret = 0;
struct efi_runtime_map_entry *entry;
if (!efi_runtime_map)
return 0;
map_entries = kzalloc(nr_efi_runtime_map * sizeof(entry), GFP_KERNEL);
if (!map_entries) {
ret = -ENOMEM;
goto out;
}
for (i = 0; i < nr_efi_runtime_map; i++) {
entry = add_sysfs_runtime_map_entry(efi_kobj, i);
if (IS_ERR(entry)) {
ret = PTR_ERR(entry);
goto out_add_entry;
}
*(map_entries + i) = entry;
}
return 0;
out_add_entry:
for (j = i - 1; j > 0; j--) {
entry = *(map_entries + j);
kobject_put(&entry->kobj);
}
if (map_kset)
kset_unregister(map_kset);
out:
return ret;
}

View File

@ -872,4 +872,17 @@ int efivars_sysfs_init(void);
#endif /* CONFIG_EFI_VARS */
#ifdef CONFIG_EFI_RUNTIME_MAP
int efi_runtime_map_init(struct kobject *);
void efi_runtime_map_setup(void *, int, u32);
#else
static inline int efi_runtime_map_init(struct kobject *kobj)
{
return 0;
}
static inline void
efi_runtime_map_setup(void *map, int nr_entries, u32 desc_size) {}
#endif
#endif /* _LINUX_EFI_H */