From: Michael Brown Date: Mon, 12 May 2025 10:58:23 +0000 (+0100) Subject: [riscv] Relocate to a safe physical address on startup X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=17fd67ce03179cb3ed0e16c309d2a7f78e29c919;p=thirdparty%2Fipxe.git [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 --- diff --git a/src/arch/riscv/prefix/libprefix.S b/src/arch/riscv/prefix/libprefix.S index a24b34fa4..c5f4961c0 100644 --- a/src/arch/riscv/prefix/libprefix.S +++ b/src/arch/riscv/prefix/libprefix.S @@ -224,6 +224,17 @@ print_hex_value_alt: #endif .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 @@ -296,8 +307,20 @@ apply_relocs_loop: /* Loop until we have reached a terminator record (MSB=0, offset=0) */ 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: /* Return to caller */ + progress " ok\n" ret .size apply_relocs, . - apply_relocs @@ -424,6 +447,21 @@ paging_mode_names: .globl _max_align .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 @@ -643,6 +681,13 @@ enable_paging_64_loop: ret .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 @@ -807,6 +852,15 @@ enable_paging_32_xstart: .section ".bss.enable_paging_32_xcheck", "aw", @nobits .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 @@ -880,6 +934,126 @@ disable_paging_32_xstart: .section ".bss.disable_paging_32_xcheck", "aw", @nobits .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 @@ -921,3 +1095,30 @@ reset_system: 1: wfi j 1b .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 diff --git a/src/arch/riscv/prefix/sbiprefix.S b/src/arch/riscv/prefix/sbiprefix.S index e83167602..da3202aa2 100644 --- a/src/arch/riscv/prefix/sbiprefix.S +++ b/src/arch/riscv/prefix/sbiprefix.S @@ -32,23 +32,8 @@ .section ".note.GNU-stack", "", @progbits .text -/* ELF machine type */ -#define EM_RISCV 243 - - /* - * 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 +/* Page size */ +#define PAGE_SIZE 4096 /* * SBI entry point @@ -57,42 +42,21 @@ progress_\@: .org 0 .globl _sbi_start _sbi_start: - /* Initialise virtual address offset */ - mv tp, zero - - /* Preserve arguments */ - mv s0, a0 - 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 + /* Identify temporary page table and stack space + * + * 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 - /* Register device tree */ - la a0, sysfdt - mv a1, s1 - li a2, -1 - call fdt_parse + /* Install iPXE */ + call install /* Call main program */ - progress "\n\n" call main /* We have no return path, since the M-mode SBI implementation @@ -104,17 +68,3 @@ _sbi_start: */ j reset_system .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 diff --git a/src/arch/riscv/scripts/sbi.lds b/src/arch/riscv/scripts/sbi.lds index f746097ba..9fec07ecb 100644 --- a/src/arch/riscv/scripts/sbi.lds +++ b/src/arch/riscv/scripts/sbi.lds @@ -53,6 +53,8 @@ SECTIONS { KEEP(*(.provided.*)) *(.got) *(.got.plt) + /* Ensure compressed relocations end up aligned */ + . = ALIGN ( 16 ); _edata = .; } diff --git a/src/core/fdtmem.c b/src/core/fdtmem.c index 3a4b3fb1a..a41604eb5 100644 --- a/src/core/fdtmem.c +++ b/src/core/fdtmem.c @@ -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;