return status;
 }
-
-static efi_status_t relocate_kernel(struct setup_header *hdr)
+/*
+ * Relocate a kernel image, either compressed or uncompressed.
+ * In the ARM64 case, all kernel images are currently
+ * uncompressed, and as such when we relocate it we need to
+ * allocate additional space for the BSS segment. Any low
+ * memory that this function should avoid needs to be
+ * unavailable in the EFI memory map, as if the preferred
+ * address is not available the lowest available address will
+ * be used.
+ */
+static efi_status_t efi_relocate_kernel(efi_system_table_t *sys_table_arg,
+                                       unsigned long *image_addr,
+                                       unsigned long image_size,
+                                       unsigned long alloc_size,
+                                       unsigned long preferred_addr,
+                                       unsigned long alignment)
 {
-       unsigned long start, nr_pages;
+       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 various restrictions
-        * on the max physical address it can run at. Attempt to move
-        * the kernel to boot_params.pref_address, or as low as
-        * possible.
+        * 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.
         */
-       start = hdr->pref_address;
-       nr_pages = round_up(hdr->init_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
-
-       status = efi_call_phys4(sys_table->boottime->allocate_pages,
+       nr_pages = round_up(alloc_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
+       status = efi_call_phys4(sys_table_arg->boottime->allocate_pages,
                                EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
-                               nr_pages, &start);
+                               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(sys_table, hdr->init_size,
-                                  hdr->kernel_alignment, &start);
-               if (status != EFI_SUCCESS)
-                       efi_printk(sys_table, "Failed to alloc mem for kernel\n");
+               status = efi_low_alloc(sys_table_arg, alloc_size, alignment,
+                                      &new_addr);
+       }
+       if (status != EFI_SUCCESS) {
+               efi_printk(sys_table_arg, "ERROR: Failed to allocate usable memory for kernel.\n");
+               return status;
        }
 
-       if (status == EFI_SUCCESS)
-               memcpy((void *)start, (void *)(unsigned long)hdr->code32_start,
-                      hdr->init_size);
+       /*
+        * 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);
+       /* Zero any extra space we may have allocated for BSS. */
+       memset((void *)(new_addr + image_size), alloc_size - image_size, 0);
 
-       hdr->pref_address = hdr->code32_start;
-       hdr->code32_start = (__u32)start;
+       /* Return the new address of the relocated image. */
+       *image_addr = new_addr;
 
        return status;
 }