[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

@@ -224,6 +224,17 @@ print_hex_value_alt:
#endif #endif
.endm .endm
/*
* Display hexadecimal data value (if debugging is enabled)
*/
.macro print_hex_data sym
#ifndef NDEBUG
LOADN t1, \sym
li t2, __riscv_xlen
jal t0, print_hex_value_alt
#endif
.endm
/***************************************************************************** /*****************************************************************************
* *
* Apply compressed relocation records * Apply compressed relocation records
@@ -296,8 +307,20 @@ apply_relocs_loop:
/* Loop until we have reached a terminator record (MSB=0, offset=0) */ /* Loop until we have reached a terminator record (MSB=0, offset=0) */
bnez a3, apply_relocs_loop bnez a3, apply_relocs_loop
/* Check that relocations were applied successfully
*
* Failure to apply relocations (if relocations were needed)
* is a fatal error.
*/
la t0, _prefix
LOADN t1, prefix_virt
beq t0, t1, apply_relocs_done
progress " failed\n"
j reset_system
apply_relocs_done: apply_relocs_done:
/* Return to caller */ /* Return to caller */
progress " ok\n"
ret ret
.size apply_relocs, . - apply_relocs .size apply_relocs, . - apply_relocs
@@ -424,6 +447,21 @@ paging_mode_names:
.globl _max_align .globl _max_align
.equ _max_align, ( 1 << VPN1_LSB ) .equ _max_align, ( 1 << VPN1_LSB )
/* Space for page table
*
* This can be used only once .bss is known to be writable.
*/
.section ".bss.page_table", "a", @nobits
.balign PAGE_SIZE
page_table:
.space PAGE_SIZE
.size page_table, . - page_table
/* Convert physical address to virtual address */
.macro phys_to_virt rd, rs:vararg
_C2 ( phys_to_virt_, __riscv_xlen ) \rd, \rs
.endm
/***************************************************************************** /*****************************************************************************
* *
* Disable paging * Disable paging
@@ -643,6 +681,13 @@ enable_paging_64_loop:
ret ret
.size enable_paging_64, . - enable_paging_64 .size enable_paging_64, . - enable_paging_64
/* Convert 64-bit physical address to virtual address */
.macro phys_to_virt_64 rd, rs:vararg
.ifnb \rs
mv \rd, \rs
.endif
.endm
/***************************************************************************** /*****************************************************************************
* *
* Disable 64-bit paging * Disable 64-bit paging
@@ -807,6 +852,15 @@ enable_paging_32_xstart:
.section ".bss.enable_paging_32_xcheck", "aw", @nobits .section ".bss.enable_paging_32_xcheck", "aw", @nobits
.org . + enable_paging_32_xalign - enable_paging_32_xlen .org . + enable_paging_32_xalign - enable_paging_32_xlen
/* Convert 32-bit physical address to virtual address */
.macro phys_to_virt_32 rd, rs:vararg
.ifnb \rs
sub \rd, \rs, tp
.else
sub \rd, \rd, tp
.endif
.endm
/***************************************************************************** /*****************************************************************************
* *
* Disable 32-bit paging * Disable 32-bit paging
@@ -880,6 +934,126 @@ disable_paging_32_xstart:
.section ".bss.disable_paging_32_xcheck", "aw", @nobits .section ".bss.disable_paging_32_xcheck", "aw", @nobits
.org . + disable_paging_32_xalign - disable_paging_32_xlen .org . + disable_paging_32_xalign - disable_paging_32_xlen
/*****************************************************************************
*
* Install iPXE to a suitable runtime address
*
*****************************************************************************
*
* Identify a suitable runtime address for iPXE, relocate there, and
* set up for running normal C code.
*
* A valid temporary stack pointer is required. A 4kB space for a
* temporary page table may be provided, and must be provided if the
* iPXE image is running from read-only memory.
*
* Note that this function does not preserve the callee-save registers.
*
* Parameters:
*
* a0 - Boot hart ID
* a1 - Device tree physical address
* a2 - Optional temporary page table space (4kB, aligned to a 4kB boundary)
* sp - Valid temporary stack pointer
*
* Returns:
*
* pc - Updated to be within the relocated iPXE
* sp - Top of internal stack
* tp - Virtual address offset
*
*/
.section ".prefix.install", "ax", @progbits
.globl install
install:
/* Register usage:
*
* s0 - boot hart ID
* s1 - device tree physical address
* s2 - saved return address
* s3 - relocation records physical address
* s4 - accessible physical address limit
* s5 - relocation physical address
* s6 - relocation offset
* tp - virtual address offset
*/
progress "\nSBI->iPXE hart:"
print_hex_reg a0
progress " temp:"
print_hex_reg a2
progress " fdt:"
print_hex_reg a1
progress "\nSBI->iPXE phys:"
print_hex_addr _prefix
progress " virt:"
print_hex_data prefix_virt
mv s0, a0
mv s1, a1
mv s2, ra
la s3, _edata
/* Initialise virtual address offset */
mv tp, zero
/* Attempt to enable paging, if we have temporary page table space */
mv a0, a2
beqz a2, 1f
call enable_paging
1: mv s4, a0
/* Apply relocations, if still needed after enabling paging */
mv a0, s3
call apply_relocs
/* Find a suitable address for relocation (using temporary stack) */
phys_to_virt a0, s1
mv a1, s4
phys_to_virt sp
call fdtmem_relocate
mv s5, a0
progress "SBI->iPXE dest:"
print_hex_reg a0
/* Disable paging */
call disable_paging
/* Determine relocation offset */
la s6, _prefix
sub s6, s5, s6
/* Jump to relocated copy */
la t0, 1f
add t0, t0, s6
jr t0
1:
/* Attempt to re-enable paging */
la a0, page_table
call enable_paging
/* Reapply relocations, if still needed after enabling paging */
phys_to_virt a0, s3
call apply_relocs
/* Load stack pointer */
la sp, _estack
/* Store boot hart */
STOREN s0, boot_hart, t0
/* Register copy of device tree as system device tree */
la a0, sysfdt
la a1, _end
li a2, -1
call fdt_parse
/* Return to a virtual address in the relocated copy */
add ra, s2, s6
sub ra, ra, tp
progress "\n"
ret
.size install, . - install
/***************************************************************************** /*****************************************************************************
* *
* Reset (or lock up) system * Reset (or lock up) system
@@ -921,3 +1095,30 @@ reset_system:
1: wfi 1: wfi
j 1b j 1b
.size reset_system, . - reset_system .size reset_system, . - reset_system
/*****************************************************************************
*
* File split information for the compressor
*
*****************************************************************************
*/
/* ELF machine type */
#define EM_RISCV 243
.section ".zinfo", "a", @progbits
.org 0
/* Copy initialised-data portion of image */
.ascii "COPY"
.word 0
.word _filesz
.word 1
/* Notify compressor of link-time base address */
.ascii "BASE"
.word 0
.dword _base
/* Construct compressed relocation records */
.ascii "ZREL"
.word _reloc_offset
.word _reloc_filesz
.word EM_RISCV

View File

@@ -32,23 +32,8 @@
.section ".note.GNU-stack", "", @progbits .section ".note.GNU-stack", "", @progbits
.text .text
/* ELF machine type */ /* Page size */
#define EM_RISCV 243 #define PAGE_SIZE 4096
/*
* Display progress message via debug console
*/
.macro progress message
#ifndef NDEBUG
.section ".rodata.progress_\@", "a", @progbits
progress_\@:
.asciz "\message"
.size progress_\@, . - progress_\@
.previous
la t1, progress_\@
call print_message
#endif
.endm
/* /*
* SBI entry point * SBI entry point
@@ -57,42 +42,21 @@ progress_\@:
.org 0 .org 0
.globl _sbi_start .globl _sbi_start
_sbi_start: _sbi_start:
/* Initialise virtual address offset */ /* Identify temporary page table and stack space
mv tp, zero *
* Assume that there is sufficient writable memory (~8kB)
* directly below the device tree.
*/
li t0, ~( PAGE_SIZE - 1 )
and sp, a1, t0
li t0, PAGE_SIZE
sub sp, sp, t0
mv a2, sp
/* Preserve arguments */ /* Install iPXE */
mv s0, a0 call install
mv s1, a1
progress "\nSBI->iPXE"
/* Apply dynamic relocations */
la a0, _edata
call apply_relocs
progress " .reloc"
/* Zero the bss */
la t0, _bss
la t1, _ebss
1: STOREN zero, (t0)
addi t0, t0, ( __riscv_xlen / 8 )
blt t0, t1, 1b
progress " .bss"
/* Set up stack */
la sp, _estack
progress " .stack"
/* Store boot hart */
STOREN s0, boot_hart, t0
/* Register device tree */
la a0, sysfdt
mv a1, s1
li a2, -1
call fdt_parse
/* Call main program */ /* Call main program */
progress "\n\n"
call main call main
/* We have no return path, since the M-mode SBI implementation /* We have no return path, since the M-mode SBI implementation
@@ -104,17 +68,3 @@ _sbi_start:
*/ */
j reset_system j reset_system
.size _sbi_start, . - _sbi_start .size _sbi_start, . - _sbi_start
/* File split information for the compressor */
.section ".zinfo", "a", @progbits
.ascii "COPY"
.word 0
.word _filesz
.word 1
.ascii "BASE"
.word 0
.dword _base
.ascii "ZREL"
.word _reloc_offset
.word _reloc_filesz
.word EM_RISCV

View File

@@ -53,6 +53,8 @@ SECTIONS {
KEEP(*(.provided.*)) KEEP(*(.provided.*))
*(.got) *(.got)
*(.got.plt) *(.got.plt)
/* Ensure compressed relocations end up aligned */
. = ALIGN ( 16 );
_edata = .; _edata = .;
} }

View File

@@ -41,6 +41,10 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/** Start address of the iPXE image */ /** Start address of the iPXE image */
extern char _prefix[]; 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) */ /** In-memory size of the iPXE image (defined by linker) */
extern size_t ABS_SYMBOL ( _memsz ); extern size_t ABS_SYMBOL ( _memsz );
static size_t memsz = ABS_VALUE_INIT ( _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 new;
physaddr_t try; physaddr_t try;
size_t len; size_t len;
void *dest;
int rc; int rc;
/* Sanity check */ /* Sanity check */
@@ -294,7 +299,6 @@ physaddr_t fdtmem_relocate ( struct fdt_header *hdr, size_t limit ) {
/* Determine required length */ /* Determine required length */
assert ( memsz > 0 ); assert ( memsz > 0 );
assert ( ( memsz % FDT_MAX_ALIGN ) == 0 ); assert ( ( memsz % FDT_MAX_ALIGN ) == 0 );
assert ( ( fdt.len % FDT_MAX_ALIGN ) == 0 );
len = ( memsz + fdt.len ); len = ( memsz + fdt.len );
assert ( len > 0 ); assert ( len > 0 );
DBGC ( colour, "FDTMEM requires %#zx + %#zx => %#zx bytes for " 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; 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", DBGC ( colour, "FDTMEM relocating %#08lx => [%#08lx,%#08lx]\n",
old, new, ( ( physaddr_t ) ( new + len - 1 ) ) ); old, new, ( ( physaddr_t ) ( new + len - 1 ) ) );
return new; return new;