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 
24 FILE_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 */
44 extern char _prefix[];
45 
46 /** End address of the iPXE image */
47 extern char _end[];
48 
49 /** Total in-memory size (calculated by linker) */
50 extern size_t ABS_SYMBOL ( _memsz );
51 static size_t memsz = ABS_VALUE_INIT ( _memsz );
52 
53 /** Relocation required alignment (defined by prefix or linker) */
54 extern size_t ABS_SYMBOL ( _max_align );
55 static size_t max_align = ABS_VALUE_INIT ( _max_align );
56 
57 /** In-use memory region for iPXE and system device tree copy */
58 struct 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  */
78 static 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;
85  uint64_t size;
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  */
170 static 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 */
182  for_each_fdt_reservation ( rsv, fdt ) {
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  */
211 static void fdtmem_describe ( uint64_t min, uint64_t max, struct fdt *fdt,
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  */
233 static 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;
269  physaddr_t old;
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 )
303  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  */
389 static 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 
static int memmap_is_usable(const struct memmap_region *region)
Check if memory region is usable.
Definition: memmap.h:86
static void memmap_use(struct used_region *used, physaddr_t start, size_t size)
Update an in-use memory region.
Definition: memmap.h:153
iPXE I/O API
struct arbelprm_rc_send_wqe rc
Definition: arbel.h:14
PROVIDE_MEMMAP_INLINE(fdt, memmap_sync)
#define FDTMEM_MAX32
Maximum 32-bit physical address.
Definition: fdtmem.c:66
A memory reservation.
Definition: fdt.h:80
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
#define max(x, y)
Definition: ath.h:40
uint64_t max
Maximum address in region.
Definition: memmap.h:52
static size_t fdtmem_len(struct fdt *fdt)
Get length for copy of iPXE and device tree.
Definition: fdtmem.c:233
size_t ABS_SYMBOL(_memsz)
Total in-memory size (calculated by linker)
Error codes.
size_t len
Length of tree.
Definition: fdt.h:97
int fdt_parse(struct fdt *fdt, struct fdt_header *hdr, size_t max_len)
Parse device tree.
Definition: fdt.c:903
Device tree header.
Definition: fdt.h:18
struct golan_inbox_hdr hdr
Message header.
Definition: CIB_PRM.h:28
uint16_t size
Buffer size.
Definition: dwmac.h:14
int fdt_describe(struct fdt *fdt, unsigned int offset, struct fdt_descriptor *desc)
Describe device tree token.
Definition: fdt.c:89
int fdt_path(struct fdt *fdt, const char *path, unsigned int *offset)
Find node by path.
Definition: fdt.c:425
Flattened Device Tree memory map.
#define DBGC(...)
Definition: compiler.h:505
#define min(x, y)
Definition: ath.h:35
A device tree region cell size specification.
Definition: fdt.h:136
long index
Definition: bigint.h:62
unsigned long long uint64_t
Definition: stdint.h:13
int old
Definition: bitops.h:64
#define PAGE_SIZE
Page size.
Definition: io.h:27
#define DBGC_MEMMAP(...)
Definition: memmap.h:206
#define MEMMAP_FL_USED
Is in use by iPXE.
Definition: memmap.h:61
#define for_each_fdt_reservation(rsv, fdt)
Iterate over memory reservations.
Definition: fdt.h:167
uint32_t start
Starting offset.
Definition: netvsc.h:12
struct ena_llq_option desc
Descriptor counts.
Definition: ena.h:20
void * memcpy(void *dest, const void *src, size_t len) __nonnull
struct used_region fdtmem_used __used_region
In-use memory region for iPXE and system device tree copy.
Definition: fdtmem.c:58
static void fdtmem_describe(uint64_t min, uint64_t max, struct fdt *fdt, struct memmap_region *region)
Describe memory region.
Definition: fdtmem.c:211
void memmap_describe(uint64_t min, int hide, struct memmap_region *region)
Describe memory region from system memory map.
Definition: null_memmap.h:28
Assertions.
assert((readw(&hdr->flags) &(GTF_reading|GTF_writing))==0)
Access to external ("user") memory.
ring len
Length.
Definition: dwmac.h:231
PROVIDE_MEMMAP(fdt, memmap_describe, fdtmem_describe_region)
static unsigned int count
Number of entries.
Definition: dwmac.h:225
void fdt_reg_cells(struct fdt *fdt, unsigned int offset, struct fdt_reg_cells *regs)
Get region cell size specification.
Definition: fdt.c:715
const char * fdt_string(struct fdt *fdt, unsigned int offset, const char *name)
Find string property.
Definition: fdt.c:578
uint64_t size
Length of reservation.
Definition: fdt.h:84
uint8_t flags
Flags.
Definition: ena.h:18
char * strerror(int errno)
Retrieve string representation of error number.
Definition: strerror.c:78
#define be64_to_cpu(value)
Definition: byteswap.h:117
An in-use memory region.
Definition: memmap.h:105
static int fdtmem_update_tree(struct memmap_region *region, struct fdt *fdt)
Update memory region descriptor based on device tree.
Definition: fdtmem.c:170
uint32_t addr
Buffer address.
Definition: dwmac.h:20
int fdt_reg_count(struct fdt *fdt, unsigned int offset, struct fdt_reg_cells *regs)
Get number of regions.
Definition: fdt.c:766
static physaddr_t fdtmem_max
Maximum accessible physical address.
Definition: fdtmem.c:63
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:792
char _end[]
End address of the iPXE image.
uint32_t next
Next descriptor address.
Definition: dwmac.h:22
struct i386_regs regs
Definition: registers.h:15
unsigned long physaddr_t
Definition: stdint.h:20
static size_t max_align
Definition: fdtmem.c:55
int fdtmem_register(struct fdt_header *hdr, physaddr_t max)
Copy and register system device tree.
Definition: fdtmem.c:345
static void memmap_dump_all(int hide)
Dump system memory map (for debugging)
Definition: memmap.h:215
FILE_LICENCE(GPL2_OR_LATER_OR_UBDL)
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
A device tree.
Definition: fdt.h:88
int strcmp(const char *first, const char *second)
Compare strings.
Definition: string.c:173
#define MEMMAP_FL_RESERVED
Is reserved.
Definition: memmap.h:60
#define MEMMAP_FL_MEMORY
Contains memory.
Definition: memmap.h:59
static memmap_sync(void)
Synchronise in-use regions with the externally visible system memory map.
Definition: fdtmem.h:25
uint64_t start
Starting address.
Definition: fdt.h:82
Flattened Device Tree.
const char * name
Region name.
Definition: memmap.h:107
static size_t memsz
Definition: fdtmem.c:51
uint64_t min
Minimum address in region.
Definition: memmap.h:50
#define MEMMAP_FL_INACCESSIBLE
Outside of addressable range.
Definition: memmap.h:62
A memory region descriptor.
Definition: memmap.h:48
static void memmap_init(uint64_t min, struct memmap_region *region)
Initialise memory region descriptor.
Definition: memmap.h:71
A device tree token descriptor.
Definition: fdt.h:120
uint16_t offset
Offset to command line.
Definition: bzimage.h:8
#define ABS_VALUE_INIT(name)
Get value of an absolute symbol for use in a static initializer.
Definition: compiler.h:668
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:817
#define NULL
NULL pointer (VOID *)
Definition: Base.h:321
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
System memory map.
String functions.
void memmap_update_used(struct memmap_region *region)
Update memory region descriptor based on all in-use memory regions.
Definition: memmap.c:104
#define FDT_MAX_ALIGN
Maximum alignment of any block.
Definition: fdt.h:77
physaddr_t fdtmem_relocate(struct fdt_header *hdr, physaddr_t max)
Find a relocation address for iPXE.
Definition: fdtmem.c:264
char _prefix[]
Start address of the iPXE image.
struct fdt sysfdt
The system flattened device tree (if present)
Definition: fdt.c:44