[PATCH v3 2/3] efi: Allow use of malloc() for the EFI pool
Simon Glass
sjg at chromium.org
Fri Sep 6 15:01:25 CEST 2024
Hi Ilias,
On Fri, 6 Sept 2024 at 01:13, Ilias Apalodimas
<ilias.apalodimas at linaro.org> wrote:
>
> Hi Simon,
>
> Apologies for the late reply, I was attending a conference.
>
> On Mon, 2 Sept 2024 at 01:23, Simon Glass <sjg at chromium.org> wrote:
> >
> > This API call is intended for allocating small amounts of memory,
> > similar to malloc(). The current implementation rounds up to whole pages
> > which can waste large amounts of memory. It also implements its own
> > malloc()-style header on each block.
> >
> > For certain allocations (those of type EFI_BOOT_SERVICES_DATA) we can
> > use U-Boot's built-in malloc() instead, at least until the app starts.
> > This avoids poluting the memory space with blocks of data which may
> > interfere with boot scripts, etc.
> >
> > Once the app has started, there is no advantage to using malloc(), since
> > it doesn't matter what memory is used: everything is under control of
> > the EFI subsystem. Also, using malloc() after the app starts might
> > result in running of memory, since U-Boot's malloc() space is typically
> > quite small.
> >
> > In fact, malloc() is already used for most EFI-related allocations, so
> > the impact of this change is fairly small.
> >
> > One side effect is that this seems to be showing up some bugs in the
> > EFI code, since the malloc() pool becomes corrupted with some tests.
> > This has likely crept in due to the very large gaps between allocations
> > (around 4KB), which provides a lot of leeway when the allocation size is
> > too small. Work around this by increasing the size for now, until these
> > (presumed) bugs are located.
> >
> > Signed-off-by: Simon Glass <sjg at chromium.org>
> > ---
> >
> > (no changes since v1)
> >
> > common/dlmalloc.c | 7 +++
> > include/efi_loader.h | 18 ++++++
> > include/malloc.h | 7 +++
> > lib/efi_loader/efi_bootbin.c | 2 +
> > lib/efi_loader/efi_memory.c | 110 ++++++++++++++++++++++++++---------
> > 5 files changed, 117 insertions(+), 27 deletions(-)
> >
> > diff --git a/common/dlmalloc.c b/common/dlmalloc.c
> > index 1ac7ce3f43c..48e9f3515f7 100644
> > --- a/common/dlmalloc.c
> > +++ b/common/dlmalloc.c
> > @@ -613,6 +613,13 @@ void mem_malloc_init(ulong start, ulong size)
> > #endif
> > }
> >
> > +bool malloc_check_in_range(void *ptr)
> > +{
> > + ulong val = (ulong)ptr;
> > +
> > + return val >= mem_malloc_start && val < mem_malloc_end;
> > +}
> > +
> > /* field-extraction macros */
> >
> > #define first(b) ((b)->fd)
> > diff --git a/include/efi_loader.h b/include/efi_loader.h
> > index 38971d01442..d07bc06bad4 100644
> > --- a/include/efi_loader.h
> > +++ b/include/efi_loader.h
> > @@ -805,6 +805,24 @@ int efi_disk_probe(void *ctx, struct event *event);
> > int efi_disk_remove(void *ctx, struct event *event);
> > /* Called by board init to initialize the EFI memory map */
> > int efi_memory_init(void);
> > +
> > +/**
> > + * enum efi_alloc_flags - controls EFI memory allocation
> > + *
> > + * @EFIAF_USE_MALLOC: Use malloc() pool for pool allocations of type
> > + * EFI_BOOT_SERVICES_DATA, otherwise use page allocation
> > + */
> > +enum efi_alloc_flags {
> > + EFIAF_USE_MALLOC = BIT(0),
> > +};
>
> Why do we need to handle cases differently? IOW can't all EFI
> allocations that need a pool gi via malloc?
Once the app boots, as Heinrich pointed out, it expects to be able to
malloc() very large amount of memory, but the malloc() pool is small.
>
> [...]
>
> > @@ -24,6 +24,14 @@ DECLARE_GLOBAL_DATA_PTR;
> > /* Magic number identifying memory allocated from pool */
> > #define EFI_ALLOC_POOL_MAGIC 0x1fe67ddf6491caa2
> >
> > +/* Flags controlling EFI memory-allocation - see enum efi_alloc_flags */
> > +static int alloc_flags;
> > +
> > +void efi_set_alloc(int flags)
> > +{
> > + alloc_flags = flags;
> > +}
> > +
> > efi_uintn_t efi_memory_map_key;
> >
> > struct efi_mem_list {
> > @@ -57,8 +65,12 @@ void *efi_bounce_buffer;
> > * The checksum calculated in function checksum() is used in FreePool() to avoid
> > * freeing memory not allocated by AllocatePool() and duplicate freeing.
> > *
> > - * EFI requires 8 byte alignment for pool allocations, so we can
> > - * prepend each allocation with these header fields.
> > + * EFI requires 8-byte alignment for pool allocations, so we can prepend each
> > + * allocation with these header fields.
> > + *
> > + * Note that before the EFI app is booted, EFI_BOOT_SERVICES_DATA allocations
> > + * are served using malloc(), bypassing this struct. This helps to avoid memory
> > + * fragmentation, since efi_allocate_pages() uses any pages it likes.
> > */
> > struct efi_pool_allocation {
> > u64 num_pages;
> > @@ -631,18 +643,19 @@ void *efi_alloc_aligned_pages(u64 len, int memory_type, size_t align)
> > /**
> > * efi_allocate_pool - allocate memory from pool
> > *
> > + * This uses malloc() for EFI_BOOT_SERVICES_DATA allocations if EFIAF_USE_MALLOC
> > + * is enabled
> > + *
> > * @pool_type: type of the pool from which memory is to be allocated
> > * @size: number of bytes to be allocated
> > * @buffer: allocated memory
> > * Return: status code
> > */
> > -efi_status_t efi_allocate_pool(enum efi_memory_type pool_type, efi_uintn_t size, void **buffer)
> > +efi_status_t efi_allocate_pool(enum efi_memory_type pool_type, efi_uintn_t size,
> > + void **buffer)
> > {
> > efi_status_t r;
> > u64 addr;
> > - struct efi_pool_allocation *alloc;
> > - u64 num_pages = efi_size_in_pages(size +
> > - sizeof(struct efi_pool_allocation));
> >
> > if (!buffer)
> > return EFI_INVALID_PARAMETER;
> > @@ -652,13 +665,43 @@ efi_status_t efi_allocate_pool(enum efi_memory_type pool_type, efi_uintn_t size,
> > return EFI_SUCCESS;
> > }
> >
> > - r = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, pool_type, num_pages,
> > - &addr);
> > - if (r == EFI_SUCCESS) {
> > - alloc = (struct efi_pool_allocation *)(uintptr_t)addr;
> > - alloc->num_pages = num_pages;
> > - alloc->checksum = checksum(alloc);
> > - *buffer = alloc->data;
> > + if ((alloc_flags & EFIAF_USE_MALLOC) &&
> > + pool_type == EFI_BOOT_SERVICES_DATA) {
> > + void *ptr;
> > +
> > + /*
> > + * Some tests crash on qemu_arm etc. if the correct size is
> > + * allocated.
> > + * Adding 0x10 seems to fix test_efi_selftest_device_tree
> > + * Increasing it to 0x20 seems to fix test_efi_selftest_base
> > + * except * for riscv64 (in CI only). But 0x100 fixes CI too.
> > + *
> > + * This workaround can be dropped once these problems are
> > + * resolved
> > + */
> > + ptr = memalign(8, size + 0x100);
>
> I don't think the explanation is enough here. On top of that adding
> random values to fix the problem doesn't sound right. Can we figure
> out why?
My guess is that an allocated pointer is going beyond its limits. The
newer upstream dlmalloc() has a checker which might help. I fiddled
around a bit but could not work one where the problem was.
>
> > + if (!ptr)
> > + return EFI_OUT_OF_RESOURCES;
> > +
> > + *buffer = ptr;
> > + r = EFI_SUCCESS;
> > + log_debug("EFI pool: malloc(%zx) = %p\n", size, ptr);
>
> So as I commented above, I think this is papering over whatever
> problem you are seeing. If you want to move the pool to use malloc()
> that's fine, but *all* of the pool allocations should use it. Not just
> boot services because its easier to retrofit it on the current code.
Please see above. Also, please see the commit message. This change
actually solves the problems I am seeing, quite well.
>
> > + } else {
> > + u64 num_pages = efi_size_in_pages(size +
> > + sizeof(struct efi_pool_allocation));
> > +
> > + r = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, pool_type,
> > + num_pages, &addr);
> > + if (r == EFI_SUCCESS) {
> > + struct efi_pool_allocation *alloc;
> > +
> > + alloc = (struct efi_pool_allocation *)(uintptr_t)addr;
> > + alloc->num_pages = num_pages;
> > + alloc->checksum = checksum(alloc);
> > + *buffer = alloc->data;
> > + log_debug("EFI pool: pages alloc(%zx) type %d = %p\n",
> > + size, pool_type, *buffer);
> > + }
> > }
> >
> > return r;
> > @@ -686,27 +729,37 @@ void *efi_alloc(size_t size)
> > efi_status_t efi_free_pool(void *buffer)
> > {
> > efi_status_t ret;
> > - struct efi_pool_allocation *alloc;
> >
> > if (!buffer)
> > return EFI_INVALID_PARAMETER;
> >
> > - ret = efi_check_allocated((uintptr_t)buffer, true);
> > - if (ret != EFI_SUCCESS)
> > - return ret;
> > + if (malloc_check_in_range(buffer)) {
> > + log_debug("EFI pool: free(%p)\n", buffer);
> > + free(buffer);
> > + ret = EFI_SUCCESS;
> > + } else {
> > + struct efi_pool_allocation *alloc;
> >
> > - alloc = container_of(buffer, struct efi_pool_allocation, data);
> > + ret = efi_check_allocated((uintptr_t)buffer, true);
> > + if (ret != EFI_SUCCESS)
> > + return ret;
> >
> [...]
Regards,
Simon
More information about the U-Boot
mailing list