blob: 3f93bc030281aadc1cf286aba5d29937cb3e6a69 [file] [log] [blame]
/*
* 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 );