blob: 920ee4b9fc0a6876b40142e50c24d1178aef7447 [file] [log] [blame]
/* Copyright (c) 2017-2018 SiFive Inc. All rights reserved.
This copyrighted material is made available to anyone wishing to use,
modify, copy, or redistribute it subject to the terms and conditions
of the FreeBSD License. This program is distributed in the hope that
it will be useful, but WITHOUT ANY WARRANTY expressed or implied,
including the implied warranties of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. A copy of this license is available at
http://www.opensource.org/licenses.
*/
/* crt0.S: Entry point for RISC-V METAL programs. */
.section .text.libgloss.start
.global _start
.type _start, @function
/* _start is defined by the METAL to have been called with the following
* arguments:
* a0: the hart ID of the currently executing hart. Harts can start at
* any arbitrary point, it's the C library's job to ensure the code is
* safe.
* a1: a pointer to a description of the machine on which this code is
* currently executing. This is probably 0 on an embedded system
* because they tend to not be dynamically portable. As such, newlib
* ignores this argument.
* a2: a pointer to a function that must be run after the envirnoment has
* been initialized, but before user code can be expected to be run.
* If this is 0 then there is no function to be run. */
_start:
.cfi_startproc
.cfi_undefined ra
/* This is a bit funky: it's not usually sane for _start to return, but in
* this case we actually want to in order to signal an error to the METAL. */
mv s0, ra
/* Before doing anything we must initialize the global pointer, as we cannot
* safely perform any access that may be relaxed without GP being set. This
* is done with relaxation disabled to avoid relaxing the address calculation
* to just "addi gp, gp, 0". */
.option push
.option norelax
la gp, __global_pointer$
.option pop
/* The METAL is designed for a bare-metal environment and therefor is expected
* to define its own stack pointer. We also align the stack pointer here
* because the only RISC-V ABI that's currently defined mandates 16-byte
* stack alignment. */
la sp, _sp
/* Increment by hartid number of stack sizes */
li t0, 0
la t1, __stack_size
1:
beq t0, a0, 1f
add sp, sp, t1
addi t0, t0, 1
j 1b
1:
andi sp, sp, -16
/* If we're not hart 0, skip the initialization work */
la t0, __metal_boot_hart
bne a0, t0, _skip_init
/* Embedded systems frequently require relocating the data segment before C
* code can be run -- for example, the data segment may exist in flash upon
* boot and then need to get relocated into a non-persistant writable memory
* before C code can execute. If this is the case we do so here. This step
* is optional: if the METAL provides an environment in which this relocation
* is not necessary then it must simply set metal_segment_data_source_start to
* be equal to metal_segment_data_target_start. */
la t0, metal_segment_data_source_start
la t1, metal_segment_data_target_start
la t2, metal_segment_data_target_end
beq t0, t1, 2f
bge t1, t2, 2f
1:
#if __riscv_xlen == 32
lw a0, 0(t0)
addi t0, t0, 4
sw a0, 0(t1)
addi t1, t1, 4
blt t1, t2, 1b
#else
ld a0, 0(t0)
addi t0, t0, 8
sd a0, 0(t1)
addi t1, t1, 8
blt t1, t2, 1b
#endif
2:
/* Copy the ITIM section */
la t0, metal_segment_itim_source_start
la t1, metal_segment_itim_target_start
la t2, metal_segment_itim_target_end
beq t0, t1, 2f
bge t1, t2, 2f
1:
#if __riscv_xlen == 32
lw a0, 0(t0)
addi t0, t0, 4
sw a0, 0(t1)
addi t1, t1, 4
blt t1, t2, 1b
#else
ld a0, 0(t0)
addi t0, t0, 8
sd a0, 0(t1)
addi t1, t1, 8
blt t1, t2, 1b
#endif
2:
/* Fence all subsequent instruction fetches until after the ITIM writes
complete */
fence.i
/* Zero the BSS segment. */
la t1, metal_segment_bss_target_start
la t2, metal_segment_bss_target_end
bge t1, t2, 2f
1:
#if __riscv_xlen == 32
sw x0, 0(t1)
addi t1, t1, 4
blt t1, t2, 1b
#else
sd x0, 0(t1)
addi t1, t1, 8
blt t1, t2, 1b
#endif
2:
/* At this point we're in an environment that can execute C code. The first
* thing to do is to make the callback to the parent environment if it's been
* requested to do so. */
beqz a2, 1f
jalr a2
1:
/* The RISC-V port only uses new-style constructors and destructors. */
la a0, __libc_fini_array
call atexit
call __libc_init_array
_skip_init:
/* Synchronize harts so that secondary harts wait until hart 0 finishes
initializing */
call __metal_synchronize_harts
/* Check RISC-V isa and enable FS bits if Floating Point architecture. */
csrr a5, misa
li a4, 0x10028
and a5, a5, a4
beqz a5, 1f
csrr a5, mstatus
lui a4, 0x2
or a5, a5, a4
csrw mstatus, a5
csrwi fcsr, 0
1:
/* This is a C runtime, so main() is defined to have some arguments. Since
* there's nothing sane the METAL can pass we don't bother with that but
* instead just setup as close to a NOP as we can. */
li a0, 1 /* argc=1 */
la a1, argv /* argv = {"libgloss", NULL} */
la a2, envp /* envp = {NULL} */
call secondary_main
/* Call exit to handle libc's cleanup routines. Under normal contains this
* shouldn't even get called, but I'm still not using a tail call here
* because returning to the METAL is the right thing to do in pathological
* situations. */
call exit
/* And here's where we return. Again, it's a bit odd but the METAL defines
* this as a bad idea (ie, as opposed to leaving it undefined) and at this
* point it's really the only thing left to do. */
mv ra, s0
ret
.cfi_endproc
/* RISC-V systems always use __libc_{init,fini}_array, but for compatibility we
* define _{init,fini} to do nothing. */
.global _init
.type _init, @function
.global _fini
.type _fini, @function
_init:
_fini:
ret
.size _init, .-_init
.size _fini, .-_fini
/* By default, secondary_main will cause secondary harts to spin forever.
* Users can redefine secondary_main themselves to run code on secondary harts */
.weak secondary_main
.global secondary_main
.type secondary_main, @function
secondary_main:
addi sp, sp, -16
#if __riscv_xlen == 32
sw ra, 4(sp)
#else
sd ra, 8(sp)
#endif
csrr t0, mhartid
la t1, __metal_boot_hart
beq t0, t1, 2f
1:
wfi
j 1b
2:
call main
#if __riscv_xlen == 32
lw ra, 4(sp)
#else
ld ra, 8(sp)
#endif
addi sp, sp, 16
ret
/* This shim allows main() to be passed a set of arguments that can satisfy the
* requirements of the C API. */
.section .rodata.libgloss.start
argv:
.dc.a name
envp:
.dc.a 0
name:
.asciz "libgloss"