These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / firmware / efi / libstub / arm-stub.c
index e29560e..950c87f 100644 (file)
@@ -13,6 +13,7 @@
  */
 
 #include <linux/efi.h>
+#include <linux/sort.h>
 #include <asm/efi.h>
 
 #include "efistub.h"
@@ -305,6 +306,44 @@ fail:
  */
 #define EFI_RT_VIRTUAL_BASE    0x40000000
 
+static int cmp_mem_desc(const void *l, const void *r)
+{
+       const efi_memory_desc_t *left = l, *right = r;
+
+       return (left->phys_addr > right->phys_addr) ? 1 : -1;
+}
+
+/*
+ * Returns whether region @left ends exactly where region @right starts,
+ * or false if either argument is NULL.
+ */
+static bool regions_are_adjacent(efi_memory_desc_t *left,
+                                efi_memory_desc_t *right)
+{
+       u64 left_end;
+
+       if (left == NULL || right == NULL)
+               return false;
+
+       left_end = left->phys_addr + left->num_pages * EFI_PAGE_SIZE;
+
+       return left_end == right->phys_addr;
+}
+
+/*
+ * Returns whether region @left and region @right have compatible memory type
+ * mapping attributes, and are both EFI_MEMORY_RUNTIME regions.
+ */
+static bool regions_have_compatible_memory_type_attrs(efi_memory_desc_t *left,
+                                                     efi_memory_desc_t *right)
+{
+       static const u64 mem_type_mask = EFI_MEMORY_WB | EFI_MEMORY_WT |
+                                        EFI_MEMORY_WC | EFI_MEMORY_UC |
+                                        EFI_MEMORY_RUNTIME;
+
+       return ((left->attribute ^ right->attribute) & mem_type_mask) == 0;
+}
+
 /*
  * efi_get_virtmap() - create a virtual mapping for the EFI memory map
  *
@@ -317,33 +356,52 @@ void efi_get_virtmap(efi_memory_desc_t *memory_map, unsigned long map_size,
                     int *count)
 {
        u64 efi_virt_base = EFI_RT_VIRTUAL_BASE;
-       efi_memory_desc_t *out = runtime_map;
+       efi_memory_desc_t *in, *prev = NULL, *out = runtime_map;
        int l;
 
-       for (l = 0; l < map_size; l += desc_size) {
-               efi_memory_desc_t *in = (void *)memory_map + l;
+       /*
+        * To work around potential issues with the Properties Table feature
+        * introduced in UEFI 2.5, which may split PE/COFF executable images
+        * in memory into several RuntimeServicesCode and RuntimeServicesData
+        * regions, we need to preserve the relative offsets between adjacent
+        * EFI_MEMORY_RUNTIME regions with the same memory type attributes.
+        * The easiest way to find adjacent regions is to sort the memory map
+        * before traversing it.
+        */
+       sort(memory_map, map_size / desc_size, desc_size, cmp_mem_desc, NULL);
+
+       for (l = 0; l < map_size; l += desc_size, prev = in) {
                u64 paddr, size;
 
+               in = (void *)memory_map + l;
                if (!(in->attribute & EFI_MEMORY_RUNTIME))
                        continue;
 
+               paddr = in->phys_addr;
+               size = in->num_pages * EFI_PAGE_SIZE;
+
                /*
                 * Make the mapping compatible with 64k pages: this allows
                 * a 4k page size kernel to kexec a 64k page size kernel and
                 * vice versa.
                 */
-               paddr = round_down(in->phys_addr, SZ_64K);
-               size = round_up(in->num_pages * EFI_PAGE_SIZE +
-                               in->phys_addr - paddr, SZ_64K);
-
-               /*
-                * Avoid wasting memory on PTEs by choosing a virtual base that
-                * is compatible with section mappings if this region has the
-                * appropriate size and physical alignment. (Sections are 2 MB
-                * on 4k granule kernels)
-                */
-               if (IS_ALIGNED(in->phys_addr, SZ_2M) && size >= SZ_2M)
-                       efi_virt_base = round_up(efi_virt_base, SZ_2M);
+               if (!regions_are_adjacent(prev, in) ||
+                   !regions_have_compatible_memory_type_attrs(prev, in)) {
+
+                       paddr = round_down(in->phys_addr, SZ_64K);
+                       size += in->phys_addr - paddr;
+
+                       /*
+                        * Avoid wasting memory on PTEs by choosing a virtual
+                        * base that is compatible with section mappings if this
+                        * region has the appropriate size and physical
+                        * alignment. (Sections are 2 MB on 4k granule kernels)
+                        */
+                       if (IS_ALIGNED(in->phys_addr, SZ_2M) && size >= SZ_2M)
+                               efi_virt_base = round_up(efi_virt_base, SZ_2M);
+                       else
+                               efi_virt_base = round_up(efi_virt_base, SZ_64K);
+               }
 
                in->virt_addr = efi_virt_base + in->phys_addr - paddr;
                efi_virt_base += size;