Files
chopstx/chopstx-riscv32.c
NIIBE Yutaka af3ef1f93d Add comments to show access to RUNNING.
Signed-off-by: NIIBE Yutaka <gniibe@fsij.org>
2021-02-09 13:33:51 +09:00

685 lines
18 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* chopstx-riscv32.c - Threads and only threads: Arch specific code
* for RISC-V 32 IMAC (Bumblebee core)
*
* Copyright (C) 2019, 2021
* Flying Stone Technology
* Author: NIIBE Yutaka <gniibe@fsij.org>
*
* This file is a part of Chopstx, a thread library for embedded.
*
* Chopstx is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Chopstx is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As additional permission under GNU GPL version 3 section 7, you may
* distribute non-source form of the Program without the copy of the
* GNU GPL normally required by section 4, provided you inform the
* recipients of GNU GPL by a written offer.
*
*/
/*
* Define Bumblebee specific CSRs
*/
asm (
".equ msubm,0x7c4\n\t" /* No use (not needed to care about) */
".equ mtvt,0x307\n\t"
".equ mtvt2,0x7ec\n\t"
);
/* Data Memory Barrier. */
static void
chx_dmb (void)
{
asm volatile ("fence" : : : "memory");
}
/* No saved registers on the stack. */
/*
* Constants for RISC-V.
*/
#define REG_PC 0
#define REG_RA 1
#define REG_SP 2
#define REG_GP 3
#define REG_TP 4
#define REG_A0 10
#define MACHINE_STATUS_INIT 0x00001880 /* Machine-mode, interrupt enabled. */
/*
* We keep the TP register to the thread context address.
* Also, it is kept at the MSCRATCH register.
*/
static struct chx_thread *
chx_running (void)
{
uint32_t r;
asm ( "mv %0,tp" : "=r" (r));
if (r == 0)
return NULL;
return (struct chx_thread *)(r - 20);
}
static void
chx_set_running (struct chx_thread *tp)
{
asm ( "addi tp,%0,20\n\t"
"csrw mscratch,tp" : /* no output */ : "r" (tp) : "memory");
}
/*
* Lower layer architecture specific functions.
*
* system tick and interrupt
*/
/*
* System tick
*
* SYSTICK timer model is decreasing counter, where counter value 0
* means the timer stop.
*
* Since RISC-V MTIME is ever increasing counter, we need a bit of
* glue code here. Fortunately, we have MSTOP register for Bumblebee
* core, so, we can use the register to stop the counter. (If no such
* register stopping MTIME, we could put possible largest value in
* MTIMECMP, so that the timer won't be fired.)
*/
/* TIMER registers. */
struct TIMER {
volatile uint32_t mtime_lo;
volatile uint32_t mtime_hi;
volatile uint32_t mtimecmp_lo;
volatile uint32_t mtimecmp_hi;
uint32_t rsv0[1018];
volatile uint8_t mstop;
uint8_t rsv1[3];
volatile uint8_t msip;
uint8_t rsv2[3];
} __attribute__ ((packed));
static struct TIMER *const TIMER = (struct TIMER *)0xD1000000;
/* In Chopstx, we only use the lower 32-bit of the timer. */
static void
chx_systick_init_arch (void)
{
TIMER->mstop |= 1;
TIMER->mtime_hi = TIMER->mtime_lo = 0;
TIMER->mtimecmp_lo = 0xffffffff;
TIMER->mtimecmp_hi = 0;
}
/*
* @ticks: 0 means to stop the timer, otherwise update the timer
*/
static void
chx_systick_reload (uint32_t ticks)
{
if (!ticks)
{
TIMER->mstop |= 1;
TIMER->mtimecmp_lo = 0xffffffff;
}
else
{
TIMER->mtime_lo = 0;
TIMER->mtimecmp_lo = ticks;
if ((TIMER->mstop & 1))
TIMER->mstop &= ~1;
}
}
static uint32_t
chx_systick_get (void)
{
int stopped = (TIMER->mstop & 1);
if (stopped)
return 0;
else
{
uint32_t now = TIMER->mtime_lo;
if (TIMER->mtimecmp_lo <= now)
return 0;
return TIMER->mtimecmp_lo - now;
}
}
/* TIMER runs at 1/4 of core clock. */
static uint32_t
usec_to_ticks (uint32_t usec)
{
return usec * (MHZ/4);
}
static uint32_t
ticks_to_usec (uint32_t ticks)
{
return ticks / (MHZ/4);
}
/*
* Interrupt Handling of (E)CLIC
*/
/* CLIC registers. */
struct CLIC {
volatile uint8_t cfg;
uint8_t rsv[3];
volatile uint32_t info;
volatile uint8_t uth;
volatile uint8_t sth;
volatile uint8_t hth;
volatile uint8_t mth;
} __attribute__ ((packed));
static struct CLIC *const CLIC = (struct CLIC *)0xD2000000;
struct CLIC_INT {
volatile uint8_t ip;
volatile uint8_t ie;
volatile uint8_t attr;
volatile uint8_t ctl;
} __attribute__ ((packed));
static struct CLIC_INT *const CLIC_INT = (struct CLIC_INT *)0xD2001000;
static void
chx_enable_intr (uint8_t irq_num)
{
CLIC_INT[irq_num].ie |= 1;
}
static void
chx_clr_intr (uint8_t irq_num)
{
/* Clear pending interrupt is done automatically by the hardware. */
(void)irq_num;
}
static int
chx_disable_intr (uint8_t irq_num)
{
int already_disabled = !(CLIC_INT[irq_num].ie & 1);
CLIC_INT[irq_num].ie &= ~1;
return already_disabled;
}
static void
chx_set_intr_prio (uint8_t irq_num)
{
CLIC_INT[irq_num].attr = 0xc0; /* Level triggered, SHV=0 */
/* Note: SHV: Selective Hardware Vectoring: off (use common routine). */
CLIC_INT[irq_num].ctl = 0xff;
}
#define SWINT_IRQ 3
#define TIMER_IRQ 7
#define MEMERR_IRQ 17 /* Why on earth it's IRQ??? */
static void chx_handle_intr (void);
static void exception_handler (void);
/* Alignment to 64 is needed to enable ECLIC mode. */
static void __attribute__ ((naked,aligned(64)))
exception_handler (void)
{
asm volatile (
"0: j 0b"
: /* no output */);
}
static void __attribute__ ((naked))
memory_error (void)
{
asm volatile (
"0: j 0b"
: /* no output */);
}
typedef void (*handler)(void);
/* Not used (because we always disable SHV for all interrupts),
* Just in case, if some interrupt uses SHV=1.
*/
static const handler vector_table[] __attribute__ ((aligned(512))) = {
0, 0, 0, chx_handle_intr,
0, 0, 0, chx_handle_intr,
0, 0, 0, 0,
0, 0, 0, 0,
0, memory_error, 0, 0,
};
static void
chx_interrupt_controller_init (void)
{
asm volatile (
"csrw mtvt,%0"
: /* no output */ : "r" (vector_table) : "memory" );
asm volatile (
"csrw mtvt2,%0\n\t"
"csrsi mtvt2,1"
: /* no output */ : "r" (chx_handle_intr) : "memory");
asm volatile (
"csrw mtvec,%0\n\t"
"csrsi mtvec,3" /* Enable ECLC mode */
: /* no output */ : "r" (exception_handler) : "memory" );
CLIC->cfg &= 0xe1; /* NLBITS = 0 */
CLIC->mth = 0;
/* In Bumblebee core, timer interrupt is also handled by CLIC. */
chx_set_intr_prio (SWINT_IRQ);
chx_enable_intr (SWINT_IRQ);
chx_set_intr_prio (TIMER_IRQ);
chx_enable_intr (TIMER_IRQ);
chx_set_intr_prio (MEMERR_IRQ);
chx_enable_intr (MEMERR_IRQ);
}
/* Just for testing TIMER and ECLIC. No real use for now. */
static void
chx_sw_int (int go)
{
if (go)
TIMER->msip |= 1;
else
TIMER->msip &= ~1;
}
static void
chx_cpu_sched_lock (void)
{
asm volatile ("csrci mstatus,8" : : : "memory" );
}
static void
chx_cpu_sched_unlock (void)
{
asm volatile ("csrsi mstatus,8" : : : "memory" );
}
static void
chx_init_arch (struct chx_thread *tp)
{
memset (&tp->tc, 0, sizeof (tp->tc));
chx_set_running (tp);
}
#define SAVE_CALLEE_SAVE_REGISTERS \
"# Save callee save registers\n\t" \
"sw s0,32(sp)\n\t" \
"sw s1,36(sp)\n\t" \
"sw s2,72(sp)\n\t" \
"sw s3,76(sp)\n\t" \
"sw s4,80(sp)\n\t" \
"sw s5,84(sp)\n\t" \
"sw s6,88(sp)\n\t" \
"sw s7,92(sp)\n\t" \
"sw s8,96(sp)\n\t" \
"sw s9,100(sp)\n\t" \
"sw s10,104(sp)\n\t" \
"sw s11,108(sp)\n\t"
#define RESTORE_CALLEE_SAVE_REGISTERS \
"# Restore callee save registers\n\t" \
"lw s0,32(sp)\n\t" \
"lw s1,36(sp)\n\t" \
"lw s2,72(sp)\n\t" \
"lw s3,76(sp)\n\t" \
"lw s4,80(sp)\n\t" \
"lw s5,84(sp)\n\t" \
"lw s6,88(sp)\n\t" \
"lw s7,92(sp)\n\t" \
"lw s8,96(sp)\n\t" \
"lw s9,100(sp)\n\t" \
"lw s10,104(sp)\n\t" \
"lw s11,108(sp)\n\t"
#define SAVE_OTHER_REGISTERS \
"# Save other registers\n\t" \
"sw ra,4(sp)\n\t" \
"sw gp,12(sp)\n\t" \
"sw t0,20(sp)\n\t" \
"sw t1,24(sp)\n\t" \
"sw t2,28(sp)\n\t" \
"sw a0,40(sp)\n\t" \
"sw a1,44(sp)\n\t" \
"sw a2,48(sp)\n\t" \
"sw a3,52(sp)\n\t" \
"sw a4,56(sp)\n\t" \
"sw a5,60(sp)\n\t" \
"sw a6,64(sp)\n\t" \
"sw a7,68(sp)\n\t" \
"sw t3,112(sp)\n\t" \
"sw t4,116(sp)\n\t" \
"sw t5,120(sp)\n\t" \
"sw t6,124(sp)\n\t"
#define RESTORE_OTHER_REGISTERS \
"# Restore other registers\n\t" \
"lw ra,4(sp)\n\t" \
"lw gp,12(sp)\n\t" \
"lw t0,20(sp)\n\t" \
"lw t1,24(sp)\n\t" \
"lw t2,28(sp)\n\t" \
"lw a0,40(sp)\n\t" \
"lw a1,44(sp)\n\t" \
"lw a2,48(sp)\n\t" \
"lw a3,52(sp)\n\t" \
"lw a4,56(sp)\n\t" \
"lw a5,60(sp)\n\t" \
"lw a6,64(sp)\n\t" \
"lw a7,68(sp)\n\t" \
"lw t3,112(sp)\n\t" \
"lw t4,116(sp)\n\t" \
"lw t5,120(sp)\n\t" \
"lw t6,124(sp)\n\t"
/*
* MSTATUS register:
* 31: SD
* 16-15: XS (Extra Unit state)
* 14-13: FS (Floating-point Unit state)
* 12-11: MPP (Previous Privilege)
* 7: MPIE (Previous Interrupt Enable flag)
* 3: MIE (Interrupt Enable flag)
*/
/*
* MSUBM register:
* 9-8 PTYP (Previous Type-of-execution)
* 7-6 TYP (Currunt Type-of-execution)
* 0: Normal, 1: Interrupt, 2: Excep, 3: NMI
*
* No need to store PTYP field of MSUBM in MACHINE_STATUS.
* No need to setup MSUBM for PTYP.
* Since it's always 0 when this is called.
*
* Save MPP..MPIE in MACHINE_STATUS in the thread context.
*/
#define SETUP_MSTATUS_FROM_MACHINE_STATUS \
"lw a0,128(sp)\n\t" /* MPP..MPIE is in MACHINE_STATUS */ \
"csrr a1,mstatus\n\t" \
"srli a1,a1,13\n\t" \
"slli a1,a1,13\n\t" /* SD, XS, and FS bits from MSTATUS */ \
"or a0,a0,a1\n\t" \
"csrw mstatus,a0\n\t" /* Note: keep MIE=0 */
/* It is good if ISA has a single instruction for this operation. */
#define CATCH_AN_INTERRUPT_SYNCHRONOUSLY \
"mv a0,zero\n" \
"0:\n\t" \
/* \
* Interrupt is masked here, and it executes the WFI \
* instruction. When an interrupt occurs, the core is waken \
* up (even if it is masked). \
*/ \
"csrci mstatus,8\n\t" /* Interrupt should be masked already. */ \
/* Nevertheless, make sure it's masked. */ \
"wfi\n\t" \
/* \
* It is good if MCAUSE were updated here, but it is only \
* updated when the control goes into the interrupt service, \
* in a step of the interrupt handling steps of the core. So, \
* we let it go to chx_handle_intr, by unmasking. \
*/ \
"csrsi mstatus,8\n\t" /* Unmask interrupts to catch one. */ \
/* Just before this line, it is interrupted. */ \
/* And interrupt is masked and a0 is set. */ \
"beqz a0,0b\n\t" /* Just in case if not, loop. */
/*
* The idle function.
*
* NOTE: In this function, interrupt is masked (MIE=0) and interrupt
* is synchronously handled.
*/
static struct chx_thread * __attribute__ ((used))
chx_idle (void)
{
extern void chx_prepare_sleep_mode (void);
register struct chx_thread *tp_next asm ("a0") = NULL;
while (tp_next == NULL)
{
register uint32_t irq_num asm ("a0");
chx_prepare_sleep_mode (); /* MCU specific sleep setup */
asm volatile (
CATCH_AN_INTERRUPT_SYNCHRONOUSLY
/*
* In chx_handle_intr, a0 is set by the value of MCAUSE.
*/
"slli a0,a0,20\n\t"
"srli a0,a0,20" /* Take lower 12-bit of MCAUSE */
: "=r" (irq_num));
/* Note: here, interrupt is masked again. */
if (irq_num == SWINT_IRQ)
chx_sw_int (0);
else if (irq_num == TIMER_IRQ)
tp_next = chx_timer_expired ();
else if (irq_num == MEMERR_IRQ)
memory_error ();
else
tp_next = chx_recv_irq (irq_num);
}
return tp_next;
}
static uintptr_t
voluntary_context_switch (struct chx_thread *tp_next)
{
register uintptr_t result asm ("a0");
asm volatile (
/* Here, %0 (a0) points to pointer (struct chx_thread *) to be
* switched. We get the thread context pointer adding the
* offset.
*/
"# Voluntary context switch\n\t"
"sw sp,8(tp)\n\t"
"mv sp,tp\n\t" /* Using SP, we can use C.SWSP instruction */
"sw zero,0(sp)\n\t"
SAVE_CALLEE_SAVE_REGISTERS
"# Check if going to IDLE function\n\t"
"bnez %0,0f\n\t"
/*
* NOTE: Here, for running chx_idle, we can use
* __main_stack_end__, because neither any threads, nor
* interrupt context use that. Using __main_stack_end__, we
* can minimize stack memory usage of each thread.
*/
"# Call the IDLE function, interrupt masked.\n\t"
"mv tp,zero\n\t"
"csrw mscratch,tp\n\t" /* chx_set_running */
"la sp,__main_stack_end__\n\t"
"call chx_idle\n"
".L_V_CONTEXT_SWITCH_BEGIN:\n"
"0:\n\t"
"addi %0,%0,20\n\t"
"mv sp,%0\n\t"
RESTORE_CALLEE_SAVE_REGISTERS
/**/
"csrw mscratch,sp\n\t" /* chx_set_running */
"lw a0,0(sp)\n\t"
"beqz a0,1f\n"
".L_RETURN_TO_PREEMPTED_THREAD:\n\t"
"csrw mepc,a0\n\t"
SETUP_MSTATUS_FROM_MACHINE_STATUS
RESTORE_OTHER_REGISTERS
"lw tp,16(sp)\n\t" /* Application is free to other use of TP */
"lw sp,8(sp)\n\t"
"mret\n"
"1:\n\t"
"lw a0,-4(sp)\n\t" /* Get the result value */
"mv tp,sp\n\t"
"lw sp,8(sp)\n\t"
"csrsi mstatus,8\n" /* Unmask interrupts. */
".L_V_CONTEXT_SWITCH_FINISH:"
: "=r" (result)
: "0" (tp_next)
: "ra", "t0", "t1", "t2", "t3", "t4", "t5", "t6",
"a1", "a2", "a3", "a4", "a5", "a6", "a7",
"memory");
return result;
}
extern void cause_link_time_error_unexpected_size_of_struct_chx_thread (void);
static struct chx_thread *
chopstx_create_arch (uintptr_t stack_addr, size_t stack_size,
voidfunc thread_entry, void *arg)
{
struct chx_thread *tp;
void *stack;
register uint32_t gp;
asm ( "mv %0,gp" : "=r" (gp));
if (CHOPSTX_THREAD_SIZE != sizeof (struct chx_thread))
cause_link_time_error_unexpected_size_of_struct_chx_thread ();
if (stack_size < sizeof (struct chx_thread))
chx_fatal (CHOPSTX_ERR_THREAD_CREATE);
stack = (void *)(stack_addr + stack_size - sizeof (struct chx_thread));
tp = (struct chx_thread *)stack;
memset (&tp->tc, 0, sizeof (tp->tc));
tp->tc.reg[REG_A0] = (uint32_t)arg;
tp->tc.reg[REG_RA] = (uint32_t)chopstx_exit;
tp->tc.reg[REG_PC] = (uint32_t)thread_entry;
tp->tc.reg[REG_GP] = gp;
tp->tc.reg[REG_TP] = (uint32_t)&tp->tc;
tp->tc.reg[REG_SP] = (uint32_t)stack;
tp->tc.machine_status = MACHINE_STATUS_INIT;
return tp;
}
/*
* Note: Examine the assembler output carefully, because it has
* ((naked)) attribute
*/
static void __attribute__ ((naked,aligned(4)))
chx_handle_intr (void)
{
struct chx_thread *tp_next;
uint32_t irq_num;
/*
* stack setup to __main_stack_end__
* save registers.
*/
asm volatile (
"csrrw sp,mscratch,sp\n\t" /* SP to MSCRATCH, thread pointer into SP */
"# Check if it is IDLE\n\t"
"bnez sp,0f\n\t"
/**/
"csrrw sp,mscratch,sp\n\t" /* Recover MSCRATCH, the thread pointer */
"li a0,0x80\n\t"
"csrrc x0,mstatus,a0\n\t" /* Clear MPIE bit to mask interrupt */
"csrr a0,mcause\n\t"
"mret\n"
"0:\n\t"
"sw tp,16(sp)\n\t" /* Application is free to other use of TP */
SAVE_OTHER_REGISTERS
"csrr a0,mepc\n\t"
"sw a0,0(sp)\n\t"
/**/
"mv tp,sp\n\t" /* TP is now the thread pointer */
"csrrw sp,mscratch,sp\n\t" /* TP to MSCRATCH, SP_old into SP */
"sw sp,8(tp)\n\t"
"la sp,__main_stack_end__");
/*
* The stack at __main_stack_end__ is shared data between chx_idle
* and this handler. When it comes here, there is no active chx_idle,
* so, it is safe to use the stack.
*
* Note that when there are multiple cores, we need this stack for
* each core.
*/
asm ( "csrr %0,mcause\n\t"
"slli %0,%0,20\n\t"
"srli %0,%0,20" /* Take lower 12-bit of MCAUSE */
: "=r" (irq_num));
tp_next = NULL;
if (irq_num == SWINT_IRQ)
chx_sw_int (0);
else if (irq_num == TIMER_IRQ)
tp_next = chx_timer_expired ();
else if (irq_num == MEMERR_IRQ)
memory_error ();
else
tp_next = chx_recv_irq (irq_num);
if (!tp_next)
asm volatile (
"mv sp,tp\n\t" /* Using SP, we can use C.SWSP instruction */
RESTORE_OTHER_REGISTERS
"lw tp,16(sp)\n\t" /* Application is free to other use of TP */
"lw sp,8(sp)\n\t"
"mret");
tp_next = chx_running_preempted (tp_next);
asm volatile (
"# Involuntary context switch\n\t"
"mv sp,tp\n\t" /* Using SP, we can use C.SWSP instruction */
SAVE_CALLEE_SAVE_REGISTERS
/*
* MACHINE_STATUS = (MSTATUS & 0x00001f80)
*/
"csrr a1,mstatus\n\t"
"andi a1,a1,-9\n\t" /* Clear MIE (bit3) */
"slli a1,a1,19\n\t" /* Clear bit31 to bit13 */
"srli a1,a1,19\n\t" /* MPP..MPIE from MSTATUS */
"sw a1,128(sp)\n"
".L_IV_CONTEXT_SWITCH_BEGIN:\n\t"
"addi %0,%0,20\n\t"
"mv sp,%0\n\t"
RESTORE_CALLEE_SAVE_REGISTERS
/**/
"csrw mscratch,sp\n\t" /* chx_set_running */
"lw a0,0(sp)\n\t"
"bnez a0,.L_RETURN_TO_PREEMPTED_THREAD\n\t"
/**/
"lw a0,-4(sp)\n\t" /* Get the result value */
"mv tp,sp\n\t"
"lw sp,8(sp)\n\t"
"la a1,.L_V_CONTEXT_SWITCH_FINISH\n\t"
"csrw mepc,a1\n\t"
"li a1,0x188\n\t" /* Set MPIE and MPP bits */
"slli a1,a1,4\n\t"
"csrrs x0,mstatus,a1\n\t"/* Prev: Machine mode, enable interrupt */
"mret" /* Return to Prev */
: /* no output */ : "r" (tp_next) : "memory");
}