|
|
|
@@ -0,0 +1,357 @@
|
|
|
|
|
/*
|
|
|
|
|
* 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/fdt.h>
|
|
|
|
|
#include <ipxe/fdtmem.h>
|
|
|
|
|
|
|
|
|
|
/** @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, "<rsv>" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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 = "<empty>";
|
|
|
|
|
|
|
|
|
|
/* 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,
|
|
|
|
|
"<inaccessible>" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|