efi/x86: Simplify mixed mode call wrapper
authorArd Biesheuvel <ardb@kernel.org>
Fri, 3 Jan 2020 11:39:43 +0000 (12:39 +0100)
committerIngo Molnar <mingo@kernel.org>
Fri, 10 Jan 2020 17:55:03 +0000 (18:55 +0100)
Calling 32-bit EFI runtime services from a 64-bit OS involves
switching back to the flat mapping with a stack carved out of
memory that is 32-bit addressable.

There is no need to actually execute the 64-bit part of this
routine from the flat mapping as well, as long as the entry
and return address fit in 32 bits. There is also no need to
preserve part of the calling context in global variables: we
can simply push the old stack pointer value to the new stack,
and keep the return address from the code32 section in EBX.

While at it, move the conditional check whether to invoke
the mixed mode version of SetVirtualAddressMap() into the
64-bit implementation of the wrapper routine.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Arvind Sankar <nivedita@alum.mit.edu>
Cc: Matthew Garrett <mjg59@google.com>
Cc: linux-efi@vger.kernel.org
Link: https://lkml.kernel.org/r/20200103113953.9571-11-ardb@kernel.org
Signed-off-by: Ingo Molnar <mingo@kernel.org>
arch/x86/include/asm/efi.h
arch/x86/platform/efi/efi.c
arch/x86/platform/efi/efi_64.c
arch/x86/platform/efi/efi_thunk_64.S

index cb08035b89a014e7f7c221b5a46683982caaab2a..e7e9c6e057f977b31dbc73b5d089b493d20a27e1 100644 (file)
@@ -164,12 +164,6 @@ extern void parse_efi_setup(u64 phys_addr, u32 data_len);
 extern void efifb_setup_from_dmi(struct screen_info *si, const char *opt);
 
 extern void efi_thunk_runtime_setup(void);
-extern efi_status_t efi_thunk_set_virtual_address_map(
-       void *phys_set_virtual_address_map,
-       unsigned long memory_map_size,
-       unsigned long descriptor_size,
-       u32 descriptor_version,
-       efi_memory_desc_t *virtual_map);
 efi_status_t efi_set_virtual_address_map(unsigned long memory_map_size,
                                         unsigned long descriptor_size,
                                         u32 descriptor_version,
index 50f8123e658a8c78afcc71327b08803afb682fc0..e4d3afac7be3d7f8904d3df2b200f49c535fae6e 100644 (file)
@@ -1015,21 +1015,10 @@ static void __init __efi_enter_virtual_mode(void)
 
        efi_sync_low_kernel_mappings();
 
-       if (!efi_is_mixed()) {
-               status = efi_set_virtual_address_map(
-                               efi.memmap.desc_size * count,
-                               efi.memmap.desc_size,
-                               efi.memmap.desc_version,
-                               (efi_memory_desc_t *)pa);
-       } else {
-               status = efi_thunk_set_virtual_address_map(
-                               efi_phys.set_virtual_address_map,
-                               efi.memmap.desc_size * count,
-                               efi.memmap.desc_size,
-                               efi.memmap.desc_version,
-                               (efi_memory_desc_t *)pa);
-       }
-
+       status = efi_set_virtual_address_map(efi.memmap.desc_size * count,
+                                            efi.memmap.desc_size,
+                                            efi.memmap.desc_version,
+                                            (efi_memory_desc_t *)pa);
        if (status != EFI_SUCCESS) {
                pr_alert("Unable to switch EFI into virtual mode (status=%lx)!\n",
                         status);
index 03565dad0c4b45385b191c670df447e2b0928490..910e9ec03b0924366ae4ae34e729da7d49ba970d 100644 (file)
@@ -626,61 +626,74 @@ void efi_switch_mm(struct mm_struct *mm)
        switch_mm(efi_scratch.prev_mm, mm, NULL);
 }
 
-#ifdef CONFIG_EFI_MIXED
 static DEFINE_SPINLOCK(efi_runtime_lock);
 
-#define runtime_service32(func)                                                 \
-({                                                                      \
-       u32 table = (u32)(unsigned long)efi.systab;                      \
-       u32 *rt, *___f;                                                  \
-                                                                        \
-       rt = (u32 *)(table + offsetof(efi_system_table_32_t, runtime));  \
-       ___f = (u32 *)(*rt + offsetof(efi_runtime_services_32_t, func)); \
-       *___f;                                                           \
+/*
+ * DS and ES contain user values.  We need to save them.
+ * The 32-bit EFI code needs a valid DS, ES, and SS.  There's no
+ * need to save the old SS: __KERNEL_DS is always acceptable.
+ */
+#define __efi_thunk(func, ...)                                         \
+({                                                                     \
+       efi_runtime_services_32_t *__rt;                                \
+       unsigned short __ds, __es;                                      \
+       efi_status_t ____s;                                             \
+                                                                       \
+       __rt = (void *)(unsigned long)efi.systab->mixed_mode.runtime;   \
+                                                                       \
+       savesegment(ds, __ds);                                          \
+       savesegment(es, __es);                                          \
+                                                                       \
+       loadsegment(ss, __KERNEL_DS);                                   \
+       loadsegment(ds, __KERNEL_DS);                                   \
+       loadsegment(es, __KERNEL_DS);                                   \
+                                                                       \
+       ____s = efi64_thunk(__rt->func, __VA_ARGS__);                   \
+                                                                       \
+       loadsegment(ds, __ds);                                          \
+       loadsegment(es, __es);                                          \
+                                                                       \
+       ____s ^= (____s & BIT(31)) | (____s & BIT_ULL(31)) << 32;       \
+       ____s;                                                          \
 })
 
 /*
  * Switch to the EFI page tables early so that we can access the 1:1
  * runtime services mappings which are not mapped in any other page
- * tables. This function must be called before runtime_service32().
+ * tables.
  *
  * Also, disable interrupts because the IDT points to 64-bit handlers,
  * which aren't going to function correctly when we switch to 32-bit.
  */
-#define efi_thunk(f, ...)                                              \
+#define efi_thunk(func...)                                             \
 ({                                                                     \
        efi_status_t __s;                                               \
-       u32 __func;                                                     \
                                                                        \
        arch_efi_call_virt_setup();                                     \
                                                                        \
-       __func = runtime_service32(f);                                  \
-       __s = efi64_thunk(__func, __VA_ARGS__);                         \
+       __s = __efi_thunk(func);                                        \
                                                                        \
        arch_efi_call_virt_teardown();                                  \
                                                                        \
        __s;                                                            \
 })
 
-efi_status_t efi_thunk_set_virtual_address_map(
-       void *phys_set_virtual_address_map,
-       unsigned long memory_map_size,
-       unsigned long descriptor_size,
-       u32 descriptor_version,
-       efi_memory_desc_t *virtual_map)
+static efi_status_t __init
+efi_thunk_set_virtual_address_map(unsigned long memory_map_size,
+                                 unsigned long descriptor_size,
+                                 u32 descriptor_version,
+                                 efi_memory_desc_t *virtual_map)
 {
        efi_status_t status;
        unsigned long flags;
-       u32 func;
 
        efi_sync_low_kernel_mappings();
        local_irq_save(flags);
 
        efi_switch_mm(&efi_mm);
 
-       func = (u32)(unsigned long)phys_set_virtual_address_map;
-       status = efi64_thunk(func, memory_map_size, descriptor_size,
-                            descriptor_version, virtual_map);
+       status = __efi_thunk(set_virtual_address_map, memory_map_size,
+                            descriptor_size, descriptor_version, virtual_map);
 
        efi_switch_mm(efi_scratch.prev_mm);
        local_irq_restore(flags);
@@ -983,8 +996,11 @@ efi_thunk_query_capsule_caps(efi_capsule_header_t **capsules,
        return EFI_UNSUPPORTED;
 }
 
-void efi_thunk_runtime_setup(void)
+void __init efi_thunk_runtime_setup(void)
 {
+       if (!IS_ENABLED(CONFIG_EFI_MIXED))
+               return;
+
        efi.get_time = efi_thunk_get_time;
        efi.set_time = efi_thunk_set_time;
        efi.get_wakeup_time = efi_thunk_get_wakeup_time;
@@ -1000,7 +1016,6 @@ void efi_thunk_runtime_setup(void)
        efi.update_capsule = efi_thunk_update_capsule;
        efi.query_capsule_caps = efi_thunk_query_capsule_caps;
 }
-#endif /* CONFIG_EFI_MIXED */
 
 efi_status_t __init efi_set_virtual_address_map(unsigned long memory_map_size,
                                                unsigned long descriptor_size,
@@ -1011,6 +1026,12 @@ efi_status_t __init efi_set_virtual_address_map(unsigned long memory_map_size,
        unsigned long flags;
        pgd_t *save_pgd = NULL;
 
+       if (efi_is_mixed())
+               return efi_thunk_set_virtual_address_map(memory_map_size,
+                                                        descriptor_size,
+                                                        descriptor_version,
+                                                        virtual_map);
+
        if (efi_enabled(EFI_OLD_MEMMAP)) {
                save_pgd = efi_old_memmap_phys_prolog();
                if (!save_pgd)
index 3189f139470183eb6a00844dc228f27aa0fe71b9..162b357296335378e531c9a994b8d0ea0815ff4d 100644 (file)
 
        .text
        .code64
-SYM_FUNC_START(efi64_thunk)
+SYM_CODE_START(efi64_thunk)
        push    %rbp
        push    %rbx
 
        /*
         * Switch to 1:1 mapped 32-bit stack pointer.
         */
-       movq    %rsp, efi_saved_sp(%rip)
+       movq    %rsp, %rax
        movq    efi_scratch(%rip), %rsp
+       push    %rax
 
        /*
         * Calculate the physical address of the kernel text.
@@ -41,113 +42,31 @@ SYM_FUNC_START(efi64_thunk)
        movq    $__START_KERNEL_map, %rax
        subq    phys_base(%rip), %rax
 
-       /*
-        * Push some physical addresses onto the stack. This is easier
-        * to do now in a code64 section while the assembler can address
-        * 64-bit values. Note that all the addresses on the stack are
-        * 32-bit.
-        */
-       subq    $16, %rsp
-       leaq    efi_exit32(%rip), %rbx
-       subq    %rax, %rbx
-       movl    %ebx, 8(%rsp)
-
-       leaq    __efi64_thunk(%rip), %rbx
+       leaq    1f(%rip), %rbp
+       leaq    2f(%rip), %rbx
+       subq    %rax, %rbp
        subq    %rax, %rbx
-       call    *%rbx
-
-       movq    efi_saved_sp(%rip), %rsp
-       pop     %rbx
-       pop     %rbp
-       retq
-SYM_FUNC_END(efi64_thunk)
 
-/*
- * We run this function from the 1:1 mapping.
- *
- * This function must be invoked with a 1:1 mapped stack.
- */
-SYM_FUNC_START_LOCAL(__efi64_thunk)
-       movl    %ds, %eax
-       push    %rax
-       movl    %es, %eax
-       push    %rax
-       movl    %ss, %eax
-       push    %rax
-
-       subq    $32, %rsp
-       movl    %esi, 0x0(%rsp)
-       movl    %edx, 0x4(%rsp)
-       movl    %ecx, 0x8(%rsp)
-       movq    %r8, %rsi
-       movl    %esi, 0xc(%rsp)
-       movq    %r9, %rsi
-       movl    %esi,  0x10(%rsp)
-
-       leaq    1f(%rip), %rbx
-       movq    %rbx, func_rt_ptr(%rip)
+       subq    $28, %rsp
+       movl    %ebx, 0x0(%rsp)         /* return address */
+       movl    %esi, 0x4(%rsp)
+       movl    %edx, 0x8(%rsp)
+       movl    %ecx, 0xc(%rsp)
+       movl    %r8d, 0x10(%rsp)
+       movl    %r9d, 0x14(%rsp)
 
        /* Switch to 32-bit descriptor */
        pushq   $__KERNEL32_CS
-       leaq    efi_enter32(%rip), %rax
-       pushq   %rax
+       pushq   %rdi                    /* EFI runtime service address */
        lretq
 
-1:     addq    $32, %rsp
-
+1:     movq    24(%rsp), %rsp
        pop     %rbx
-       movl    %ebx, %ss
-       pop     %rbx
-       movl    %ebx, %es
-       pop     %rbx
-       movl    %ebx, %ds
-
-       /*
-        * Convert 32-bit status code into 64-bit.
-        */
-       test    %rax, %rax
-       jz      1f
-       movl    %eax, %ecx
-       andl    $0x0fffffff, %ecx
-       andl    $0xf0000000, %eax
-       shl     $32, %rax
-       or      %rcx, %rax
-1:
-       ret
-SYM_FUNC_END(__efi64_thunk)
-
-SYM_FUNC_START_LOCAL(efi_exit32)
-       movq    func_rt_ptr(%rip), %rax
-       push    %rax
-       mov     %rdi, %rax
-       ret
-SYM_FUNC_END(efi_exit32)
+       pop     %rbp
+       retq
 
        .code32
-/*
- * EFI service pointer must be in %edi.
- *
- * The stack should represent the 32-bit calling convention.
- */
-SYM_FUNC_START_LOCAL(efi_enter32)
-       movl    $__KERNEL_DS, %eax
-       movl    %eax, %ds
-       movl    %eax, %es
-       movl    %eax, %ss
-
-       call    *%edi
-
-       /* We must preserve return value */
-       movl    %eax, %edi
-
-       movl    72(%esp), %eax
-       pushl   $__KERNEL_CS
-       pushl   %eax
-
+2:     pushl   $__KERNEL_CS
+       pushl   %ebp
        lret
-SYM_FUNC_END(efi_enter32)
-
-       .data
-       .balign 8
-func_rt_ptr:           .quad 0
-efi_saved_sp:          .quad 0
+SYM_CODE_END(efi64_thunk)