riscv: new TLS-based arch_is_user_context() implementation
This reverts the bulk of commit c8bfc2afda
("riscv: make
arch_is_user_context() SMP compatible") and replaces it with a flag
stored in the thread local storage (TLS) area, therefore making TLS
mandatory for userspace support on RISC-V.
This has many advantages:
- The tp (x4) register is already dedicated by the standard for this
purpose, making TLS support almost free.
- This is very efficient, requiring only a single instruction to clear
and 2 instructions to set.
- This makes the SMP case much more efficient. No need for funky
exception code any longer.
- SMP and non-SMP now use the same implementation making maintenance
easier.
- The is_user_mode variable no longer requires a dedicated PMP mapping
and therefore freeing one PMP slot for other purposes.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
5f65dbcc9dab3d39473b05397e05.
This commit is contained in:
parent
3f8e326d1a
commit
00a9634c05
7 changed files with 34 additions and 104 deletions
|
@ -128,6 +128,7 @@ config RISCV_PMP
|
||||||
select SRAM_REGION_PERMISSIONS
|
select SRAM_REGION_PERMISSIONS
|
||||||
select ARCH_MEM_DOMAIN_SYNCHRONOUS_API if USERSPACE
|
select ARCH_MEM_DOMAIN_SYNCHRONOUS_API if USERSPACE
|
||||||
select ARCH_MEM_DOMAIN_DATA if USERSPACE
|
select ARCH_MEM_DOMAIN_DATA if USERSPACE
|
||||||
|
select THREAD_LOCAL_STORAGE if USERSPACE
|
||||||
help
|
help
|
||||||
MCU implements Physical Memory Protection.
|
MCU implements Physical Memory Protection.
|
||||||
|
|
||||||
|
|
|
@ -41,11 +41,9 @@
|
||||||
op fa6, __z_arch_esf_t_fa6_OFFSET(reg) ;\
|
op fa6, __z_arch_esf_t_fa6_OFFSET(reg) ;\
|
||||||
op fa7, __z_arch_esf_t_fa7_OFFSET(reg) ;
|
op fa7, __z_arch_esf_t_fa7_OFFSET(reg) ;
|
||||||
|
|
||||||
#define DO_CALLER_SAVED_T0T1(op) \
|
#define DO_CALLER_SAVED(op) \
|
||||||
RV_E( op t0, __z_arch_esf_t_t0_OFFSET(sp) );\
|
RV_E( op t0, __z_arch_esf_t_t0_OFFSET(sp) );\
|
||||||
RV_E( op t1, __z_arch_esf_t_t1_OFFSET(sp) )
|
RV_E( op t1, __z_arch_esf_t_t1_OFFSET(sp) );\
|
||||||
|
|
||||||
#define DO_CALLER_SAVED_REST(op) \
|
|
||||||
RV_E( op t2, __z_arch_esf_t_t2_OFFSET(sp) );\
|
RV_E( op t2, __z_arch_esf_t_t2_OFFSET(sp) );\
|
||||||
RV_I( op t3, __z_arch_esf_t_t3_OFFSET(sp) );\
|
RV_I( op t3, __z_arch_esf_t_t3_OFFSET(sp) );\
|
||||||
RV_I( op t4, __z_arch_esf_t_t4_OFFSET(sp) );\
|
RV_I( op t4, __z_arch_esf_t_t4_OFFSET(sp) );\
|
||||||
|
@ -139,46 +137,12 @@ SECTION_FUNC(exception.entry, _isr_wrapper)
|
||||||
|
|
||||||
/* restore privileged stack pointer and zero the scratch reg */
|
/* restore privileged stack pointer and zero the scratch reg */
|
||||||
csrrw sp, mscratch, sp
|
csrrw sp, mscratch, sp
|
||||||
|
|
||||||
#ifdef CONFIG_SMP
|
|
||||||
j 2f
|
|
||||||
|
|
||||||
1: /*
|
|
||||||
* We were in user space. Determine if it attempted to execute an
|
|
||||||
* arch_is_user_context() based on mscratch access. We want to return
|
|
||||||
* to u-mode with t0!=0 as quickly as possible if so.
|
|
||||||
*/
|
|
||||||
addi sp, sp, -__z_arch_esf_t_SIZEOF
|
|
||||||
DO_CALLER_SAVED_T0T1(sr) ;
|
|
||||||
/* First, determine if we had an illegal instruction exception. */
|
|
||||||
csrr t0, mcause
|
|
||||||
li t1, SOC_MCAUSE_EXP_MASK
|
|
||||||
and t0, t0, t1
|
|
||||||
addi t0, t0, -2 /* = 2 = illegal instruction */
|
|
||||||
bnez t0, 3f
|
|
||||||
/* Make sure it was actually a "csrr t0, mscratch" */
|
|
||||||
csrr t0, mepc
|
|
||||||
lw t0, 0(t0)
|
|
||||||
li t1, 0x340022f3
|
|
||||||
bne t0, t1, 3f
|
|
||||||
/* So it was: skip over it and return leaving t0 clobbered. */
|
|
||||||
csrr t0, mepc
|
|
||||||
addi t0, t0, 4
|
|
||||||
csrw mepc, t0
|
|
||||||
lr t1, __z_arch_esf_t_t1_OFFSET(sp)
|
|
||||||
addi sp, sp, __z_arch_esf_t_SIZEOF
|
|
||||||
/* restore user stack pointer and leave */
|
|
||||||
csrrw sp, mscratch, sp
|
|
||||||
mret
|
|
||||||
2:
|
|
||||||
#endif /* CONFIG_SMP */
|
|
||||||
1:
|
1:
|
||||||
#endif /* CONFIG_USERSPACE */
|
#endif
|
||||||
|
|
||||||
/* Save caller-saved registers on current thread stack. */
|
/* Save caller-saved registers on current thread stack. */
|
||||||
addi sp, sp, -__z_arch_esf_t_SIZEOF
|
addi sp, sp, -__z_arch_esf_t_SIZEOF
|
||||||
DO_CALLER_SAVED_T0T1(sr) ;
|
DO_CALLER_SAVED(sr) ;
|
||||||
3: DO_CALLER_SAVED_REST(sr) ;
|
|
||||||
|
|
||||||
/* Save s0 in the esf and load it with &_current_cpu. */
|
/* Save s0 in the esf and load it with &_current_cpu. */
|
||||||
sr s0, __z_arch_esf_t_s0_OFFSET(sp)
|
sr s0, __z_arch_esf_t_s0_OFFSET(sp)
|
||||||
|
@ -199,17 +163,14 @@ SECTION_FUNC(exception.entry, _isr_wrapper)
|
||||||
/* save stack value to be restored later */
|
/* save stack value to be restored later */
|
||||||
sr t0, __z_arch_esf_t_sp_OFFSET(sp)
|
sr t0, __z_arch_esf_t_sp_OFFSET(sp)
|
||||||
|
|
||||||
#if defined(CONFIG_THREAD_LOCAL_STORAGE)
|
|
||||||
/* Make sure tls pointer is sane */
|
/* Make sure tls pointer is sane */
|
||||||
lr t0, ___cpu_t_current_OFFSET(s0)
|
lr t0, ___cpu_t_current_OFFSET(s0)
|
||||||
lr tp, _thread_offset_to_tls(t0)
|
lr tp, _thread_offset_to_tls(t0)
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(CONFIG_SMP)
|
/* Clear our per-thread usermode flag */
|
||||||
/* Clear user mode variable */
|
lui t0, %tprel_hi(is_user_mode)
|
||||||
la t0, is_user_mode
|
add t0, t0, tp, %tprel_add(is_user_mode)
|
||||||
sw zero, 0(t0)
|
sb zero, %tprel_lo(is_user_mode)(t0)
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Save MEPC register */
|
/* Save MEPC register */
|
||||||
|
@ -608,12 +569,11 @@ no_fp: /* make sure this is reflected in the restored mstatus */
|
||||||
call z_riscv_pmp_usermode_enable
|
call z_riscv_pmp_usermode_enable
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !defined(CONFIG_SMP)
|
/* Set our per-thread usermode flag */
|
||||||
/* Set user mode variable */
|
li t1, 1
|
||||||
li t0, 1
|
lui t0, %tprel_hi(is_user_mode)
|
||||||
la t1, is_user_mode
|
add t0, t0, tp, %tprel_add(is_user_mode)
|
||||||
sw t0, 0(t1)
|
sb t1, %tprel_lo(is_user_mode)(t0)
|
||||||
#endif
|
|
||||||
|
|
||||||
/* load scratch reg with stack pointer for next exception entry */
|
/* load scratch reg with stack pointer for next exception entry */
|
||||||
add t0, sp, __z_arch_esf_t_SIZEOF
|
add t0, sp, __z_arch_esf_t_SIZEOF
|
||||||
|
@ -625,8 +585,7 @@ no_fp: /* make sure this is reflected in the restored mstatus */
|
||||||
lr s0, __z_arch_esf_t_s0_OFFSET(sp)
|
lr s0, __z_arch_esf_t_s0_OFFSET(sp)
|
||||||
|
|
||||||
/* Restore caller-saved registers from thread stack */
|
/* Restore caller-saved registers from thread stack */
|
||||||
DO_CALLER_SAVED_T0T1(lr)
|
DO_CALLER_SAVED(lr)
|
||||||
DO_CALLER_SAVED_REST(lr)
|
|
||||||
|
|
||||||
#ifdef CONFIG_USERSPACE
|
#ifdef CONFIG_USERSPACE
|
||||||
/* retrieve saved stack pointer */
|
/* retrieve saved stack pointer */
|
||||||
|
|
|
@ -434,15 +434,6 @@ void z_riscv_pmp_usermode_prepare(struct k_thread *thread)
|
||||||
/* Retrieve pmpcfg0 partial content from global entries */
|
/* Retrieve pmpcfg0 partial content from global entries */
|
||||||
thread->arch.u_mode_pmpcfg_regs[0] = global_pmp_cfg[0];
|
thread->arch.u_mode_pmpcfg_regs[0] = global_pmp_cfg[0];
|
||||||
|
|
||||||
#if !defined(CONFIG_SMP)
|
|
||||||
/* Map the is_user_mode variable */
|
|
||||||
extern uint32_t is_user_mode;
|
|
||||||
|
|
||||||
set_pmp_entry(&index, PMP_R,
|
|
||||||
(uintptr_t) &is_user_mode, sizeof(is_user_mode),
|
|
||||||
PMP_U_MODE(thread));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Map the usermode stack */
|
/* Map the usermode stack */
|
||||||
set_pmp_entry(&index, PMP_R | PMP_W,
|
set_pmp_entry(&index, PMP_R | PMP_W,
|
||||||
thread->stack_info.start, thread->stack_info.size,
|
thread->stack_info.start, thread->stack_info.size,
|
||||||
|
@ -542,11 +533,6 @@ int arch_mem_domain_max_partitions_get(void)
|
||||||
/* remove those slots dedicated to global entries */
|
/* remove those slots dedicated to global entries */
|
||||||
available_pmp_slots -= global_pmp_end_index;
|
available_pmp_slots -= global_pmp_end_index;
|
||||||
|
|
||||||
#if !defined(CONFIG_SMP)
|
|
||||||
/* One slot needed to map the is_user_mode variable */
|
|
||||||
available_pmp_slots -= 1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* At least one slot to map the user thread's stack */
|
/* At least one slot to map the user thread's stack */
|
||||||
available_pmp_slots -= 1;
|
available_pmp_slots -= 1;
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,9 @@ void arch_start_cpu(int cpu_num, k_thread_stack_t *stack, int sz,
|
||||||
|
|
||||||
void z_riscv_secondary_cpu_init(int cpu_num)
|
void z_riscv_secondary_cpu_init(int cpu_num)
|
||||||
{
|
{
|
||||||
|
#ifdef CONFIG_THREAD_LOCAL_STORAGE
|
||||||
|
__asm__("mv tp, %0" : : "r" (z_idle_threads[cpu_num].tls));
|
||||||
|
#endif
|
||||||
#if defined(CONFIG_RISCV_SOC_INTERRUPT_INIT)
|
#if defined(CONFIG_RISCV_SOC_INTERRUPT_INIT)
|
||||||
soc_interrupt_init();
|
soc_interrupt_init();
|
||||||
#endif
|
#endif
|
||||||
|
@ -40,9 +43,6 @@ void z_riscv_secondary_cpu_init(int cpu_num)
|
||||||
#endif
|
#endif
|
||||||
#ifdef CONFIG_SMP
|
#ifdef CONFIG_SMP
|
||||||
irq_enable(RISCV_MACHINE_SOFT_IRQ);
|
irq_enable(RISCV_MACHINE_SOFT_IRQ);
|
||||||
#endif
|
|
||||||
#ifdef CONFIG_THREAD_LOCAL_STORAGE
|
|
||||||
__asm__("mv tp, %0" : : "r" (z_idle_threads[cpu_num].tls));
|
|
||||||
#endif
|
#endif
|
||||||
riscv_cpu_init[cpu_num].fn(riscv_cpu_init[cpu_num].arg);
|
riscv_cpu_init[cpu_num].fn(riscv_cpu_init[cpu_num].arg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,11 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <pmp.h>
|
#include <pmp.h>
|
||||||
|
|
||||||
#if defined(CONFIG_USERSPACE) && !defined(CONFIG_SMP)
|
#ifdef CONFIG_USERSPACE
|
||||||
/*
|
/*
|
||||||
* Glogal variable used to know the current mode running.
|
* Per-thread (TLS) variable indicating whether execution is in user mode.
|
||||||
* Is not boolean because it must match the PMP granularity of the arch.
|
|
||||||
*/
|
*/
|
||||||
uint32_t is_user_mode;
|
__thread uint8_t is_user_mode;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void arch_new_thread(struct k_thread *thread, k_thread_stack_t *stack,
|
void arch_new_thread(struct k_thread *thread, k_thread_stack_t *stack,
|
||||||
|
@ -246,9 +245,7 @@ FUNC_NORETURN void arch_user_mode_enter(k_thread_entry_t user_entry,
|
||||||
/* exception stack has to be in mscratch */
|
/* exception stack has to be in mscratch */
|
||||||
csr_write(mscratch, top_of_priv_stack);
|
csr_write(mscratch, top_of_priv_stack);
|
||||||
|
|
||||||
#if !defined(CONFIG_SMP)
|
|
||||||
is_user_mode = true;
|
is_user_mode = true;
|
||||||
#endif
|
|
||||||
|
|
||||||
register void *a0 __asm__("a0") = user_entry;
|
register void *a0 __asm__("a0") = user_entry;
|
||||||
register void *a1 __asm__("a1") = p1;
|
register void *a1 __asm__("a1") = p1;
|
||||||
|
|
|
@ -26,6 +26,9 @@ extern "C" {
|
||||||
|
|
||||||
static ALWAYS_INLINE void arch_kernel_init(void)
|
static ALWAYS_INLINE void arch_kernel_init(void)
|
||||||
{
|
{
|
||||||
|
#ifdef CONFIG_THREAD_LOCAL_STORAGE
|
||||||
|
__asm__ volatile ("li tp, 0");
|
||||||
|
#endif
|
||||||
#ifdef CONFIG_USERSPACE
|
#ifdef CONFIG_USERSPACE
|
||||||
csr_write(mscratch, 0);
|
csr_write(mscratch, 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -147,35 +147,19 @@ static inline uintptr_t arch_syscall_invoke0(uintptr_t call_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_USERSPACE
|
#ifdef CONFIG_USERSPACE
|
||||||
|
register ulong_t riscv_tp_reg __asm__ ("tp");
|
||||||
|
|
||||||
static inline bool arch_is_user_context(void)
|
static inline bool arch_is_user_context(void)
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_SMP
|
/* don't try accessing TLS variables if tp is not initialized */
|
||||||
/*
|
if (riscv_tp_reg == 0) {
|
||||||
* This is painful. There is no way for u-mode code to know if we're
|
return false;
|
||||||
* currently executing in u-mode without generating a fault, besides
|
}
|
||||||
* stealing a general purpose register away from the standard ABI
|
|
||||||
* that is. And a global variable doesn't work on SMP as this must be
|
|
||||||
* per-CPU and we could be migrated to another CPU just at the right
|
|
||||||
* moment to peek at the wrong CPU variable (and u-mode can't disable
|
|
||||||
* preemption either).
|
|
||||||
*
|
|
||||||
* So, given that we'll have to pay the price of an exception entry
|
|
||||||
* anyway, let's at least make it free to privileged threads by using
|
|
||||||
* the mscratch register as the non-user context indicator (it must
|
|
||||||
* be zero in m-mode for exception entry to work properly). In the
|
|
||||||
* case of u-mode we'll simulate a proper return value in the
|
|
||||||
* exception trap code. Let's settle on the return value in t0
|
|
||||||
* and omit the volatile to give the compiler a chance to cache
|
|
||||||
* the result.
|
|
||||||
*/
|
|
||||||
register ulong_t is_user __asm__ ("t1");
|
|
||||||
__asm__ ("csrr %0, mscratch" : "=r" (is_user));
|
|
||||||
return is_user != 0;
|
|
||||||
#else
|
|
||||||
/* Defined in arch/riscv/core/thread.c */
|
/* Defined in arch/riscv/core/thread.c */
|
||||||
extern uint32_t is_user_mode;
|
extern __thread uint8_t is_user_mode;
|
||||||
return is_user_mode;
|
|
||||||
#endif
|
return is_user_mode != 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue