xref: /openbmc/linux/drivers/firmware/efi/libstub/mem.c (revision f57db62c)
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  */
71f57db62cSArd Biesheuvel efi_status_t efi_high_alloc(unsigned long size, unsigned long align,
72f57db62cSArd Biesheuvel 			    unsigned long *addr, unsigned long max)
73f57db62cSArd Biesheuvel {
74f57db62cSArd Biesheuvel 	unsigned long map_size, desc_size, buff_size;
75f57db62cSArd Biesheuvel 	efi_memory_desc_t *map;
76f57db62cSArd Biesheuvel 	efi_status_t status;
77f57db62cSArd Biesheuvel 	unsigned long nr_pages;
78f57db62cSArd Biesheuvel 	u64 max_addr = 0;
79f57db62cSArd Biesheuvel 	int i;
80f57db62cSArd Biesheuvel 	struct efi_boot_memmap boot_map;
81f57db62cSArd Biesheuvel 
82f57db62cSArd Biesheuvel 	boot_map.map =		&map;
83f57db62cSArd Biesheuvel 	boot_map.map_size =	&map_size;
84f57db62cSArd Biesheuvel 	boot_map.desc_size =	&desc_size;
85f57db62cSArd Biesheuvel 	boot_map.desc_ver =	NULL;
86f57db62cSArd Biesheuvel 	boot_map.key_ptr =	NULL;
87f57db62cSArd Biesheuvel 	boot_map.buff_size =	&buff_size;
88f57db62cSArd Biesheuvel 
89f57db62cSArd Biesheuvel 	status = efi_get_memory_map(&boot_map);
90f57db62cSArd Biesheuvel 	if (status != EFI_SUCCESS)
91f57db62cSArd Biesheuvel 		goto fail;
92f57db62cSArd Biesheuvel 
93f57db62cSArd Biesheuvel 	/*
94f57db62cSArd Biesheuvel 	 * Enforce minimum alignment that EFI or Linux requires when
95f57db62cSArd Biesheuvel 	 * requesting a specific address.  We are doing page-based (or
96f57db62cSArd Biesheuvel 	 * larger) allocations, and both the address and size must meet
97f57db62cSArd Biesheuvel 	 * alignment constraints.
98f57db62cSArd Biesheuvel 	 */
99f57db62cSArd Biesheuvel 	if (align < EFI_ALLOC_ALIGN)
100f57db62cSArd Biesheuvel 		align = EFI_ALLOC_ALIGN;
101f57db62cSArd Biesheuvel 
102f57db62cSArd Biesheuvel 	size = round_up(size, EFI_ALLOC_ALIGN);
103f57db62cSArd Biesheuvel 	nr_pages = size / EFI_PAGE_SIZE;
104f57db62cSArd Biesheuvel again:
105f57db62cSArd Biesheuvel 	for (i = 0; i < map_size / desc_size; i++) {
106f57db62cSArd Biesheuvel 		efi_memory_desc_t *desc;
107f57db62cSArd Biesheuvel 		unsigned long m = (unsigned long)map;
108f57db62cSArd Biesheuvel 		u64 start, end;
109f57db62cSArd Biesheuvel 
110f57db62cSArd Biesheuvel 		desc = efi_early_memdesc_ptr(m, desc_size, i);
111f57db62cSArd Biesheuvel 		if (desc->type != EFI_CONVENTIONAL_MEMORY)
112f57db62cSArd Biesheuvel 			continue;
113f57db62cSArd Biesheuvel 
114f57db62cSArd Biesheuvel 		if (efi_soft_reserve_enabled() &&
115f57db62cSArd Biesheuvel 		    (desc->attribute & EFI_MEMORY_SP))
116f57db62cSArd Biesheuvel 			continue;
117f57db62cSArd Biesheuvel 
118f57db62cSArd Biesheuvel 		if (desc->num_pages < nr_pages)
119f57db62cSArd Biesheuvel 			continue;
120f57db62cSArd Biesheuvel 
121f57db62cSArd Biesheuvel 		start = desc->phys_addr;
122f57db62cSArd Biesheuvel 		end = start + desc->num_pages * EFI_PAGE_SIZE;
123f57db62cSArd Biesheuvel 
124f57db62cSArd Biesheuvel 		if (end > max)
125f57db62cSArd Biesheuvel 			end = max;
126f57db62cSArd Biesheuvel 
127f57db62cSArd Biesheuvel 		if ((start + size) > end)
128f57db62cSArd Biesheuvel 			continue;
129f57db62cSArd Biesheuvel 
130f57db62cSArd Biesheuvel 		if (round_down(end - size, align) < start)
131f57db62cSArd Biesheuvel 			continue;
132f57db62cSArd Biesheuvel 
133f57db62cSArd Biesheuvel 		start = round_down(end - size, align);
134f57db62cSArd Biesheuvel 
135f57db62cSArd Biesheuvel 		/*
136f57db62cSArd Biesheuvel 		 * Don't allocate at 0x0. It will confuse code that
137f57db62cSArd Biesheuvel 		 * checks pointers against NULL.
138f57db62cSArd Biesheuvel 		 */
139f57db62cSArd Biesheuvel 		if (start == 0x0)
140f57db62cSArd Biesheuvel 			continue;
141f57db62cSArd Biesheuvel 
142f57db62cSArd Biesheuvel 		if (start > max_addr)
143f57db62cSArd Biesheuvel 			max_addr = start;
144f57db62cSArd Biesheuvel 	}
145f57db62cSArd Biesheuvel 
146f57db62cSArd Biesheuvel 	if (!max_addr)
147f57db62cSArd Biesheuvel 		status = EFI_NOT_FOUND;
148f57db62cSArd Biesheuvel 	else {
149f57db62cSArd Biesheuvel 		status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
150f57db62cSArd Biesheuvel 				     EFI_LOADER_DATA, nr_pages, &max_addr);
151f57db62cSArd Biesheuvel 		if (status != EFI_SUCCESS) {
152f57db62cSArd Biesheuvel 			max = max_addr;
153f57db62cSArd Biesheuvel 			max_addr = 0;
154f57db62cSArd Biesheuvel 			goto again;
155f57db62cSArd Biesheuvel 		}
156f57db62cSArd Biesheuvel 
157f57db62cSArd Biesheuvel 		*addr = max_addr;
158f57db62cSArd Biesheuvel 	}
159f57db62cSArd Biesheuvel 
160f57db62cSArd Biesheuvel 	efi_bs_call(free_pool, map);
161f57db62cSArd Biesheuvel fail:
162f57db62cSArd Biesheuvel 	return status;
163f57db62cSArd Biesheuvel }
164f57db62cSArd Biesheuvel 
165f57db62cSArd Biesheuvel /*
166f57db62cSArd Biesheuvel  * Allocate at the lowest possible address that is not below 'min'.
167f57db62cSArd Biesheuvel  */
168f57db62cSArd Biesheuvel efi_status_t efi_low_alloc_above(unsigned long size, unsigned long align,
169f57db62cSArd Biesheuvel 				 unsigned long *addr, unsigned long min)
170f57db62cSArd Biesheuvel {
171f57db62cSArd Biesheuvel 	unsigned long map_size, desc_size, buff_size;
172f57db62cSArd Biesheuvel 	efi_memory_desc_t *map;
173f57db62cSArd Biesheuvel 	efi_status_t status;
174f57db62cSArd Biesheuvel 	unsigned long nr_pages;
175f57db62cSArd Biesheuvel 	int i;
176f57db62cSArd Biesheuvel 	struct efi_boot_memmap boot_map;
177f57db62cSArd Biesheuvel 
178f57db62cSArd Biesheuvel 	boot_map.map		= &map;
179f57db62cSArd Biesheuvel 	boot_map.map_size	= &map_size;
180f57db62cSArd Biesheuvel 	boot_map.desc_size	= &desc_size;
181f57db62cSArd Biesheuvel 	boot_map.desc_ver	= NULL;
182f57db62cSArd Biesheuvel 	boot_map.key_ptr	= NULL;
183f57db62cSArd Biesheuvel 	boot_map.buff_size	= &buff_size;
184f57db62cSArd Biesheuvel 
185f57db62cSArd Biesheuvel 	status = efi_get_memory_map(&boot_map);
186f57db62cSArd Biesheuvel 	if (status != EFI_SUCCESS)
187f57db62cSArd Biesheuvel 		goto fail;
188f57db62cSArd Biesheuvel 
189f57db62cSArd Biesheuvel 	/*
190f57db62cSArd Biesheuvel 	 * Enforce minimum alignment that EFI or Linux requires when
191f57db62cSArd Biesheuvel 	 * requesting a specific address.  We are doing page-based (or
192f57db62cSArd Biesheuvel 	 * larger) allocations, and both the address and size must meet
193f57db62cSArd Biesheuvel 	 * alignment constraints.
194f57db62cSArd Biesheuvel 	 */
195f57db62cSArd Biesheuvel 	if (align < EFI_ALLOC_ALIGN)
196f57db62cSArd Biesheuvel 		align = EFI_ALLOC_ALIGN;
197f57db62cSArd Biesheuvel 
198f57db62cSArd Biesheuvel 	size = round_up(size, EFI_ALLOC_ALIGN);
199f57db62cSArd Biesheuvel 	nr_pages = size / EFI_PAGE_SIZE;
200f57db62cSArd Biesheuvel 	for (i = 0; i < map_size / desc_size; i++) {
201f57db62cSArd Biesheuvel 		efi_memory_desc_t *desc;
202f57db62cSArd Biesheuvel 		unsigned long m = (unsigned long)map;
203f57db62cSArd Biesheuvel 		u64 start, end;
204f57db62cSArd Biesheuvel 
205f57db62cSArd Biesheuvel 		desc = efi_early_memdesc_ptr(m, desc_size, i);
206f57db62cSArd Biesheuvel 
207f57db62cSArd Biesheuvel 		if (desc->type != EFI_CONVENTIONAL_MEMORY)
208f57db62cSArd Biesheuvel 			continue;
209f57db62cSArd Biesheuvel 
210f57db62cSArd Biesheuvel 		if (efi_soft_reserve_enabled() &&
211f57db62cSArd Biesheuvel 		    (desc->attribute & EFI_MEMORY_SP))
212f57db62cSArd Biesheuvel 			continue;
213f57db62cSArd Biesheuvel 
214f57db62cSArd Biesheuvel 		if (desc->num_pages < nr_pages)
215f57db62cSArd Biesheuvel 			continue;
216f57db62cSArd Biesheuvel 
217f57db62cSArd Biesheuvel 		start = desc->phys_addr;
218f57db62cSArd Biesheuvel 		end = start + desc->num_pages * EFI_PAGE_SIZE;
219f57db62cSArd Biesheuvel 
220f57db62cSArd Biesheuvel 		if (start < min)
221f57db62cSArd Biesheuvel 			start = min;
222f57db62cSArd Biesheuvel 
223f57db62cSArd Biesheuvel 		start = round_up(start, align);
224f57db62cSArd Biesheuvel 		if ((start + size) > end)
225f57db62cSArd Biesheuvel 			continue;
226f57db62cSArd Biesheuvel 
227f57db62cSArd Biesheuvel 		status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
228f57db62cSArd Biesheuvel 				     EFI_LOADER_DATA, nr_pages, &start);
229f57db62cSArd Biesheuvel 		if (status == EFI_SUCCESS) {
230f57db62cSArd Biesheuvel 			*addr = start;
231f57db62cSArd Biesheuvel 			break;
232f57db62cSArd Biesheuvel 		}
233f57db62cSArd Biesheuvel 	}
234f57db62cSArd Biesheuvel 
235f57db62cSArd Biesheuvel 	if (i == map_size / desc_size)
236f57db62cSArd Biesheuvel 		status = EFI_NOT_FOUND;
237f57db62cSArd Biesheuvel 
238f57db62cSArd Biesheuvel 	efi_bs_call(free_pool, map);
239f57db62cSArd Biesheuvel fail:
240f57db62cSArd Biesheuvel 	return status;
241f57db62cSArd Biesheuvel }
242f57db62cSArd Biesheuvel 
243f57db62cSArd Biesheuvel void efi_free(unsigned long size, unsigned long addr)
244f57db62cSArd Biesheuvel {
245f57db62cSArd Biesheuvel 	unsigned long nr_pages;
246f57db62cSArd Biesheuvel 
247f57db62cSArd Biesheuvel 	if (!size)
248f57db62cSArd Biesheuvel 		return;
249f57db62cSArd Biesheuvel 
250f57db62cSArd Biesheuvel 	nr_pages = round_up(size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;
251f57db62cSArd Biesheuvel 	efi_bs_call(free_pages, addr, nr_pages);
252f57db62cSArd Biesheuvel }
253f57db62cSArd Biesheuvel 
254f57db62cSArd Biesheuvel /*
255f57db62cSArd Biesheuvel  * Relocate a kernel image, either compressed or uncompressed.
256f57db62cSArd Biesheuvel  * In the ARM64 case, all kernel images are currently
257f57db62cSArd Biesheuvel  * uncompressed, and as such when we relocate it we need to
258f57db62cSArd Biesheuvel  * allocate additional space for the BSS segment. Any low
259f57db62cSArd Biesheuvel  * memory that this function should avoid needs to be
260f57db62cSArd Biesheuvel  * unavailable in the EFI memory map, as if the preferred
261f57db62cSArd Biesheuvel  * address is not available the lowest available address will
262f57db62cSArd Biesheuvel  * be used.
263f57db62cSArd Biesheuvel  */
264f57db62cSArd Biesheuvel efi_status_t efi_relocate_kernel(unsigned long *image_addr,
265f57db62cSArd Biesheuvel 				 unsigned long image_size,
266f57db62cSArd Biesheuvel 				 unsigned long alloc_size,
267f57db62cSArd Biesheuvel 				 unsigned long preferred_addr,
268f57db62cSArd Biesheuvel 				 unsigned long alignment,
269f57db62cSArd Biesheuvel 				 unsigned long min_addr)
270f57db62cSArd Biesheuvel {
271f57db62cSArd Biesheuvel 	unsigned long cur_image_addr;
272f57db62cSArd Biesheuvel 	unsigned long new_addr = 0;
273f57db62cSArd Biesheuvel 	efi_status_t status;
274f57db62cSArd Biesheuvel 	unsigned long nr_pages;
275f57db62cSArd Biesheuvel 	efi_physical_addr_t efi_addr = preferred_addr;
276f57db62cSArd Biesheuvel 
277f57db62cSArd Biesheuvel 	if (!image_addr || !image_size || !alloc_size)
278f57db62cSArd Biesheuvel 		return EFI_INVALID_PARAMETER;
279f57db62cSArd Biesheuvel 	if (alloc_size < image_size)
280f57db62cSArd Biesheuvel 		return EFI_INVALID_PARAMETER;
281f57db62cSArd Biesheuvel 
282f57db62cSArd Biesheuvel 	cur_image_addr = *image_addr;
283f57db62cSArd Biesheuvel 
284f57db62cSArd Biesheuvel 	/*
285f57db62cSArd Biesheuvel 	 * The EFI firmware loader could have placed the kernel image
286f57db62cSArd Biesheuvel 	 * anywhere in memory, but the kernel has restrictions on the
287f57db62cSArd Biesheuvel 	 * max physical address it can run at.  Some architectures
288f57db62cSArd Biesheuvel 	 * also have a prefered address, so first try to relocate
289f57db62cSArd Biesheuvel 	 * to the preferred address.  If that fails, allocate as low
290f57db62cSArd Biesheuvel 	 * as possible while respecting the required alignment.
291f57db62cSArd Biesheuvel 	 */
292f57db62cSArd Biesheuvel 	nr_pages = round_up(alloc_size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;
293f57db62cSArd Biesheuvel 	status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
294f57db62cSArd Biesheuvel 			     EFI_LOADER_DATA, nr_pages, &efi_addr);
295f57db62cSArd Biesheuvel 	new_addr = efi_addr;
296f57db62cSArd Biesheuvel 	/*
297f57db62cSArd Biesheuvel 	 * If preferred address allocation failed allocate as low as
298f57db62cSArd Biesheuvel 	 * possible.
299f57db62cSArd Biesheuvel 	 */
300f57db62cSArd Biesheuvel 	if (status != EFI_SUCCESS) {
301f57db62cSArd Biesheuvel 		status = efi_low_alloc_above(alloc_size, alignment, &new_addr,
302f57db62cSArd Biesheuvel 					     min_addr);
303f57db62cSArd Biesheuvel 	}
304f57db62cSArd Biesheuvel 	if (status != EFI_SUCCESS) {
305f57db62cSArd Biesheuvel 		pr_efi_err("Failed to allocate usable memory for kernel.\n");
306f57db62cSArd Biesheuvel 		return status;
307f57db62cSArd Biesheuvel 	}
308f57db62cSArd Biesheuvel 
309f57db62cSArd Biesheuvel 	/*
310f57db62cSArd Biesheuvel 	 * We know source/dest won't overlap since both memory ranges
311f57db62cSArd Biesheuvel 	 * have been allocated by UEFI, so we can safely use memcpy.
312f57db62cSArd Biesheuvel 	 */
313f57db62cSArd Biesheuvel 	memcpy((void *)new_addr, (void *)cur_image_addr, image_size);
314f57db62cSArd Biesheuvel 
315f57db62cSArd Biesheuvel 	/* Return the new address of the relocated image. */
316f57db62cSArd Biesheuvel 	*image_addr = new_addr;
317f57db62cSArd Biesheuvel 
318f57db62cSArd Biesheuvel 	return status;
319f57db62cSArd Biesheuvel }
320