diff options
Diffstat (limited to 'src/core/fdtmem.c')
| -rw-r--r-- | src/core/fdtmem.c | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/src/core/fdtmem.c b/src/core/fdtmem.c new file mode 100644 index 000000000..97e3bb2f6 --- /dev/null +++ b/src/core/fdtmem.c @@ -0,0 +1,401 @@ +/* + * 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 <stdint.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <byteswap.h> +#include <ipxe/uaccess.h> +#include <ipxe/memmap.h> +#include <ipxe/io.h> +#include <ipxe/fdt.h> +#include <ipxe/fdtmem.h> + +/** @file + * + * Flattened Device Tree memory map + * + */ + +/** Start address of the iPXE image */ +extern char _prefix[]; + +/** End address of the iPXE image */ +extern char _end[]; + +/** Total in-memory size (calculated 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 size_t ABS_SYMBOL ( _max_align ); +static size_t max_align = ABS_VALUE_INIT ( _max_align ); + +/** In-use memory region for iPXE and system device tree copy */ +struct used_region fdtmem_used __used_region = { + .name = "iPXE/FDT", +}; + +/** Maximum accessible physical address */ +static physaddr_t fdtmem_max; + +/** Maximum 32-bit physical address */ +#define FDTMEM_MAX32 0xffffffff + +/** + * 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 memmap_region *region, struct fdt *fdt, + unsigned int offset, const char *match, + unsigned 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 ( region, "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 ) { + if ( flags & MEMMAP_FL_RESERVED ) { + /* Assume this is a non-fixed reservation */ + continue; + } + rc = count; + DBGC ( region, "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 ( region, "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 ( region, "FDTMEM %s region %d has " + "malformed size: %s\n", + desc.name, index, strerror ( rc ) ); + break; + } + + /* Update memory region descriptor */ + memmap_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 memmap_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", + MEMMAP_FL_MEMORY ) ) != 0 ) + return rc; + + /* Update based on memory reservations block */ + for_each_fdt_reservation ( rsv, fdt ) { + memmap_update ( region, be64_to_cpu ( rsv->start ), + be64_to_cpu ( rsv->size ), MEMMAP_FL_RESERVED, + NULL ); + } + + /* Locate reserved-memory node */ + if ( ( rc = fdt_path ( fdt, "/reserved-memory", &offset ) ) != 0 ) { + DBGC ( region, "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, + MEMMAP_FL_RESERVED ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Describe memory region + * + * @v min Minimum address + * @v max Maximum accessible physical address + * @v fdt Device tree + * @v region Region descriptor to fill in + */ +static void fdtmem_describe ( uint64_t min, uint64_t max, struct fdt *fdt, + struct memmap_region *region ) { + uint64_t inaccessible; + + /* Initialise region */ + memmap_init ( min, region ); + + /* Update region based on device tree */ + fdtmem_update_tree ( region, fdt ); + + /* Treat inaccessible physical memory as such */ + inaccessible = ( max + 1 ); + memmap_update ( region, inaccessible, -inaccessible, + MEMMAP_FL_INACCESSIBLE, NULL ); +} + +/** + * Get length for copy of iPXE and device tree + * + * @v fdt Device tree + * @ret len Total length + */ +static size_t fdtmem_len ( struct fdt *fdt ) { + size_t len; + + /* Calculate total length and check device tree alignment */ + len = ( memsz + fdt->len ); + assert ( ( memsz % FDT_MAX_ALIGN ) == 0 ); + + /* Align length. Not technically necessary, but keeps the + * resulting memory maps looking relatively sane. + */ + len = ( ( len + PAGE_SIZE - 1 ) & ~( PAGE_SIZE - 1 ) ); + + return len; +} + +/** + * Find a relocation address for iPXE + * + * @v hdr FDT header + * @v max Maximum accessible physical address + * @ret new New physical address for relocation + * + * Find a suitably aligned address towards the top of existent 32-bit + * 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, physaddr_t max ) { + struct fdt fdt; + struct memmap_region region; + physaddr_t addr; + physaddr_t next; + 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 ( hdr, "FDTMEM could not parse FDT: %s\n", + strerror ( rc ) ); + /* Refuse relocation if we have no FDT */ + return old; + } + + /* Determine required length */ + len = fdtmem_len ( &fdt ); + assert ( len > 0 ); + DBGC ( hdr, "FDTMEM requires %#zx + %#zx => %#zx bytes for " + "relocation\n", memsz, fdt.len, len ); + + /* Limit relocation to 32-bit address space + * + * Devices with only 32-bit DMA addressing are relatively + * common even on systems with 64-bit CPUs. Limit relocation + * of iPXE to 32-bit address space so that I/O buffers and + * other DMA allocations will be accessible by 32-bit devices. + */ + if ( max > FDTMEM_MAX32 ) + max = FDTMEM_MAX32; + + /* Construct memory map and choose a relocation address */ + new = old; + for ( addr = 0, next = 1 ; next ; addr = next ) { + + /* Describe region and in-use memory */ + fdtmem_describe ( addr, max, &fdt, ®ion ); + memmap_update ( ®ion, old, memsz, MEMMAP_FL_USED, "iPXE" ); + memmap_update ( ®ion, virt_to_phys ( hdr ), fdt.len, + MEMMAP_FL_RESERVED, "FDT" ); + next = ( region.max + 1 ); + + /* Dump region descriptor (for debugging) */ + DBGC_MEMMAP ( hdr, ®ion ); + assert ( region.max >= region.min ); + + /* Use highest possible region */ + if ( memmap_is_usable ( ®ion ) && + ( ( next == 0 ) || ( next >= len ) ) ) { + + /* Determine candidate address after alignment */ + try = ( ( next - len ) & ~( max_align - 1 ) ); + + /* Use this address if within region */ + if ( try >= addr ) + new = try; + } + } + + DBGC ( hdr, "FDTMEM relocating %#08lx => [%#08lx,%#08lx]\n", + old, new, ( ( physaddr_t ) ( new + len - 1 ) ) ); + return new; +} + +/** + * Copy and register system device tree + * + * @v hdr FDT header + * @v max Maximum accessible physical address + * @ret rc Return status code + */ +int fdtmem_register ( struct fdt_header *hdr, physaddr_t max ) { + struct fdt_header *copy; + struct fdt fdt; + int rc; + + /* Record maximum accessible physical address */ + fdtmem_max = max; + + /* Parse FDT to obtain length */ + if ( ( rc = fdt_parse ( &fdt, hdr, -1UL ) ) != 0 ) { + DBGC ( hdr, "FDTMEM could not parse FDT: %s\n", + strerror ( rc ) ); + return rc; + } + + /* Copy device tree to end of iPXE image */ + copy = ( ( void * ) _end ); + memcpy ( copy, hdr, fdt.len ); + + /* Update in-use memory region */ + memmap_use ( &fdtmem_used, virt_to_phys ( _prefix ), + fdtmem_len ( &fdt ) ); + + /* Register copy as system device tree */ + if ( ( rc = fdt_parse ( &sysfdt, copy, -1UL ) ) != 0 ) { + DBGC ( hdr, "FDTMEM could not register FDT: %s\n", + strerror ( rc ) ); + return rc; + } + assert ( sysfdt.len == fdt.len ); + + /* Dump system memory map (for debugging) */ + memmap_dump_all ( 1 ); + + return 0; +} + +/** + * Describe memory region from system memory map + * + * @v min Minimum address + * @v hide Hide in-use regions from the memory map + * @v region Region descriptor to fill in + */ +static void fdtmem_describe_region ( uint64_t min, int hide, + struct memmap_region *region ) { + + /* Describe memory region based on device tree */ + fdtmem_describe ( min, fdtmem_max, &sysfdt, region ); + + /* Update memory region based on in-use regions, if applicable */ + if ( hide ) + memmap_update_used ( region ); +} + +PROVIDE_MEMMAP ( fdt, memmap_describe, fdtmem_describe_region ); +PROVIDE_MEMMAP_INLINE ( fdt, memmap_sync ); |
