diff --git a/src/arch/riscv/prefix/libprefix.S b/src/arch/riscv/prefix/libprefix.S index e9caa3945..a9d2ac0f1 100644 --- a/src/arch/riscv/prefix/libprefix.S +++ b/src/arch/riscv/prefix/libprefix.S @@ -401,6 +401,14 @@ paging_mode_names: #endif .endm + /* Maximum physical alignment + * + * We align to a "megapage" boundary to simplify the task of + * setting up page table mappings. + */ + .globl _max_align + .equ _max_align, ( 1 << VPN1_LSB ) + /***************************************************************************** * * Disable paging diff --git a/src/arch/riscv/scripts/sbi.lds b/src/arch/riscv/scripts/sbi.lds index efda0aa81..f746097ba 100644 --- a/src/arch/riscv/scripts/sbi.lds +++ b/src/arch/riscv/scripts/sbi.lds @@ -87,6 +87,9 @@ SECTIONS { } } + /* End virtual address */ + _end = .; + /* Base virtual address */ _base = ABSOLUTE ( _prefix ); @@ -97,6 +100,9 @@ SECTIONS { /* Length of initialised data */ _filesz = ( ABSOLUTE ( _edata ) - ABSOLUTE ( _prefix ) ); + /* Length of in-memory image */ + _memsz = ( ABSOLUTE ( _end ) - ABSOLUTE ( _prefix ) ); + /* Unwanted sections */ /DISCARD/ : { *(.comment) diff --git a/src/core/fdtmem.c b/src/core/fdtmem.c new file mode 100644 index 000000000..3a4b3fb1a --- /dev/null +++ b/src/core/fdtmem.c @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2025 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +/** @file + * + * Flattened Device Tree memory map + * + */ + +/** Start address of the iPXE image */ +extern char _prefix[]; + +/** In-memory size of the iPXE image (defined by linker) */ +extern size_t ABS_SYMBOL ( _memsz ); +static size_t memsz = ABS_VALUE_INIT ( _memsz ); + +/** Relocation required alignment (defined by prefix or linker) */ +extern physaddr_t ABS_SYMBOL ( _max_align ); +static physaddr_t max_align = ABS_VALUE_INIT ( _max_align ); + +/** Colour for debug messages */ +#define colour &memsz + +/** A memory region descriptor */ +struct fdtmem_region { + /** Region start address */ + physaddr_t start; + /** Region end address */ + physaddr_t end; + /** Region flags */ + int flags; + /** Region name (for debug messages) */ + const char *name; +}; + +/** Region is usable as RAM */ +#define FDTMEM_RAM 0x0001 + +/** + * Update memory region descriptor + * + * @v region Memory region of interest to be updated + * @v start Start address of this region + * @v size Size of this region + * @v flags Region flags + * @v name Region name (for debugging) + */ +static void fdtmem_update ( struct fdtmem_region *region, uint64_t start, + uint64_t size, int flags, const char *name ) { + uint64_t end; + + /* Ignore empty regions */ + if ( ! size ) + return; + + /* Calculate end address (and truncate if necessary) */ + end = ( start + size - 1 ); + if ( end < start ) { + end = ~( ( uint64_t ) 0 ); + DBGC ( colour, "FDTMEM [%#08llx,%#08llx] %s truncated " + "(invalid size %#08llx)\n", + ( ( unsigned long long ) start ), + ( ( unsigned long long ) end ), name, + ( ( unsigned long long ) size ) ); + } + + /* Ignore regions entirely below the region of interest */ + if ( end < region->start ) + return; + + /* Ignore regions entirely above the region of interest */ + if ( start > region->end ) + return; + + /* Update region of interest as applicable */ + if ( start <= region->start ) { + + /* This region covers the region of interest */ + region->flags = flags; + if ( DBG_LOG ) + region->name = name; + + /* Update end address if no closer boundary exists */ + if ( end < region->end ) + region->end = end; + + } else if ( start < region->end ) { + + /* Update end address if no closer boundary exists */ + region->end = ( start - 1 ); + } +} + +/** + * Update memory region descriptor based on device tree node + * + * @v region Memory region of interest to be updated + * @v fdt Device tree + * @v offset Starting node offset + * @v match Required device type (or NULL) + * @v flags Region flags + * @ret rc Return status code + */ +static int fdtmem_update_node ( struct fdtmem_region *region, struct fdt *fdt, + unsigned int offset, const char *match, + int flags ) { + struct fdt_descriptor desc; + struct fdt_reg_cells regs; + const char *devtype; + uint64_t start; + uint64_t size; + int depth; + int count; + int index; + int rc; + + /* Parse region cell sizes */ + fdt_reg_cells ( fdt, offset, ®s ); + + /* Scan through reservations */ + for ( depth = -1 ; ; depth += desc.depth, offset = desc.next ) { + + /* Describe token */ + if ( ( rc = fdt_describe ( fdt, offset, &desc ) ) != 0 ) { + DBGC ( colour, "FDTMEM has malformed node: %s\n", + strerror ( rc ) ); + return rc; + } + + /* Terminate when we exit this node */ + if ( ( depth == 0 ) && ( desc.depth < 0 ) ) + break; + + /* Ignore any non-immediate child nodes */ + if ( ! ( ( depth == 0 ) && desc.name && ( ! desc.data ) ) ) + continue; + + /* Ignore any non-matching children */ + if ( match ) { + devtype = fdt_string ( fdt, desc.offset, + "device_type" ); + if ( ! devtype ) + continue; + if ( strcmp ( devtype, match ) != 0 ) + continue; + } + + /* Count regions */ + count = fdt_reg_count ( fdt, desc.offset, ®s ); + if ( count < 0 ) { + rc = count; + DBGC ( colour, "FDTMEM has malformed region %s: %s\n", + desc.name, strerror ( rc ) ); + continue; + } + + /* Scan through this region */ + for ( index = 0 ; index < count ; index++ ) { + + /* Get region starting address and size */ + if ( ( rc = fdt_reg_address ( fdt, desc.offset, ®s, + index, &start ) ) != 0 ){ + DBGC ( colour, "FDTMEM %s region %d has " + "malformed start address: %s\n", + desc.name, index, strerror ( rc ) ); + break; + } + if ( ( rc = fdt_reg_size ( fdt, desc.offset, ®s, + index, &size ) ) != 0 ) { + DBGC ( colour, "FDTMEM %s region %d has " + "malformed size: %s\n", + desc.name, index, strerror ( rc ) ); + break; + } + + /* Update memory region descriptor */ + fdtmem_update ( region, start, size, flags, + desc.name ); + } + } + + return 0; +} + +/** + * Update memory region descriptor based on device tree + * + * @v region Memory region of interest to be updated + * @v fdt Device tree + * @ret rc Return status code + */ +static int fdtmem_update_tree ( struct fdtmem_region *region, + struct fdt *fdt ) { + const struct fdt_reservation *rsv; + unsigned int offset; + int rc; + + /* Update based on memory regions in the root node */ + if ( ( rc = fdtmem_update_node ( region, fdt, 0, "memory", + FDTMEM_RAM ) ) != 0 ) + return rc; + + /* Update based on memory reservations block */ + for_each_fdt_reservation ( rsv, fdt ) { + fdtmem_update ( region, be64_to_cpu ( rsv->start ), + be64_to_cpu ( rsv->size ), 0, "" ); + } + + /* Locate reserved-memory node */ + if ( ( rc = fdt_path ( fdt, "/reserved-memory", &offset ) ) != 0 ) { + DBGC ( colour, "FDTMEM could not locate /reserved-memory: " + "%s\n", strerror ( rc ) ); + return rc; + } + + /* Update based on memory regions in the reserved-memory node */ + if ( ( rc = fdtmem_update_node ( region, fdt, offset, NULL, + 0 ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Find a relocation address for iPXE + * + * @v hdr FDT header + * @v limit Size of accessible physical address space (or zero) + * @ret new New physical address for relocation + * + * Find a suitably aligned address towards the top of existent memory + * to which iPXE may be relocated, along with a copy of the system + * device tree. + * + * This function may be called very early in initialisation, before + * .data is writable or .bss has been zeroed. Neither this function + * nor any function that it calls may write to or rely upon the zero + * initialisation of any static variables. + */ +physaddr_t fdtmem_relocate ( struct fdt_header *hdr, size_t limit ) { + struct fdt fdt; + struct fdtmem_region region; + physaddr_t old; + physaddr_t new; + physaddr_t try; + size_t len; + int rc; + + /* Sanity check */ + assert ( ( max_align & ( max_align - 1 ) ) == 0 ); + + /* Get current physical address */ + old = virt_to_phys ( _prefix ); + + /* Parse FDT */ + if ( ( rc = fdt_parse ( &fdt, hdr, -1UL ) ) != 0 ) { + DBGC ( colour, "FDTMEM could not parse FDT: %s\n", + strerror ( rc ) ); + /* Refuse relocation if we have no FDT */ + return old; + } + + /* Determine required length */ + assert ( memsz > 0 ); + assert ( ( memsz % FDT_MAX_ALIGN ) == 0 ); + assert ( ( fdt.len % FDT_MAX_ALIGN ) == 0 ); + len = ( memsz + fdt.len ); + assert ( len > 0 ); + DBGC ( colour, "FDTMEM requires %#zx + %#zx => %#zx bytes for " + "relocation\n", memsz, fdt.len, len ); + + /* Construct memory map and choose a relocation address */ + region.start = 0; + new = old; + while ( 1 ) { + + /* Initialise region */ + region.end = ~( ( physaddr_t ) 0 ); + region.flags = 0; + region.name = ""; + + /* Update region based on device tree */ + if ( ( rc = fdtmem_update_tree ( ®ion, &fdt ) ) != 0 ) + break; + + /* Treat existing iPXE image as reserved */ + fdtmem_update ( ®ion, old, memsz, 0, "iPXE" ); + + /* Treat existing device tree as reserved */ + fdtmem_update ( ®ion, virt_to_phys ( hdr ), fdt.len, 0, + "FDT" ); + + /* Treat inaccessible physical memory as reserved */ + if ( limit ) { + fdtmem_update ( ®ion, limit, -limit, 0, + "" ); + } + + /* Dump region descriptor (for debugging) */ + DBGC ( colour, "FDTMEM [%#08lx,%#08lx] %s\n", + region.start, region.end, region.name ); + assert ( region.end >= region.start ); + + /* Use highest possible region */ + if ( ( region.flags & FDTMEM_RAM ) && + ( ( region.end - region.start ) > len ) ) { + + /* Determine candidate address after alignment */ + try = ( ( region.end - len - 1 ) & + ~( max_align - 1 ) ); + + /* Use this address if within region */ + if ( try >= region.start ) + new = try; + } + + /* Move to next region */ + region.start = ( region.end + 1 ); + if ( ! region.start ) + break; + } + + DBGC ( colour, "FDTMEM relocating %#08lx => [%#08lx,%#08lx]\n", + old, new, ( ( physaddr_t ) ( new + len - 1 ) ) ); + return new; +} diff --git a/src/include/ipxe/fdt.h b/src/include/ipxe/fdt.h index 6c58f5687..45ae0f92b 100644 --- a/src/include/ipxe/fdt.h +++ b/src/include/ipxe/fdt.h @@ -76,6 +76,14 @@ struct fdt_prop { /** Maximum alignment of any block */ #define FDT_MAX_ALIGN 8 +/** A memory reservation */ +struct fdt_reservation { + /** Starting address */ + uint64_t start; + /** Length of reservation */ + uint64_t size; +} __attribute__ (( packed )); + /** A device tree */ struct fdt { /** Tree data */ @@ -143,6 +151,23 @@ struct fdt_reg_cells { extern struct image_tag fdt_image __image_tag; extern struct fdt sysfdt; +/** + * Get memory reservations + * + * @v fdt Device tree + * @ret rsv Memory reservations + */ +static inline const struct fdt_reservation * +fdt_reservations ( struct fdt *fdt ) { + + return ( fdt->raw + fdt->reservations ); +} + +/** Iterate over memory reservations */ +#define for_each_fdt_reservation( rsv, fdt ) \ + for ( rsv = fdt_reservations ( (fdt) ) ; \ + ( (rsv)->start || (rsv)->size ) ; rsv++ ) + extern int fdt_describe ( struct fdt *fdt, unsigned int offset, struct fdt_descriptor *desc ); extern int fdt_path ( struct fdt *fdt, const char *path, diff --git a/src/include/ipxe/fdtmem.h b/src/include/ipxe/fdtmem.h new file mode 100644 index 000000000..98395b8e9 --- /dev/null +++ b/src/include/ipxe/fdtmem.h @@ -0,0 +1,17 @@ +#ifndef _IPXE_FDTMEM_H +#define _IPXE_FDTMEM_H + +/** @file + * + * Flattened Device Tree memory map + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include + +extern physaddr_t fdtmem_relocate ( struct fdt_header *hdr, size_t limit ); + +#endif /* _IPXE_FDTMEM_H */