iPXE
fdtmem.c
Go to the documentation of this file.
1/*
2 * Copyright (C) 2025 Michael Brown <mbrown@fensystems.co.uk>.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301, USA.
18 *
19 * You can also choose to distribute this program under the terms of
20 * the Unmodified Binary Distribution Licence (as given in the file
21 * COPYING.UBDL), provided that you have satisfied its requirements.
22 */
23
24FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25
26#include <stdint.h>
27#include <string.h>
28#include <errno.h>
29#include <assert.h>
30#include <byteswap.h>
31#include <ipxe/uaccess.h>
32#include <ipxe/memmap.h>
33#include <ipxe/io.h>
34#include <ipxe/fdt.h>
35#include <ipxe/fdtmem.h>
36
37/** @file
38 *
39 * Flattened Device Tree memory map
40 *
41 */
42
43/** Start address of the iPXE image */
44extern char _prefix[];
45
46/** End address of the iPXE image */
47extern char _end[];
48
49/** Total in-memory size (calculated by linker) */
50extern size_t ABS_SYMBOL ( _memsz );
51static size_t memsz = ABS_VALUE_INIT ( _memsz );
52
53/** Relocation required alignment (defined by prefix or linker) */
54extern size_t ABS_SYMBOL ( _max_align );
55static size_t max_align = ABS_VALUE_INIT ( _max_align );
56
57/** In-use memory region for iPXE and system device tree copy */
58struct used_region fdtmem_used __used_region = {
59 .name = "iPXE/FDT",
60};
61
62/** Maximum accessible physical address */
64
65/** Maximum 32-bit physical address */
66#define FDTMEM_MAX32 0xffffffff
67
68/**
69 * Update memory region descriptor based on device tree node
70 *
71 * @v region Memory region of interest to be updated
72 * @v fdt Device tree
73 * @v offset Starting node offset
74 * @v match Required device type (or NULL)
75 * @v flags Region flags
76 * @ret rc Return status code
77 */
78static int fdtmem_update_node ( struct memmap_region *region, struct fdt *fdt,
79 unsigned int offset, const char *match,
80 unsigned int flags ) {
81 struct fdt_descriptor desc;
82 struct fdt_reg_cells regs;
83 const char *devtype;
86 int depth;
87 int count;
88 int index;
89 int rc;
90
91 /* Parse region cell sizes */
93
94 /* Scan through reservations */
95 for ( depth = -1 ; ; depth += desc.depth, offset = desc.next ) {
96
97 /* Describe token */
98 if ( ( rc = fdt_describe ( fdt, offset, &desc ) ) != 0 ) {
99 DBGC ( region, "FDTMEM has malformed node: %s\n",
100 strerror ( rc ) );
101 return rc;
102 }
103
104 /* Terminate when we exit this node */
105 if ( ( depth == 0 ) && ( desc.depth < 0 ) )
106 break;
107
108 /* Ignore any non-immediate child nodes */
109 if ( ! ( ( depth == 0 ) && desc.name && ( ! desc.data ) ) )
110 continue;
111
112 /* Ignore any non-matching children */
113 if ( match ) {
114 devtype = fdt_string ( fdt, desc.offset,
115 "device_type" );
116 if ( ! devtype )
117 continue;
118 if ( strcmp ( devtype, match ) != 0 )
119 continue;
120 }
121
122 /* Count regions */
123 count = fdt_reg_count ( fdt, desc.offset, &regs );
124 if ( count < 0 ) {
125 if ( flags & MEMMAP_FL_RESERVED ) {
126 /* Assume this is a non-fixed reservation */
127 continue;
128 }
129 rc = count;
130 DBGC ( region, "FDTMEM has malformed region %s: %s\n",
131 desc.name, strerror ( rc ) );
132 continue;
133 }
134
135 /* Scan through this region */
136 for ( index = 0 ; index < count ; index++ ) {
137
138 /* Get region starting address and size */
139 if ( ( rc = fdt_reg_address ( fdt, desc.offset, &regs,
140 index, &start ) ) != 0 ){
141 DBGC ( region, "FDTMEM %s region %d has "
142 "malformed start address: %s\n",
143 desc.name, index, strerror ( rc ) );
144 break;
145 }
146 if ( ( rc = fdt_reg_size ( fdt, desc.offset, &regs,
147 index, &size ) ) != 0 ) {
148 DBGC ( region, "FDTMEM %s region %d has "
149 "malformed size: %s\n",
150 desc.name, index, strerror ( rc ) );
151 break;
152 }
153
154 /* Update memory region descriptor */
155 memmap_update ( region, start, size, flags,
156 desc.name );
157 }
158 }
159
160 return 0;
161}
162
163/**
164 * Update memory region descriptor based on device tree
165 *
166 * @v region Memory region of interest to be updated
167 * @v fdt Device tree
168 * @ret rc Return status code
169 */
170static int fdtmem_update_tree ( struct memmap_region *region,
171 struct fdt *fdt ) {
172 const struct fdt_reservation *rsv;
173 unsigned int offset;
174 int rc;
175
176 /* Update based on memory regions in the root node */
177 if ( ( rc = fdtmem_update_node ( region, fdt, 0, "memory",
178 MEMMAP_FL_MEMORY ) ) != 0 )
179 return rc;
180
181 /* Update based on memory reservations block */
183 memmap_update ( region, be64_to_cpu ( rsv->start ),
185 NULL );
186 }
187
188 /* Locate reserved-memory node */
189 if ( ( rc = fdt_path ( fdt, "/reserved-memory", &offset ) ) != 0 ) {
190 DBGC ( region, "FDTMEM could not locate /reserved-memory: "
191 "%s\n", strerror ( rc ) );
192 return rc;
193 }
194
195 /* Update based on memory regions in the reserved-memory node */
196 if ( ( rc = fdtmem_update_node ( region, fdt, offset, NULL,
197 MEMMAP_FL_RESERVED ) ) != 0 )
198 return rc;
199
200 return 0;
201}
202
203/**
204 * Describe memory region
205 *
206 * @v min Minimum address
207 * @v max Maximum accessible physical address
208 * @v fdt Device tree
209 * @v region Region descriptor to fill in
210 */
212 struct memmap_region *region ) {
213 uint64_t inaccessible;
214
215 /* Initialise region */
216 memmap_init ( min, region );
217
218 /* Update region based on device tree */
219 fdtmem_update_tree ( region, fdt );
220
221 /* Treat inaccessible physical memory as such */
222 inaccessible = ( max + 1 );
223 memmap_update ( region, inaccessible, -inaccessible,
225}
226
227/**
228 * Get length for copy of iPXE and device tree
229 *
230 * @v fdt Device tree
231 * @ret len Total length
232 */
233static size_t fdtmem_len ( struct fdt *fdt ) {
234 size_t len;
235
236 /* Calculate total length and check device tree alignment */
237 len = ( memsz + fdt->len );
238 assert ( ( memsz % FDT_MAX_ALIGN ) == 0 );
239
240 /* Align length. Not technically necessary, but keeps the
241 * resulting memory maps looking relatively sane.
242 */
243 len = ( ( len + PAGE_SIZE - 1 ) & ~( PAGE_SIZE - 1 ) );
244
245 return len;
246}
247
248/**
249 * Find a relocation address for iPXE
250 *
251 * @v hdr FDT header
252 * @v max Maximum accessible physical address
253 * @ret new New physical address for relocation
254 *
255 * Find a suitably aligned address towards the top of existent 32-bit
256 * memory to which iPXE may be relocated, along with a copy of the
257 * system device tree.
258 *
259 * This function may be called very early in initialisation, before
260 * .data is writable or .bss has been zeroed. Neither this function
261 * nor any function that it calls may write to or rely upon the zero
262 * initialisation of any static variables.
263 */
265 struct fdt fdt;
266 struct memmap_region region;
270 physaddr_t new;
271 physaddr_t try;
272 size_t len;
273 int rc;
274
275 /* Sanity check */
276 assert ( ( max_align & ( max_align - 1 ) ) == 0 );
277
278 /* Get current physical address */
279 old = virt_to_phys ( _prefix );
280
281 /* Parse FDT */
282 if ( ( rc = fdt_parse ( &fdt, hdr, -1UL ) ) != 0 ) {
283 DBGC ( hdr, "FDTMEM could not parse FDT: %s\n",
284 strerror ( rc ) );
285 /* Refuse relocation if we have no FDT */
286 return old;
287 }
288
289 /* Determine required length */
290 len = fdtmem_len ( &fdt );
291 assert ( len > 0 );
292 DBGC ( hdr, "FDTMEM requires %#zx + %#zx => %#zx bytes for "
293 "relocation\n", memsz, fdt.len, len );
294
295 /* Limit relocation to 32-bit address space
296 *
297 * Devices with only 32-bit DMA addressing are relatively
298 * common even on systems with 64-bit CPUs. Limit relocation
299 * of iPXE to 32-bit address space so that I/O buffers and
300 * other DMA allocations will be accessible by 32-bit devices.
301 */
302 if ( max > FDTMEM_MAX32 )
304
305 /* Construct memory map and choose a relocation address */
306 new = old;
307 for ( addr = 0, next = 1 ; next ; addr = next ) {
308
309 /* Describe region and in-use memory */
310 fdtmem_describe ( addr, max, &fdt, &region );
311 memmap_update ( &region, old, memsz, MEMMAP_FL_USED, "iPXE" );
312 memmap_update ( &region, virt_to_phys ( hdr ), fdt.len,
313 MEMMAP_FL_RESERVED, "FDT" );
314 next = ( region.max + 1 );
315
316 /* Dump region descriptor (for debugging) */
317 DBGC_MEMMAP ( hdr, &region );
318 assert ( region.max >= region.min );
319
320 /* Use highest possible region */
321 if ( memmap_is_usable ( &region ) &&
322 ( ( next == 0 ) || ( next >= len ) ) ) {
323
324 /* Determine candidate address after alignment */
325 try = ( ( next - len ) & ~( max_align - 1 ) );
326
327 /* Use this address if within region */
328 if ( try >= addr )
329 new = try;
330 }
331 }
332
333 DBGC ( hdr, "FDTMEM relocating %#08lx => [%#08lx,%#08lx]\n",
334 old, new, ( ( physaddr_t ) ( new + len - 1 ) ) );
335 return new;
336}
337
338/**
339 * Copy and register system device tree
340 *
341 * @v hdr FDT header
342 * @v max Maximum accessible physical address
343 * @ret rc Return status code
344 */
346 struct fdt_header *copy;
347 struct fdt fdt;
348 int rc;
349
350 /* Record maximum accessible physical address */
351 fdtmem_max = max;
352
353 /* Parse FDT to obtain length */
354 if ( ( rc = fdt_parse ( &fdt, hdr, -1UL ) ) != 0 ) {
355 DBGC ( hdr, "FDTMEM could not parse FDT: %s\n",
356 strerror ( rc ) );
357 return rc;
358 }
359
360 /* Copy device tree to end of iPXE image */
361 copy = ( ( void * ) _end );
362 memcpy ( copy, hdr, fdt.len );
363
364 /* Update in-use memory region */
365 memmap_use ( &fdtmem_used, virt_to_phys ( _prefix ),
366 fdtmem_len ( &fdt ) );
367
368 /* Register copy as system device tree */
369 if ( ( rc = fdt_parse ( &sysfdt, copy, -1UL ) ) != 0 ) {
370 DBGC ( hdr, "FDTMEM could not register FDT: %s\n",
371 strerror ( rc ) );
372 return rc;
373 }
374 assert ( sysfdt.len == fdt.len );
375
376 /* Dump system memory map (for debugging) */
377 memmap_dump_all ( 1 );
378
379 return 0;
380}
381
382/**
383 * Describe memory region from system memory map
384 *
385 * @v min Minimum address
386 * @v hide Hide in-use regions from the memory map
387 * @v region Region descriptor to fill in
388 */
389static void fdtmem_describe_region ( uint64_t min, int hide,
390 struct memmap_region *region ) {
391
392 /* Describe memory region based on device tree */
393 fdtmem_describe ( min, fdtmem_max, &sysfdt, region );
394
395 /* Update memory region based on in-use regions, if applicable */
396 if ( hide )
397 memmap_update_used ( region );
398}
399
#define NULL
NULL pointer (VOID *)
Definition Base.h:322
struct golan_inbox_hdr hdr
Message header.
Definition CIB_PRM.h:0
struct arbelprm_rc_send_wqe rc
Definition arbel.h:3
unsigned long physaddr_t
Definition stdint.h:20
unsigned long long uint64_t
Definition stdint.h:13
long index
Definition bigint.h:65
int old
Definition bitops.h:65
Assertions.
#define assert(condition)
Assert a condition at run-time.
Definition assert.h:50
#define min(x, y)
Definition ath.h:36
#define max(x, y)
Definition ath.h:41
uint16_t offset
Offset to command line.
Definition bzimage.h:3
uint32_t next
Next descriptor address.
Definition dwmac.h:11
ring len
Length.
Definition dwmac.h:226
uint32_t addr
Buffer address.
Definition dwmac.h:9
uint8_t flags
Flags.
Definition ena.h:7
struct ena_llq_option desc
Descriptor counts.
Definition ena.h:9
Error codes.
int fdt_describe(struct fdt *fdt, unsigned int offset, struct fdt_descriptor *desc)
Describe device tree token.
Definition fdt.c:90
const char * fdt_string(struct fdt *fdt, unsigned int offset, const char *name)
Find string property.
Definition fdt.c:579
int fdt_parse(struct fdt *fdt, struct fdt_header *hdr, size_t max_len)
Parse device tree.
Definition fdt.c:904
int fdt_reg_count(struct fdt *fdt, unsigned int offset, struct fdt_reg_cells *regs)
Get number of regions.
Definition fdt.c:767
int fdt_reg_size(struct fdt *fdt, unsigned int offset, struct fdt_reg_cells *regs, unsigned int index, uint64_t *size)
Get region size.
Definition fdt.c:818
int fdt_reg_address(struct fdt *fdt, unsigned int offset, struct fdt_reg_cells *regs, unsigned int index, uint64_t *address)
Get region address.
Definition fdt.c:793
struct fdt sysfdt
The system flattened device tree (if present)
Definition fdt.c:45
int fdt_path(struct fdt *fdt, const char *path, unsigned int *offset)
Find node by path.
Definition fdt.c:426
void fdt_reg_cells(struct fdt *fdt, unsigned int offset, struct fdt_reg_cells *regs)
Get region cell size specification.
Definition fdt.c:716
static void fdtmem_describe(uint64_t min, uint64_t max, struct fdt *fdt, struct memmap_region *region)
Describe memory region.
Definition fdtmem.c:211
static void fdtmem_describe_region(uint64_t min, int hide, struct memmap_region *region)
Describe memory region from system memory map.
Definition fdtmem.c:389
static size_t max_align
Definition fdtmem.c:55
static size_t fdtmem_len(struct fdt *fdt)
Get length for copy of iPXE and device tree.
Definition fdtmem.c:233
static size_t memsz
Definition fdtmem.c:51
static physaddr_t fdtmem_max
Maximum accessible physical address.
Definition fdtmem.c:63
physaddr_t fdtmem_relocate(struct fdt_header *hdr, physaddr_t max)
Find a relocation address for iPXE.
Definition fdtmem.c:264
char _end[]
End address of the iPXE image.
static int fdtmem_update_node(struct memmap_region *region, struct fdt *fdt, unsigned int offset, const char *match, unsigned int flags)
Update memory region descriptor based on device tree node.
Definition fdtmem.c:78
static int fdtmem_update_tree(struct memmap_region *region, struct fdt *fdt)
Update memory region descriptor based on device tree.
Definition fdtmem.c:170
#define FDTMEM_MAX32
Maximum 32-bit physical address.
Definition fdtmem.c:66
int fdtmem_register(struct fdt_header *hdr, physaddr_t max)
Copy and register system device tree.
Definition fdtmem.c:345
char _prefix[]
Start address of the iPXE image.
Flattened Device Tree memory map.
static memmap_sync(void)
Synchronise in-use regions with the externally visible system memory map.
Definition fdtmem.h:26
#define ABS_SYMBOL(name)
Declare an absolute symbol (e.g.
Definition compiler.h:655
#define ABS_VALUE_INIT(name)
Get value of an absolute symbol for use in a static initializer.
Definition compiler.h:668
#define DBGC(...)
Definition compiler.h:505
uint32_t start
Starting offset.
Definition netvsc.h:1
uint16_t size
Buffer size.
Definition dwmac.h:3
static unsigned int count
Number of entries.
Definition dwmac.h:220
#define FILE_LICENCE(_licence)
Declare a particular licence as applying to a file.
Definition compiler.h:896
#define be64_to_cpu(value)
Definition byteswap.h:118
Flattened Device Tree.
#define FDT_MAX_ALIGN
Maximum alignment of any block.
Definition fdt.h:78
#define for_each_fdt_reservation(rsv, fdt)
Iterate over memory reservations.
Definition fdt.h:168
iPXE I/O API
#define PAGE_SIZE
Page size.
Definition io.h:28
System memory map.
#define MEMMAP_FL_INACCESSIBLE
Outside of addressable range.
Definition memmap.h:63
static int memmap_is_usable(const struct memmap_region *region)
Check if memory region is usable.
Definition memmap.h:87
#define MEMMAP_FL_USED
Is in use by iPXE.
Definition memmap.h:62
#define MEMMAP_FL_MEMORY
Contains memory.
Definition memmap.h:60
void memmap_describe(uint64_t min, int hide, struct memmap_region *region)
Describe memory region from system memory map.
Definition null_memmap.h:29
static void memmap_use(struct used_region *used, physaddr_t start, size_t size)
Update an in-use memory region.
Definition memmap.h:154
#define PROVIDE_MEMMAP_INLINE(_subsys, _api_func)
Provide a static inline memory map API implementation.
Definition memmap.h:45
static void memmap_dump_all(int hide)
Dump system memory map (for debugging)
Definition memmap.h:216
#define PROVIDE_MEMMAP(_subsys, _api_func, _func)
Provide a memory map API implementation.
Definition memmap.h:36
static void memmap_init(uint64_t min, struct memmap_region *region)
Initialise memory region descriptor.
Definition memmap.h:72
#define __used_region
Declare an in-use memory region.
Definition memmap.h:119
#define MEMMAP_FL_RESERVED
Is reserved.
Definition memmap.h:61
#define DBGC_MEMMAP(...)
Definition memmap.h:207
String functions.
void * memcpy(void *dest, const void *src, size_t len) __nonnull
Access to external ("user") memory.
void memmap_update(struct memmap_region *region, uint64_t start, uint64_t size, unsigned int flags, const char *name)
Update memory region descriptor.
Definition memmap.c:47
void memmap_update_used(struct memmap_region *region)
Update memory region descriptor based on all in-use memory regions.
Definition memmap.c:104
struct i386_regs regs
Definition registers.h:1
char * strerror(int errno)
Retrieve string representation of error number.
Definition strerror.c:79
int strcmp(const char *first, const char *second)
Compare strings.
Definition string.c:174
A device tree token descriptor.
Definition fdt.h:121
Device tree header.
Definition fdt.h:19
A device tree region cell size specification.
Definition fdt.h:137
A memory reservation.
Definition fdt.h:81
uint64_t start
Starting address.
Definition fdt.h:83
uint64_t size
Length of reservation.
Definition fdt.h:85
A device tree.
Definition fdt.h:89
size_t len
Length of tree.
Definition fdt.h:98
A memory region descriptor.
Definition memmap.h:49
uint64_t min
Minimum address in region.
Definition memmap.h:51
uint64_t max
Maximum address in region.
Definition memmap.h:53
An in-use memory region.
Definition memmap.h:106