| /* | |
| * 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 ); |