| /* SPDX-License-Identifier: GPL-2.0+ */ | |
| /* | |
| * Startup Code for RISC-V Core | |
| * | |
| * Copyright (c) 2017 Microsemi Corporation. | |
| * Copyright (c) 2017 Padmarao Begari <Padmarao.Begari@microsemi.com> | |
| * | |
| * Copyright (C) 2017 Andes Technology Corporation | |
| * Rick Chen, Andes Technology Corporation <rick@andestech.com> | |
| */ | |
| #include <asm-offsets.h> | |
| #include <config.h> | |
| #include <elf.h> | |
| #include <system-constants.h> | |
| #include <asm/encoding.h> | |
| #include <generated/asm-offsets.h> | |
| #ifdef CONFIG_32BIT | |
| #define LREG lw | |
| #define SREG sw | |
| #define REGBYTES 4 | |
| #define RELOC_TYPE R_RISCV_32 | |
| #define SYM_INDEX 0x8 | |
| #define SYM_SIZE 0x10 | |
| #else | |
| #define LREG ld | |
| #define SREG sd | |
| #define REGBYTES 8 | |
| #define RELOC_TYPE R_RISCV_64 | |
| #define SYM_INDEX 0x20 | |
| #define SYM_SIZE 0x18 | |
| #endif | |
| .section .data | |
| secondary_harts_relocation_error: | |
| .ascii "Relocation of secondary harts has failed, error %d\n" | |
| .section .text | |
| .globl _start | |
| _start: | |
| #if CONFIG_IS_ENABLED(RISCV_MMODE) | |
| csrr a0, CSR_MHARTID | |
| #endif | |
| /* | |
| * Save hart id and dtb pointer. The thread pointer register is not | |
| * modified by C code. It is used by secondary_hart_loop. | |
| */ | |
| mv tp, a0 | |
| mv s1, a1 | |
| /* | |
| * Set the global data pointer to a known value in case we get a very | |
| * early trap. The global data pointer will be set its actual value only | |
| * after it has been initialized. | |
| */ | |
| mv gp, zero | |
| /* | |
| * Set the trap handler. This must happen after initializing gp because | |
| * the handler may use it. | |
| */ | |
| la t0, trap_entry | |
| csrw MODE_PREFIX(tvec), t0 | |
| /* | |
| * Mask all interrupts. Interrupts are disabled globally (in m/sstatus) | |
| * for U-Boot, but we will need to read m/sip to determine if we get an | |
| * IPI | |
| */ | |
| csrw MODE_PREFIX(ie), zero | |
| #if CONFIG_IS_ENABLED(SMP) | |
| /* check if hart is within range */ | |
| /* tp: hart id */ | |
| li t0, CONFIG_NR_CPUS | |
| bge tp, t0, hart_out_of_bounds_loop | |
| /* set xSIE bit to receive IPIs */ | |
| #if CONFIG_IS_ENABLED(RISCV_MMODE) | |
| li t0, MIE_MSIE | |
| #else | |
| li t0, SIE_SSIE | |
| #endif | |
| csrs MODE_PREFIX(ie), t0 | |
| #endif | |
| /* | |
| * Set stackpointer in internal/ex RAM to call board_init_f | |
| */ | |
| call_board_init_f: | |
| #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) | |
| li t0, CONFIG_SPL_STACK | |
| #else | |
| li t0, SYS_INIT_SP_ADDR | |
| #endif | |
| and t0, t0, -16 /* force 16 byte alignment */ | |
| /* setup stack */ | |
| #if CONFIG_IS_ENABLED(SMP) | |
| /* tp: hart id */ | |
| slli t1, tp, CONFIG_STACK_SIZE_SHIFT | |
| sub sp, t0, t1 | |
| #else | |
| mv sp, t0 | |
| #endif | |
| /* | |
| * Now sp points to the right stack belonging to current CPU. | |
| * It's essential before any function call, otherwise, we get data-race. | |
| */ | |
| /* clear stack if necessary */ | |
| #if CONFIG_IS_ENABLED(ZERO_MEM_BEFORE_USE) | |
| clear_stack: | |
| li t1, 1 | |
| slli t1, t1, CONFIG_STACK_SIZE_SHIFT | |
| sub t1, sp, t1 | |
| clear_stack_loop: | |
| SREG zero, 0(t1) /* t1 is always 16 byte aligned */ | |
| addi t1, t1, REGBYTES | |
| blt t1, sp, clear_stack_loop | |
| #endif | |
| call_board_init_f_0: | |
| /* find top of reserve space */ | |
| #if CONFIG_IS_ENABLED(SMP) | |
| li t1, CONFIG_NR_CPUS | |
| #else | |
| li t1, 1 | |
| #endif | |
| slli t1, t1, CONFIG_STACK_SIZE_SHIFT | |
| sub a0, t0, t1 /* t1 -> size of all CPU stacks */ | |
| jal board_init_f_alloc_reserve | |
| /* | |
| * Save global data pointer for later. We don't set it here because it | |
| * is not initialized yet. | |
| */ | |
| mv s0, a0 | |
| /* Configure proprietary settings and customized CSRs of harts */ | |
| call_harts_early_init: | |
| jal harts_early_init | |
| #if !CONFIG_IS_ENABLED(XIP) | |
| /* | |
| * Pick hart to initialize global data and run U-Boot. The other harts | |
| * wait for initialization to complete. | |
| */ | |
| la t0, hart_lottery | |
| li t1, 1 | |
| amoswap.w s2, t1, 0(t0) | |
| bnez s2, wait_for_gd_init | |
| #else | |
| /* | |
| * FIXME: gp is set before it is initialized. If an XIP U-Boot ever | |
| * encounters a pending IPI on boot it is liable to jump to whatever | |
| * memory happens to be in ipi_data.addr on boot. It may also run into | |
| * problems if it encounters an exception too early (because printf/puts | |
| * accesses gd). | |
| */ | |
| mv gp, s0 | |
| #if CONFIG_IS_ENABLED(RISCV_MMODE) | |
| bnez tp, secondary_hart_loop | |
| #endif | |
| #endif | |
| mv a0, s0 | |
| jal board_init_f_init_reserve | |
| SREG s1, GD_FIRMWARE_FDT_ADDR(gp) | |
| /* save the boot hart id to global_data */ | |
| SREG tp, GD_BOOT_HART(gp) | |
| #if !CONFIG_IS_ENABLED(XIP) | |
| #ifdef CONFIG_AVAILABLE_HARTS | |
| la t0, available_harts_lock | |
| amoswap.w.rl zero, zero, 0(t0) | |
| #endif | |
| wait_for_gd_init: | |
| /* | |
| * Set the global data pointer only when gd_t has been initialized. | |
| * This was already set by arch_setup_gd on the boot hart, but all other | |
| * harts' global data pointers gets set here. | |
| */ | |
| mv gp, s0 | |
| #ifdef CONFIG_AVAILABLE_HARTS | |
| la t0, available_harts_lock | |
| li t1, 1 | |
| 1: amoswap.w.aq t1, t1, 0(t0) | |
| bnez t1, 1b | |
| /* register available harts in the available_harts mask */ | |
| li t1, 1 | |
| sll t1, t1, tp | |
| LREG t2, GD_AVAILABLE_HARTS(gp) | |
| or t2, t2, t1 | |
| SREG t2, GD_AVAILABLE_HARTS(gp) | |
| amoswap.w.rl zero, zero, 0(t0) | |
| #endif | |
| /* | |
| * Continue on hart lottery winner, others branch to | |
| * secondary_hart_loop. | |
| */ | |
| bnez s2, secondary_hart_loop | |
| #endif | |
| #ifdef CONFIG_DEBUG_UART | |
| jal debug_uart_init | |
| #endif | |
| mv a0, zero /* a0 <-- boot_flags = 0 */ | |
| la t5, board_init_f | |
| jalr t5 /* jump to board_init_f() */ | |
| #ifdef CONFIG_SPL_BUILD | |
| spl_clear_bss: | |
| la t0, __bss_start | |
| la t1, __bss_end | |
| beq t0, t1, spl_stack_gd_setup | |
| spl_clear_bss_loop: | |
| SREG zero, 0(t0) | |
| addi t0, t0, REGBYTES | |
| blt t0, t1, spl_clear_bss_loop | |
| spl_stack_gd_setup: | |
| jal spl_relocate_stack_gd | |
| /* skip setup if we did not relocate */ | |
| beqz a0, spl_call_board_init_r | |
| mv s0, a0 | |
| /* setup stack on main hart */ | |
| #if CONFIG_IS_ENABLED(SMP) | |
| /* tp: hart id */ | |
| slli t0, tp, CONFIG_STACK_SIZE_SHIFT | |
| sub sp, s0, t0 | |
| #else | |
| mv sp, s0 | |
| #endif | |
| #if CONFIG_IS_ENABLED(SMP) | |
| /* set new stack and global data pointer on secondary harts */ | |
| spl_secondary_hart_stack_gd_setup: | |
| la a0, secondary_hart_relocate | |
| mv a1, s0 | |
| mv a2, s0 | |
| mv a3, zero | |
| jal smp_call_function | |
| /* hang if relocation of secondary harts has failed */ | |
| beqz a0, 1f | |
| mv a1, a0 | |
| la a0, secondary_harts_relocation_error | |
| jal printf | |
| jal hang | |
| #endif | |
| /* set new global data pointer on main hart */ | |
| 1: mv gp, s0 | |
| spl_call_board_init_r: | |
| mv a0, zero | |
| mv a1, zero | |
| j board_init_r | |
| #endif | |
| #if !defined(CONFIG_SPL_BUILD) | |
| /* | |
| * void relocate_code(addr_sp, gd, addr_moni) | |
| * | |
| * This "function" does not return, instead it continues in RAM | |
| * after relocating the monitor code. | |
| * | |
| */ | |
| .globl relocate_code | |
| relocate_code: | |
| mv s2, a0 /* save addr_sp */ | |
| mv s3, a1 /* save addr of gd */ | |
| mv s4, a2 /* save addr of destination */ | |
| /* | |
| *Set up the stack | |
| */ | |
| stack_setup: | |
| #if CONFIG_IS_ENABLED(SMP) | |
| /* tp: hart id */ | |
| slli t0, tp, CONFIG_STACK_SIZE_SHIFT | |
| sub sp, s2, t0 | |
| #else | |
| mv sp, s2 | |
| #endif | |
| la t0, _start | |
| sub t6, s4, t0 /* t6 <- relocation offset */ | |
| beq t0, s4, clear_bss /* skip relocation */ | |
| mv t1, s4 /* t1 <- scratch for copy_loop */ | |
| la t2, __bss_start /* t2 <- source end address */ | |
| copy_loop: | |
| LREG t5, 0(t0) | |
| addi t0, t0, REGBYTES | |
| SREG t5, 0(t1) | |
| addi t1, t1, REGBYTES | |
| blt t0, t2, copy_loop | |
| /* | |
| * Update dynamic relocations after board_init_f | |
| */ | |
| fix_rela_dyn: | |
| la t1, __rel_dyn_start | |
| la t2, __rel_dyn_end | |
| beq t1, t2, clear_bss | |
| add t1, t1, t6 /* t1 <- rela_dyn_start in RAM */ | |
| add t2, t2, t6 /* t2 <- rela_dyn_end in RAM */ | |
| 6: | |
| LREG t5, REGBYTES(t1) /* t5 <-- relocation info:type */ | |
| li t3, R_RISCV_RELATIVE /* reloc type R_RISCV_RELATIVE */ | |
| bne t5, t3, 8f /* skip non-RISCV_RELOC entries */ | |
| LREG t3, 0(t1) | |
| LREG t5, (REGBYTES * 2)(t1) /* t5 <-- addend */ | |
| add t5, t5, t6 /* t5 <-- location to fix up in RAM */ | |
| add t3, t3, t6 /* t3 <-- location to fix up in RAM */ | |
| SREG t5, 0(t3) | |
| j 10f | |
| 8: | |
| la t4, __dyn_sym_start | |
| add t4, t4, t6 | |
| 9: | |
| srli t0, t5, SYM_INDEX /* t0 <--- sym table index */ | |
| andi t5, t5, 0xFF /* t5 <--- relocation type */ | |
| li t3, RELOC_TYPE | |
| bne t5, t3, 10f /* skip non-addned entries */ | |
| LREG t3, 0(t1) | |
| li t5, SYM_SIZE | |
| mul t0, t0, t5 | |
| add s5, t4, t0 | |
| LREG t0, (REGBYTES * 2)(t1) /* t0 <-- addend */ | |
| LREG t5, REGBYTES(s5) | |
| add t5, t5, t0 | |
| add t5, t5, t6 /* t5 <-- location to fix up in RAM */ | |
| add t3, t3, t6 /* t3 <-- location to fix up in RAM */ | |
| SREG t5, 0(t3) | |
| 10: | |
| addi t1, t1, (REGBYTES * 3) | |
| blt t1, t2, 6b | |
| /* | |
| * trap update | |
| */ | |
| la t0, trap_entry | |
| add t0, t0, t6 | |
| csrw MODE_PREFIX(tvec), t0 | |
| clear_bss: | |
| la t0, __bss_start /* t0 <- rel __bss_start in FLASH */ | |
| add t0, t0, t6 /* t0 <- rel __bss_start in RAM */ | |
| la t1, __bss_end /* t1 <- rel __bss_end in FLASH */ | |
| add t1, t1, t6 /* t1 <- rel __bss_end in RAM */ | |
| beq t0, t1, relocate_secondary_harts | |
| clbss_l: | |
| SREG zero, 0(t0) /* clear loop... */ | |
| addi t0, t0, REGBYTES | |
| blt t0, t1, clbss_l | |
| relocate_secondary_harts: | |
| #if CONFIG_IS_ENABLED(SMP) | |
| /* send relocation IPI */ | |
| la t0, secondary_hart_relocate | |
| add a0, t0, t6 | |
| /* store relocation offset */ | |
| mv s5, t6 | |
| mv a1, s2 | |
| mv a2, s3 | |
| mv a3, zero | |
| jal smp_call_function | |
| /* hang if relocation of secondary harts has failed */ | |
| beqz a0, 1f | |
| mv a1, a0 | |
| la a0, secondary_harts_relocation_error | |
| jal printf | |
| jal hang | |
| /* restore relocation offset */ | |
| 1: mv t6, s5 | |
| #endif | |
| /* | |
| * We are done. Do not return, instead branch to second part of board | |
| * initialization, now running from RAM. | |
| */ | |
| call_board_init_r: | |
| jal invalidate_icache_all | |
| jal flush_dcache_all | |
| la t0, board_init_r /* offset of board_init_r() */ | |
| add t4, t0, t6 /* real address of board_init_r() */ | |
| /* | |
| * setup parameters for board_init_r | |
| */ | |
| mv a0, s3 /* gd_t */ | |
| mv a1, s4 /* dest_addr */ | |
| mv s0, zero /* fp == NULL */ | |
| /* | |
| * jump to it ... | |
| */ | |
| jr t4 /* jump to board_init_r() */ | |
| #endif /* !defined(CONFIG_SPL_BUILD) */ | |
| #if CONFIG_IS_ENABLED(SMP) | |
| hart_out_of_bounds_loop: | |
| /* Harts in this loop are out of bounds, increase CONFIG_NR_CPUS. */ | |
| wfi | |
| j hart_out_of_bounds_loop | |
| /* SMP relocation entry */ | |
| secondary_hart_relocate: | |
| /* a1: new sp */ | |
| /* a2: new gd */ | |
| /* tp: hart id */ | |
| /* setup stack */ | |
| slli t0, tp, CONFIG_STACK_SIZE_SHIFT | |
| sub sp, a1, t0 | |
| /* update global data pointer */ | |
| mv gp, a2 | |
| #endif | |
| /* | |
| * Interrupts are disabled globally, but they can still be read from m/sip. The | |
| * wfi function will wake us up if we get an IPI, even if we do not trap. | |
| */ | |
| secondary_hart_loop: | |
| wfi | |
| #if CONFIG_IS_ENABLED(SMP) | |
| csrr t0, MODE_PREFIX(ip) | |
| #if CONFIG_IS_ENABLED(RISCV_MMODE) | |
| andi t0, t0, MIE_MSIE | |
| #else | |
| andi t0, t0, SIE_SSIE | |
| #endif | |
| beqz t0, secondary_hart_loop | |
| mv a0, tp | |
| jal handle_ipi | |
| #endif | |
| j secondary_hart_loop |