Skip to content

Commit

Permalink
efi/libstub: Move memory map handling and allocation routines to mem.c
Browse files Browse the repository at this point in the history
Create a new source file mem.c to keep the routines involved in memory
allocation and deallocation and manipulation of the EFI memory map.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
  • Loading branch information
Ard Biesheuvel committed Feb 23, 2020
1 parent 184d7e0 commit f57db62
Show file tree
Hide file tree
Showing 3 changed files with 320 additions and 314 deletions.
2 changes: 1 addition & 1 deletion drivers/firmware/efi/libstub/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ OBJECT_FILES_NON_STANDARD := y
KCOV_INSTRUMENT := n

lib-y := efi-stub-helper.o gop.o secureboot.o tpm.o \
random.o pci.o
mem.o random.o pci.o

# include the stub's generic dependencies from lib/ when building for ARM/arm64
arm-deps-y := fdt_rw.c fdt_ro.c fdt_wip.c fdt.c fdt_empty_tree.c fdt_sw.c
Expand Down
313 changes: 0 additions & 313 deletions drivers/firmware/efi/libstub/efi-stub-helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ bool __pure __efi_soft_reserve_enabled(void)
return !efi_nosoftreserve;
}

#define EFI_MMAP_NR_SLACK_SLOTS 8

struct file_info {
efi_file_handle_t *handle;
u64 size;
Expand All @@ -77,64 +75,6 @@ void efi_printk(char *str)
}
}

static inline bool mmap_has_headroom(unsigned long buff_size,
unsigned long map_size,
unsigned long desc_size)
{
unsigned long slack = buff_size - map_size;

return slack / desc_size >= EFI_MMAP_NR_SLACK_SLOTS;
}

efi_status_t efi_get_memory_map(struct efi_boot_memmap *map)
{
efi_memory_desc_t *m = NULL;
efi_status_t status;
unsigned long key;
u32 desc_version;

*map->desc_size = sizeof(*m);
*map->map_size = *map->desc_size * 32;
*map->buff_size = *map->map_size;
again:
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
*map->map_size, (void **)&m);
if (status != EFI_SUCCESS)
goto fail;

*map->desc_size = 0;
key = 0;
status = efi_bs_call(get_memory_map, map->map_size, m,
&key, map->desc_size, &desc_version);
if (status == EFI_BUFFER_TOO_SMALL ||
!mmap_has_headroom(*map->buff_size, *map->map_size,
*map->desc_size)) {
efi_bs_call(free_pool, m);
/*
* Make sure there is some entries of headroom so that the
* buffer can be reused for a new map after allocations are
* no longer permitted. Its unlikely that the map will grow to
* exceed this headroom once we are ready to trigger
* ExitBootServices()
*/
*map->map_size += *map->desc_size * EFI_MMAP_NR_SLACK_SLOTS;
*map->buff_size = *map->map_size;
goto again;
}

if (status != EFI_SUCCESS)
efi_bs_call(free_pool, m);

if (map->key_ptr && status == EFI_SUCCESS)
*map->key_ptr = key;
if (map->desc_ver && status == EFI_SUCCESS)
*map->desc_ver = desc_version;

fail:
*map->map = m;
return status;
}


unsigned long get_dram_base(void)
{
Expand Down Expand Up @@ -170,192 +110,6 @@ unsigned long get_dram_base(void)
return membase;
}

/*
* Allocate at the highest possible address that is not above 'max'.
*/
efi_status_t efi_high_alloc(unsigned long size, unsigned long align,
unsigned long *addr, unsigned long max)
{
unsigned long map_size, desc_size, buff_size;
efi_memory_desc_t *map;
efi_status_t status;
unsigned long nr_pages;
u64 max_addr = 0;
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;
again:
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 (end > max)
end = max;

if ((start + size) > end)
continue;

if (round_down(end - size, align) < start)
continue;

start = round_down(end - size, align);

/*
* Don't allocate at 0x0. It will confuse code that
* checks pointers against NULL.
*/
if (start == 0x0)
continue;

if (start > max_addr)
max_addr = start;
}

if (!max_addr)
status = EFI_NOT_FOUND;
else {
status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
EFI_LOADER_DATA, nr_pages, &max_addr);
if (status != EFI_SUCCESS) {
max = max_addr;
max_addr = 0;
goto again;
}

*addr = max_addr;
}

efi_bs_call(free_pool, map);
fail:
return status;
}

/*
* Allocate at the lowest possible address that is not below 'min'.
*/
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;
}

void efi_free(unsigned long size, unsigned long addr)
{
unsigned long nr_pages;

if (!size)
return;

nr_pages = round_up(size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;
efi_bs_call(free_pages, addr, nr_pages);
}

static efi_status_t efi_file_size(void *__fh, efi_char16_t *filename_16,
void **handle, u64 *file_sz)
{
Expand Down Expand Up @@ -695,73 +449,6 @@ efi_status_t handle_cmdline_files(efi_loaded_image_t *image,

return status;
}
/*
* 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.
*/
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;
}

/*
* Get the number of UTF-8 bytes corresponding to an UTF-16 character.
* This overestimates for surrogates, but that is okay.
Expand Down
Loading

0 comments on commit f57db62

Please sign in to comment.