x86, efi: 1:1 pagetable mapping for virtual EFI calls
authorMatt Fleming <matt.fleming@intel.com>
Fri, 7 Sep 2012 17:28:04 +0000 (18:28 +0100)
committerMatt Fleming <matt.fleming@intel.com>
Tue, 30 Oct 2012 10:39:19 +0000 (10:39 +0000)
Some firmware still needs a 1:1 (virt->phys) mapping even after we've
called SetVirtualAddressMap(). So install the mapping alongside our
existing kernel mapping whenever we make EFI calls in virtual mode.

This bug was discovered on ASUS machines where the firmware
implementation of GetTime() accesses the RTC device via physical
addresses, even though that's bogus per the UEFI spec since we've
informed the firmware via SetVirtualAddressMap() that the boottime
memory map is no longer valid.

This bug seems to be present in a lot of consumer devices, so there's
not a lot we can do about this spec violation apart from workaround
it.

Cc: JérômeCarretero <cJ-ko@zougloub.eu>
Cc: Vasco Dias <rafa.vasco@gmail.com>
Acked-by: Jan Beulich <jbeulich@suse.com>
Signed-off-by: Matt Fleming <matt.fleming@intel.com>
arch/x86/include/asm/efi.h
arch/x86/platform/efi/efi_64.c

index c9dcc181d4d1a814c10d6822d0801c7a1b2d7196..ae3bf3b8da71684a19775ca60a79e58849a1fa9f 100644 (file)
@@ -69,23 +69,37 @@ extern u64 efi_call6(void *fp, u64 arg1, u64 arg2, u64 arg3,
        efi_call6((void *)(f), (u64)(a1), (u64)(a2), (u64)(a3),         \
                  (u64)(a4), (u64)(a5), (u64)(a6))
 
+extern unsigned long efi_call_virt_prelog(void);
+extern void efi_call_virt_epilog(unsigned long);
+
+#define efi_callx(x, func, ...)                                        \
+       ({                                                      \
+               efi_status_t __status;                          \
+               unsigned long __pgd;                            \
+                                                               \
+               __pgd = efi_call_virt_prelog();                 \
+               __status = efi_call##x(func, __VA_ARGS__);      \
+               efi_call_virt_epilog(__pgd);                    \
+               __status;                                       \
+       })
+
 #define efi_call_virt0(f)                              \
-       efi_call0((void *)(efi.systab->runtime->f))
+       efi_callx(0, (void *)(efi.systab->runtime->f))
 #define efi_call_virt1(f, a1)                                  \
-       efi_call1((void *)(efi.systab->runtime->f), (u64)(a1))
+       efi_callx(1, (void *)(efi.systab->runtime->f), (u64)(a1))
 #define efi_call_virt2(f, a1, a2)                                      \
-       efi_call2((void *)(efi.systab->runtime->f), (u64)(a1), (u64)(a2))
+       efi_callx(2, (void *)(efi.systab->runtime->f), (u64)(a1), (u64)(a2))
 #define efi_call_virt3(f, a1, a2, a3)                                  \
-       efi_call3((void *)(efi.systab->runtime->f), (u64)(a1), (u64)(a2), \
+       efi_callx(3, (void *)(efi.systab->runtime->f), (u64)(a1), (u64)(a2), \
                  (u64)(a3))
 #define efi_call_virt4(f, a1, a2, a3, a4)                              \
-       efi_call4((void *)(efi.systab->runtime->f), (u64)(a1), (u64)(a2), \
+       efi_callx(4, (void *)(efi.systab->runtime->f), (u64)(a1), (u64)(a2), \
                  (u64)(a3), (u64)(a4))
 #define efi_call_virt5(f, a1, a2, a3, a4, a5)                          \
-       efi_call5((void *)(efi.systab->runtime->f), (u64)(a1), (u64)(a2), \
+       efi_callx(5, (void *)(efi.systab->runtime->f), (u64)(a1), (u64)(a2), \
                  (u64)(a3), (u64)(a4), (u64)(a5))
 #define efi_call_virt6(f, a1, a2, a3, a4, a5, a6)                      \
-       efi_call6((void *)(efi.systab->runtime->f), (u64)(a1), (u64)(a2), \
+       efi_callx(6, (void *)(efi.systab->runtime->f), (u64)(a1), (u64)(a2), \
                  (u64)(a3), (u64)(a4), (u64)(a5), (u64)(a6))
 
 extern void __iomem *efi_ioremap(unsigned long addr, unsigned long size,
index ac3aa54e26546ba5cb4121eba0c58ca00f06ea82..ddb0174cf093dbaac31f0b760f748386e65ac1a0 100644 (file)
@@ -58,6 +58,21 @@ static void __init early_code_mapping_set_exec(int executable)
        }
 }
 
+unsigned long efi_call_virt_prelog(void)
+{
+       unsigned long saved;
+
+       saved = read_cr3();
+       write_cr3(real_mode_header->trampoline_pgd);
+
+       return saved;
+}
+
+void efi_call_virt_epilog(unsigned long saved)
+{
+       write_cr3(saved);
+}
+
 void __init efi_call_phys_prelog(void)
 {
        unsigned long vaddress;