18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci
38c2ecf20Sopenharmony_ci#include <linux/efi.h>
48c2ecf20Sopenharmony_ci#include <asm/efi.h>
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include "efistub.h"
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci/**
98c2ecf20Sopenharmony_ci * efi_low_alloc_above() - allocate pages at or above given address
108c2ecf20Sopenharmony_ci * @size:	size of the memory area to allocate
118c2ecf20Sopenharmony_ci * @align:	minimum alignment of the allocated memory area. It should
128c2ecf20Sopenharmony_ci *		a power of two.
138c2ecf20Sopenharmony_ci * @addr:	on exit the address of the allocated memory
148c2ecf20Sopenharmony_ci * @min:	minimum address to used for the memory allocation
158c2ecf20Sopenharmony_ci *
168c2ecf20Sopenharmony_ci * Allocate at the lowest possible address that is not below @min as
178c2ecf20Sopenharmony_ci * EFI_LOADER_DATA. The allocated pages are aligned according to @align but at
188c2ecf20Sopenharmony_ci * least EFI_ALLOC_ALIGN. The first allocated page will not below the address
198c2ecf20Sopenharmony_ci * given by @min.
208c2ecf20Sopenharmony_ci *
218c2ecf20Sopenharmony_ci * Return:	status code
228c2ecf20Sopenharmony_ci */
238c2ecf20Sopenharmony_ciefi_status_t efi_low_alloc_above(unsigned long size, unsigned long align,
248c2ecf20Sopenharmony_ci				 unsigned long *addr, unsigned long min)
258c2ecf20Sopenharmony_ci{
268c2ecf20Sopenharmony_ci	struct efi_boot_memmap *map;
278c2ecf20Sopenharmony_ci	efi_status_t status;
288c2ecf20Sopenharmony_ci	unsigned long nr_pages;
298c2ecf20Sopenharmony_ci	int i;
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci	status = efi_get_memory_map(&map, false);
328c2ecf20Sopenharmony_ci	if (status != EFI_SUCCESS)
338c2ecf20Sopenharmony_ci		goto fail;
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	/*
368c2ecf20Sopenharmony_ci	 * Enforce minimum alignment that EFI or Linux requires when
378c2ecf20Sopenharmony_ci	 * requesting a specific address.  We are doing page-based (or
388c2ecf20Sopenharmony_ci	 * larger) allocations, and both the address and size must meet
398c2ecf20Sopenharmony_ci	 * alignment constraints.
408c2ecf20Sopenharmony_ci	 */
418c2ecf20Sopenharmony_ci	if (align < EFI_ALLOC_ALIGN)
428c2ecf20Sopenharmony_ci		align = EFI_ALLOC_ALIGN;
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	size = round_up(size, EFI_ALLOC_ALIGN);
458c2ecf20Sopenharmony_ci	nr_pages = size / EFI_PAGE_SIZE;
468c2ecf20Sopenharmony_ci	for (i = 0; i < map->map_size / map->desc_size; i++) {
478c2ecf20Sopenharmony_ci		efi_memory_desc_t *desc;
488c2ecf20Sopenharmony_ci		unsigned long m = (unsigned long)map->map;
498c2ecf20Sopenharmony_ci		u64 start, end;
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci		desc = efi_early_memdesc_ptr(m, map->desc_size, i);
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci		if (desc->type != EFI_CONVENTIONAL_MEMORY)
548c2ecf20Sopenharmony_ci			continue;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci		if (efi_soft_reserve_enabled() &&
578c2ecf20Sopenharmony_ci		    (desc->attribute & EFI_MEMORY_SP))
588c2ecf20Sopenharmony_ci			continue;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci		if (desc->num_pages < nr_pages)
618c2ecf20Sopenharmony_ci			continue;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci		start = desc->phys_addr;
648c2ecf20Sopenharmony_ci		end = start + desc->num_pages * EFI_PAGE_SIZE;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci		if (start < min)
678c2ecf20Sopenharmony_ci			start = min;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci		start = round_up(start, align);
708c2ecf20Sopenharmony_ci		if ((start + size) > end)
718c2ecf20Sopenharmony_ci			continue;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci		status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
748c2ecf20Sopenharmony_ci				     EFI_LOADER_DATA, nr_pages, &start);
758c2ecf20Sopenharmony_ci		if (status == EFI_SUCCESS) {
768c2ecf20Sopenharmony_ci			*addr = start;
778c2ecf20Sopenharmony_ci			break;
788c2ecf20Sopenharmony_ci		}
798c2ecf20Sopenharmony_ci	}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	if (i == map->map_size / map->desc_size)
828c2ecf20Sopenharmony_ci		status = EFI_NOT_FOUND;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	efi_bs_call(free_pool, map);
858c2ecf20Sopenharmony_cifail:
868c2ecf20Sopenharmony_ci	return status;
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci/**
908c2ecf20Sopenharmony_ci * efi_relocate_kernel() - copy memory area
918c2ecf20Sopenharmony_ci * @image_addr:		pointer to address of memory area to copy
928c2ecf20Sopenharmony_ci * @image_size:		size of memory area to copy
938c2ecf20Sopenharmony_ci * @alloc_size:		minimum size of memory to allocate, must be greater or
948c2ecf20Sopenharmony_ci *			equal to image_size
958c2ecf20Sopenharmony_ci * @preferred_addr:	preferred target address
968c2ecf20Sopenharmony_ci * @alignment:		minimum alignment of the allocated memory area. It
978c2ecf20Sopenharmony_ci *			should be a power of two.
988c2ecf20Sopenharmony_ci * @min_addr:		minimum target address
998c2ecf20Sopenharmony_ci *
1008c2ecf20Sopenharmony_ci * Copy a memory area to a newly allocated memory area aligned according
1018c2ecf20Sopenharmony_ci * to @alignment but at least EFI_ALLOC_ALIGN. If the preferred address
1028c2ecf20Sopenharmony_ci * is not available, the allocated address will not be below @min_addr.
1038c2ecf20Sopenharmony_ci * On exit, @image_addr is updated to the target copy address that was used.
1048c2ecf20Sopenharmony_ci *
1058c2ecf20Sopenharmony_ci * This function is used to copy the Linux kernel verbatim. It does not apply
1068c2ecf20Sopenharmony_ci * any relocation changes.
1078c2ecf20Sopenharmony_ci *
1088c2ecf20Sopenharmony_ci * Return:		status code
1098c2ecf20Sopenharmony_ci */
1108c2ecf20Sopenharmony_ciefi_status_t efi_relocate_kernel(unsigned long *image_addr,
1118c2ecf20Sopenharmony_ci				 unsigned long image_size,
1128c2ecf20Sopenharmony_ci				 unsigned long alloc_size,
1138c2ecf20Sopenharmony_ci				 unsigned long preferred_addr,
1148c2ecf20Sopenharmony_ci				 unsigned long alignment,
1158c2ecf20Sopenharmony_ci				 unsigned long min_addr)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	unsigned long cur_image_addr;
1188c2ecf20Sopenharmony_ci	unsigned long new_addr = 0;
1198c2ecf20Sopenharmony_ci	efi_status_t status;
1208c2ecf20Sopenharmony_ci	unsigned long nr_pages;
1218c2ecf20Sopenharmony_ci	efi_physical_addr_t efi_addr = preferred_addr;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	if (!image_addr || !image_size || !alloc_size)
1248c2ecf20Sopenharmony_ci		return EFI_INVALID_PARAMETER;
1258c2ecf20Sopenharmony_ci	if (alloc_size < image_size)
1268c2ecf20Sopenharmony_ci		return EFI_INVALID_PARAMETER;
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	cur_image_addr = *image_addr;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	/*
1318c2ecf20Sopenharmony_ci	 * The EFI firmware loader could have placed the kernel image
1328c2ecf20Sopenharmony_ci	 * anywhere in memory, but the kernel has restrictions on the
1338c2ecf20Sopenharmony_ci	 * max physical address it can run at.  Some architectures
1348c2ecf20Sopenharmony_ci	 * also have a preferred address, so first try to relocate
1358c2ecf20Sopenharmony_ci	 * to the preferred address.  If that fails, allocate as low
1368c2ecf20Sopenharmony_ci	 * as possible while respecting the required alignment.
1378c2ecf20Sopenharmony_ci	 */
1388c2ecf20Sopenharmony_ci	nr_pages = round_up(alloc_size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;
1398c2ecf20Sopenharmony_ci	status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
1408c2ecf20Sopenharmony_ci			     EFI_LOADER_DATA, nr_pages, &efi_addr);
1418c2ecf20Sopenharmony_ci	new_addr = efi_addr;
1428c2ecf20Sopenharmony_ci	/*
1438c2ecf20Sopenharmony_ci	 * If preferred address allocation failed allocate as low as
1448c2ecf20Sopenharmony_ci	 * possible.
1458c2ecf20Sopenharmony_ci	 */
1468c2ecf20Sopenharmony_ci	if (status != EFI_SUCCESS) {
1478c2ecf20Sopenharmony_ci		status = efi_low_alloc_above(alloc_size, alignment, &new_addr,
1488c2ecf20Sopenharmony_ci					     min_addr);
1498c2ecf20Sopenharmony_ci	}
1508c2ecf20Sopenharmony_ci	if (status != EFI_SUCCESS) {
1518c2ecf20Sopenharmony_ci		efi_err("Failed to allocate usable memory for kernel.\n");
1528c2ecf20Sopenharmony_ci		return status;
1538c2ecf20Sopenharmony_ci	}
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	/*
1568c2ecf20Sopenharmony_ci	 * We know source/dest won't overlap since both memory ranges
1578c2ecf20Sopenharmony_ci	 * have been allocated by UEFI, so we can safely use memcpy.
1588c2ecf20Sopenharmony_ci	 */
1598c2ecf20Sopenharmony_ci	memcpy((void *)new_addr, (void *)cur_image_addr, image_size);
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	/* Return the new address of the relocated image. */
1628c2ecf20Sopenharmony_ci	*image_addr = new_addr;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	return status;
1658c2ecf20Sopenharmony_ci}
166