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:
Nicolas Pitre 2022-06-07 09:37:59 -04:00 committed by Anas Nashif
commit 00a9634c05
7 changed files with 34 additions and 104 deletions

View file

@ -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.

View file

@ -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 */

View file

@ -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;

View file

@ -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);
} }

View file

@ -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;

View file

@ -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

View file

@ -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