mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-08 04:56:43 +00:00
Atomic operations were previously broken. RISC-V breaks atomic operations into multiple instructions, and there is some state kept between those instructions. This state is not visible, and is internal to the processor. We were ignoring this state, which resulted in inconsistencies throughout the system. The following sequence was possible: 1. A thread loads a value, and obtains the `reservation set` 2. A context switch happens on the following instruction 3. A different thread loads the value, modifies it, and continues 4. A third thread (or a different process) loads a value and obtains the `reservation set` 5. The original thread runs, sees the `reservation set` is held, and updates the value, clobbering what the second thread did in #3 A workaround for this is to perform a dummy `sc` as part of the context switch. This invalidates any atomic operations that may be pending. Signed-off-by: Sean Cross <sean@xobs.io>
297 lines
9 KiB
ArmAsm
297 lines
9 KiB
ArmAsm
#if __riscv_xlen == 64
|
|
# define STORE sd
|
|
# define LOAD ld
|
|
# define LOG_REGBYTES 3
|
|
#else
|
|
# define STORE sw
|
|
# define LOAD lw
|
|
# define LOG_REGBYTES 2
|
|
#endif
|
|
#define REGBYTES (1 << LOG_REGBYTES)
|
|
|
|
/*
|
|
Entry point of all programs (_start).
|
|
|
|
It initializes DWARF call frame information, the stack pointer, the
|
|
frame pointer (needed for closures to work in start_rust) and the global
|
|
pointer. Then it calls _start_rust.
|
|
*/
|
|
|
|
|
|
.section .text.init, "ax"
|
|
|
|
.global _start
|
|
|
|
_start:
|
|
// Set trap handler, which will be called
|
|
// on interrupts and cpu faults
|
|
la t0, _start_trap
|
|
csrw stvec, t0
|
|
|
|
/////// resume prep code
|
|
// if resume flag is false (0), skip this section
|
|
beq a7, zero, coldboot
|
|
|
|
/*
|
|
This is the resume code. The overall strategy is to have a suspend/resume cycle
|
|
occur within an interrupt context. Thus, at this point of the suspend/resume
|
|
cycle (e.g. the resume half), our strategy is to restore a minimal environment which
|
|
allows a Xous interrupt context to run, which allows the Xous return-from-interrupt
|
|
handler to set up the remainder of the resume tasks.
|
|
*/
|
|
// Restore a default stack pointer - used by interrupt handlers and considered temporary/smashable
|
|
li sp, 0xfffefffc
|
|
|
|
// setup sie [ssoft, sext]. This corresponds to bits 1 and 9, or 0b10_0000_0010
|
|
li t0, 0x202
|
|
csrw sie, t0
|
|
|
|
// restore the SIM with the value previously socked away by the interrupt handler routine during the suspend half
|
|
jal ra, _enable_all_irqs
|
|
|
|
// set scause to simulate an interrupt's occurence -- this makes the resume "feel like" an interrupt
|
|
// vectored into the same routine that caused the suspend, but without having gone through the typical
|
|
// interrupt handler mechanisms
|
|
li t0, 0x80000000
|
|
csrw scause, t0
|
|
|
|
// this is the same path the suspend interrupt took, but we set it up manually
|
|
// and the loader had flipped the "resume" bit, so this should bring us into the resume branch.
|
|
j _start_trap_rust
|
|
|
|
coldboot:
|
|
// Note that registers $a0-$a7 still contain the kernel arguments
|
|
|
|
// Jump to a program to initialize global variables.
|
|
// Note that at this point, the .data section should
|
|
// have already been set up by the loader.
|
|
jal x1, init
|
|
|
|
// Call the kmain function. This function diverges, so
|
|
// shouldn't ever return.
|
|
j kmain
|
|
|
|
|
|
/*
|
|
Trap entry point (_start_trap)
|
|
|
|
Saves all process context into a structure that is
|
|
mapped to each thread at 0xff801000. Return happens
|
|
elsewhere.
|
|
*/
|
|
.section .trap, "ax"
|
|
.global _start_trap
|
|
|
|
_start_trap:
|
|
csrw sscratch, sp
|
|
li sp, 0xff801000
|
|
STORE x1, 0*REGBYTES(sp) // Store x1 in the scratch field
|
|
LOAD x1, 1*REGBYTES(sp) // Load current context number
|
|
slli x1, x1, 7 // Multiply current context number by 32
|
|
add sp, sp, x1 // Set $sp to 0xff801000 + (current_context * 32)
|
|
|
|
STORE x1, 0*REGBYTES(sp)
|
|
// Skip SP for now
|
|
STORE x3, 2*REGBYTES(sp)
|
|
STORE x4, 3*REGBYTES(sp)
|
|
STORE x5, 4*REGBYTES(sp)
|
|
STORE x6, 5*REGBYTES(sp)
|
|
STORE x7, 6*REGBYTES(sp)
|
|
STORE x8, 7*REGBYTES(sp)
|
|
STORE x9, 8*REGBYTES(sp)
|
|
STORE x10, 9*REGBYTES(sp)
|
|
STORE x11, 10*REGBYTES(sp)
|
|
STORE x12, 11*REGBYTES(sp)
|
|
STORE x13, 12*REGBYTES(sp)
|
|
STORE x14, 13*REGBYTES(sp)
|
|
STORE x15, 14*REGBYTES(sp)
|
|
STORE x16, 15*REGBYTES(sp)
|
|
STORE x17, 16*REGBYTES(sp)
|
|
STORE x18, 17*REGBYTES(sp)
|
|
STORE x19, 18*REGBYTES(sp)
|
|
STORE x20, 19*REGBYTES(sp)
|
|
STORE x21, 20*REGBYTES(sp)
|
|
STORE x22, 21*REGBYTES(sp)
|
|
STORE x23, 22*REGBYTES(sp)
|
|
STORE x24, 23*REGBYTES(sp)
|
|
STORE x25, 24*REGBYTES(sp)
|
|
STORE x26, 25*REGBYTES(sp)
|
|
STORE x27, 26*REGBYTES(sp)
|
|
STORE x28, 27*REGBYTES(sp)
|
|
STORE x29, 28*REGBYTES(sp)
|
|
STORE x30, 29*REGBYTES(sp)
|
|
STORE x31, 30*REGBYTES(sp)
|
|
|
|
// Save SEPC
|
|
csrr t0, sepc
|
|
STORE t0, 31*REGBYTES(sp)
|
|
|
|
// Save x1, which was used to calculate the offset. Prior to
|
|
// calculating, it was stashed at 0xff801000.
|
|
li t0, 0xff801000
|
|
LOAD t1, 0*REGBYTES(t0)
|
|
STORE t1, 0*REGBYTES(sp)
|
|
|
|
// Finally, save SP
|
|
csrr t0, sscratch
|
|
STORE t0, 1*REGBYTES(sp)
|
|
|
|
// Restore a default stack pointer
|
|
li sp, 0xfffefffc
|
|
|
|
// Note that registers $a0-$a7 still contain the arguments
|
|
j _start_trap_rust
|
|
|
|
|
|
/*
|
|
Resume a context (_xous_resume_context)
|
|
|
|
Restores all registers from a Context passed in $a0.
|
|
Note that the SEPC and SSTATUS should be set already.
|
|
*/
|
|
.global _xous_resume_context
|
|
_xous_resume_context:
|
|
move sp, a0
|
|
|
|
LOAD x1, 0*REGBYTES(sp)
|
|
// Clear any reservations, invalidating any atomic operations
|
|
#ifndef SKIP_MULTICORE
|
|
sc.w zero, x1, 0*REGBYTES(sp)
|
|
#endif
|
|
|
|
// Skip SP for now
|
|
LOAD x3, 2*REGBYTES(sp)
|
|
LOAD x4, 3*REGBYTES(sp)
|
|
LOAD x5, 4*REGBYTES(sp)
|
|
LOAD x6, 5*REGBYTES(sp)
|
|
LOAD x7, 6*REGBYTES(sp)
|
|
LOAD x8, 7*REGBYTES(sp)
|
|
LOAD x9, 8*REGBYTES(sp)
|
|
LOAD x10, 9*REGBYTES(sp)
|
|
LOAD x11, 10*REGBYTES(sp)
|
|
LOAD x12, 11*REGBYTES(sp)
|
|
LOAD x13, 12*REGBYTES(sp)
|
|
LOAD x14, 13*REGBYTES(sp)
|
|
LOAD x15, 14*REGBYTES(sp)
|
|
LOAD x16, 15*REGBYTES(sp)
|
|
LOAD x17, 16*REGBYTES(sp)
|
|
LOAD x18, 17*REGBYTES(sp)
|
|
LOAD x19, 18*REGBYTES(sp)
|
|
LOAD x20, 19*REGBYTES(sp)
|
|
LOAD x21, 20*REGBYTES(sp)
|
|
LOAD x22, 21*REGBYTES(sp)
|
|
LOAD x23, 22*REGBYTES(sp)
|
|
LOAD x24, 23*REGBYTES(sp)
|
|
LOAD x25, 24*REGBYTES(sp)
|
|
LOAD x26, 25*REGBYTES(sp)
|
|
LOAD x27, 26*REGBYTES(sp)
|
|
LOAD x28, 27*REGBYTES(sp)
|
|
LOAD x29, 28*REGBYTES(sp)
|
|
LOAD x30, 29*REGBYTES(sp)
|
|
LOAD x31, 30*REGBYTES(sp)
|
|
|
|
// Restore SP
|
|
LOAD x2, 1*REGBYTES(sp)
|
|
sret
|
|
|
|
|
|
/*
|
|
Return from a syscall with arguments (_xous_syscall_return_result)
|
|
|
|
Xous passes arguments in $a0-$a7, however the RISC-V calling convention
|
|
states that only $a0 and $a1 are used. To work around this, we pass
|
|
a pointer to a 32-byte structure in $a0, and this function moves that
|
|
into the registers. It then restores the context from other registers.
|
|
*/
|
|
.global _xous_syscall_return_result
|
|
_xous_syscall_return_result:
|
|
|
|
move sp, a1
|
|
LOAD t0, 31*REGBYTES(sp)
|
|
csrw sepc, t0
|
|
|
|
LOAD x1, 0*REGBYTES(sp)
|
|
// An atomic operation is broken up into multiple instructions.
|
|
// A program first issues a `lr` instruction, which basically
|
|
// raises a flag saying that an atomic operation is now live. It
|
|
// then issues an `sc` instruction, and if the flag is still held
|
|
// it succeeds and clears the flag.
|
|
//
|
|
// This flag is a bit of state within the processor, and the
|
|
// RISC-V spec doesn't say how to access it. However, it is only
|
|
// one bit, and we can clear the bit by performing a dummy `sc`
|
|
// here. This will cause a failure in any atomic operation in the
|
|
// program we're switching to.
|
|
#ifndef SKIP_MULTICORE
|
|
sc.w zero, x1, 0*REGBYTES(sp)
|
|
#endif
|
|
|
|
// Skip SP for now
|
|
LOAD x3, 2*REGBYTES(sp)
|
|
LOAD x4, 3*REGBYTES(sp)
|
|
LOAD x5, 4*REGBYTES(sp)
|
|
LOAD x6, 5*REGBYTES(sp)
|
|
LOAD x7, 6*REGBYTES(sp)
|
|
LOAD x8, 7*REGBYTES(sp)
|
|
LOAD x9, 8*REGBYTES(sp)
|
|
|
|
// Load argument registers from the value pointed
|
|
// to by $a0
|
|
LOAD a7, 7*REGBYTES(a0)
|
|
LOAD a6, 6*REGBYTES(a0)
|
|
LOAD a5, 5*REGBYTES(a0)
|
|
LOAD a4, 4*REGBYTES(a0)
|
|
LOAD a3, 3*REGBYTES(a0)
|
|
LOAD a2, 2*REGBYTES(a0)
|
|
LOAD a1, 1*REGBYTES(a0)
|
|
LOAD a0, 0*REGBYTES(a0)
|
|
|
|
LOAD x18, 17*REGBYTES(sp)
|
|
LOAD x19, 18*REGBYTES(sp)
|
|
LOAD x20, 19*REGBYTES(sp)
|
|
LOAD x21, 20*REGBYTES(sp)
|
|
LOAD x22, 21*REGBYTES(sp)
|
|
LOAD x23, 22*REGBYTES(sp)
|
|
LOAD x24, 23*REGBYTES(sp)
|
|
LOAD x25, 24*REGBYTES(sp)
|
|
LOAD x26, 25*REGBYTES(sp)
|
|
LOAD x27, 26*REGBYTES(sp)
|
|
LOAD x28, 27*REGBYTES(sp)
|
|
LOAD x29, 28*REGBYTES(sp)
|
|
LOAD x30, 29*REGBYTES(sp)
|
|
LOAD x31, 30*REGBYTES(sp)
|
|
|
|
// Restore SP
|
|
//csrr t0, sscratch
|
|
LOAD x2, 1*REGBYTES(sp)
|
|
sret
|
|
|
|
|
|
.global flush_mmu
|
|
flush_mmu:
|
|
sfence.vma
|
|
ret
|
|
|
|
/*
|
|
Endcap of valid RISC-V code, to ensure speculative reads
|
|
past the end of the .text section to not trigger an illegal
|
|
instuction opcode. This cap is equal to the width of a cache line,
|
|
using the 2-byte RV32C encoding.
|
|
*/
|
|
.section .endcap, "ax"
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|