Skip to content

Commit

Permalink
elfloader/risc-v: rework multi core handling
Browse files Browse the repository at this point in the history
- 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 <axelheider@gmx.de>
  • Loading branch information
axel-h committed Jan 7, 2022
1 parent 4ed4d66 commit 9bad7bd
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 82 deletions.
44 changes: 30 additions & 14 deletions elfloader-tool/src/arch-riscv/boot.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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, (unsigned int)CONFIG_MAX_NUM_NODES);

printf(" paddr=[%p..%p]\n", _text, _end - 1);
printf("ELF-loader started on hart %"PRIu_word"\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);
Expand Down Expand Up @@ -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);
Expand Down
181 changes: 113 additions & 68 deletions elfloader-tool/src/arch-riscv/crt0.S
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 */
Expand All @@ -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
Expand All @@ -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

0 comments on commit 9bad7bd

Please sign in to comment.