306 lines
7.8 KiB
C
Raw Normal View History

2026-01-21 18:59:54 +08:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2016 Linaro Ltd; <ard.biesheuvel@linaro.org>
*/
#include <linux/efi.h>
#include <linux/log2.h>
#include <asm/efi.h>
#include "efistub.h"
2026-01-29 22:25:33 +08:00
#if defined (CONFIG_KASLR_SKIP_MEM_RANGE) && defined (CONFIG_ARM64)
#define CAL_SLOTS_NUMBER 0
#define CAL_SLOTS_PHYADDR 1
#define MAX_MEMMAP_REGIONS 32
#define MAX_MEM_NOKASLR_REGIONS 4
enum mem_avoid_index {
MAX_MEMMAP_REGIONS_BEGIN = 0,
MAX_MEMMAP_REGIONS_END = MAX_MEMMAP_REGIONS_BEGIN + MAX_MEMMAP_REGIONS - 1,
MEM_AVOID_MEM_NOKASLR_BEGIN,
MEM_AVOID_MEM_NOKASLR_END = MEM_AVOID_MEM_NOKASLR_BEGIN + MAX_MEM_NOKASLR_REGIONS - 1,
MEM_AVOID_MAX,
};
struct mem_vector {
unsigned long long start;
unsigned long long size;
};
static struct mem_vector mem_avoid[MEM_AVOID_MAX];
static bool mem_overlaps(struct mem_vector *one, struct mem_vector *two)
{
if (one->start + one->size <= two->start)
return false;
if (one->start >= two->start + two->size)
return false;
return true;
}
static bool mem_avoid_overlap(struct mem_vector *region, struct mem_vector *overlap)
{
int i;
u64 earliest = region->start + region->size;
bool is_overlapping = false;
for (i = 0; i < MEM_AVOID_MAX; i++) {
if (mem_overlaps(region, &mem_avoid[i]) &&
mem_avoid[i].start < earliest) {
*overlap = mem_avoid[i];
earliest = overlap->start;
is_overlapping = true;
}
}
return is_overlapping;
}
static unsigned long cal_slots_avoid_overlap(efi_memory_desc_t *md, unsigned long size, u8 cal_type,
unsigned long align_shift, unsigned long target)
{
struct mem_vector region, overlap;
unsigned long region_end, first, last;
unsigned long align = 1UL << align_shift;
unsigned long total_slots = 0, slots;
region.start = md->phys_addr;
region_end = min(md->phys_addr + md->num_pages * EFI_PAGE_SIZE - 1, (u64)ULONG_MAX);
while (region.start < region_end) {
first = round_up(region.start, align);
last = round_down(region_end - size + 1, align);
if (first > last)
break;
region.size = region_end - region.start + 1;
if (!mem_avoid_overlap(&region, &overlap)) {
slots = ((last - first) >> align_shift) + 1;
total_slots += slots;
if (cal_type == CAL_SLOTS_PHYADDR)
return first + target * align;
break;
}
if (overlap.start >= region.start + size) {
slots = ((round_up(overlap.start - size + 1, align) - first) >>
align_shift) + 1;
total_slots += slots;
if (cal_type == CAL_SLOTS_PHYADDR) {
if (target > slots)
target -= slots;
else
return first + target * align;
}
}
/* Clip off the overlapping region and start over. */
region.start = overlap.start + overlap.size;
}
return total_slots;
}
void mem_avoid_memmap(char *str)
{
static int i;
while (str && (i < MAX_MEMMAP_REGIONS)) {
char *oldstr;
u64 start, size;
char *k = strchr(str, ',');
if (k)
*k++ = 0;
oldstr = str;
size = memparse(str, &str);
if (str == oldstr || *str != '$') {
efi_warn("memap values error.\n");
break;
}
start = memparse(str + 1, &str);
if (size <= 0) {
efi_warn("memap values error, size should be more than 0.\n");
break;
}
mem_avoid[MAX_MEMMAP_REGIONS_BEGIN + i].start = start;
mem_avoid[MAX_MEMMAP_REGIONS_BEGIN + i].size = size;
str = k;
i++;
}
}
void mem_avoid_mem_nokaslr(char *str)
{
int i = 0;
while (str && (i < MAX_MEM_NOKASLR_REGIONS)) {
char *oldstr;
u64 start, end;
char *k = strchr(str, ',');
if (k)
*k++ = 0;
oldstr = str;
start = memparse(str, &str);
if (str == oldstr || *str != '-') {
efi_warn("nokaslr values error.\n");
break;
}
end = memparse(str + 1, &str);
if (start >= end) {
efi_warn("nokaslr values error, start should be less than end.\n");
break;
}
mem_avoid[MEM_AVOID_MEM_NOKASLR_BEGIN + i].start = start;
mem_avoid[MEM_AVOID_MEM_NOKASLR_BEGIN + i].size = end - start;
str = k;
i++;
}
}
#endif
2026-01-21 18:59:54 +08:00
/*
* Return the number of slots covered by this entry, i.e., the number of
* addresses it covers that are suitably aligned and supply enough room
* for the allocation.
*/
static unsigned long get_entry_num_slots(efi_memory_desc_t *md,
unsigned long size,
2026-01-29 22:25:33 +08:00
unsigned long align_shift,
u64 alloc_min, u64 alloc_max)
2026-01-21 18:59:54 +08:00
{
unsigned long align = 1UL << align_shift;
u64 first_slot, last_slot, region_end;
if (md->type != EFI_CONVENTIONAL_MEMORY)
return 0;
if (efi_soft_reserve_enabled() &&
(md->attribute & EFI_MEMORY_SP))
return 0;
region_end = min(md->phys_addr + md->num_pages * EFI_PAGE_SIZE - 1,
2026-01-29 22:25:33 +08:00
alloc_max);
2026-01-21 18:59:54 +08:00
if (region_end < size)
return 0;
2026-01-29 22:25:33 +08:00
first_slot = round_up(max(md->phys_addr, alloc_min), align);
2026-01-21 18:59:54 +08:00
last_slot = round_down(region_end - size + 1, align);
if (first_slot > last_slot)
return 0;
2026-01-29 22:25:33 +08:00
#if defined (CONFIG_KASLR_SKIP_MEM_RANGE) && defined (CONFIG_ARM64)
return cal_slots_avoid_overlap(md, size, CAL_SLOTS_NUMBER, align_shift, 0);
#else
2026-01-21 18:59:54 +08:00
return ((unsigned long)(last_slot - first_slot) >> align_shift) + 1;
2026-01-29 22:25:33 +08:00
#endif
2026-01-21 18:59:54 +08:00
}
/*
* The UEFI memory descriptors have a virtual address field that is only used
* when installing the virtual mapping using SetVirtualAddressMap(). Since it
* is unused here, we can reuse it to keep track of each descriptor's slot
* count.
*/
#define MD_NUM_SLOTS(md) ((md)->virt_addr)
efi_status_t efi_random_alloc(unsigned long size,
unsigned long align,
unsigned long *addr,
2026-01-29 22:25:33 +08:00
unsigned long random_seed,
int memory_type,
unsigned long alloc_min,
unsigned long alloc_max)
2026-01-21 18:59:54 +08:00
{
2026-01-29 22:25:33 +08:00
unsigned long total_slots = 0, target_slot;
unsigned long total_mirrored_slots = 0;
struct efi_boot_memmap *map;
2026-01-21 18:59:54 +08:00
efi_status_t status;
int map_offset;
2026-01-29 22:25:33 +08:00
status = efi_get_memory_map(&map, false);
2026-01-21 18:59:54 +08:00
if (status != EFI_SUCCESS)
return status;
if (align < EFI_ALLOC_ALIGN)
align = EFI_ALLOC_ALIGN;
size = round_up(size, EFI_ALLOC_ALIGN);
/* count the suitable slots in each memory map entry */
2026-01-29 22:25:33 +08:00
for (map_offset = 0; map_offset < map->map_size; map_offset += map->desc_size) {
efi_memory_desc_t *md = (void *)map->map + map_offset;
2026-01-21 18:59:54 +08:00
unsigned long slots;
2026-01-29 22:25:33 +08:00
slots = get_entry_num_slots(md, size, ilog2(align), alloc_min,
alloc_max);
2026-01-21 18:59:54 +08:00
MD_NUM_SLOTS(md) = slots;
total_slots += slots;
2026-01-29 22:25:33 +08:00
if (md->attribute & EFI_MEMORY_MORE_RELIABLE)
total_mirrored_slots += slots;
2026-01-21 18:59:54 +08:00
}
2026-01-29 22:25:33 +08:00
/* consider only mirrored slots for randomization if any exist */
if (total_mirrored_slots > 0)
total_slots = total_mirrored_slots;
2026-01-21 18:59:54 +08:00
/* find a random number between 0 and total_slots */
target_slot = (total_slots * (u64)(random_seed & U32_MAX)) >> 32;
/*
* target_slot is now a value in the range [0, total_slots), and so
* it corresponds with exactly one of the suitable slots we recorded
* when iterating over the memory map the first time around.
*
* So iterate over the memory map again, subtracting the number of
* slots of each entry at each iteration, until we have found the entry
* that covers our chosen slot. Use the residual value of target_slot
* to calculate the randomly chosen address, and allocate it directly
* using EFI_ALLOCATE_ADDRESS.
*/
2026-01-29 22:25:33 +08:00
status = EFI_OUT_OF_RESOURCES;
for (map_offset = 0; map_offset < map->map_size; map_offset += map->desc_size) {
efi_memory_desc_t *md = (void *)map->map + map_offset;
2026-01-21 18:59:54 +08:00
efi_physical_addr_t target;
unsigned long pages;
2026-01-29 22:25:33 +08:00
if (total_mirrored_slots > 0 &&
!(md->attribute & EFI_MEMORY_MORE_RELIABLE))
continue;
2026-01-21 18:59:54 +08:00
if (target_slot >= MD_NUM_SLOTS(md)) {
target_slot -= MD_NUM_SLOTS(md);
continue;
}
2026-01-29 22:25:33 +08:00
#if defined (CONFIG_KASLR_SKIP_MEM_RANGE) && defined (CONFIG_ARM64)
target = cal_slots_avoid_overlap(md, size, CAL_SLOTS_PHYADDR, ilog2(align),
target_slot);
#else
2026-01-21 18:59:54 +08:00
target = round_up(md->phys_addr, align) + target_slot * align;
2026-01-29 22:25:33 +08:00
#endif
2026-01-21 18:59:54 +08:00
pages = size / EFI_PAGE_SIZE;
status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
2026-01-29 22:25:33 +08:00
memory_type, pages, &target);
2026-01-21 18:59:54 +08:00
if (status == EFI_SUCCESS)
*addr = target;
break;
}
2026-01-29 22:25:33 +08:00
efi_bs_call(free_pool, map);
2026-01-21 18:59:54 +08:00
return status;
}