From ff353501af5705eee47a6d32e874319580a3d39d Mon Sep 17 00:00:00 2001 From: Axel Heider Date: Thu, 11 Nov 2021 10:20:48 +0100 Subject: [PATCH] elfloader/risc-v: rework multi core handling - build code around SBI HSM extension - Ensure DTB is always passed to primary core boot - Drop variable hsm_exists, pass information as parameter - Print more log messages Signed-off-by: Axel Heider --- elfloader-tool/src/arch-riscv/boot.c | 44 ++++--- elfloader-tool/src/arch-riscv/crt0.S | 181 +++++++++++++++++---------- 2 files changed, 143 insertions(+), 82 deletions(-) diff --git a/elfloader-tool/src/arch-riscv/boot.c b/elfloader-tool/src/arch-riscv/boot.c index 4e8987a9..feb4035a 100644 --- a/elfloader-tool/src/arch-riscv/boot.c +++ b/elfloader-tool/src/arch-riscv/boot.c @@ -166,11 +166,10 @@ static int map_kernel_window(struct image_info *kernel_info) return 0; } -int hsm_exists = 0; /* assembly startup code will initialise this */ - #if CONFIG_MAX_NUM_NODES > 1 -extern void secondary_harts(word_t hart_id, word_t core_id); +/* entry if secondary harts are started via SBI HSM extension */ +extern void hsm_start_secondary_core(word_t hart_id, word_t core_id); int secondary_go = 0; int next_logical_core_id = 1; /* incremented by assembly code */ @@ -318,13 +317,22 @@ NORETURN void boot_hart(word_t hart_id, word_t core_id) UNREACHABLE(); } -void main(word_t hart_id, void *bootloader_dtb) +void main(word_t hart_id, void *bootloader_dtb, word_t hsm_exists) { /* Printing uses SBI, so there is no need to initialize any UART. */ - printf("ELF-loader started on (HART %"PRIu_word") (NODES %d)\n", - hart_id, CONFIG_MAX_NUM_NODES); - - printf(" paddr=[%p..%p]\n", _text, _end - 1); + printf("ELF-loader started on hart %u\n", hart_id); + printf(" MAX_NUM_NODES: %u, SBI HSM extension: %s\n", + (unsigned int)CONFIG_MAX_NUM_NODES, + hsm_exists ? "available" : "missing"); + printf(" phys area of binary: [%p..%p]\n", _text, _end - 1); + printf(" DTB from bootloader: %p\n", bootloader_dtb); + + if (hart_id != CONFIG_FIRST_HART_ID) { + printf("ERROR: ELF-loader not is running on FIRST_HART_ID (%d)\n", + (unsigned int)CONFIG_FIRST_HART_ID); + abort(); + UNREACHABLE(); + } /* Load the ELF images and setup the MMU tables. */ int ret = run_elfloader(bootloader_dtb); @@ -352,16 +360,24 @@ void main(word_t hart_id, void *bootloader_dtb) */ printf("no HSM extension, let's hope secondary cores have been started\n"); } else { - /* Start all cores */ + /* If we are running on a platform with SBI HSM extension support, no + * other hart is running. The system start in a random hart, but the + * assembly startup code has done the migration to the designated + * primary hart already. The global variable logical_core_id must be + * untpuched here, otherwise something is badly wrong. + */ + if (1 != next_logical_core_id) { + printf("ERROR: logical core IDs have been assigned already\n"); + abort(); + UNREACHABLE(); + } for (int i = 0; i < CONFIG_MAX_NUM_NODES; i++) { word_t remote_hart_id = i + 1; /* hart IDs start at 1 */ if (remote_hart_id != hart_id) { - /* The remote's hart ID is passed as custom parameter, but this - * value is not used anywhere at the moment. - */ + /* start a secondary core and pass a unique logical core ID */ sbi_hsm_ret_t ret = sbi_hart_start(remote_hart_id, - secondary_harts, - remote_hart_id); + hsm_start_secondary_core, + next_logical_core_id++); if (SBI_SUCCESS != ret.code) { printf("ERROR: could not start hart %"PRIu_word", failure" " (%d, %d)\n", remote_hart_id, ret.code, ret.data); diff --git a/elfloader-tool/src/arch-riscv/crt0.S b/elfloader-tool/src/arch-riscv/crt0.S index c04180e0..447dd266 100644 --- a/elfloader-tool/src/arch-riscv/crt0.S +++ b/elfloader-tool/src/arch-riscv/crt0.S @@ -10,7 +10,6 @@ .extern main .extern __global_pointer$ .extern elfloader_stack -.extern hsm_exists #if CONFIG_MAX_NUM_NODES > 1 .extern boot_hart .extern next_logical_core_id @@ -49,15 +48,9 @@ .global _start _start: -.option push -.option norelax -1:auipc gp, %pcrel_hi(__global_pointer$) - addi gp, gp, %pcrel_lo(1b) -.option pop - /* save the parameters passed */ mv s0, a0 /* preserve a0 (hart id) in s0 */ - mv s2, a1 /* preserve a1 (dtb) in s2 */ + mv s1, a1 /* preserve a1 (dtb) in s1 */ #ifdef CONFIG_IMAGE_BINARY /* Clear the BSS before we get to do anything more specific */ @@ -74,27 +67,86 @@ _start: li a6, SBI_EXT_BASE_PROBE_EXT li a0, SBI_HSM_BASE ecall /* call SBI to probe for HSM extension */ - mv a2, a0 /* move SBI call generic return code to s2 as we need a0 */ - mv a0, s0 /* restore a0 to hold hart ID passed by the boot loader */ - bnez a2, _start1 /* goto _start1 if SBI did not return SBI_SUCCESS (0) */ - beqz a1, _start1 /* goto _start1 if HSM extension is missing */ - - /* Update global bool variable to tell boot code the HSM extension exists. */ - la t1, hsm_exists - li t2, 1 - amoadd.w t1, t2, (t1) - - /* Check if we are on CONFIG_FIRST_HART_ID */ - li s1, CONFIG_FIRST_HART_ID - beq a0, s1, _start1 /* goto _start1 if we are on CONFIG_FIRST_HART_ID */ - - /* Use HSM extension to start hart CONFIG_FIRST_HART_ID. */ -hsm_switch_hart: + seqz t0, a0 /* t0 = (a0 == 0) to check SBI returned SBI_SUCCESS (0) */ + snez t1, a1 /* t1 = (a1 != 0) to HSM extension exist */ + and a2, t0, t1 /* a2 = 1 if HSM extension is available, otherwise 0 */ + li t0, CONFIG_FIRST_HART_ID + bne s0, t0, start_on_secondary + mv a0, s0 /* restore a0 to hold hart ID */ + mv a1, s1 /* restore a1 to hold DTB passed on entry */ +boot_on_primary: + /* We end up here, we are running on the designated primary hart, which might + * not be hart ID 0. The register setup is: + * a0: hart ID from SBI, this must be CONFIG_FIRST_HART_ID + * a1: DTB + * a2: HSM extension exists flag + */ + la sp, (elfloader_stack + BIT(CONFIG_KERNEL_STACK_BITS)) + la t1, main +enter_c_world: + /* if we end up here, assembly startup if finished and control will be handed + * over to C code. Registers a0-n and sp must be set up, t1 holds the address + * of the C function to call. We avoid using t0 (x5), because this is a + * designated additional link register that would make this technically a call + * and not a jump. + */ +.option push +.option norelax +1:auipc gp, %pcrel_hi(__global_pointer$) + addi gp, gp, %pcrel_lo(1b) +.option pop + jr t1 + +/*----------------------------------------------------------------------------*/ +hsm_start_primary_core: + /* SBI has started us on a designated secondary hart, so we used the SBI HSM + * extension to switch to the designated primary hart. The secondary hart is + * shut down here, so we can bring is up via the HSM extension when needed. + * The register setup is: + * a0: hard ID + * a1: custom parameter: DTB from bootloader + */ + li a2, 1 /* remember that the HSM extension is available */ + j boot_on_primary + +/*----------------------------------------------------------------------------*/ +#if CONFIG_MAX_NUM_NODES > 1 +.global hsm_start_secondary_core +hsm_start_secondary_core: + /* We enter here when the ELF-Loader starts a secondary hart via the SBI HSM + * extension. All we have to do here is se up a stack and jump to the C code. + * The register setup is: + * a0: hard ID + * a1: custom parameter: logical core ID + */ + /* setup stack based on the logical ID */ + addi t0, a0, 1 /* increment by one because we need to set sp to the end */ + slli t0, t0, CONFIG_KERNEL_STACK_BITS /* t0 *= BIT(CONFIG_KERNEL_STACK_BITS) */ + la sp, elfloader_stack + add sp, sp, t0 + /* prepare C code entry with paramters: a0 = hard ID, a1 = logical core ID */ + la t1, boot_hart + j enter_c_world + +#endif /* CONFIG_MAX_NUM_NODES > 1 */ + +/*----------------------------------------------------------------------------*/ +start_on_secondary: + /* We end up here if the startup code has detected that SBI has started us on + * a hart that is not the designated primary hart. Try to switch to the + * primary hart and continue the boot process there. This must be supported + * even if CONFIG_MAX_NUM_NODES is set to 1. The register setup is: + * s0: hard ID + * s1: DTB passed from SBI + * a2: HSM extension exists flag + */ + beqz a2, no_hsm_start_secondary + /* Try to bring up the primary hart via the HSM extension */ li a7, SBI_HSM_BASE li a6, SBI_HSM_BASE_HART_START li a0, CONFIG_FIRST_HART_ID /* hart id to start */ - la a1, _start1 /* where to start the hart */ - li a2, 0 /* logical hart_id to be passed in a1 when new hart starts */ + la a1, hsm_start_primary_core /* where to start the hart */ + mv a2, s1 /* custom parameter passed in a1 is the DTB */ ecall /* call SBI to start hart FIRST_HART_ID */ /* Stop current hart, the boot code may bring it up again when needed. */ li a7, SBI_HSM_BASE @@ -104,51 +156,44 @@ hsm_switch_hart_error: wfi j hsm_switch_hart_error -_start1: /* a0 must hold current hard ID passed by bootloader */ +/*----------------------------------------------------------------------------*/ +no_hsm_start_secondary: + /* We end up here if we are not starting in the designated primary core and SBI + * does no implement the HSM extension, so we can't switch to the designated + * primary hart. Lokkls like we are running on a legacy platform where all + * harts start in parallel. The register setup is: + * s0: hard ID + * s1: DTB passed from SBI + * a2: HSM extension exists flag + */ -.option push -.option norelax -1:auipc gp, %pcrel_hi(__global_pointer$) - addi gp, gp, %pcrel_lo(1b) -.option pop - - li s0, CONFIG_FIRST_HART_ID - bne a0, s0, secondary_harts +#if CONFIG_MAX_NUM_NODES > 1 - la sp, (elfloader_stack + BIT(CONFIG_KERNEL_STACK_BITS)) - /* The C code expects the registers to be set up as: - * a0 = hart id - * a1 = dtb + /* Simulate an SBI HSM extension entry, where a0 holds the hart ID and a1 a + * custom value, which is the logical core ID in our usage. Determine it from + * an atomic increment operation on the global variable next_logical_core_id, + * what we use as our ID is the value it had before incrementing it. */ - mv a1, s2 /* restore dtb passed on entry */ - la s0, main - jr s0 - - -.global secondary_harts -secondary_harts: + mv a0, s0 /* restore a0 with hart ID */ + la t0, next_logical_core_id + li t1, 1 + amoadd.w a1, t1, (t0) /* a1 is set to old value of next_logical_core_id */ + /* The logical core ID is valid only less than CONFIG_MAX_NUM_NODES. */ + li t0, CONFIG_MAX_NUM_NODES + blt a1, t0, hsm_start_secondary_core -.option push -.option norelax -1:auipc gp, %pcrel_hi(__global_pointer$) - addi gp, gp, %pcrel_lo(1b) -.option pop - -#if CONFIG_MAX_NUM_NODES > 1 - la a1, next_logical_core_id - li t2, 1 - amoadd.w a1, t2, (a1) - /* now a1 has the logical core id */ - li t2, CONFIG_MAX_NUM_NODES - bge a1, t2, spin_hart - /* setup the core specific stack pointer */ - la sp, elfloader_stack - addi t0, a1, 1 /* increment by one because we need to set sp to the end */ - slli t0, t0, CONFIG_KERNEL_STACK_BITS /* t0 = t0 * BIT(CONFIG_KERNEL_STACK_BITS) */ - add sp, sp, t0 - la s0, boot_hart - jr s0 #endif -spin_hart: + + /* If we arrive here, this hart cannot be used because the number of supported + * secondary hart has been exeeded. Maybe multi core support is not even + * enabled at all. Here is no SBI HSM extension to turn off this hart, so all + * we can do is spinning over a WFI. However, this is not guaranteed to work + * forever, because the memory where the ELF loader keeps the loop can be + * reused and overwritten by the kernel. This will lead to undefined behavior, + * as we don't know what the new contents will be. If we are lucky, the loop + * keeps running from a hart specific instruction cache, so the new memory + * contents are ignored because no synchronization is triggered. + */ +secondary_hart_wfi_loop: wfi - j spin_hart + j secondary_hart_wfi_loop