Unlike on ARM, the privilege mode is part of the RISC-V boot protocol and
even the cache maintenance during relocation depends on it, so the head
needs to know which mode it was started in.

Let's add two heads: one for images that firmware enters in supervisor
mode like a Linux kernel, with the hart ID in a0 and the device tree in
a1, and one for images entered in machine mode without a boot protocol,
like Erizo and LiteX. Both lay out the same header as
__barebox_riscv_head(), so the image tools keep working, and branch to
pbl_head_start_v1(), which RISC-V implements in assembly.

Boards whose stage describes the memory for barebox proper can use the
new pbl_stage_barebox_entry() as chainload callback.

Assisted-by: Claude:fable-5.1
Assisted-by: Claude:opus-5.5
Signed-off-by: Ahmad Fatoum <[email protected]>
---
 arch/riscv/Kconfig                     |   2 +
 arch/riscv/boot/Makefile               |   2 +-
 arch/riscv/boot/entry.c                |  16 ++
 arch/riscv/boot/head.S                 | 198 +++++++++++++++++++++++++
 arch/riscv/include/asm/barebox-riscv.h |   6 +
 arch/riscv/lib/asm-offsets.c           |  27 ++++
 arch/riscv/lib/pbl.lds.S               |  13 +-
 7 files changed, 262 insertions(+), 2 deletions(-)
 create mode 100644 arch/riscv/boot/entry.c
 create mode 100644 arch/riscv/boot/head.S

diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
index 40a79b6834ff..0041269123e3 100644
--- a/arch/riscv/Kconfig
+++ b/arch/riscv/Kconfig
@@ -20,6 +20,8 @@ config RISCV
        select RISCV_TIMER if RISCV_SBI
        select HW_HAS_PCI
        select PBL_CONSTRUCTORS
+       select ARCH_HAS_PBL_ENTRY
+       select ARCH_HAS_PBL_HEAD_START_ASM
        select PBL_IMAGE_ELF
        select HAVE_ARCH_BOARD_GENERIC_DT
        select HAVE_ARCH_BOOTM_OFTREE
diff --git a/arch/riscv/boot/Makefile b/arch/riscv/boot/Makefile
index 2cd6f098d5ef..d98adf7f724b 100644
--- a/arch/riscv/boot/Makefile
+++ b/arch/riscv/boot/Makefile
@@ -1,4 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0
-pbl-y += entry_ll.o uncompress.o
+pbl-y += head.o entry.o entry_ll.o uncompress.o
 pbl-y += nmon_entry.o
 pbl-$(CONFIG_BOARD_GENERIC_DT) += board-dt-2nd.o
diff --git a/arch/riscv/boot/entry.c b/arch/riscv/boot/entry.c
new file mode 100644
index 000000000000..11b66ab48b63
--- /dev/null
+++ b/arch/riscv/boot/entry.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <pbl/entry.h>
+#include <asm/barebox-riscv.h>
+
+/*
+ * soc_chainload callback for boards whose stage describes the memory for
+ * barebox proper. The head has already recorded the mode in the flags.
+ */
+void __noreturn pbl_stage_barebox_entry(void *boarddata)
+{
+       struct pbl_stage *stage = pbl_get_stage();
+
+       barebox_riscv_entry(stage->mem_start, stage->mem_minsz, boarddata,
+                           riscv_get_flags());
+}
diff --git a/arch/riscv/boot/head.S b/arch/riscv/boot/head.S
new file mode 100644
index 000000000000..a205965e33ad
--- /dev/null
+++ b/arch/riscv/boot/head.S
@@ -0,0 +1,198 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#include <linux/linkage.h>
+#include <asm/asm.h>
+#include <asm/asm-offsets.h>
+
+/*
+ * PBL image heads for ENTRY_POINT() boards. The privilege mode is part of
+ * the RISC-V boot protocol and needed as early as the cache maintenance
+ * during relocation, so there is a head per mode, which records it like
+ * riscv_set_flags() would.
+ */
+
+/* Linker will point this at the selected board's struct pbl_board_info */
+.globl __pbl_board_info
+
+/*
+ * Same header as __barebox_riscv_head(), so the image tools keep working.
+ * Execution continues at \setup.
+ */
+.macro pbl_image_header setup
+       /* Relaxation and compressed instructions would shift the fields */
+       .option push
+       .option norelax
+       .option norvc
+       nop                             /* code0 */
+       j       \setup                  /* code1 */
+       .balign 8
+       .dword  0                       /* Image load offset from start of RAM 
*/
+       .dword  _barebox_image_size     /* Effective Image size */
+       .dword  0                       /* Kernel flags */
+       .word   0                       /* version */
+       .word   0                       /* reserved */
+       .dword  0                       /* reserved */
+       .asciz  "barebox"               /* magic 1 */
+       .balign 8
+       .ascii  "RSCV"                  /* magic 2 */
+       .word   0                       /* reserved (PE-COFF offset) */
+       .option pop
+.endm
+
+/*
+ * Head for images entered like a Linux kernel: supervisor mode, hart ID
+ * in a0 for tp, where riscv_hartid() expects it, and the device tree in
+ * a1, which becomes entry_data.
+ */
+.section .text_head_prologue_v1_supervisor, "ax"
+ENTRY(__barebox_pbl_head_v1_supervisor)
+       pbl_image_header .Lsupervisor_setup
+
+.Lsupervisor_setup:
+       li      fp, 0
+
+       li      t0, RISCV_S_MODE_ASM
+       csrw    sscratch, t0
+       mv      tp, a0
+       mv      a0, a1                  /* entry_data = device tree */
+       tail    pbl_head_start_v1
+ENDPROC(__barebox_pbl_head_v1_supervisor)
+
+/*
+ * Head for images entered in machine mode without a boot protocol, e.g.
+ * out of reset. a0 is passed on as entry_data.
+ */
+.section .text_head_prologue_v1_machine, "ax"
+ENTRY(__barebox_pbl_head_v1_machine)
+       pbl_image_header .Lmachine_setup
+
+.Lmachine_setup:
+       li      fp, 0
+
+       li      t0, RISCV_M_MODE_ASM
+       csrw    mscratch, t0
+       tail    pbl_head_start_v1
+ENDPROC(__barebox_pbl_head_v1_machine)
+
+/*
+ * asm_pbl_head_start_v1 - assembly version of pbl_head_start_v1()
+ *
+ *   a0 = entry_data on entry, then struct pbl_board_info *
+ *   a1 = struct pbl_stage * (may be NULL)
+ *   s1 = entry_data
+ *   s2 = runtime offset
+ *   s3 = struct pbl_stage * across the prereloc_cpu_init() call
+ *
+ * s1-s3 are callee-saved, so they survive the callbacks.
+ */
+.section .text.asm_pbl_head_start_v1, "ax"
+ENTRY(asm_pbl_head_start_v1)
+       mv      s1, a0
+
+       /* Board info pointers need the runtime offset until relocation ran */
+       call    get_runtime_offset
+       mv      s2, a0
+
+       lla     a0, __pbl_board_info
+
+       /* Stage determination, mirroring pbl_determine_stage_idx() */
+       REG_L   t0, PBI_LOCATE_PC(a0)
+       beqz    t0, .Lscan
+
+       /* Must be a leaf function: there may be no stack yet */
+       add     t0, t0, s2
+       jalr    t0                      /* returns stage index in a0 */
+       mv      t1, a0                  /* idx */
+       lla     a0, __pbl_board_info
+       li      a1, 0                   /* pbl_stage(): NULL by default */
+
+       /*
+        * pbl_stage_case - one case of the pbl_stage() switch; the cases
+        * are mutually exclusive, so each falls through to the next.
+        */
+       .macro  pbl_stage_case idx, offs
+       li      t2, \idx
+       bne     t1, t2, .Lnext_case\@
+       addi    a1, a0, \offs
+.Lnext_case\@:
+       .endm
+
+       pbl_stage_case  PBL_STAGE_ROM_ASM, PBI_ROM_STAGE
+       pbl_stage_case  PBL_STAGE_OCRAM_ASM, PBI_FIRST_STAGE
+       pbl_stage_case  PBL_STAGE_DRAM_ASM, PBI_SECOND_STAGE
+       j       .Lhave_stage            /* a1 stays NULL for other indices */
+
+.Lscan:
+       li      a1, 0                   /* earliest stage with entry */
+       auipc   t3, 0                   /* pc, as get_pc() does */
+
+       /*
+        * pbl_check_stage - one pbl_determine_stage_idx() iteration for the
+        * stage at byte offset \offs
+        */
+       .macro  pbl_check_stage offs
+       addi    t0, a0, \offs
+       /* stage_has_entry()? */
+       REG_L   t4, PBS_SOC_INIT(t0)
+       REG_L   t5, PBS_BOARD_INIT(t0)
+       or      t4, t4, t5
+       REG_L   t5, PBS_SOC_CHAINLOAD(t0)
+       or      t4, t4, t5
+       beqz    t4, .Lcheck_pc\@
+       bnez    a1, .Lcheck_pc\@
+       mv      a1, t0                  /* earliest ?: stage */
+.Lcheck_pc\@:
+       /* pc_inside(mem_start, mem_maxsz ?: mem_minsz)? */
+       REG_L   t4, PBS_MEM_START(t0)
+       REG_L   t5, PBS_MEM_MAXSZ(t0)
+       bnez    t5, .Lhave_memsz\@
+       REG_L   t5, PBS_MEM_MINSZ(t0)
+.Lhave_memsz\@:
+       sub     t4, t3, t4
+       bltu    t4, t5, .Lfound
+       .endm
+
+       pbl_check_stage PBI_ROM_STAGE
+       pbl_check_stage PBI_FIRST_STAGE
+       pbl_check_stage PBI_SECOND_STAGE
+       j       .Lhave_stage            /* stage = earliest (a1) */
+.Lfound:
+       mv      a1, t0
+
+.Lhave_stage:
+       /* Misconfigured board info; there may be no stack to call with */
+.Lno_stage:
+       beqz    a1, .Lno_stage
+
+       /* stage->stacktop is a stack top or a PBL_STACKTOP_* value */
+       REG_L   t0, PBS_STACKTOP(a1)
+       li      t1, PBS_STACKTOP_BEFORE_BAREBOX
+       bne     t0, t1, .Lnot_before_barebox
+       lla     t0, __image_start
+       j       .Lhave_stacktop
+.Lnot_before_barebox:
+       li      t1, PBS_STACKTOP_AFTER_BAREBOX
+       bne     t0, t1, .Lhave_stacktop
+       lla     t0, __image_end
+       li      t1, CONFIG_STACK_SIZE
+       add     t0, t0, t1
+.Lhave_stacktop:
+       andi    t0, t0, -16             /* ALIGN_DOWN(stacktop, 16) */
+       beqz    t0, .Lstack_set
+       mv      sp, t0
+.Lstack_set:
+       /* CPU init callback, unrelocated like assembly_locate_pc() above */
+       REG_L   t0, PBS_CPU_INIT(a1)
+       beqz    t0, .Lcpu_init_done
+       add     t0, t0, s2
+       mv      s3, a1
+       jalr    t0
+       mv      a1, s3
+.Lcpu_init_done:
+       mv      a0, a1
+       mv      a1, s1
+       tail    pbl_head_reloc_v1       /* (a0 = stage, a1 = entry_data) */
+ENDPROC(asm_pbl_head_start_v1)
+
+#ifdef CONFIG_PBL_HEAD_START_ASM
+SYM_FUNC_ALIAS(pbl_head_start_v1, asm_pbl_head_start_v1)
+#endif
diff --git a/arch/riscv/include/asm/barebox-riscv.h 
b/arch/riscv/include/asm/barebox-riscv.h
index 4a7726ddc508..c7ca339607c4 100644
--- a/arch/riscv/include/asm/barebox-riscv.h
+++ b/arch/riscv/include/asm/barebox-riscv.h
@@ -33,6 +33,12 @@
        barebox_riscv_entry(membase, memsize, boarddata, RISCV_S_MODE); \
 } while (0)
 
+/* PBL image heads for ENTRY_POINT_HEAD(), see arch/riscv/boot/head.S */
+extern char __barebox_pbl_head_v1_supervisor[];
+extern char __barebox_pbl_head_v1_machine[];
+
+void __noreturn pbl_stage_barebox_entry(void *boarddata);
+
 static inline unsigned long riscv_mem_stack_top(unsigned long membase,
                                                unsigned long endmem)
 {
diff --git a/arch/riscv/lib/asm-offsets.c b/arch/riscv/lib/asm-offsets.c
index 48960e58a6bc..a7a092cb5d72 100644
--- a/arch/riscv/lib/asm-offsets.c
+++ b/arch/riscv/lib/asm-offsets.c
@@ -9,6 +9,8 @@
 #include <linux/kbuild.h>
 #include <linux/kernel.h>
 #include <asm/ptrace.h>
+#include <asm/system.h>
+#include <pbl/entry.h>
 
 #define STACK_ALIGN            16
 
@@ -56,5 +58,30 @@ int main(void)
         * We allocate a pt_regs on the stack. This ensures the alignment is 
sane.
         */
        DEFINE(PT_SIZE_ON_STACK, ALIGN(sizeof(struct pt_regs), STACK_ALIGN));
+       BLANK();
+       COMMENT("struct pbl_board_info offsets");
+       OFFSET(PBI_LOCATE_PC,           pbl_board_info, assembly_locate_pc);
+       OFFSET(PBI_ROM_STAGE,           pbl_board_info, rom_stage);
+       OFFSET(PBI_FIRST_STAGE,         pbl_board_info, first_stage);
+       OFFSET(PBI_SECOND_STAGE,        pbl_board_info, second_stage);
+       DEFINE(PBI_SIZEOF,              sizeof(struct pbl_board_info));
+       BLANK();
+       COMMENT("struct pbl_stage offsets");
+       OFFSET(PBS_STACKTOP,            pbl_stage, stacktop);
+       OFFSET(PBS_MEM_START,           pbl_stage, mem_start);
+       OFFSET(PBS_MEM_MINSZ,           pbl_stage, mem_minsz);
+       OFFSET(PBS_MEM_MAXSZ,           pbl_stage, mem_maxsz);
+       OFFSET(PBS_CPU_INIT,            pbl_stage, prereloc_cpu_init);
+       OFFSET(PBS_SOC_INIT,            pbl_stage, soc_init);
+       OFFSET(PBS_BOARD_INIT,          pbl_stage, board_init);
+       OFFSET(PBS_SOC_CHAINLOAD,       pbl_stage, soc_chainload);
+       BLANK();
+       DEFINE(PBL_STAGE_ROM_ASM,       PBL_STAGE_ROM);
+       DEFINE(PBL_STAGE_OCRAM_ASM,     PBL_STAGE_OCRAM);
+       DEFINE(PBL_STAGE_DRAM_ASM,      PBL_STAGE_DRAM);
+       DEFINE(PBS_STACKTOP_BEFORE_BAREBOX,     PBL_STACKTOP_BEFORE_BAREBOX);
+       DEFINE(PBS_STACKTOP_AFTER_BAREBOX,      PBL_STACKTOP_AFTER_BAREBOX);
+       DEFINE(RISCV_S_MODE_ASM,        RISCV_S_MODE);
+       DEFINE(RISCV_M_MODE_ASM,        RISCV_M_MODE);
        return 0;
 }
diff --git a/arch/riscv/lib/pbl.lds.S b/arch/riscv/lib/pbl.lds.S
index e580ec06f32c..84c4917e67ad 100644
--- a/arch/riscv/lib/pbl.lds.S
+++ b/arch/riscv/lib/pbl.lds.S
@@ -5,6 +5,7 @@
 #include <asm/barebox.lds.h>
 #include <asm-generic/memory_layout.h>
 #include <asm-generic/pointer.h>
+#include <asm/asm-offsets.h>
 
 OUTPUT_ARCH(BAREBOX_OUTPUT_ARCH)
 OUTPUT_FORMAT(BAREBOX_OUTPUT_FORMAT)
@@ -18,6 +19,9 @@ SECTIONS
        .text      :
        {
                _stext = .;
+               /* Custom image headers go before the common heads */
+               *(.text_image_header*)
+               *(.text_head_prologue*)
                *(.text_head_entry*)
                __bare_init_start = .;
                *(.text_bare_init*)
@@ -28,8 +32,15 @@ SECTIONS
        BAREBOX_BARE_INIT_SIZE
        BAREBOX_PBL_SIZE
 
-       . = ALIGN(4);
+       . = ALIGN(ASM_SZPTR);
        __start_rodata = .;
+       __pbl_board_info = .;
+       .rodata.pbl_board_info : {
+               *(.pbl_board_info_*)
+       }
+       ASSERT(. - __pbl_board_info <= PBI_SIZEOF, "Only one PBL per image 
allowed")
+
+       . = ALIGN(4);
        .rodata : { *(.rodata*) }
 
        . = ALIGN(ASM_SZPTR);
-- 
2.47.3


Reply via email to