]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[riscv] Relocate to a safe physical address on startup
authorMichael Brown <mcb30@ipxe.org>
Mon, 12 May 2025 10:58:23 +0000 (11:58 +0100)
committerMichael Brown <mcb30@ipxe.org>
Mon, 12 May 2025 12:59:42 +0000 (13:59 +0100)
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>
src/arch/riscv/prefix/libprefix.S
src/arch/riscv/prefix/sbiprefix.S
src/arch/riscv/scripts/sbi.lds
src/core/fdtmem.c

index a24b34fa41301af7e94c5c8612e30d0b25457722..c5f4961c003efda968cd4e5c9c44a68304231d86 100644 (file)
@@ -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
index e831676025346d0d7f137d56d484322b2e52ac1d..da3202aa26c3193614fa36dca12806a9355b435c 100644 (file)
        .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
index f746097ba28f09fe65f8704554e75ef28f9465d0..9fec07ecb6b8e4eb4c0ef38f012469f77528ca80 100644 (file)
@@ -53,6 +53,8 @@ SECTIONS {
        KEEP(*(.provided.*))
        *(.got)
        *(.got.plt)
+       /* Ensure compressed relocations end up aligned */
+       . = ALIGN ( 16 );
        _edata = .;
     }
 
index 3a4b3fb1ad6b89a41b24344fb22be5345b2eabaa..a41604eb5399c8f955699b0d318f773c5419400d 100644 (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;