[riscv] Relocate to a safe physical address on startup

On startup, we may be running from read-only memory.  We need to parse
the devicetree to obtain the system memory map, and identify a safe
location to which we can copy our own binary image along with a
stashed copy of the devicetree, and then transfer execution to this
new location.

Parsing the system memory map realistically requires running C code.
This in turn requires a small temporary stack, and some way to ensure
that symbol references are valid.

We first attempt to enable paging, to make the runtime virtual
addresses equal to the link-time virtual addresses.  If this fails,
then we attempt to apply the compressed relocation records.

Assuming that one of these has worked (i.e. that either the CPU
supports paging or that our image started execution in writable
memory), then we call fdtmem_relocate() to parse the system memory map
to find a suitable relocation target address.

After the copy we disable paging, jump to the relocated copy,
re-enable paging, and reapply relocation records (if needed).  At this
point, we have a full runtime environment, and can transfer control to
normal C code.

Provide this functionality as part of libprefix.S, since it is likely
to be shared by multiple prefixes.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown
2025-05-12 11:58:23 +01:00
parent 3dfc88158c
commit 17fd67ce03
4 changed files with 230 additions and 65 deletions

View File

@@ -41,6 +41,10 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/** Start address of the iPXE image */
extern char _prefix[];
/** Initialised-data size of the iPXE image (defined by linker) */
extern size_t ABS_SYMBOL ( _filesz );
static size_t filesz = ABS_VALUE_INIT ( _filesz );
/** In-memory size of the iPXE image (defined by linker) */
extern size_t ABS_SYMBOL ( _memsz );
static size_t memsz = ABS_VALUE_INIT ( _memsz );
@@ -275,6 +279,7 @@ physaddr_t fdtmem_relocate ( struct fdt_header *hdr, size_t limit ) {
physaddr_t new;
physaddr_t try;
size_t len;
void *dest;
int rc;
/* Sanity check */
@@ -294,7 +299,6 @@ physaddr_t fdtmem_relocate ( struct fdt_header *hdr, size_t limit ) {
/* 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 "
@@ -351,6 +355,14 @@ physaddr_t fdtmem_relocate ( struct fdt_header *hdr, size_t limit ) {
break;
}
/* Copy iPXE and device tree to new location */
if ( new != old ) {
dest = phys_to_virt ( new );
memset ( dest, 0, len );
memcpy ( dest, _prefix, filesz );
memcpy ( ( dest + memsz ), hdr, fdt.len );
}
DBGC ( colour, "FDTMEM relocating %#08lx => [%#08lx,%#08lx]\n",
old, new, ( ( physaddr_t ) ( new + len - 1 ) ) );
return new;