summaryrefslogtreecommitdiffstats
path: root/src/core/uheap.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/uheap.c')
-rw-r--r--src/core/uheap.c197
1 files changed, 197 insertions, 0 deletions
diff --git a/src/core/uheap.c b/src/core/uheap.c
new file mode 100644
index 000000000..3f93bc030
--- /dev/null
+++ b/src/core/uheap.c
@@ -0,0 +1,197 @@
+/*
+ * 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;
+
+/** Minimum possible start of external heap */
+physaddr_t uheap_limit;
+
+/** Start of external heap */
+physaddr_t uheap_start;
+
+/** End of external heap */
+physaddr_t uheap_end;
+
+/** In-use memory region */
+struct used_region uheap_used __used_region = {
+ .name = "uheap",
+};
+
+/**
+ * Adjust size of external heap in-use memory region
+ *
+ * @v delta Size change
+ */
+static void uheap_resize ( ssize_t delta ) {
+
+ /* Update in-use memory region */
+ assert ( ( delta & ( UHEAP_ALIGN - 1 ) ) == 0 );
+ uheap_start -= delta;
+ assert ( uheap_limit <= uheap_start );
+ assert ( uheap_start <= uheap_end );
+ assert ( ( uheap_limit & ( UHEAP_ALIGN - 1 ) ) == 0 );
+ assert ( ( uheap_start & ( UHEAP_ALIGN - 1 ) ) == 0 );
+ assert ( ( uheap_end & ( UHEAP_ALIGN - 1 ) ) == 0 );
+ memmap_use ( &uheap_used, uheap_start, ( uheap_end - uheap_start ) );
+ DBGC ( &uheap, "UHEAP now at (%#08lx)...[%#08lx,%#08lx)\n",
+ uheap_limit, uheap_start, uheap_end );
+ 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_start == uheap_end );
+ assert ( uheap_limit == uheap_end );
+ assert ( uheap_used.size == 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, and prevent overflow to zero */
+ after = ( end ? ( end & ( UHEAP_ALIGN - 1 ) ) : UHEAP_ALIGN );
+ before = ( ( -start ) & ( UHEAP_ALIGN - 1 ) );
+ strip = ( before + after );
+ if ( strip > size )
+ return;
+ start += before;
+ end -= after;
+ size -= strip;
+ assert ( ( end - start ) == size );
+
+ /* Record region */
+ uheap_limit = start;
+ uheap_start = end;
+ uheap_end = end;
+ uheap_resize ( 0 );
+}
+
+/**
+ * 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_limit == uheap_end )
+ uheap_find();
+
+ /* Fail if insufficient space remains */
+ if ( size > ( uheap_start - uheap_limit ) )
+ return 0;
+
+ /* Grow heap */
+ new = ( phys_to_virt ( uheap_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_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 );