xref: /openbmc/linux/drivers/firmware/efi/libstub/mem.c (revision a7495c28)
1f57db62cSArd Biesheuvel // SPDX-License-Identifier: GPL-2.0
2f57db62cSArd Biesheuvel 
3f57db62cSArd Biesheuvel #include <linux/efi.h>
4f57db62cSArd Biesheuvel #include <asm/efi.h>
5f57db62cSArd Biesheuvel 
6f57db62cSArd Biesheuvel #include "efistub.h"
7f57db62cSArd Biesheuvel 
8f57db62cSArd Biesheuvel #define EFI_MMAP_NR_SLACK_SLOTS	8
9f57db62cSArd Biesheuvel 
10f57db62cSArd Biesheuvel static inline bool mmap_has_headroom(unsigned long buff_size,
11f57db62cSArd Biesheuvel 				     unsigned long map_size,
12f57db62cSArd Biesheuvel 				     unsigned long desc_size)
13f57db62cSArd Biesheuvel {
14f57db62cSArd Biesheuvel 	unsigned long slack = buff_size - map_size;
15f57db62cSArd Biesheuvel 
16f57db62cSArd Biesheuvel 	return slack / desc_size >= EFI_MMAP_NR_SLACK_SLOTS;
17f57db62cSArd Biesheuvel }
18f57db62cSArd Biesheuvel 
19f57db62cSArd Biesheuvel efi_status_t efi_get_memory_map(struct efi_boot_memmap *map)
20f57db62cSArd Biesheuvel {
21f57db62cSArd Biesheuvel 	efi_memory_desc_t *m = NULL;
22f57db62cSArd Biesheuvel 	efi_status_t status;
23f57db62cSArd Biesheuvel 	unsigned long key;
24f57db62cSArd Biesheuvel 	u32 desc_version;
25f57db62cSArd Biesheuvel 
26f57db62cSArd Biesheuvel 	*map->desc_size =	sizeof(*m);
27f57db62cSArd Biesheuvel 	*map->map_size =	*map->desc_size * 32;
28f57db62cSArd Biesheuvel 	*map->buff_size =	*map->map_size;
29f57db62cSArd Biesheuvel again:
30f57db62cSArd Biesheuvel 	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
31f57db62cSArd Biesheuvel 			     *map->map_size, (void **)&m);
32f57db62cSArd Biesheuvel 	if (status != EFI_SUCCESS)
33f57db62cSArd Biesheuvel 		goto fail;
34f57db62cSArd Biesheuvel 
35f57db62cSArd Biesheuvel 	*map->desc_size = 0;
36f57db62cSArd Biesheuvel 	key = 0;
37f57db62cSArd Biesheuvel 	status = efi_bs_call(get_memory_map, map->map_size, m,
38f57db62cSArd Biesheuvel 			     &key, map->desc_size, &desc_version);
39f57db62cSArd Biesheuvel 	if (status == EFI_BUFFER_TOO_SMALL ||
40f57db62cSArd Biesheuvel 	    !mmap_has_headroom(*map->buff_size, *map->map_size,
41f57db62cSArd Biesheuvel 			       *map->desc_size)) {
42f57db62cSArd Biesheuvel 		efi_bs_call(free_pool, m);
43f57db62cSArd Biesheuvel 		/*
44f57db62cSArd Biesheuvel 		 * Make sure there is some entries of headroom so that the
45f57db62cSArd Biesheuvel 		 * buffer can be reused for a new map after allocations are
46f57db62cSArd Biesheuvel 		 * no longer permitted.  Its unlikely that the map will grow to
47f57db62cSArd Biesheuvel 		 * exceed this headroom once we are ready to trigger
48f57db62cSArd Biesheuvel 		 * ExitBootServices()
49f57db62cSArd Biesheuvel 		 */
50f57db62cSArd Biesheuvel 		*map->map_size += *map->desc_size * EFI_MMAP_NR_SLACK_SLOTS;
51f57db62cSArd Biesheuvel 		*map->buff_size = *map->map_size;
52f57db62cSArd Biesheuvel 		goto again;
53f57db62cSArd Biesheuvel 	}
54f57db62cSArd Biesheuvel 
55f57db62cSArd Biesheuvel 	if (status != EFI_SUCCESS)
56f57db62cSArd Biesheuvel 		efi_bs_call(free_pool, m);
57f57db62cSArd Biesheuvel 
58f57db62cSArd Biesheuvel 	if (map->key_ptr && status == EFI_SUCCESS)
59f57db62cSArd Biesheuvel 		*map->key_ptr = key;
60f57db62cSArd Biesheuvel 	if (map->desc_ver && status == EFI_SUCCESS)
61f57db62cSArd Biesheuvel 		*map->desc_ver = desc_version;
62f57db62cSArd Biesheuvel 
63f57db62cSArd Biesheuvel fail:
64f57db62cSArd Biesheuvel 	*map->map = m;
65f57db62cSArd Biesheuvel 	return status;
66f57db62cSArd Biesheuvel }
67f57db62cSArd Biesheuvel 
68f57db62cSArd Biesheuvel /*
69f57db62cSArd Biesheuvel  * Allocate at the highest possible address that is not above 'max'.
70f57db62cSArd Biesheuvel  */
71a7495c28SArd Biesheuvel efi_status_t efi_allocate_pages(unsigned long size, unsigned long *addr,
72a7495c28SArd Biesheuvel 				unsigned long max)
73f57db62cSArd Biesheuvel {
74a7495c28SArd Biesheuvel 	efi_physical_addr_t alloc_addr = ALIGN_DOWN(max + 1, EFI_ALLOC_ALIGN) - 1;
75a7495c28SArd Biesheuvel 	int slack = EFI_ALLOC_ALIGN / EFI_PAGE_SIZE - 1;
76f57db62cSArd Biesheuvel 	efi_status_t status;
77f57db62cSArd Biesheuvel 
78f57db62cSArd Biesheuvel 	size = round_up(size, EFI_ALLOC_ALIGN);
79a7495c28SArd Biesheuvel 	status = efi_bs_call(allocate_pages, EFI_ALLOCATE_MAX_ADDRESS,
80a7495c28SArd Biesheuvel 			     EFI_LOADER_DATA, size / EFI_PAGE_SIZE + slack,
81a7495c28SArd Biesheuvel 			     &alloc_addr);
82a7495c28SArd Biesheuvel 	if (status != EFI_SUCCESS)
83f57db62cSArd Biesheuvel 		return status;
84f57db62cSArd Biesheuvel 
85a7495c28SArd Biesheuvel 	*addr = ALIGN((unsigned long)alloc_addr, EFI_ALLOC_ALIGN);
86a7495c28SArd Biesheuvel 
87a7495c28SArd Biesheuvel 	if (slack > 0) {
88a7495c28SArd Biesheuvel 		int l = (alloc_addr % EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;
89a7495c28SArd Biesheuvel 
90a7495c28SArd Biesheuvel 		if (l) {
91a7495c28SArd Biesheuvel 			efi_bs_call(free_pages, alloc_addr, slack - l + 1);
92a7495c28SArd Biesheuvel 			slack = l - 1;
93a7495c28SArd Biesheuvel 		}
94a7495c28SArd Biesheuvel 		if (slack)
95a7495c28SArd Biesheuvel 			efi_bs_call(free_pages, *addr + size, slack);
96a7495c28SArd Biesheuvel 	}
97a7495c28SArd Biesheuvel 	return EFI_SUCCESS;
98a7495c28SArd Biesheuvel }
99f57db62cSArd Biesheuvel /*
100f57db62cSArd Biesheuvel  * Allocate at the lowest possible address that is not below 'min'.
101f57db62cSArd Biesheuvel  */
102f57db62cSArd Biesheuvel efi_status_t efi_low_alloc_above(unsigned long size, unsigned long align,
103f57db62cSArd Biesheuvel 				 unsigned long *addr, unsigned long min)
104f57db62cSArd Biesheuvel {
105f57db62cSArd Biesheuvel 	unsigned long map_size, desc_size, buff_size;
106f57db62cSArd Biesheuvel 	efi_memory_desc_t *map;
107f57db62cSArd Biesheuvel 	efi_status_t status;
108f57db62cSArd Biesheuvel 	unsigned long nr_pages;
109f57db62cSArd Biesheuvel 	int i;
110f57db62cSArd Biesheuvel 	struct efi_boot_memmap boot_map;
111f57db62cSArd Biesheuvel 
112f57db62cSArd Biesheuvel 	boot_map.map		= &map;
113f57db62cSArd Biesheuvel 	boot_map.map_size	= &map_size;
114f57db62cSArd Biesheuvel 	boot_map.desc_size	= &desc_size;
115f57db62cSArd Biesheuvel 	boot_map.desc_ver	= NULL;
116f57db62cSArd Biesheuvel 	boot_map.key_ptr	= NULL;
117f57db62cSArd Biesheuvel 	boot_map.buff_size	= &buff_size;
118f57db62cSArd Biesheuvel 
119f57db62cSArd Biesheuvel 	status = efi_get_memory_map(&boot_map);
120f57db62cSArd Biesheuvel 	if (status != EFI_SUCCESS)
121f57db62cSArd Biesheuvel 		goto fail;
122f57db62cSArd Biesheuvel 
123f57db62cSArd Biesheuvel 	/*
124f57db62cSArd Biesheuvel 	 * Enforce minimum alignment that EFI or Linux requires when
125f57db62cSArd Biesheuvel 	 * requesting a specific address.  We are doing page-based (or
126f57db62cSArd Biesheuvel 	 * larger) allocations, and both the address and size must meet
127f57db62cSArd Biesheuvel 	 * alignment constraints.
128f57db62cSArd Biesheuvel 	 */
129f57db62cSArd Biesheuvel 	if (align < EFI_ALLOC_ALIGN)
130f57db62cSArd Biesheuvel 		align = EFI_ALLOC_ALIGN;
131f57db62cSArd Biesheuvel 
132f57db62cSArd Biesheuvel 	size = round_up(size, EFI_ALLOC_ALIGN);
133f57db62cSArd Biesheuvel 	nr_pages = size / EFI_PAGE_SIZE;
134f57db62cSArd Biesheuvel 	for (i = 0; i < map_size / desc_size; i++) {
135f57db62cSArd Biesheuvel 		efi_memory_desc_t *desc;
136f57db62cSArd Biesheuvel 		unsigned long m = (unsigned long)map;
137f57db62cSArd Biesheuvel 		u64 start, end;
138f57db62cSArd Biesheuvel 
139f57db62cSArd Biesheuvel 		desc = efi_early_memdesc_ptr(m, desc_size, i);
140f57db62cSArd Biesheuvel 
141f57db62cSArd Biesheuvel 		if (desc->type != EFI_CONVENTIONAL_MEMORY)
142f57db62cSArd Biesheuvel 			continue;
143f57db62cSArd Biesheuvel 
144f57db62cSArd Biesheuvel 		if (efi_soft_reserve_enabled() &&
145f57db62cSArd Biesheuvel 		    (desc->attribute & EFI_MEMORY_SP))
146f57db62cSArd Biesheuvel 			continue;
147f57db62cSArd Biesheuvel 
148f57db62cSArd Biesheuvel 		if (desc->num_pages < nr_pages)
149f57db62cSArd Biesheuvel 			continue;
150f57db62cSArd Biesheuvel 
151f57db62cSArd Biesheuvel 		start = desc->phys_addr;
152f57db62cSArd Biesheuvel 		end = start + desc->num_pages * EFI_PAGE_SIZE;
153f57db62cSArd Biesheuvel 
154f57db62cSArd Biesheuvel 		if (start < min)
155f57db62cSArd Biesheuvel 			start = min;
156f57db62cSArd Biesheuvel 
157f57db62cSArd Biesheuvel 		start = round_up(start, align);
158f57db62cSArd Biesheuvel 		if ((start + size) > end)
159f57db62cSArd Biesheuvel 			continue;
160f57db62cSArd Biesheuvel 
161f57db62cSArd Biesheuvel 		status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
162f57db62cSArd Biesheuvel 				     EFI_LOADER_DATA, nr_pages, &start);
163f57db62cSArd Biesheuvel 		if (status == EFI_SUCCESS) {
164f57db62cSArd Biesheuvel 			*addr = start;
165f57db62cSArd Biesheuvel 			break;
166f57db62cSArd Biesheuvel 		}
167f57db62cSArd Biesheuvel 	}
168f57db62cSArd Biesheuvel 
169f57db62cSArd Biesheuvel 	if (i == map_size / desc_size)
170f57db62cSArd Biesheuvel 		status = EFI_NOT_FOUND;
171f57db62cSArd Biesheuvel 
172f57db62cSArd Biesheuvel 	efi_bs_call(free_pool, map);
173f57db62cSArd Biesheuvel fail:
174f57db62cSArd Biesheuvel 	return status;
175f57db62cSArd Biesheuvel }
176f57db62cSArd Biesheuvel 
177f57db62cSArd Biesheuvel void efi_free(unsigned long size, unsigned long addr)
178f57db62cSArd Biesheuvel {
179f57db62cSArd Biesheuvel 	unsigned long nr_pages;
180f57db62cSArd Biesheuvel 
181f57db62cSArd Biesheuvel 	if (!size)
182f57db62cSArd Biesheuvel 		return;
183f57db62cSArd Biesheuvel 
184f57db62cSArd Biesheuvel 	nr_pages = round_up(size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;
185f57db62cSArd Biesheuvel 	efi_bs_call(free_pages, addr, nr_pages);
186f57db62cSArd Biesheuvel }
187f57db62cSArd Biesheuvel 
188f57db62cSArd Biesheuvel /*
189f57db62cSArd Biesheuvel  * Relocate a kernel image, either compressed or uncompressed.
190f57db62cSArd Biesheuvel  * In the ARM64 case, all kernel images are currently
191f57db62cSArd Biesheuvel  * uncompressed, and as such when we relocate it we need to
192f57db62cSArd Biesheuvel  * allocate additional space for the BSS segment. Any low
193f57db62cSArd Biesheuvel  * memory that this function should avoid needs to be
194f57db62cSArd Biesheuvel  * unavailable in the EFI memory map, as if the preferred
195f57db62cSArd Biesheuvel  * address is not available the lowest available address will
196f57db62cSArd Biesheuvel  * be used.
197f57db62cSArd Biesheuvel  */
198f57db62cSArd Biesheuvel efi_status_t efi_relocate_kernel(unsigned long *image_addr,
199f57db62cSArd Biesheuvel 				 unsigned long image_size,
200f57db62cSArd Biesheuvel 				 unsigned long alloc_size,
201f57db62cSArd Biesheuvel 				 unsigned long preferred_addr,
202f57db62cSArd Biesheuvel 				 unsigned long alignment,
203f57db62cSArd Biesheuvel 				 unsigned long min_addr)
204f57db62cSArd Biesheuvel {
205f57db62cSArd Biesheuvel 	unsigned long cur_image_addr;
206f57db62cSArd Biesheuvel 	unsigned long new_addr = 0;
207f57db62cSArd Biesheuvel 	efi_status_t status;
208f57db62cSArd Biesheuvel 	unsigned long nr_pages;
209f57db62cSArd Biesheuvel 	efi_physical_addr_t efi_addr = preferred_addr;
210f57db62cSArd Biesheuvel 
211f57db62cSArd Biesheuvel 	if (!image_addr || !image_size || !alloc_size)
212f57db62cSArd Biesheuvel 		return EFI_INVALID_PARAMETER;
213f57db62cSArd Biesheuvel 	if (alloc_size < image_size)
214f57db62cSArd Biesheuvel 		return EFI_INVALID_PARAMETER;
215f57db62cSArd Biesheuvel 
216f57db62cSArd Biesheuvel 	cur_image_addr = *image_addr;
217f57db62cSArd Biesheuvel 
218f57db62cSArd Biesheuvel 	/*
219f57db62cSArd Biesheuvel 	 * The EFI firmware loader could have placed the kernel image
220f57db62cSArd Biesheuvel 	 * anywhere in memory, but the kernel has restrictions on the
221f57db62cSArd Biesheuvel 	 * max physical address it can run at.  Some architectures
222f57db62cSArd Biesheuvel 	 * also have a prefered address, so first try to relocate
223f57db62cSArd Biesheuvel 	 * to the preferred address.  If that fails, allocate as low
224f57db62cSArd Biesheuvel 	 * as possible while respecting the required alignment.
225f57db62cSArd Biesheuvel 	 */
226f57db62cSArd Biesheuvel 	nr_pages = round_up(alloc_size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;
227f57db62cSArd Biesheuvel 	status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
228f57db62cSArd Biesheuvel 			     EFI_LOADER_DATA, nr_pages, &efi_addr);
229f57db62cSArd Biesheuvel 	new_addr = efi_addr;
230f57db62cSArd Biesheuvel 	/*
231f57db62cSArd Biesheuvel 	 * If preferred address allocation failed allocate as low as
232f57db62cSArd Biesheuvel 	 * possible.
233f57db62cSArd Biesheuvel 	 */
234f57db62cSArd Biesheuvel 	if (status != EFI_SUCCESS) {
235f57db62cSArd Biesheuvel 		status = efi_low_alloc_above(alloc_size, alignment, &new_addr,
236f57db62cSArd Biesheuvel 					     min_addr);
237f57db62cSArd Biesheuvel 	}
238f57db62cSArd Biesheuvel 	if (status != EFI_SUCCESS) {
239f57db62cSArd Biesheuvel 		pr_efi_err("Failed to allocate usable memory for kernel.\n");
240f57db62cSArd Biesheuvel 		return status;
241f57db62cSArd Biesheuvel 	}
242f57db62cSArd Biesheuvel 
243f57db62cSArd Biesheuvel 	/*
244f57db62cSArd Biesheuvel 	 * We know source/dest won't overlap since both memory ranges
245f57db62cSArd Biesheuvel 	 * have been allocated by UEFI, so we can safely use memcpy.
246f57db62cSArd Biesheuvel 	 */
247f57db62cSArd Biesheuvel 	memcpy((void *)new_addr, (void *)cur_image_addr, image_size);
248f57db62cSArd Biesheuvel 
249f57db62cSArd Biesheuvel 	/* Return the new address of the relocated image. */
250f57db62cSArd Biesheuvel 	*image_addr = new_addr;
251f57db62cSArd Biesheuvel 
252f57db62cSArd Biesheuvel 	return status;
253f57db62cSArd Biesheuvel }
254