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
