Merge remote-tracking branch 'tip/x86/efi-mixed' into efi-for-mingo
authorMatt Fleming <matt.fleming@intel.com>
Wed, 5 Mar 2014 18:15:37 +0000 (18:15 +0000)
committerMatt Fleming <matt.fleming@intel.com>
Wed, 5 Mar 2014 18:15:37 +0000 (18:15 +0000)
Conflicts:
arch/x86/kernel/setup.c
arch/x86/platform/efi/efi.c
arch/x86/platform/efi/efi_64.c

1  2 
arch/x86/include/asm/efi.h
arch/x86/include/asm/pgtable_types.h
arch/x86/mm/fault.c
arch/x86/mm/pageattr.c
arch/x86/platform/efi/efi.c
arch/x86/platform/efi/efi_64.c
include/linux/efi.h

Simple merge
Simple merge
Simple merge
Simple merge
@@@ -609,10 -645,29 +633,31 @@@ static int __init efi_runtime_init64(vo
         * virtual mode.
         */
        efi.get_time = phys_efi_get_time;
-       early_iounmap(runtime, sizeof(efi_runtime_services_t));
+       early_iounmap(runtime, sizeof(efi_runtime_services_64_t));
+       return 0;
+ }
+ static int __init efi_runtime_init(void)
+ {
+       int rv;
+       /*
+        * Check out the runtime services table. We need to map
+        * the runtime services table so that we can grab the physical
+        * address of several of the EFI runtime functions, needed to
+        * set the firmware into virtual mode.
+        */
+       if (efi_enabled(EFI_64BIT))
+               rv = efi_runtime_init64();
+       else
+               rv = efi_runtime_init32();
+       if (rv)
+               return rv;
  
 +      set_bit(EFI_RUNTIME_SERVICES, &efi.flags);
 +
        return 0;
  }
  
@@@ -973,72 -1032,9 +1034,63 @@@ static void * __init efi_map_regions(in
        }
  
        return new_memmap;
 -out:
 -      kfree(new_memmap);
 -      return NULL;
 +}
 +
 +static void __init kexec_enter_virtual_mode(void)
 +{
 +#ifdef CONFIG_KEXEC
 +      efi_memory_desc_t *md;
 +      void *p;
 +
 +      efi.systab = NULL;
 +
 +      /*
 +       * We don't do virtual mode, since we don't do runtime services, on
 +       * non-native EFI
 +       */
 +      if (!efi_is_native()) {
 +              efi_unmap_memmap();
 +              return;
 +      }
 +
 +      /*
 +      * Map efi regions which were passed via setup_data. The virt_addr is a
 +      * fixed addr which was used in first kernel of a kexec boot.
 +      */
 +      for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {
 +              md = p;
 +              efi_map_region_fixed(md); /* FIXME: add error handling */
 +              get_systab_virt_addr(md);
 +      }
 +
 +      save_runtime_map();
 +
 +      BUG_ON(!efi.systab);
 +
 +      efi_sync_low_kernel_mappings();
 +
 +      /*
 +       * Now that EFI is in virtual mode, update the function
 +       * pointers in the runtime service table to the new virtual addresses.
 +       *
 +       * 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;
++
++      native_runtime_setup();
++
 +      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;
 +
 +      if (efi_enabled(EFI_OLD_MEMMAP) && (__supported_pte_mask & _PAGE_NX))
 +              runtime_code_page_mkexec();
 +
 +      /* clean DUMMY object */
 +      efi.set_variable(efi_dummy_name, &EFI_DUMMY_GUID,
 +                       EFI_VARIABLE_NON_VOLATILE |
 +                       EFI_VARIABLE_BOOTSERVICE_ACCESS |
 +                       EFI_VARIABLE_RUNTIME_ACCESS,
 +                       0, NULL);
 +#endif
  }
  
  /*
@@@ -1071,42 -1066,47 +1123,42 @@@ static void __init __efi_enter_virtual_
  
        efi.systab = NULL;
  
-       /*
-        * We don't do virtual mode, since we don't do runtime services, on
-        * non-native EFI
-        */
-       if (!efi_is_native()) {
-               efi_unmap_memmap();
-               return;
-       }
 -      if (efi_setup) {
 -              efi_map_regions_fixed();
 -      } else {
 -              efi_merge_regions();
 -              new_memmap = efi_map_regions(&count);
 -              if (!new_memmap) {
 -                      pr_err("Error reallocating memory, EFI runtime non-functional!\n");
 -                      return;
 -              }
 +      efi_merge_regions();
 +      new_memmap = efi_map_regions(&count, &pg_shift);
 +      if (!new_memmap) {
 +              pr_err("Error reallocating memory, EFI runtime non-functional!\n");
 +              return;
        }
  
 -      err = save_runtime_map();
 -      if (err)
 -              pr_err("Error saving runtime map, efi runtime on kexec non-functional!!\n");
 +      save_runtime_map();
  
        BUG_ON(!efi.systab);
  
 -      efi_setup_page_tables();
 -      efi_sync_low_kernel_mappings();
 +      if (efi_setup_page_tables(__pa(new_memmap), 1 << pg_shift))
 +              return;
  
 -      if (!efi_setup) {
 -              if (efi_is_native()) {
 -                      status = phys_efi_set_virtual_address_map(
 -                                      memmap.desc_size * count,
 -                                      memmap.desc_size,
 -                                      memmap.desc_version,
 -                                      (efi_memory_desc_t *)__pa(new_memmap));
 -              } else {
 -                      status = efi_thunk_set_virtual_address_map(
 -                                      efi_phys.set_virtual_address_map,
 -                                      memmap.desc_size * count,
 -                                      memmap.desc_size,
 -                                      memmap.desc_version,
 -                                      (efi_memory_desc_t *)__pa(new_memmap));
 -              }
 +      efi_sync_low_kernel_mappings();
 +      efi_dump_pagetable();
 +
-       status = phys_efi_set_virtual_address_map(
-                       memmap.desc_size * count,
-                       memmap.desc_size,
-                       memmap.desc_version,
-                       (efi_memory_desc_t *)__pa(new_memmap));
++      if (efi_is_native()) {
++              status = phys_efi_set_virtual_address_map(
++                              memmap.desc_size * count,
++                              memmap.desc_size,
++                              memmap.desc_version,
++                              (efi_memory_desc_t *)__pa(new_memmap));
++      } else {
++              status = efi_thunk_set_virtual_address_map(
++                              efi_phys.set_virtual_address_map,
++                              memmap.desc_size * count,
++                              memmap.desc_size,
++                              memmap.desc_version,
++                              (efi_memory_desc_t *)__pa(new_memmap));
++      }
  
 -              if (status != EFI_SUCCESS) {
 -                      pr_alert("Unable to switch EFI into virtual mode (status=%lx)!\n",
 -                               status);
 -                      panic("EFI call to SetVirtualAddressMap() failed!");
 -              }
 +      if (status != EFI_SUCCESS) {
 +              pr_alert("Unable to switch EFI into virtual mode (status=%lx)!\n",
 +                       status);
 +              panic("EFI call to SetVirtualAddressMap() failed!");
        }
  
        /*
@@@ -1303,22 -1265,3 +1349,22 @@@ static int __init parse_efi_cmdline(cha
        return 0;
  }
  early_param("efi", parse_efi_cmdline);
-       if (!efi_is_native()) {
 +
 +void __init efi_apply_memmap_quirks(void)
 +{
 +      /*
 +       * Once setup is done earlier, unmap the EFI memory map on mismatched
 +       * firmware/kernel architectures since there is no support for runtime
 +       * services.
 +       */
++      if (!efi_runtime_supported()) {
 +              pr_info("efi: Setup done, disabling due to 32/64-bit mismatch\n");
 +              efi_unmap_memmap();
 +      }
 +
 +      /*
 +       * UV doesn't support the new EFI pagetable mapping yet.
 +       */
 +      if (is_uv_system())
 +              set_bit(EFI_OLD_MEMMAP, &efi.flags);
 +}
@@@ -137,38 -139,42 +139,64 @@@ void efi_sync_low_kernel_mappings(void
                sizeof(pgd_t) * num_pgds);
  }
  
 -void efi_setup_page_tables(void)
 +int efi_setup_page_tables(unsigned long pa_memmap, unsigned num_pages)
  {
 -      unsigned npages;
+       unsigned long text;
+       struct page *page;
++      unsigned npages;
 +      pgd_t *pgd;
 +
 +      if (efi_enabled(EFI_OLD_MEMMAP))
 +              return 0;
  
        efi_scratch.efi_pgt = (pgd_t *)(unsigned long)real_mode_header->trampoline_pgd;
 +      pgd = __va(efi_scratch.efi_pgt);
  
 -      if (efi_enabled(EFI_OLD_MEMMAP))
 -              return;
 +      /*
 +       * It can happen that the physical address of new_memmap lands in memory
 +       * which is not mapped in the EFI page table. Therefore we need to go
 +       * and ident-map those pages containing the map before calling
 +       * phys_efi_set_virtual_address_map().
 +       */
 +      if (kernel_map_pages_in_pgd(pgd, pa_memmap, pa_memmap, num_pages, _PAGE_NX)) {
 +              pr_err("Error ident-mapping new memmap (0x%lx)!\n", pa_memmap);
 +              return 1;
 +      }
  
        efi_scratch.use_pgd = true;
  
 -              return;
+       /*
+        * When making calls to the firmware everything needs to be 1:1
+        * mapped and addressable with 32-bit pointers. Map the kernel
+        * text and allocate a new stack because we can't rely on the
+        * stack pointer being < 4GB.
+        */
+       if (!IS_ENABLED(CONFIG_EFI_MIXED))
 -      if (kernel_map_pages_in_pgd(__va(efi_scratch.efi_pgt),
 -                                  text >> PAGE_SHIFT, text, npages, 0)) {
++              return 0;
+       page = alloc_page(GFP_KERNEL|__GFP_DMA32);
+       if (!page)
+               panic("Unable to allocate EFI runtime stack < 4GB\n");
+       efi_scratch.phys_stack = virt_to_phys(page_address(page));
+       efi_scratch.phys_stack += PAGE_SIZE; /* stack grows down */
+       npages = (_end - _text) >> PAGE_SHIFT;
+       text = __pa(_text);
++      if (kernel_map_pages_in_pgd(pgd, text >> PAGE_SHIFT, text, npages, 0)) {
+               pr_err("Failed to map kernel text 1:1\n");
++              return 1;
+       }
 +
 +      return 0;
 +}
 +
 +void efi_cleanup_page_tables(unsigned long pa_memmap, unsigned num_pages)
 +{
 +      pgd_t *pgd = (pgd_t *)__va(real_mode_header->trampoline_pgd);
 +
 +      kernel_unmap_pages_in_pgd(pgd, pa_memmap, num_pages);
  }
  
  static void __init __map_region(efi_memory_desc_t *md, u64 va)
@@@ -269,11 -285,289 +307,298 @@@ void __init efi_runtime_mkexec(void
                runtime_code_page_mkexec();
  }
  
 +void __init efi_dump_pagetable(void)
 +{
 +#ifdef CONFIG_EFI_PGT_DUMP
 +      pgd_t *pgd = (pgd_t *)__va(real_mode_header->trampoline_pgd);
 +
 +      ptdump_walk_pgd_level(NULL, pgd);
 +#endif
 +}
++
+ #ifdef CONFIG_EFI_MIXED
+ extern efi_status_t efi64_thunk(u32, ...);
+ #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;                                                           \
+ })
+ /*
+  * 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().
+  *
+  * 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, ...)                                             \
+ ({                                                                    \
+       efi_status_t __s;                                               \
+       unsigned long flags;                                            \
+       u32 func;                                                       \
+                                                                       \
+       efi_sync_low_kernel_mappings();                                 \
+       local_irq_save(flags);                                          \
+                                                                       \
+       efi_scratch.prev_cr3 = read_cr3();                              \
+       write_cr3((unsigned long)efi_scratch.efi_pgt);                  \
+       __flush_tlb_all();                                              \
+                                                                       \
+       func = runtime_service32(f);                                    \
+       __s = efi64_thunk(func, __VA_ARGS__);                   \
+                                                                       \
+       write_cr3(efi_scratch.prev_cr3);                                \
+       __flush_tlb_all();                                              \
+       local_irq_restore(flags);                                       \
+                                                                       \
+       __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)
+ {
+       efi_status_t status;
+       unsigned long flags;
+       u32 func;
+       efi_sync_low_kernel_mappings();
+       local_irq_save(flags);
+       efi_scratch.prev_cr3 = read_cr3();
+       write_cr3((unsigned long)efi_scratch.efi_pgt);
+       __flush_tlb_all();
+       func = (u32)(unsigned long)phys_set_virtual_address_map;
+       status = efi64_thunk(func, memory_map_size, descriptor_size,
+                            descriptor_version, virtual_map);
+       write_cr3(efi_scratch.prev_cr3);
+       __flush_tlb_all();
+       local_irq_restore(flags);
+       return status;
+ }
+ static efi_status_t efi_thunk_get_time(efi_time_t *tm, efi_time_cap_t *tc)
+ {
+       efi_status_t status;
+       u32 phys_tm, phys_tc;
+       spin_lock(&rtc_lock);
+       phys_tm = virt_to_phys(tm);
+       phys_tc = virt_to_phys(tc);
+       status = efi_thunk(get_time, phys_tm, phys_tc);
+       spin_unlock(&rtc_lock);
+       return status;
+ }
+ static efi_status_t efi_thunk_set_time(efi_time_t *tm)
+ {
+       efi_status_t status;
+       u32 phys_tm;
+       spin_lock(&rtc_lock);
+       phys_tm = virt_to_phys(tm);
+       status = efi_thunk(set_time, phys_tm);
+       spin_unlock(&rtc_lock);
+       return status;
+ }
+ static efi_status_t
+ efi_thunk_get_wakeup_time(efi_bool_t *enabled, efi_bool_t *pending,
+                         efi_time_t *tm)
+ {
+       efi_status_t status;
+       u32 phys_enabled, phys_pending, phys_tm;
+       spin_lock(&rtc_lock);
+       phys_enabled = virt_to_phys(enabled);
+       phys_pending = virt_to_phys(pending);
+       phys_tm = virt_to_phys(tm);
+       status = efi_thunk(get_wakeup_time, phys_enabled,
+                            phys_pending, phys_tm);
+       spin_unlock(&rtc_lock);
+       return status;
+ }
+ static efi_status_t
+ efi_thunk_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm)
+ {
+       efi_status_t status;
+       u32 phys_tm;
+       spin_lock(&rtc_lock);
+       phys_tm = virt_to_phys(tm);
+       status = efi_thunk(set_wakeup_time, enabled, phys_tm);
+       spin_unlock(&rtc_lock);
+       return status;
+ }
+ static efi_status_t
+ efi_thunk_get_variable(efi_char16_t *name, efi_guid_t *vendor,
+                      u32 *attr, unsigned long *data_size, void *data)
+ {
+       efi_status_t status;
+       u32 phys_name, phys_vendor, phys_attr;
+       u32 phys_data_size, phys_data;
+       phys_data_size = virt_to_phys(data_size);
+       phys_vendor = virt_to_phys(vendor);
+       phys_name = virt_to_phys(name);
+       phys_attr = virt_to_phys(attr);
+       phys_data = virt_to_phys(data);
+       status = efi_thunk(get_variable, phys_name, phys_vendor,
+                          phys_attr, phys_data_size, phys_data);
+       return status;
+ }
+ static efi_status_t
+ efi_thunk_set_variable(efi_char16_t *name, efi_guid_t *vendor,
+                      u32 attr, unsigned long data_size, void *data)
+ {
+       u32 phys_name, phys_vendor, phys_data;
+       efi_status_t status;
+       phys_name = virt_to_phys(name);
+       phys_vendor = virt_to_phys(vendor);
+       phys_data = virt_to_phys(data);
+       /* If data_size is > sizeof(u32) we've got problems */
+       status = efi_thunk(set_variable, phys_name, phys_vendor,
+                          attr, data_size, phys_data);
+       return status;
+ }
+ static efi_status_t
+ efi_thunk_get_next_variable(unsigned long *name_size,
+                           efi_char16_t *name,
+                           efi_guid_t *vendor)
+ {
+       efi_status_t status;
+       u32 phys_name_size, phys_name, phys_vendor;
+       phys_name_size = virt_to_phys(name_size);
+       phys_vendor = virt_to_phys(vendor);
+       phys_name = virt_to_phys(name);
+       status = efi_thunk(get_next_variable, phys_name_size,
+                          phys_name, phys_vendor);
+       return status;
+ }
+ static efi_status_t
+ efi_thunk_get_next_high_mono_count(u32 *count)
+ {
+       efi_status_t status;
+       u32 phys_count;
+       phys_count = virt_to_phys(count);
+       status = efi_thunk(get_next_high_mono_count, phys_count);
+       return status;
+ }
+ static void
+ efi_thunk_reset_system(int reset_type, efi_status_t status,
+                      unsigned long data_size, efi_char16_t *data)
+ {
+       u32 phys_data;
+       phys_data = virt_to_phys(data);
+       efi_thunk(reset_system, reset_type, status, data_size, phys_data);
+ }
+ static efi_status_t
+ efi_thunk_update_capsule(efi_capsule_header_t **capsules,
+                        unsigned long count, unsigned long sg_list)
+ {
+       /*
+        * To properly support this function we would need to repackage
+        * 'capsules' because the firmware doesn't understand 64-bit
+        * pointers.
+        */
+       return EFI_UNSUPPORTED;
+ }
+ static efi_status_t
+ efi_thunk_query_variable_info(u32 attr, u64 *storage_space,
+                             u64 *remaining_space,
+                             u64 *max_variable_size)
+ {
+       efi_status_t status;
+       u32 phys_storage, phys_remaining, phys_max;
+       if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
+               return EFI_UNSUPPORTED;
+       phys_storage = virt_to_phys(storage_space);
+       phys_remaining = virt_to_phys(remaining_space);
+       phys_max = virt_to_phys(max_variable_size);
+       status = efi_thunk(query_variable_info, phys_storage,
+                          phys_remaining, phys_max);
+       return status;
+ }
+ static efi_status_t
+ efi_thunk_query_capsule_caps(efi_capsule_header_t **capsules,
+                            unsigned long count, u64 *max_size,
+                            int *reset_type)
+ {
+       /*
+        * To properly support this function we would need to repackage
+        * 'capsules' because the firmware doesn't understand 64-bit
+        * pointers.
+        */
+       return EFI_UNSUPPORTED;
+ }
+ void efi_thunk_runtime_setup(void)
+ {
+       efi.get_time = efi_thunk_get_time;
+       efi.set_time = efi_thunk_set_time;
+       efi.get_wakeup_time = efi_thunk_get_wakeup_time;
+       efi.set_wakeup_time = efi_thunk_set_wakeup_time;
+       efi.get_variable = efi_thunk_get_variable;
+       efi.get_next_variable = efi_thunk_get_next_variable;
+       efi.set_variable = efi_thunk_set_variable;
+       efi.get_next_high_mono_count = efi_thunk_get_next_high_mono_count;
+       efi.reset_system = efi_thunk_reset_system;
+       efi.query_variable_info = efi_thunk_query_variable_info;
+       efi.update_capsule = efi_thunk_update_capsule;
+       efi.query_capsule_caps = efi_thunk_query_capsule_caps;
+ }
+ #endif /* CONFIG_EFI_MIXED */
Simple merge