From: Borislav Petkov <[email protected]>

Map EFI runtime services 1:1 into the trampoline pgd so that all those
functions can be used in a kexec kernel. As we all know, the braindead
design of SetVirtualAddressMap() doesn't allow a subsequent call to this
function to reestablish virtual mappings, leading us to do all kinds of
crazy dances in the kernel.

64-bit only for now.

Signed-off-by: Borislav Petkov <[email protected]>
---
 arch/x86/include/asm/efi.h          |  2 +
 arch/x86/platform/efi/efi.c         | 84 +++++++++++++++++++++++++++----------
 arch/x86/platform/efi/efi_stub_64.S | 39 +++++++++++++++++
 3 files changed, 102 insertions(+), 23 deletions(-)

diff --git a/arch/x86/include/asm/efi.h b/arch/x86/include/asm/efi.h
index 60c89f30c727..3ed4b8c51548 100644
--- a/arch/x86/include/asm/efi.h
+++ b/arch/x86/include/asm/efi.h
@@ -39,6 +39,8 @@ extern unsigned long asmlinkage efi_call_phys(void *, ...);
 
 #else /* !CONFIG_X86_32 */
 
+extern pgd_t *efi_pgt;
+
 #define EFI_LOADER_SIGNATURE   "EL64"
 
 extern u64 efi_call0(void *fp);
diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c
index 4b70be21fe0a..9e45eac3c33a 100644
--- a/arch/x86/platform/efi/efi.c
+++ b/arch/x86/platform/efi/efi.c
@@ -649,15 +649,24 @@ static int __init efi_runtime_init(void)
                pr_err("Could not map the runtime service table!\n");
                return -ENOMEM;
        }
-       /*
-        * We will only need *early* access to the following
-        * two EFI runtime services before set_virtual_address_map
-        * is invoked.
-        */
-       efi_phys.get_time = (efi_get_time_t *)runtime->get_time;
-       efi_phys.set_virtual_address_map =
-               (efi_set_virtual_address_map_t *)
-               runtime->set_virtual_address_map;
+
+#define efi_phys_assign(f) \
+       efi_phys.f = (efi_ ##f## _t *)runtime->f
+
+       efi_phys_assign(get_time);
+       efi_phys_assign(set_time);
+       efi_phys_assign(get_wakeup_time);
+       efi_phys_assign(set_wakeup_time);
+       efi_phys_assign(get_variable);
+       efi_phys_assign(get_next_variable);
+       efi_phys_assign(set_variable);
+       efi_phys_assign(get_next_high_mono_count);
+       efi_phys_assign(reset_system);
+       efi_phys_assign(set_virtual_address_map);
+       efi_phys_assign(query_variable_info);
+       efi_phys_assign(update_capsule);
+       efi_phys_assign(query_capsule_caps);
+
        /*
         * Make efi_get_time can be called before entering
         * virtual mode.
@@ -845,9 +854,10 @@ void efi_memory_uc(u64 addr, unsigned long size)
  */
 void __init efi_enter_virtual_mode(void)
 {
+       pgd_t *pgd = (pgd_t *)__va(real_mode_header->trampoline_pgd);
        efi_memory_desc_t *md, *prev_md = NULL;
        efi_status_t status;
-       unsigned long size;
+       unsigned long size, page_flags;
        u64 end, systab, start_pfn, end_pfn;
        void *p, *va, *new_memmap = NULL;
        int count = 0;
@@ -895,7 +905,8 @@ void __init efi_enter_virtual_mode(void)
                md = p;
                if (!(md->attribute & EFI_MEMORY_RUNTIME) &&
                    md->type != EFI_BOOT_SERVICES_CODE &&
-                   md->type != EFI_BOOT_SERVICES_DATA)
+                   md->type != EFI_BOOT_SERVICES_DATA &&
+                   md->type != EFI_CONVENTIONAL_MEMORY)
                        continue;
 
                size = md->num_pages << EFI_PAGE_SHIFT;
@@ -920,11 +931,26 @@ void __init efi_enter_virtual_mode(void)
                        continue;
                }
 
+               page_flags = 0;
+
+               if (md->type == EFI_RUNTIME_SERVICES_DATA ||
+                   md->type == EFI_BOOT_SERVICES_DATA)
+                       page_flags = _PAGE_NX;
+
+               if (!(md->attribute & EFI_MEMORY_WB))
+                       page_flags |= _PAGE_PCD;
+
+               kernel_map_pages_in_pgd(pgd, md->phys_addr,
+                                       md->num_pages, page_flags);
+
                systab = (u64) (unsigned long) efi_phys.systab;
                if (md->phys_addr <= systab && systab < end) {
                        systab += md->virt_addr - md->phys_addr;
                        efi.systab = (efi_system_table_t *) (unsigned long) 
systab;
                }
+
+               md->virt_addr = md->phys_addr;
+
                new_memmap = krealloc(new_memmap,
                                      (count + 1) * memmap.desc_size,
                                      GFP_KERNEL);
@@ -935,6 +961,8 @@ void __init efi_enter_virtual_mode(void)
 
        BUG_ON(!efi.systab);
 
+       efi_pgt = (pgd_t *)(unsigned long)real_mode_header->trampoline_pgd;
+
        status = phys_efi_set_virtual_address_map(
                memmap.desc_size * count,
                memmap.desc_size,
@@ -947,6 +975,9 @@ void __init efi_enter_virtual_mode(void)
                panic("EFI call to SetVirtualAddressMap() failed!");
        }
 
+       efi.systab->runtime = kzalloc(sizeof(efi_runtime_services_t), 
GFP_KERNEL);
+       BUG_ON(!efi.systab->runtime);
+
        /*
         * Now that EFI is in virtual mode, update the function
         * pointers in the runtime service table to the new virtual addresses.
@@ -954,19 +985,26 @@ void __init efi_enter_virtual_mode(void)
         * Call EFI services through wrapper functions.
         */
        efi.runtime_version = efi_systab.hdr.revision;
-       efi.get_time = virt_efi_get_time;
-       efi.set_time = virt_efi_set_time;
-       efi.get_wakeup_time = virt_efi_get_wakeup_time;
-       efi.set_wakeup_time = virt_efi_set_wakeup_time;
-       efi.get_variable = virt_efi_get_variable;
-       efi.get_next_variable = virt_efi_get_next_variable;
-       efi.set_variable = virt_efi_set_variable;
-       efi.get_next_high_mono_count = virt_efi_get_next_high_mono_count;
-       efi.reset_system = virt_efi_reset_system;
+
+
+#define efi_assign(efi, f)                                             \
+       efi.f = virt_efi_##f;                                           \
+       efi.systab->runtime->f = (unsigned long)efi_phys.f
+
+       efi_assign(efi, get_time);
+       efi_assign(efi, set_time);
+       efi_assign(efi, get_wakeup_time);
+       efi_assign(efi, set_wakeup_time);
+       efi_assign(efi, get_variable);
+       efi_assign(efi, get_next_variable);
+       efi_assign(efi, set_variable);
+       efi_assign(efi, get_next_high_mono_count);
+       efi_assign(efi, reset_system);
        efi.set_virtual_address_map = NULL;
-       efi.query_variable_info = virt_efi_query_variable_info;
-       efi.update_capsule = virt_efi_update_capsule;
-       efi.query_capsule_caps = virt_efi_query_capsule_caps;
+       efi_assign(efi, query_variable_info);
+       efi_assign(efi, update_capsule);
+       efi_assign(efi, query_capsule_caps);
+
        if (__supported_pte_mask & _PAGE_NX)
                runtime_code_page_mkexec();
 
diff --git a/arch/x86/platform/efi/efi_stub_64.S 
b/arch/x86/platform/efi/efi_stub_64.S
index 4c07ccab8146..eec8d6d02c17 100644
--- a/arch/x86/platform/efi/efi_stub_64.S
+++ b/arch/x86/platform/efi/efi_stub_64.S
@@ -34,10 +34,34 @@
        mov %rsi, %cr0;                 \
        mov (%rsp), %rsp
 
+/* happily stolen from gcc, see __flush_tlb_global() */
+#define FLUSH_TLB_ALL                  \
+       movq %cr4, %r14;                \
+       movq %r14, %r13;                \
+       and $0x7f, %r13b;               \
+       movq %r13, %cr4;                \
+       movq %r14, %cr4
+
+/*
+ * %r15 is a non-volatile register and is preserved by UEFI so use
+ * it for stashing previous PGD in there.
+ */
+#define SWITCH_PGT                     \
+       movq %cr3, %r15;                \
+       movq efi_pgt, %rax;             \
+       movq %rax, %cr3;                \
+       FLUSH_TLB_ALL
+
+#define RESTORE_PGT                    \
+       movq %r15, %cr3;                \
+       FLUSH_TLB_ALL
+
 ENTRY(efi_call0)
        SAVE_XMM
        subq $32, %rsp
+       SWITCH_PGT
        call *%rdi
+       RESTORE_PGT
        addq $32, %rsp
        RESTORE_XMM
        ret
@@ -47,7 +71,9 @@ ENTRY(efi_call1)
        SAVE_XMM
        subq $32, %rsp
        mov  %rsi, %rcx
+       SWITCH_PGT
        call *%rdi
+       RESTORE_PGT
        addq $32, %rsp
        RESTORE_XMM
        ret
@@ -57,7 +83,9 @@ ENTRY(efi_call2)
        SAVE_XMM
        subq $32, %rsp
        mov  %rsi, %rcx
+       SWITCH_PGT
        call *%rdi
+       RESTORE_PGT
        addq $32, %rsp
        RESTORE_XMM
        ret
@@ -68,7 +96,9 @@ ENTRY(efi_call3)
        subq $32, %rsp
        mov  %rcx, %r8
        mov  %rsi, %rcx
+       SWITCH_PGT
        call *%rdi
+       RESTORE_PGT
        addq $32, %rsp
        RESTORE_XMM
        ret
@@ -80,7 +110,9 @@ ENTRY(efi_call4)
        mov %r8, %r9
        mov %rcx, %r8
        mov %rsi, %rcx
+       SWITCH_PGT
        call *%rdi
+       RESTORE_PGT
        addq $32, %rsp
        RESTORE_XMM
        ret
@@ -93,7 +125,9 @@ ENTRY(efi_call5)
        mov %r8, %r9
        mov %rcx, %r8
        mov %rsi, %rcx
+       SWITCH_PGT
        call *%rdi
+       RESTORE_PGT
        addq $48, %rsp
        RESTORE_XMM
        ret
@@ -109,8 +143,13 @@ ENTRY(efi_call6)
        mov %r8, %r9
        mov %rcx, %r8
        mov %rsi, %rcx
+       SWITCH_PGT
        call *%rdi
+       RESTORE_PGT
        addq $48, %rsp
        RESTORE_XMM
        ret
 ENDPROC(efi_call6)
+
+GLOBAL(efi_pgt)
+       .quad 0
-- 
1.8.2.135.g7b592fa

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to