[PATCH v1 05/54] efi: loader: add memory accounting
Ahmad Fatoum
a.fatoum at pengutronix.de
Thu Dec 18 02:37:25 PST 2025
From: Ahmad Fatoum <ahmad at a3f.at>
For use by the EFI runtime, we implement here a naive allocator that
allocates all memory at the highest possible free address.
In future, we may consider switching to Linux' red-black tree based interval
trees, but this is good enough for now.
Signed-off-by: Ahmad Fatoum <a.fatoum at pengutronix.de>
---
arch/Kconfig | 3 +
arch/arm/Kconfig | 1 +
efi/Kconfig | 19 ++
efi/Makefile | 1 +
efi/loader/Kconfig | 3 +
efi/loader/Makefile | 3 +
efi/loader/memory.c | 416 +++++++++++++++++++++++++++++++++++++++++
include/efi/loader.h | 33 ++++
include/efi/types.h | 14 ++
include/linux/ioport.h | 2 +
include/memory.h | 2 +
11 files changed, 497 insertions(+)
create mode 100644 efi/loader/Kconfig
create mode 100644 efi/loader/Makefile
create mode 100644 efi/loader/memory.c
create mode 100644 include/efi/loader.h
diff --git a/arch/Kconfig b/arch/Kconfig
index 5af114a6e9e5..ca9aa25a9c4b 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -107,6 +107,9 @@ config HAVE_EFI_PAYLOAD
config HAVE_EFI_STUB
bool
+config HAVE_EFI_LOADER
+ bool
+
config PHYS_ADDR_T_64BIT
bool
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index d2d81a4f90d6..4b7f5b83c67e 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -17,6 +17,7 @@ config ARM
select HAVE_ARCH_BOOTM_OFTREE
select HW_HAS_PCI
select ARCH_HAS_DMA_WRITE_COMBINE
+ select HAVE_EFI_LOADER if MMU # for payload unaligned accesses
default y
config ARCH_LINUX_NAME
diff --git a/efi/Kconfig b/efi/Kconfig
index 23ac601af647..766d6b0e3094 100644
--- a/efi/Kconfig
+++ b/efi/Kconfig
@@ -25,6 +25,25 @@ if EFI_PAYLOAD
source "efi/payload/Kconfig"
endif
+config EFI_LOADER
+ bool "barebox as EFI loader (provider)"
+ depends on HAVE_EFI_LOADER
+ select EFI
+ select EFI_GUID
+ select EFI_DEVICEPATH
+ select MEMORY_ATTRIBUTES
+ help
+ Select this option if you want to run UEFI applications (like GNU
+ GRUB or an EFI-stubbed kernel) on top of barebox.
+
+ If this option is enabled, barebox will expose the UEFI API to
+ loaded applications, enabling them to reuse barebox device drivers
+ and file systems.
+
+if EFI_LOADER
+source "efi/loader/Kconfig"
+endif
+
config EFI
bool
diff --git a/efi/Makefile b/efi/Makefile
index 4c35917475c0..e0f6ac549009 100644
--- a/efi/Makefile
+++ b/efi/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_EFI_PAYLOAD) += payload/
+obj-$(CONFIG_EFI_LOADER) += loader/
obj-$(CONFIG_EFI_GUID) += guid.o
obj-$(CONFIG_EFI_DEVICEPATH) += devicepath.o
obj-y += errno.o handle.o efivar.o efivar-filename.o
diff --git a/efi/loader/Kconfig b/efi/loader/Kconfig
new file mode 100644
index 000000000000..e7080d8d5093
--- /dev/null
+++ b/efi/loader/Kconfig
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# SPDX-Comment: Origin-URL: https://github.com/u-boot/u-boot/blob/a0fe8cedcbe8c76403a77e57eac228b8f778a3ae/lib/efi_loader/Kconfig
+
diff --git a/efi/loader/Makefile b/efi/loader/Makefile
new file mode 100644
index 000000000000..dea1e06c18cf
--- /dev/null
+++ b/efi/loader/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-y += memory.o
diff --git a/efi/loader/memory.c b/efi/loader/memory.c
new file mode 100644
index 000000000000..075b559367fe
--- /dev/null
+++ b/efi/loader/memory.c
@@ -0,0 +1,416 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#define pr_fmt(fmt) "efi-loader: memory: " fmt
+
+#include <linux/minmax.h>
+#include <linux/printk.h>
+#include <linux/sprintf.h>
+#include <efi/loader.h>
+#include <efi/error.h>
+#include <init.h>
+#include <memory.h>
+#include <linux/list_sort.h>
+#include <linux/sizes.h>
+#include <dma.h>
+
+efi_uintn_t efi_memory_map_key;
+
+static efi_status_t find_pages_max(struct list_head *banks, size_t npages, size_t *page)
+{
+ struct memory_bank *bank;
+
+ list_for_each_entry_reverse(bank, banks, list) {
+ if ((bank->res->start >> EFI_PAGE_SHIFT) > *page)
+ continue;
+
+ for_each_memory_bank_region_reverse(bank, region) {
+ resource_size_t gap_firstpage, gap_lastpage;
+ resource_size_t candidate_last;
+
+ if (!region_is_gap(region))
+ continue;
+
+ gap_firstpage = EFI_PAGE_ALIGN(region->start) >> EFI_PAGE_SHIFT;
+ gap_lastpage = (EFI_PAGE_ALIGN(region->end + 1) >> EFI_PAGE_SHIFT);
+
+ if (!gap_lastpage || gap_lastpage < gap_firstpage)
+ continue;
+
+ /* make last page inclusive */
+ gap_lastpage--;
+
+ candidate_last = min_t(resource_size_t, gap_lastpage, *page);
+ if (candidate_last < gap_firstpage)
+ continue;
+
+ if (candidate_last - gap_firstpage + 1 < npages)
+ continue;
+
+ *page = candidate_last + 1 - npages;
+ return EFI_SUCCESS;
+ }
+ }
+
+ return EFI_OUT_OF_RESOURCES;
+}
+
+
+static const char *efi_memory_type_tostr(enum efi_memory_type type)
+{
+ switch (type) {
+ case EFI_RESERVED_TYPE:
+ return "res";
+ case EFI_LOADER_CODE:
+ return "ldcode";
+ case EFI_LOADER_DATA:
+ return "lddata";
+ case EFI_BOOT_SERVICES_CODE:
+ return "bscode";
+ case EFI_BOOT_SERVICES_DATA:
+ return "bsdata";
+ case EFI_RUNTIME_SERVICES_CODE:
+ return "rtcode";
+ case EFI_RUNTIME_SERVICES_DATA:
+ return "rtdata";
+ case EFI_CONVENTIONAL_MEMORY:
+ return "memory";
+ case EFI_UNUSABLE_MEMORY:
+ return "unsable";
+ case EFI_ACPI_RECLAIM_MEMORY:
+ return "acpireclaim";
+ case EFI_ACPI_MEMORY_NVS:
+ return "acpinvs";
+ case EFI_MEMORY_MAPPED_IO:
+ return "mmio";
+ case EFI_MEMORY_MAPPED_IO_PORT_SPACE:
+ return "mmioport";
+ case EFI_PAL_CODE:
+ return "pal";
+ case EFI_PERSISTENT_MEMORY_TYPE:
+ return "persistent";
+ case EFI_UNACCEPTED_MEMORY_TYPE:
+ return "unaccepted";
+ default:
+ return "unknown";
+ }
+}
+
+static u64 efi_memory_type_default_attrs(enum efi_memory_type type)
+{
+ switch (type) {
+ case EFI_RESERVED_TYPE:
+ case EFI_MEMORY_MAPPED_IO:
+ case EFI_MEMORY_MAPPED_IO_PORT_SPACE:
+ case EFI_UNACCEPTED_MEMORY_TYPE:
+ return 0;
+ case EFI_BOOT_SERVICES_CODE:
+ case EFI_LOADER_CODE:
+ case EFI_RUNTIME_SERVICES_CODE:
+ case EFI_ACPI_RECLAIM_MEMORY:
+ case EFI_ACPI_MEMORY_NVS:
+ return MEMATTRS_RWX;
+ case EFI_LOADER_DATA:
+ case EFI_BOOT_SERVICES_DATA:
+ case EFI_RUNTIME_SERVICES_DATA:
+ return MEMATTRS_RW;
+ case EFI_CONVENTIONAL_MEMORY:
+ return MEMATTRS_RWX;
+ case EFI_PAL_CODE:
+ return MEMATTRS_FAULT;
+ case EFI_PERSISTENT_MEMORY_TYPE:
+ return MEMATTRS_RW | MEMATTR_SP;
+ case EFI_UNUSABLE_MEMORY:
+ case EFI_MAX_MEMORY_TYPE:
+ pr_warn("Unallocatable type %u\n", type); // FIXME
+ return MEMATTRS_FAULT;
+ }
+
+ return MEMATTRS_RWX;
+}
+
+/**
+ * efi_allocate_pages - allocate memory pages
+ *
+ * @type: type of allocation to be performed
+ * @memory_type: usage type of the allocated memory
+ * @pages: number of pages to be allocated
+ * @memory: allocated memory
+ * @name: name for informational purposes
+ * Return: status code
+ */
+efi_status_t efi_allocate_pages(enum efi_allocate_type type,
+ enum efi_memory_type memory_type,
+ efi_uintn_t npages, uint64_t *memory,
+ const char *name)
+{
+ char namebuf[64];
+ const char *typestr;
+ struct resource *res;
+ uint64_t attrs, new_addr = *memory;
+ size_t new_page;
+ efi_status_t r;
+
+ ++efi_memory_map_key;
+
+ if (npages == 0)
+ return EFI_INVALID_PARAMETER;
+
+ switch (type) {
+ case EFI_ALLOCATE_ANY_PAGES:
+ new_addr = U64_MAX;
+ typestr = "any";
+ fallthrough;
+ case EFI_ALLOCATE_MAX_ADDRESS:
+ new_page = new_addr >> EFI_PAGE_SHIFT;
+ r = find_pages_max(&memory_banks, npages, &new_page);
+ if (r != EFI_SUCCESS)
+ return r;
+ new_addr = new_page << EFI_PAGE_SHIFT;
+ typestr = "max";
+ break;
+ case EFI_ALLOCATE_ADDRESS:
+ typestr = "exact";
+ break;
+ default:
+ /* UEFI doesn't specify other allocation types */
+ return EFI_INVALID_PARAMETER;
+ }
+
+ scnprintf(namebuf, sizeof(namebuf), "efi%zu-%s%c%s", efi_memory_map_key,
+ efi_memory_type_tostr(memory_type),
+ name ? '-' : '\0', name ?: "");
+
+ attrs = efi_memory_type_default_attrs(memory_type);
+ if (!attrs)
+ return EFI_INVALID_PARAMETER;
+
+ res = request_sdram_region(namebuf, new_addr, npages << EFI_PAGE_SHIFT,
+ efi_memory_type_to_resource_type(memory_type),
+ attrs);
+ if (!res) {
+ pr_err("failed to request %s at page 0x%zx+%zu (%s)\n",
+ namebuf, new_page, npages, typestr);
+ dump_stack();
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ if (memory_type == EFI_RUNTIME_SERVICES_CODE ||
+ memory_type == EFI_RUNTIME_SERVICES_DATA)
+ res->runtime = true;
+
+ res->flags |= IORESOURCE_EFI_ALLOC;
+
+ *memory = new_addr;
+ return EFI_SUCCESS;
+}
+
+static int free_efi_only(struct resource *res, void *data)
+{
+ int *nfreed = data;
+
+ if (!(res->flags & IORESOURCE_EFI_ALLOC)) {
+ pr_warn("refusing to free non-EFI allocated resource %s at 0x%llx\n",
+ res->name, res->start);
+ *nfreed = -1;
+ return false;
+ }
+
+ if (nfreed >= 0)
+ ++*nfreed;
+ return true;
+}
+
+// SPDX-SnippetBegin
+// SPDX-License-Identifier: GPL-2.0+
+// SPDX-SnippetCopyrightText: 2016 Alexander Graf
+// SPDX-SnippetComment: Origin-URL: https://github.com/u-boot/u-boot/blob/aa703a816a62deb876a1e77ccff030a7cc60f344/lib/efi_loader/efi_memory.c
+
+/**
+ * efi_alloc_aligned_pages() - allocate aligned memory pages
+ *
+ * @len: len in bytes
+ * @memory_type: usage type of the allocated memory
+ * @align: alignment in bytes
+ * @name: name for informational purposes
+ * Return: aligned memory or NULL
+ */
+void *efi_alloc_aligned_pages(u64 len, int memory_type, size_t align,
+ const char *name)
+{
+ u64 req_pages = efi_size_in_pages(len);
+ u64 true_pages = req_pages + efi_size_in_pages(align) - 1;
+ u64 free_pages;
+ u64 aligned_mem;
+ efi_status_t r;
+ u64 mem;
+
+ /* align must be zero or a power of two */
+ if (align & (align - 1))
+ return NULL;
+
+ /* Check for overflow */
+ if (true_pages < req_pages)
+ return NULL;
+
+ if (align < EFI_PAGE_SIZE) {
+ r = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, memory_type,
+ req_pages, &mem, name);
+ return (r == EFI_SUCCESS) ? (void *)(uintptr_t)mem : NULL;
+ }
+
+ r = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, memory_type,
+ true_pages, &mem, name);
+ if (r != EFI_SUCCESS)
+ return NULL;
+
+ aligned_mem = ALIGN(mem, align);
+ /* Free pages before alignment */
+ free_pages = efi_size_in_pages(aligned_mem - mem);
+ if (free_pages)
+ efi_free_pages(mem, free_pages);
+
+ /* Free trailing pages */
+ free_pages = true_pages - (req_pages + free_pages);
+ if (free_pages) {
+ mem = aligned_mem + req_pages * EFI_PAGE_SIZE;
+ efi_free_pages(mem, free_pages);
+ }
+
+ return (void *)(uintptr_t)aligned_mem;
+}
+
+// SPDX-SnippetEnd
+
+/**
+ * efi_free_pages() - free memory pages
+ *
+ * @memory: start of the memory area to be freed
+ * @pages: number of pages to be freed
+ * Return: status code
+ */
+efi_status_t efi_free_pages(uint64_t memory, size_t pages)
+{
+ size_t size = pages << EFI_PAGE_SHIFT;
+ struct memory_bank *bank;
+ int nfreed = 0;
+
+ for_each_memory_bank(bank)
+ release_region_range(bank->res, memory, size, free_efi_only, &nfreed);
+
+ if (!nfreed)
+ pr_warn("can't free %llx: not found\n", memory);
+ if (nfreed <= 0)
+ return EFI_INVALID_PARAMETER;
+
+ return EFI_SUCCESS;
+}
+
+static int efi_memory_desc_from_res(const struct resource *region,
+ struct efi_memory_desc *desc)
+{
+ efi_physical_addr_t phys_start = EFI_PAGE_ALIGN(region->start);
+ efi_uintn_t npages = efi_size_in_pages(EFI_PAGE_ALIGN(region->end + 1) - phys_start);
+
+ if (!npages)
+ return 0;
+
+ desc->phys_start = phys_start;
+ desc->virt_start = efi_phys_to_virt(phys_start);
+ desc->npages = npages;
+
+ if (region->flags & IORESOURCE_TYPE_VALID) {
+ desc->type = resource_get_efi_memory_type(region);
+ desc->attrs = resource_get_efi_memory_attrs(region);
+ } else {
+ pr_warn("encountered SDRAM region 0x%pa-0x%pa without valid type\n",
+ ®ion->start, ®ion->end);
+ desc->type = EFI_RESERVED_TYPE;
+ desc->attrs = EFI_MEMORY_WB;
+ }
+
+ return 1;
+}
+
+/**
+ * efi_get_memory_map() - get map describing memory usage.
+ *
+ * @memory_map_size: on entry the size, in bytes, of the memory map buffer,
+ * on exit the size of the copied memory map
+ * @memory_map: buffer to which the memory map is written
+ * @map_key: key for the memory map
+ * @descriptor_size: size of an individual memory descriptor
+ * @descriptor_version: version number of the memory descriptor structure
+ * Return: status code
+ */
+efi_status_t efi_get_memory_map(size_t *memory_map_size,
+ struct efi_memory_desc *memory_map,
+ efi_uintn_t *map_key,
+ size_t *descriptor_size,
+ uint32_t *descriptor_version)
+{
+ size_t map_size = 0;
+ int i = 0, map_entries = 0;
+ size_t provided_map_size;
+ struct memory_bank *bank;
+
+ if (!memory_map_size)
+ return EFI_INVALID_PARAMETER;
+
+ provided_map_size = *memory_map_size;
+
+ for_each_memory_bank(bank) {
+ for_each_memory_bank_region(bank, region) {
+ if (list_empty(®ion->children)) {
+ map_entries++;
+ continue;
+ }
+
+ for_each_resource_region(region, bbregion)
+ map_entries++;
+ }
+ }
+
+ map_size = map_entries * sizeof(struct efi_memory_desc);
+
+ /* Note: some regions may end up being 0-sized after alignment to page
+ * boundaries and those will be skipped later.
+ *
+ * It's fine wrt *memory_map_size though as worst case, this means we
+ * ask the caller to allocate a little more memory than actually needed.
+ *
+ * TODO: Should we rather enforce resource allocations from memory banks
+ * to be page aligned from the outset?
+ */
+ *memory_map_size = map_size;
+
+ if (descriptor_size)
+ *descriptor_size = sizeof(struct efi_memory_desc);
+
+ if (descriptor_version)
+ *descriptor_version = EFI_MEMORY_DESCRIPTOR_VERSION;
+
+ if (provided_map_size < map_size)
+ return EFI_BUFFER_TOO_SMALL;
+
+ if (!memory_map)
+ return EFI_INVALID_PARAMETER;
+
+ for_each_memory_bank(bank) {
+ for_each_memory_bank_region(bank, region) {
+ if (list_empty(®ion->children)) {
+ i += efi_memory_desc_from_res(region, &memory_map[i]);
+ continue;
+ }
+
+ for_each_resource_region(region, bbregion)
+ i += efi_memory_desc_from_res(bbregion, &memory_map[i]);
+ }
+ }
+
+ *memory_map_size = i * sizeof(struct efi_memory_desc);
+
+ if (map_key)
+ *map_key = efi_memory_map_key;
+
+ return EFI_SUCCESS;
+}
diff --git a/include/efi/loader.h b/include/efi/loader.h
new file mode 100644
index 000000000000..a5359f07f125
--- /dev/null
+++ b/include/efi/loader.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-SnippetCopyrightText: 2016 Alexander Graf */
+
+#ifndef _EFI_LOADER_H
+#define _EFI_LOADER_H 1
+
+#include <efi/types.h>
+#include <efi/services.h>
+#include <efi/memory.h>
+
+#define EFI_SPECIFICATION_VERSION (2 << 16 | 80)
+
+/* Key identifying current memory map */
+extern efi_uintn_t efi_memory_map_key;
+
+/* Allocate pages on the specified alignment */
+void *efi_alloc_aligned_pages(u64 len, int memory_type, size_t align,
+ const char *name);
+/* More specific EFI memory allocator, called by EFI payloads */
+efi_status_t efi_allocate_pages(enum efi_allocate_type type,
+ enum efi_memory_type memory_type,
+ efi_uintn_t pages, uint64_t *memory,
+ const char *name);
+/* EFI memory free function. */
+efi_status_t efi_free_pages(uint64_t memory, efi_uintn_t pages);
+/* Returns the EFI memory map */
+efi_status_t efi_get_memory_map(efi_uintn_t *memory_map_size,
+ struct efi_memory_desc *memory_map,
+ efi_uintn_t *map_key,
+ efi_uintn_t *descriptor_size,
+ uint32_t *descriptor_version);
+
+#endif /* _EFI_LOADER_H */
diff --git a/include/efi/types.h b/include/efi/types.h
index f157f7ffe106..e9acc4c497d1 100644
--- a/include/efi/types.h
+++ b/include/efi/types.h
@@ -10,6 +10,7 @@
#include <linux/limits.h>
#include <linux/stddef.h>
#include <linux/compiler.h>
+#include <linux/align.h>
#include <linux/uuid.h>
typedef unsigned long efi_status_t;
@@ -120,6 +121,19 @@ enum efi_allocate_type {
#define EFI_PAGE_SHIFT 12
#define EFI_PAGE_SIZE (1ULL << EFI_PAGE_SHIFT)
#define EFI_PAGE_MASK (EFI_PAGE_SIZE - 1)
+#define EFI_PAGE_ALIGN(val) ALIGN((val), EFI_PAGE_SIZE)
+#define EFI_PAGE_ALIGN_DOWN(val) ALIGN_DOWN((val), EFI_PAGE_SIZE)
+
+/**
+ * efi_size_in_pages() - convert size in bytes to size in pages
+ *
+ * This macro returns the number of EFI memory pages required to hold 'size'
+ * bytes.
+ *
+ * @size: size in bytes
+ * Return: size in pages
+ */
+#define efi_size_in_pages(size) (((size) + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT)
#endif
diff --git a/include/linux/ioport.h b/include/linux/ioport.h
index d57defd20ee1..20c72e1b6cbb 100644
--- a/include/linux/ioport.h
+++ b/include/linux/ioport.h
@@ -58,6 +58,8 @@ struct resource {
#define IORESOURCE_TYPE_VALID 0x00800000 /* type & attrs are valid */
+#define IORESOURCE_EFI_ALLOC 0x02000000 /* Resource allocated by barebox EFI loader */
+
#define IORESOURCE_EXCLUSIVE 0x08000000 /* Userland may not map this resource */
#define IORESOURCE_DISABLED 0x10000000
#define IORESOURCE_UNSET 0x20000000
diff --git a/include/memory.h b/include/memory.h
index 6a6f0e2eb7d3..6189a0f65c50 100644
--- a/include/memory.h
+++ b/include/memory.h
@@ -26,6 +26,8 @@ int barebox_add_memory_bank(const char *name, resource_size_t start,
resource_size_t size);
#define for_each_memory_bank(mem) list_for_each_entry(mem, &memory_banks, list)
+#define for_each_memory_bank_reverse(mem) \
+ list_for_each_entry_reverse(mem, &memory_banks, list)
#define for_each_reserved_region(mem, rsv) \
list_for_each_entry(rsv, &(mem)->res->children, sibling) \
if (!is_reserved_resource(rsv)) {} else
--
2.47.3
More information about the barebox
mailing list