efi/libstub: Move efi_relocate_kernel() into separate source file
authorArd Biesheuvel <ardb@kernel.org>
Mon, 13 Apr 2020 12:23:57 +0000 (14:23 +0200)
committerArd Biesheuvel <ardb@kernel.org>
Fri, 24 Apr 2020 12:52:16 +0000 (14:52 +0200)
Move efi_relocate_kernel() into a separate source file, so that it
only gets pulled into builds for architectures that use it. Since
efi_relocate_kernel() is the only user of efi_low_alloc(), let's
move that over as well.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
drivers/firmware/efi/libstub/Makefile
drivers/firmware/efi/libstub/efistub.h
drivers/firmware/efi/libstub/mem.c
drivers/firmware/efi/libstub/relocate.c [new file with mode: 0644]

index bb8af2b16c4980909025bef3747790cc7fe53a13..9a712a6e2f8740c95ab61e049938309599361fda 100644 (file)
@@ -43,7 +43,7 @@ KCOV_INSTRUMENT                       := n
 lib-y                          := efi-stub-helper.o gop.o secureboot.o tpm.o \
                                   file.o mem.o random.o randomalloc.o pci.o \
                                   skip_spaces.o lib-cmdline.o lib-ctype.o \
-                                  alignedmem.o
+                                  alignedmem.o relocate.o
 
 # include the stub's generic dependencies from lib/ when building for ARM/arm64
 efi-deps-y := fdt_rw.c fdt_ro.c fdt_wip.c fdt.c fdt_empty_tree.c fdt_sw.c
index baa0bc16607496d4d1ed51f985e98dfe9450cbca..2a0698d9dc780fb166d0f0ce2803b853fed3f555 100644 (file)
@@ -639,21 +639,6 @@ char *efi_convert_cmdline(efi_loaded_image_t *image, int *cmd_line_len,
 
 efi_status_t efi_get_memory_map(struct efi_boot_memmap *map);
 
-efi_status_t efi_low_alloc_above(unsigned long size, unsigned long align,
-                                unsigned long *addr, unsigned long min);
-
-static inline
-efi_status_t efi_low_alloc(unsigned long size, unsigned long align,
-                          unsigned long *addr)
-{
-       /*
-        * Don't allocate at 0x0. It will confuse code that
-        * checks pointers against NULL. Skip the first 8
-        * bytes so we start at a nice even number.
-        */
-       return efi_low_alloc_above(size, align, addr, 0x8);
-}
-
 efi_status_t efi_allocate_pages(unsigned long size, unsigned long *addr,
                                unsigned long max);
 
index 0020b0fa9587d8577379abb555058f7623db6b76..6e0ee6b3d8976bb9cef3981aa5e1af02b4320a95 100644 (file)
@@ -111,96 +111,6 @@ efi_status_t efi_allocate_pages(unsigned long size, unsigned long *addr,
        return EFI_SUCCESS;
 }
 
-/**
- * efi_low_alloc_above() - allocate pages at or above given address
- * @size:      size of the memory area to allocate
- * @align:     minimum alignment of the allocated memory area. It should
- *             a power of two.
- * @addr:      on exit the address of the allocated memory
- * @min:       minimum address to used for the memory allocation
- *
- * Allocate at the lowest possible address that is not below @min as
- * EFI_LOADER_DATA. The allocated pages are aligned according to @align but at
- * least EFI_ALLOC_ALIGN. The first allocated page will not below the address
- * given by @min.
- *
- * Return:     status code
- */
-efi_status_t efi_low_alloc_above(unsigned long size, unsigned long align,
-                                unsigned long *addr, unsigned long min)
-{
-       unsigned long map_size, desc_size, buff_size;
-       efi_memory_desc_t *map;
-       efi_status_t status;
-       unsigned long nr_pages;
-       int i;
-       struct efi_boot_memmap boot_map;
-
-       boot_map.map            = &map;
-       boot_map.map_size       = &map_size;
-       boot_map.desc_size      = &desc_size;
-       boot_map.desc_ver       = NULL;
-       boot_map.key_ptr        = NULL;
-       boot_map.buff_size      = &buff_size;
-
-       status = efi_get_memory_map(&boot_map);
-       if (status != EFI_SUCCESS)
-               goto fail;
-
-       /*
-        * Enforce minimum alignment that EFI or Linux requires when
-        * requesting a specific address.  We are doing page-based (or
-        * larger) allocations, and both the address and size must meet
-        * alignment constraints.
-        */
-       if (align < EFI_ALLOC_ALIGN)
-               align = EFI_ALLOC_ALIGN;
-
-       size = round_up(size, EFI_ALLOC_ALIGN);
-       nr_pages = size / EFI_PAGE_SIZE;
-       for (i = 0; i < map_size / desc_size; i++) {
-               efi_memory_desc_t *desc;
-               unsigned long m = (unsigned long)map;
-               u64 start, end;
-
-               desc = efi_early_memdesc_ptr(m, desc_size, i);
-
-               if (desc->type != EFI_CONVENTIONAL_MEMORY)
-                       continue;
-
-               if (efi_soft_reserve_enabled() &&
-                   (desc->attribute & EFI_MEMORY_SP))
-                       continue;
-
-               if (desc->num_pages < nr_pages)
-                       continue;
-
-               start = desc->phys_addr;
-               end = start + desc->num_pages * EFI_PAGE_SIZE;
-
-               if (start < min)
-                       start = min;
-
-               start = round_up(start, align);
-               if ((start + size) > end)
-                       continue;
-
-               status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
-                                    EFI_LOADER_DATA, nr_pages, &start);
-               if (status == EFI_SUCCESS) {
-                       *addr = start;
-                       break;
-               }
-       }
-
-       if (i == map_size / desc_size)
-               status = EFI_NOT_FOUND;
-
-       efi_bs_call(free_pool, map);
-fail:
-       return status;
-}
-
 /**
  * efi_free() - free memory pages
  * @size:      size of the memory area to free in bytes
@@ -222,81 +132,3 @@ void efi_free(unsigned long size, unsigned long addr)
        nr_pages = round_up(size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;
        efi_bs_call(free_pages, addr, nr_pages);
 }
-
-/**
- * efi_relocate_kernel() - copy memory area
- * @image_addr:                pointer to address of memory area to copy
- * @image_size:                size of memory area to copy
- * @alloc_size:                minimum size of memory to allocate, must be greater or
- *                     equal to image_size
- * @preferred_addr:    preferred target address
- * @alignment:         minimum alignment of the allocated memory area. It
- *                     should be a power of two.
- * @min_addr:          minimum target address
- *
- * Copy a memory area to a newly allocated memory area aligned according
- * to @alignment but at least EFI_ALLOC_ALIGN. If the preferred address
- * is not available, the allocated address will not be below @min_addr.
- * On exit, @image_addr is updated to the target copy address that was used.
- *
- * This function is used to copy the Linux kernel verbatim. It does not apply
- * any relocation changes.
- *
- * Return:             status code
- */
-efi_status_t efi_relocate_kernel(unsigned long *image_addr,
-                                unsigned long image_size,
-                                unsigned long alloc_size,
-                                unsigned long preferred_addr,
-                                unsigned long alignment,
-                                unsigned long min_addr)
-{
-       unsigned long cur_image_addr;
-       unsigned long new_addr = 0;
-       efi_status_t status;
-       unsigned long nr_pages;
-       efi_physical_addr_t efi_addr = preferred_addr;
-
-       if (!image_addr || !image_size || !alloc_size)
-               return EFI_INVALID_PARAMETER;
-       if (alloc_size < image_size)
-               return EFI_INVALID_PARAMETER;
-
-       cur_image_addr = *image_addr;
-
-       /*
-        * The EFI firmware loader could have placed the kernel image
-        * anywhere in memory, but the kernel has restrictions on the
-        * max physical address it can run at.  Some architectures
-        * also have a prefered address, so first try to relocate
-        * to the preferred address.  If that fails, allocate as low
-        * as possible while respecting the required alignment.
-        */
-       nr_pages = round_up(alloc_size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;
-       status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
-                            EFI_LOADER_DATA, nr_pages, &efi_addr);
-       new_addr = efi_addr;
-       /*
-        * If preferred address allocation failed allocate as low as
-        * possible.
-        */
-       if (status != EFI_SUCCESS) {
-               status = efi_low_alloc_above(alloc_size, alignment, &new_addr,
-                                            min_addr);
-       }
-       if (status != EFI_SUCCESS) {
-               pr_efi_err("Failed to allocate usable memory for kernel.\n");
-               return status;
-       }
-
-       /*
-        * We know source/dest won't overlap since both memory ranges
-        * have been allocated by UEFI, so we can safely use memcpy.
-        */
-       memcpy((void *)new_addr, (void *)cur_image_addr, image_size);
-
-       /* Return the new address of the relocated image. */
-       *image_addr = new_addr;
-
-       return status;
-}
diff --git a/drivers/firmware/efi/libstub/relocate.c b/drivers/firmware/efi/libstub/relocate.c
new file mode 100644 (file)
index 0000000..1507d3c
--- /dev/null
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/efi.h>
+#include <asm/efi.h>
+
+#include "efistub.h"
+
+/**
+ * efi_low_alloc_above() - allocate pages at or above given address
+ * @size:      size of the memory area to allocate
+ * @align:     minimum alignment of the allocated memory area. It should
+ *             a power of two.
+ * @addr:      on exit the address of the allocated memory
+ * @min:       minimum address to used for the memory allocation
+ *
+ * Allocate at the lowest possible address that is not below @min as
+ * EFI_LOADER_DATA. The allocated pages are aligned according to @align but at
+ * least EFI_ALLOC_ALIGN. The first allocated page will not below the address
+ * given by @min.
+ *
+ * Return:     status code
+ */
+static efi_status_t efi_low_alloc_above(unsigned long size, unsigned long align,
+                                       unsigned long *addr, unsigned long min)
+{
+       unsigned long map_size, desc_size, buff_size;
+       efi_memory_desc_t *map;
+       efi_status_t status;
+       unsigned long nr_pages;
+       int i;
+       struct efi_boot_memmap boot_map;
+
+       boot_map.map            = &map;
+       boot_map.map_size       = &map_size;
+       boot_map.desc_size      = &desc_size;
+       boot_map.desc_ver       = NULL;
+       boot_map.key_ptr        = NULL;
+       boot_map.buff_size      = &buff_size;
+
+       status = efi_get_memory_map(&boot_map);
+       if (status != EFI_SUCCESS)
+               goto fail;
+
+       /*
+        * Enforce minimum alignment that EFI or Linux requires when
+        * requesting a specific address.  We are doing page-based (or
+        * larger) allocations, and both the address and size must meet
+        * alignment constraints.
+        */
+       if (align < EFI_ALLOC_ALIGN)
+               align = EFI_ALLOC_ALIGN;
+
+       size = round_up(size, EFI_ALLOC_ALIGN);
+       nr_pages = size / EFI_PAGE_SIZE;
+       for (i = 0; i < map_size / desc_size; i++) {
+               efi_memory_desc_t *desc;
+               unsigned long m = (unsigned long)map;
+               u64 start, end;
+
+               desc = efi_early_memdesc_ptr(m, desc_size, i);
+
+               if (desc->type != EFI_CONVENTIONAL_MEMORY)
+                       continue;
+
+               if (efi_soft_reserve_enabled() &&
+                   (desc->attribute & EFI_MEMORY_SP))
+                       continue;
+
+               if (desc->num_pages < nr_pages)
+                       continue;
+
+               start = desc->phys_addr;
+               end = start + desc->num_pages * EFI_PAGE_SIZE;
+
+               if (start < min)
+                       start = min;
+
+               start = round_up(start, align);
+               if ((start + size) > end)
+                       continue;
+
+               status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
+                                    EFI_LOADER_DATA, nr_pages, &start);
+               if (status == EFI_SUCCESS) {
+                       *addr = start;
+                       break;
+               }
+       }
+
+       if (i == map_size / desc_size)
+               status = EFI_NOT_FOUND;
+
+       efi_bs_call(free_pool, map);
+fail:
+       return status;
+}
+
+/**
+ * efi_relocate_kernel() - copy memory area
+ * @image_addr:                pointer to address of memory area to copy
+ * @image_size:                size of memory area to copy
+ * @alloc_size:                minimum size of memory to allocate, must be greater or
+ *                     equal to image_size
+ * @preferred_addr:    preferred target address
+ * @alignment:         minimum alignment of the allocated memory area. It
+ *                     should be a power of two.
+ * @min_addr:          minimum target address
+ *
+ * Copy a memory area to a newly allocated memory area aligned according
+ * to @alignment but at least EFI_ALLOC_ALIGN. If the preferred address
+ * is not available, the allocated address will not be below @min_addr.
+ * On exit, @image_addr is updated to the target copy address that was used.
+ *
+ * This function is used to copy the Linux kernel verbatim. It does not apply
+ * any relocation changes.
+ *
+ * Return:             status code
+ */
+efi_status_t efi_relocate_kernel(unsigned long *image_addr,
+                                unsigned long image_size,
+                                unsigned long alloc_size,
+                                unsigned long preferred_addr,
+                                unsigned long alignment,
+                                unsigned long min_addr)
+{
+       unsigned long cur_image_addr;
+       unsigned long new_addr = 0;
+       efi_status_t status;
+       unsigned long nr_pages;
+       efi_physical_addr_t efi_addr = preferred_addr;
+
+       if (!image_addr || !image_size || !alloc_size)
+               return EFI_INVALID_PARAMETER;
+       if (alloc_size < image_size)
+               return EFI_INVALID_PARAMETER;
+
+       cur_image_addr = *image_addr;
+
+       /*
+        * The EFI firmware loader could have placed the kernel image
+        * anywhere in memory, but the kernel has restrictions on the
+        * max physical address it can run at.  Some architectures
+        * also have a prefered address, so first try to relocate
+        * to the preferred address.  If that fails, allocate as low
+        * as possible while respecting the required alignment.
+        */
+       nr_pages = round_up(alloc_size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;
+       status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
+                            EFI_LOADER_DATA, nr_pages, &efi_addr);
+       new_addr = efi_addr;
+       /*
+        * If preferred address allocation failed allocate as low as
+        * possible.
+        */
+       if (status != EFI_SUCCESS) {
+               status = efi_low_alloc_above(alloc_size, alignment, &new_addr,
+                                            min_addr);
+       }
+       if (status != EFI_SUCCESS) {
+               pr_efi_err("Failed to allocate usable memory for kernel.\n");
+               return status;
+       }
+
+       /*
+        * We know source/dest won't overlap since both memory ranges
+        * have been allocated by UEFI, so we can safely use memcpy.
+        */
+       memcpy((void *)new_addr, (void *)cur_image_addr, image_size);
+
+       /* Return the new address of the relocated image. */
+       *image_addr = new_addr;
+
+       return status;
+}