summaryrefslogtreecommitdiffstats
path: root/src/core/uheap.c
blob: 3f93bc030281aadc1cf286aba5d29937cb3e6a69 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
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 );