]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[uheap] Add a generic external heap based on the system memory map
authorMichael Brown <mcb30@ipxe.org>
Mon, 19 May 2025 15:11:59 +0000 (16:11 +0100)
committerMichael Brown <mcb30@ipxe.org>
Mon, 19 May 2025 18:36:25 +0000 (19:36 +0100)
Add an implementation of umalloc() using the generalised model of a
heap, placing the external heap in the largest usable region obtained
from the system memory map.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/core/memmap.c
src/core/uheap.c [new file with mode: 0644]
src/include/ipxe/memmap.h
src/include/ipxe/uheap.h [new file with mode: 0644]
src/include/ipxe/umalloc.h

index 5d81d99847652fc45b05ad70e0931f8551192751..6c814a9ed3114cbbe7125075c9529823b260a117 100644 (file)
@@ -111,5 +111,34 @@ void memmap_update_used ( struct memmap_region *region ) {
        }
 }
 
+/**
+ * Find largest usable memory region
+ *
+ * @v start            Start address to fill in
+ * @ret len            Length of region
+ */
+size_t memmap_largest ( physaddr_t *start ) {
+       struct memmap_region region;
+       size_t largest;
+       size_t size;
+
+       /* Find largest usable region */
+       DBGC ( &region, "MEMMAP finding largest usable region\n" );
+       *start = 0;
+       largest = 0;
+       for_each_memmap ( &region, 1 ) {
+               memmap_dump ( &region );
+               if ( ! memmap_is_usable ( &region ) )
+                       continue;
+               size = memmap_size ( &region );
+               if ( size > largest ) {
+                       DBGC ( &region, "...new largest region found\n" );
+                       largest = size;
+                       *start = region.addr;
+               }
+       }
+       return largest;
+}
+
 PROVIDE_MEMMAP_INLINE ( null, memmap_describe );
 PROVIDE_MEMMAP_INLINE ( null, memmap_sync );
diff --git a/src/core/uheap.c b/src/core/uheap.c
new file mode 100644 (file)
index 0000000..60efcf0
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2025 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <ipxe/io.h>
+#include <ipxe/memmap.h>
+#include <ipxe/malloc.h>
+#include <ipxe/umalloc.h>
+
+/** @file
+ *
+ * External ("user") heap
+ *
+ * This file implements an external heap (for umalloc()) that grows
+ * downwards from the top of the largest contiguous accessible block
+ * in the system memory map.
+ */
+
+/**
+ * Alignment for external heap allocations
+ *
+ * Historically, umalloc() has produced page-aligned allocations, and
+ * the hidden region in the system memory map has been aligned to a
+ * page boundary.  Preserve this behaviour, to avoid needing to
+ * inspect and update large amounts of driver code, and also because
+ * it keeps the resulting memory maps easy to read.
+ */
+#define UHEAP_ALIGN PAGE_SIZE
+
+static struct heap uheap;
+
+/** In-use memory region */
+struct used_region uheap_used __used_region = {
+       .name = "uheap",
+};
+
+/** External heap maximum size */
+static size_t uheap_max;
+
+/**
+ * Adjust size of external heap in-use memory region
+ *
+ * @v delta            Size change
+ */
+static void uheap_resize ( ssize_t delta ) {
+       physaddr_t top;
+
+       /* Update in-use memory region */
+       assert ( ( uheap_used.start & ( UHEAP_ALIGN - 1 ) ) == 0 );
+       assert ( ( uheap_used.size & ( UHEAP_ALIGN - 1 ) ) == 0 );
+       assert ( ( delta & ( UHEAP_ALIGN - 1 ) ) == 0 );
+       memmap_use ( &uheap_used, ( uheap_used.start - delta ),
+                    ( uheap_used.size + delta ) );
+       top = ( uheap_used.start + uheap_used.size );
+       DBGC ( &uheap, "UHEAP now at [%#08lx,%#08lx)\n",
+              uheap_used.start, top );
+       memmap_dump_all ( 1 );
+}
+
+/**
+ * Find an external heap region
+ *
+ */
+static void uheap_find ( void ) {
+       physaddr_t start;
+       physaddr_t end;
+       size_t before;
+       size_t after;
+       size_t strip;
+       size_t size;
+
+       /* Sanity checks */
+       assert ( uheap_used.size == 0 );
+       assert ( uheap_max == 0 );
+
+       /* Find the largest region within the system memory map */
+       size = memmap_largest ( &start );
+       end = ( start + size );
+       DBGC ( &uheap, "UHEAP largest region is [%#08lx,%#08lx)\n",
+              start, end );
+
+       /* Align start and end addresses */
+       after = ( end & ( UHEAP_ALIGN - 1 ) );
+       before = ( ( -start ) & ( UHEAP_ALIGN - 1 ) );
+       strip = ( before + after );
+       if ( strip > size )
+               return;
+       start += before;
+       end -= after;
+       size -= strip;
+       assert ( ( end - start ) == size );
+
+       /* Record region */
+       assert ( ( start & ( UHEAP_ALIGN - 1 ) ) == 0 );
+       assert ( ( size & ( UHEAP_ALIGN - 1 ) ) == 0 );
+       assert ( ( end & ( UHEAP_ALIGN - 1 ) ) == 0 );
+       uheap_max = size;
+       uheap_used.start = end;
+       DBGC ( &uheap, "UHEAP grows downwards from %#08lx\n", end );
+}
+
+/**
+ * Attempt to grow external heap
+ *
+ * @v size             Failed allocation size
+ * @ret grown          Heap has grown: retry allocations
+ */
+static unsigned int uheap_grow ( size_t size ) {
+       void *new;
+
+       /* Initialise heap, if it does not yet exist */
+       if ( ! uheap_max )
+               uheap_find();
+
+       /* Fail if insufficient space remains */
+       if ( size > ( uheap_max - uheap_used.size ) )
+               return 0;
+
+       /* Grow heap */
+       new = ( phys_to_virt ( uheap_used.start ) - size );
+       heap_populate ( &uheap, new, size );
+       uheap_resize ( size );
+
+       return 1;
+}
+
+/**
+ * Allow external heap to shrink
+ *
+ * @v ptr              Start of free block
+ * @v size             Size of free block
+ * @ret shrunk         Heap has shrunk: discard block
+ */
+static unsigned int uheap_shrink ( void *ptr, size_t size ) {
+
+       /* Do nothing unless this is the lowest block in the heap */
+       if ( virt_to_phys ( ptr ) != uheap_used.start )
+               return 0;
+
+       /* Shrink heap */
+       uheap_resize ( -size );
+
+       return 1;
+}
+
+/** The external heap */
+static struct heap uheap = {
+       .blocks = LIST_HEAD_INIT ( uheap.blocks ),
+       .align = UHEAP_ALIGN,
+       .ptr_align = UHEAP_ALIGN,
+       .grow = uheap_grow,
+       .shrink = uheap_shrink,
+};
+
+/**
+ * Reallocate external memory
+ *
+ * @v old_ptr          Memory previously allocated by umalloc(), or NULL
+ * @v new_size         Requested size
+ * @ret new_ptr                Allocated memory, or NULL
+ *
+ * Calling urealloc() with a new size of zero is a valid way to free a
+ * memory block.
+ */
+static void * uheap_realloc ( void *old_ptr, size_t new_size ) {
+
+       return heap_realloc ( &uheap, old_ptr, new_size );
+}
+
+PROVIDE_UMALLOC ( uheap, urealloc, uheap_realloc );
index cfb9ac934d80ab39ef9a4eb592740a96aa17e528..1d0aaa3721bc1cacdcccf1180c2764c636338fbf 100644 (file)
@@ -234,5 +234,6 @@ extern void memmap_update ( struct memmap_region *region, uint64_t start,
                            uint64_t size, unsigned int flags,
                            const char *name );
 extern void memmap_update_used ( struct memmap_region *region );
+extern size_t memmap_largest ( physaddr_t *start );
 
 #endif /* _IPXE_MEMMAP_H */
diff --git a/src/include/ipxe/uheap.h b/src/include/ipxe/uheap.h
new file mode 100644 (file)
index 0000000..194c131
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef _IPXE_UHEAP_H
+#define _IPXE_UHEAP_H
+
+/** @file
+ *
+ * External ("user") heap
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#ifdef UMALLOC_UHEAP
+#define UMALLOC_PREFIX_uheap
+#else
+#define UMALLOC_PREFIX_uheap __uheap_
+#endif
+
+#endif /* _IPXE_UHEAP_H */
index a6476a39044645effb0cfab8b9989a4776c079d3..da6c341438517d3a4ebe4df6d3a87712a2e84d63 100644 (file)
@@ -26,6 +26,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
        PROVIDE_SINGLE_API ( UMALLOC_PREFIX_ ## _subsys, _api_func, _func )
 
 /* Include all architecture-independent I/O API headers */
+#include <ipxe/uheap.h>
 #include <ipxe/efi/efi_umalloc.h>
 #include <ipxe/linux/linux_umalloc.h>