aboutsummaryrefslogtreecommitdiffstats
path: root/xen/arch/x86/efi
diff options
context:
space:
mode:
authorJan Beulich <jbeulich@novell.com>2011-06-28 09:20:49 +0100
committerJan Beulich <jbeulich@novell.com>2011-06-28 09:20:49 +0100
commitfacac0af87ef3e533d3690d76acaeaaa41e1631c (patch)
tree5239fbcadbd5b1a1c695869b10707deb15770777 /xen/arch/x86/efi
parentbf6501a62e80ec1cf756290d4c3ec4991455f64e (diff)
downloadxen-facac0af87ef3e533d3690d76acaeaaa41e1631c.tar.gz
xen-facac0af87ef3e533d3690d76acaeaaa41e1631c.tar.bz2
xen-facac0af87ef3e533d3690d76acaeaaa41e1631c.zip
x86-64: EFI runtime code
This allows Dom0 access to all suitable EFI runtime services. The actual calls into EFI are done in "physical" mode, as entering virtual mode has been determined to be incompatible with kexec (EFI's SetVirtualAddressMap() can be called only once, and hence the secondary kernel can't establish its mappings). ("Physical" mode here being quoted because this is a mode with paging enabled [otherwise 64-bit mode wouldn't work] but all mappings being 1:1.) Open issue (not preventing this from being committed imo): Page (and perhaps other) faults occuring while calling runtime functions in the context of a hypercall don't get handled correctly (they don't even seem to reach do_page_fault()). I'm intending to investigate this further. Signed-off-by: Jan Beulich <jbeulich@novell.com>
Diffstat (limited to 'xen/arch/x86/efi')
-rw-r--r--xen/arch/x86/efi/boot.c81
-rw-r--r--xen/arch/x86/efi/compat.c14
-rw-r--r--xen/arch/x86/efi/efi.h7
-rw-r--r--xen/arch/x86/efi/runtime.c328
-rw-r--r--xen/arch/x86/efi/stub.c18
5 files changed, 446 insertions, 2 deletions
diff --git a/xen/arch/x86/efi/boot.c b/xen/arch/x86/efi/boot.c
index 50ea3c8ee3..9a959e306d 100644
--- a/xen/arch/x86/efi/boot.c
+++ b/xen/arch/x86/efi/boot.c
@@ -16,6 +16,7 @@
#include <xen/stringify.h>
#include <xen/vga.h>
#include <asm/e820.h>
+#include <asm/mm.h>
#include <asm/msr.h>
#include <asm/processor.h>
@@ -1149,6 +1150,53 @@ efi_start(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
for( ; ; ); /* not reached */
}
+static __init void copy_mapping(unsigned long mfn, unsigned long end,
+ bool_t (*is_valid)(unsigned long smfn,
+ unsigned long emfn))
+{
+ unsigned long next;
+
+ for ( ; mfn < end; mfn = next )
+ {
+ l4_pgentry_t l4e = efi_l4_pgtable[l4_table_offset(mfn << PAGE_SHIFT)];
+ l3_pgentry_t *l3src, *l3dst;
+ unsigned long va = (unsigned long)mfn_to_virt(mfn);
+
+ next = mfn + (1UL << (L3_PAGETABLE_SHIFT - PAGE_SHIFT));
+ if ( !is_valid(mfn, min(next, end)) )
+ continue;
+ if ( !(l4e_get_flags(l4e) & _PAGE_PRESENT) )
+ {
+ l3dst = alloc_xen_pagetable();
+ BUG_ON(!l3dst);
+ clear_page(l3dst);
+ efi_l4_pgtable[l4_table_offset(mfn << PAGE_SHIFT)] =
+ l4e_from_paddr(virt_to_maddr(l3dst), __PAGE_HYPERVISOR);
+ }
+ else
+ l3dst = l4e_to_l3e(l4e);
+ l3src = l4e_to_l3e(idle_pg_table[l4_table_offset(va)]);
+ l3dst[l3_table_offset(mfn << PAGE_SHIFT)] = l3src[l3_table_offset(va)];
+ }
+}
+
+static bool_t __init ram_range_valid(unsigned long smfn, unsigned long emfn)
+{
+ unsigned long sz = pfn_to_pdx(emfn - 1) / PDX_GROUP_COUNT + 1;
+
+ return !(smfn & pfn_hole_mask) &&
+ find_next_bit(pdx_group_valid, sz,
+ pfn_to_pdx(smfn) / PDX_GROUP_COUNT) < sz;
+}
+
+static bool_t __init rt_range_valid(unsigned long smfn, unsigned long emfn)
+{
+ return 1;
+}
+
+#define INVALID_VIRTUAL_ADDRESS (0xBAAADUL << \
+ (EFI_PAGE_SHIFT + BITS_PER_LONG - 32))
+
void __init efi_init_memory(void)
{
unsigned int i;
@@ -1169,11 +1217,11 @@ void __init efi_init_memory(void)
if ( !(desc->Attribute & EFI_MEMORY_RUNTIME) )
continue;
+ desc->VirtualStart = INVALID_VIRTUAL_ADDRESS;
+
smfn = PFN_DOWN(desc->PhysicalStart);
emfn = PFN_UP(desc->PhysicalStart + len);
- desc->VirtualStart = 0xBAAADUL << (EFI_PAGE_SHIFT + BITS_PER_LONG - 32);
-
if ( desc->Attribute & EFI_MEMORY_WB )
/* nothing */;
else if ( desc->Attribute & EFI_MEMORY_WT )
@@ -1217,5 +1265,34 @@ void __init efi_init_memory(void)
#if 0 /* Incompatible with kexec. */
efi_rs->SetVirtualAddressMap(efi_memmap_size, efi_mdesc_size,
mdesc_ver, efi_memmap);
+#else
+ /* Set up 1:1 page tables to do runtime calls in "physical" mode. */
+ efi_l4_pgtable = alloc_xen_pagetable();
+ BUG_ON(!efi_l4_pgtable);
+ clear_page(efi_l4_pgtable);
+
+ copy_mapping(0, max_page, ram_range_valid);
+
+ /* Insert non-RAM runtime mappings. */
+ for ( i = 0; i < efi_memmap_size; i += efi_mdesc_size )
+ {
+ const EFI_MEMORY_DESCRIPTOR *desc = efi_memmap + i;
+
+ if ( desc->Attribute & EFI_MEMORY_RUNTIME )
+ {
+ if ( desc->VirtualStart != INVALID_VIRTUAL_ADDRESS )
+ copy_mapping(PFN_DOWN(desc->PhysicalStart),
+ PFN_UP(desc->PhysicalStart +
+ (desc->NumberOfPages << EFI_PAGE_SHIFT)),
+ rt_range_valid);
+ else
+ /* XXX */;
+ }
+ }
+
+ /* Insert Xen mappings. */
+ for ( i = l4_table_offset(HYPERVISOR_VIRT_START);
+ i < l4_table_offset(HYPERVISOR_VIRT_END); ++i )
+ efi_l4_pgtable[i] = idle_pg_table[i];
#endif
}
diff --git a/xen/arch/x86/efi/compat.c b/xen/arch/x86/efi/compat.c
index 4277f51f9f..7cc6279b8f 100644
--- a/xen/arch/x86/efi/compat.c
+++ b/xen/arch/x86/efi/compat.c
@@ -4,13 +4,27 @@
#define efi_get_info efi_compat_get_info
#define xenpf_efi_info compat_pf_efi_info
+#define efi_runtime_call efi_compat_runtime_call
+#define xenpf_efi_runtime_call compat_pf_efi_runtime_call
+
+#define xenpf_efi_guid compat_pf_efi_guid
+#define xenpf_efi_time compat_pf_efi_time
+
#define COMPAT
#undef DEFINE_XEN_GUEST_HANDLE
#define DEFINE_XEN_GUEST_HANDLE DEFINE_COMPAT_HANDLE
+#undef XEN_GUEST_HANDLE
+#define XEN_GUEST_HANDLE COMPAT_HANDLE
#undef guest_handle_okay
#define guest_handle_okay compat_handle_okay
#undef guest_handle_cast
#define guest_handle_cast compat_handle_cast
+#undef __copy_from_guest
+#define __copy_from_guest __copy_from_compat
+#undef copy_from_guest_offset
+#define copy_from_guest_offset copy_from_compat_offset
+#undef copy_to_guest
+#define copy_to_guest copy_to_compat
#undef __copy_to_guest_offset
#define __copy_to_guest_offset __copy_to_compat_offset
#include "runtime.c"
diff --git a/xen/arch/x86/efi/efi.h b/xen/arch/x86/efi/efi.h
index e7673ee910..0a42a463a9 100644
--- a/xen/arch/x86/efi/efi.h
+++ b/xen/arch/x86/efi/efi.h
@@ -5,6 +5,8 @@
#include <efi/efidevp.h>
#include <efi/efiapi.h>
#include <xen/efi.h>
+#include <xen/spinlock.h>
+#include <asm/page.h>
extern unsigned int efi_num_ct;
extern EFI_CONFIGURATION_TABLE *efi_ct;
@@ -16,3 +18,8 @@ extern EFI_RUNTIME_SERVICES *efi_rs;
extern UINTN efi_memmap_size, efi_mdesc_size;
extern void *efi_memmap;
+
+extern l4_pgentry_t *efi_l4_pgtable;
+
+unsigned long efi_rs_enter(void);
+void efi_rs_leave(unsigned long);
diff --git a/xen/arch/x86/efi/runtime.c b/xen/arch/x86/efi/runtime.c
index 5b98d0c9df..6f4e4de77b 100644
--- a/xen/arch/x86/efi/runtime.c
+++ b/xen/arch/x86/efi/runtime.c
@@ -2,6 +2,7 @@
#include <xen/cache.h>
#include <xen/errno.h>
#include <xen/guest_access.h>
+#include <xen/time.h>
DEFINE_XEN_GUEST_HANDLE(CHAR16);
@@ -19,6 +20,7 @@ unsigned int __read_mostly efi_fw_revision;
const CHAR16 *__read_mostly efi_fw_vendor;
EFI_RUNTIME_SERVICES *__read_mostly efi_rs;
+static DEFINE_SPINLOCK(efi_rs_lock);
UINTN __read_mostly efi_memmap_size;
UINTN __read_mostly efi_mdesc_size;
@@ -30,6 +32,68 @@ struct efi __read_mostly efi = {
.smbios = EFI_INVALID_TABLE_ADDR,
};
+l4_pgentry_t *__read_mostly efi_l4_pgtable;
+
+unsigned long efi_rs_enter(void)
+{
+ unsigned long cr3 = read_cr3();
+
+ spin_lock(&efi_rs_lock);
+
+ /* prevent fixup_page_fault() from doing anything */
+ irq_enter();
+
+ write_cr3(virt_to_maddr(efi_l4_pgtable));
+
+ return cr3;
+}
+
+void efi_rs_leave(unsigned long cr3)
+{
+ write_cr3(cr3);
+ irq_exit();
+ spin_unlock(&efi_rs_lock);
+}
+
+unsigned long efi_get_time(void)
+{
+ EFI_TIME time;
+ EFI_STATUS status;
+ unsigned long cr3 = efi_rs_enter();
+
+ status = efi_rs->GetTime(&time, NULL);
+ efi_rs_leave(cr3);
+
+ if ( EFI_ERROR(status) )
+ return 0;
+
+ return mktime(time.Year, time.Month, time.Day,
+ time.Hour, time.Minute, time.Second);
+}
+
+void efi_halt_system(void)
+{
+ EFI_STATUS status;
+ unsigned long cr3 = efi_rs_enter();
+
+ status = efi_rs->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
+ efi_rs_leave(cr3);
+
+ printk(XENLOG_WARNING "EFI: could not halt system (%#lx)\n", status);
+}
+
+void efi_reset_system(bool_t warm)
+{
+ EFI_STATUS status;
+ unsigned long cr3 = efi_rs_enter();
+
+ status = efi_rs->ResetSystem(warm ? EfiResetWarm : EfiResetCold,
+ EFI_SUCCESS, 0, NULL);
+ efi_rs_leave(cr3);
+
+ printk(XENLOG_WARNING "EFI: could not reset system (%#lx)\n", status);
+}
+
#endif
int efi_get_info(uint32_t idx, union xenpf_efi_info *info)
@@ -86,3 +150,267 @@ int efi_get_info(uint32_t idx, union xenpf_efi_info *info)
return 0;
}
+
+static long gwstrlen(XEN_GUEST_HANDLE(CHAR16) str)
+{
+ unsigned long len;
+
+ for ( len = 0; ; ++len )
+ {
+ CHAR16 c;
+
+ if ( copy_from_guest_offset(&c, str, len, 1) )
+ return -EFAULT;
+ if ( !c )
+ break;
+ }
+
+ return len;
+}
+
+static inline EFI_TIME *cast_time(struct xenpf_efi_time *time)
+{
+#define chk_fld(F, f) \
+ BUILD_BUG_ON(sizeof(cast_time(NULL)->F) != sizeof(time->f) || \
+ offsetof(EFI_TIME, F) != offsetof(struct xenpf_efi_time, f))
+ chk_fld(Year, year);
+ chk_fld(Month, month);
+ chk_fld(Day, day);
+ chk_fld(Hour, hour);
+ chk_fld(Minute, min);
+ chk_fld(Second, sec);
+ chk_fld(Nanosecond, ns);
+ chk_fld(TimeZone, tz);
+ chk_fld(Daylight, daylight);
+#undef chk_fld
+ return (void *)time;
+}
+
+static inline EFI_GUID *cast_guid(struct xenpf_efi_guid *guid)
+{
+#define chk_fld(n) \
+ BUILD_BUG_ON(sizeof(cast_guid(NULL)->Data##n) != sizeof(guid->data##n) || \
+ offsetof(EFI_GUID, Data##n) != \
+ offsetof(struct xenpf_efi_guid, data##n))
+ chk_fld(1);
+ chk_fld(2);
+ chk_fld(3);
+ chk_fld(4);
+#undef chk_fld
+ return (void *)guid;
+}
+
+int efi_runtime_call(struct xenpf_efi_runtime_call *op)
+{
+ unsigned long cr3;
+ EFI_STATUS status = EFI_NOT_STARTED;
+ int rc = 0;
+
+ switch ( op->function )
+ {
+ case XEN_EFI_get_time:
+ {
+ EFI_TIME_CAPABILITIES caps;
+
+ if ( op->misc )
+ return -EINVAL;
+
+ cr3 = efi_rs_enter();
+ status = efi_rs->GetTime(cast_time(&op->u.get_time.time), &caps);
+ efi_rs_leave(cr3);
+
+ if ( !EFI_ERROR(status) )
+ {
+ op->u.get_time.resolution = caps.Resolution;
+ op->u.get_time.accuracy = caps.Accuracy;
+ if ( caps.SetsToZero )
+ op->misc = XEN_EFI_GET_TIME_SET_CLEARS_NS;
+ }
+ }
+ break;
+
+ case XEN_EFI_set_time:
+ if ( op->misc )
+ return -EINVAL;
+
+ cr3 = efi_rs_enter();
+ status = efi_rs->SetTime(cast_time(&op->u.set_time));
+ efi_rs_leave(cr3);
+ break;
+
+ case XEN_EFI_get_wakeup_time:
+ {
+ BOOLEAN enabled, pending;
+
+ if ( op->misc )
+ return -EINVAL;
+
+ cr3 = efi_rs_enter();
+ status = efi_rs->GetWakeupTime(&enabled, &pending,
+ cast_time(&op->u.get_wakeup_time));
+ efi_rs_leave(cr3);
+
+ if ( !EFI_ERROR(status) )
+ {
+ if ( enabled )
+ op->misc |= XEN_EFI_GET_WAKEUP_TIME_ENABLED;
+ if ( pending )
+ op->misc |= XEN_EFI_GET_WAKEUP_TIME_PENDING;
+ }
+ }
+ break;
+
+ case XEN_EFI_set_wakeup_time:
+ if ( op->misc & ~(XEN_EFI_SET_WAKEUP_TIME_ENABLE |
+ XEN_EFI_SET_WAKEUP_TIME_ENABLE_ONLY) )
+ return -EINVAL;
+
+ cr3 = efi_rs_enter();
+ status = efi_rs->SetWakeupTime(!!(op->misc &
+ XEN_EFI_SET_WAKEUP_TIME_ENABLE),
+ (op->misc &
+ XEN_EFI_SET_WAKEUP_TIME_ENABLE_ONLY) ?
+ NULL :
+ cast_time(&op->u.set_wakeup_time));
+ efi_rs_leave(cr3);
+
+ op->misc = 0;
+ break;
+
+ case XEN_EFI_get_next_high_monotonic_count:
+ if ( op->misc )
+ return -EINVAL;
+
+ cr3 = efi_rs_enter();
+ status = efi_rs->GetNextHighMonotonicCount(&op->misc);
+ efi_rs_leave(cr3);
+ break;
+
+ case XEN_EFI_get_variable:
+ {
+ CHAR16 *name;
+ long len;
+ unsigned char *data;
+ UINTN size;
+
+ if ( op->misc )
+ return -EINVAL;
+
+ len = gwstrlen(guest_handle_cast(op->u.get_variable.name, CHAR16));
+ if ( len < 0 )
+ return len;
+ name = xmalloc_array(CHAR16, ++len);
+ if ( !name )
+ return -ENOMEM;
+ __copy_from_guest(name, op->u.get_variable.name, len);
+
+ size = op->u.get_variable.size;
+ if ( size )
+ {
+ data = xmalloc_bytes(size);
+ if ( !data )
+ {
+ xfree(name);
+ return -ENOMEM;
+ }
+ }
+ else
+ data = NULL;
+
+ cr3 = efi_rs_enter();
+ status = efi_rs->GetVariable(
+ name, cast_guid(&op->u.get_variable.vendor_guid),
+ &op->misc, &size, data);
+ efi_rs_leave(cr3);
+
+ if ( !EFI_ERROR(status) &&
+ copy_to_guest(op->u.get_variable.data, data, size) )
+ rc = -EFAULT;
+ op->u.get_variable.size = size;
+
+ xfree(data);
+ xfree(name);
+ }
+ break;
+
+ case XEN_EFI_set_variable:
+ {
+ CHAR16 *name;
+ long len;
+ unsigned char *data;
+
+ if ( op->misc )
+ return -EINVAL;
+
+ len = gwstrlen(guest_handle_cast(op->u.set_variable.name, CHAR16));
+ if ( len < 0 )
+ return len;
+ name = xmalloc_array(CHAR16, ++len);
+ if ( !name )
+ return -ENOMEM;
+ __copy_from_guest(name, op->u.set_variable.name, len);
+
+ data = xmalloc_bytes(op->u.set_variable.size);
+ if ( !data )
+ rc = -ENOMEM;
+ else if ( copy_from_guest(data, op->u.set_variable.data,
+ op->u.set_variable.size) )
+ rc = -EFAULT;
+ else
+ {
+ cr3 = efi_rs_enter();
+ status = efi_rs->SetVariable(
+ name, cast_guid(&op->u.set_variable.vendor_guid),
+ op->misc, op->u.set_variable.size, data);
+ efi_rs_leave(cr3);
+ }
+
+ xfree(data);
+ xfree(name);
+ }
+ break;
+
+ case XEN_EFI_get_next_variable_name:
+ {
+ union {
+ CHAR16 *str;
+ unsigned char *raw;
+ } name;
+ UINTN size;
+
+ if ( op->misc )
+ return -EINVAL;
+
+ size = op->u.get_next_variable_name.size;
+ name.raw = xmalloc_bytes(size);
+ if ( !name.raw )
+ return -ENOMEM;
+ copy_from_guest(name.raw, op->u.get_next_variable_name.name, size);
+
+ cr3 = efi_rs_enter();
+ status = efi_rs->GetNextVariableName(
+ &size, name.str,
+ cast_guid(&op->u.get_next_variable_name.vendor_guid));
+ efi_rs_leave(cr3);
+
+ if ( !EFI_ERROR(status) &&
+ copy_to_guest(op->u.get_next_variable_name.name, name.raw, size) )
+ rc = -EFAULT;
+ op->u.get_next_variable_name.size = size;
+
+ xfree(name.raw);
+ }
+ break;
+
+ default:
+ return -ENOSYS;
+ }
+
+#ifndef COMPAT
+ op->status = status;
+#else
+ op->status = (status & 0x3fffffff) | (status >> 62);
+#endif
+
+ return rc;
+}
diff --git a/xen/arch/x86/efi/stub.c b/xen/arch/x86/efi/stub.c
index 12289938d1..1520bae6e3 100644
--- a/xen/arch/x86/efi/stub.c
+++ b/xen/arch/x86/efi/stub.c
@@ -1,6 +1,7 @@
#include <xen/efi.h>
#include <xen/errno.h>
#include <xen/init.h>
+#include <asm/bug.h>
#ifndef efi_enabled
const bool_t efi_enabled = 0;
@@ -8,6 +9,15 @@ const bool_t efi_enabled = 0;
void __init efi_init_memory(void) { }
+unsigned long efi_get_time(void)
+{
+ BUG();
+ return 0;
+}
+
+void efi_halt_system(void) { }
+void efi_reset_system(bool_t warm) { }
+
int efi_get_info(uint32_t idx, union xenpf_efi_info *info)
{
return -ENOSYS;
@@ -15,3 +25,11 @@ int efi_get_info(uint32_t idx, union xenpf_efi_info *info)
int efi_compat_get_info(uint32_t idx, union compat_pf_efi_info *)
__attribute__((__alias__("efi_get_info")));
+
+int efi_runtime_call(struct xenpf_efi_runtime_call *op)
+{
+ return -ENOSYS;
+}
+
+int efi_compat_runtime_call(struct compat_pf_efi_runtime_call *)
+ __attribute__((__alias__("efi_runtime_call")));