arch: riscv: add memory protection support

The IRQ handler has had a major changes to manage syscall, reschedule
and interrupt from user thread and stack guard.

Add userspace support:
- Use a global variable to know if the current execution is user or
  machine. The location of this variable is read only for all user
  thread and read/write for kernel thread.
- Memory shared is supported.
- Use dynamic allocation to optimize PMP slot usage. If the area size
  is a power of 2, only one PMP slot is used, else 2 are used.

Add stack guard support:
- Use MPRV bit to force PMP rules to machine mode execution.
- IRQ stack have a locked stack guard to avoid re-write PMP
  configuration registers for each interruption and then win some
  cycle.
- The IRQ stack is used as "temporary" stack at the beginning of IRQ
  handler to save current ESF. That avoid to trigger write fault on
  thread stack during store ESF which that call IRQ handler to
  infinity.
- A stack guard is also setup for privileged stack of a user thread.

Thread:
- A PMP setup is specific to each thread. PMP setup are saved in each
  thread structure to improve reschedule performance.

Signed-off-by: Alexandre Mergnat <amergnat@baylibre.com>
Reviewed-by: Nicolas Royer <nroyer@baylibre.com>
This commit is contained in:
Alexandre Mergnat 2020-07-21 16:00:39 +02:00 committed by Anas Nashif
commit 542a7fa25d
20 changed files with 1950 additions and 89 deletions

View file

@ -20,15 +20,172 @@
#include <arch/common/sys_bitops.h>
#include <arch/common/sys_io.h>
#include <arch/common/ffs.h>
#if defined(CONFIG_USERSPACE)
#include <arch/riscv/syscall.h>
#endif /* CONFIG_USERSPACE */
#include <irq.h>
#include <sw_isr_table.h>
#include <soc.h>
#include <devicetree.h>
#include <arch/riscv/csr.h>
/* stacks, for RISCV architecture stack should be 16byte-aligned */
#define ARCH_STACK_PTR_ALIGN 16
#ifdef CONFIG_PMP_STACK_GUARD
#define Z_RISCV_PMP_ALIGN CONFIG_PMP_STACK_GUARD_MIN_SIZE
#define Z_RISCV_STACK_GUARD_SIZE Z_RISCV_PMP_ALIGN
#else
#define Z_RISCV_PMP_ALIGN 4
#define Z_RISCV_STACK_GUARD_SIZE 0
#endif
/* Kernel-only stacks have the following layout if a stack guard is enabled:
*
* +------------+ <- thread.stack_obj
* | Guard | } Z_RISCV_STACK_GUARD_SIZE
* +------------+ <- thread.stack_info.start
* | Kernel |
* | stack |
* | |
* +............|
* | TLS | } thread.stack_info.delta
* +------------+ <- thread.stack_info.start + thread.stack_info.size
*/
#ifdef CONFIG_PMP_STACK_GUARD
#define ARCH_KERNEL_STACK_RESERVED Z_RISCV_STACK_GUARD_SIZE
#define ARCH_KERNEL_STACK_OBJ_ALIGN Z_RISCV_PMP_ALIGN
#endif
#ifdef CONFIG_USERSPACE
/* Any thread running In user mode will have full access to the region denoted
* by thread.stack_info.
*
* Thread-local storage is at the very highest memory locations of this area.
* Memory for TLS and any initial random stack pointer offset is captured
* in thread.stack_info.delta.
*/
#ifdef CONFIG_PMP_STACK_GUARD
#ifdef CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT
/* Use defaults for everything. The privilege elevation stack is located
* in another area of memory generated at build time by gen_kobject_list.py
*
* +------------+ <- thread.arch.priv_stack_start
* | Guard | } Z_RISCV_STACK_GUARD_SIZE
* +------------+
* | Priv Stack | } CONFIG_PRIVILEGED_STACK_SIZE - Z_RISCV_STACK_GUARD_SIZE
* +------------+ <- thread.arch.priv_stack_start +
* CONFIG_PRIVILEGED_STACK_SIZE
*
* +------------+ <- thread.stack_obj = thread.stack_info.start
* | Thread |
* | stack |
* | |
* +............|
* | TLS | } thread.stack_info.delta
* +------------+ <- thread.stack_info.start + thread.stack_info.size
*/
#define ARCH_THREAD_STACK_SIZE_ADJUST(size) \
Z_POW2_CEIL(ROUND_UP((size), Z_RISCV_PMP_ALIGN))
#define ARCH_THREAD_STACK_OBJ_ALIGN(size) \
ARCH_THREAD_STACK_SIZE_ADJUST(size)
#define ARCH_THREAD_STACK_RESERVED 0
#else /* !CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT */
/* The stack object will contain the PMP guard, the privilege stack, and then
* the stack buffer in that order:
*
* +------------+ <- thread.stack_obj
* | Guard | } Z_RISCV_STACK_GUARD_SIZE
* +------------+ <- thread.arch.priv_stack_start
* | Priv Stack | } CONFIG_PRIVILEGED_STACK_SIZE
* +------------+ <- thread.stack_info.start
* | Thread |
* | stack |
* | |
* +............|
* | TLS | } thread.stack_info.delta
* +------------+ <- thread.stack_info.start + thread.stack_info.size
*/
#define ARCH_THREAD_STACK_RESERVED (Z_RISCV_STACK_GUARD_SIZE + \
CONFIG_PRIVILEGED_STACK_SIZE)
#define ARCH_THREAD_STACK_OBJ_ALIGN(size) Z_RISCV_PMP_ALIGN
/* We need to be able to exactly cover the stack buffer with an PMP region,
* so round its size up to the required granularity of the PMP
*/
#define ARCH_THREAD_STACK_SIZE_ADJUST(size) \
(ROUND_UP((size), Z_RISCV_PMP_ALIGN))
#endif /* CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT */
#else /* !CONFIG_PMP_STACK_GUARD */
#ifdef CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT
/* Use defaults for everything. The privilege elevation stack is located
* in another area of memory generated at build time by gen_kobject_list.py
*
* +------------+ <- thread.arch.priv_stack_start
* | Priv Stack | } Z_KERNEL_STACK_LEN(CONFIG_PRIVILEGED_STACK_SIZE)
* +------------+
*
* +------------+ <- thread.stack_obj = thread.stack_info.start
* | Thread |
* | stack |
* | |
* +............|
* | TLS | } thread.stack_info.delta
* +------------+ <- thread.stack_info.start + thread.stack_info.size
*/
#define ARCH_THREAD_STACK_SIZE_ADJUST(size) \
Z_POW2_CEIL(ROUND_UP((size), Z_RISCV_PMP_ALIGN))
#define ARCH_THREAD_STACK_OBJ_ALIGN(size) \
ARCH_THREAD_STACK_SIZE_ADJUST(size)
#define ARCH_THREAD_STACK_RESERVED 0
#else /* !CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT */
/* Userspace enabled, but supervisor stack guards are not in use */
/* Reserved area of the thread object just contains the privilege stack:
*
* +------------+ <- thread.stack_obj = thread.arch.priv_stack_start
* | Priv Stack | } CONFIG_PRIVILEGED_STACK_SIZE
* +------------+ <- thread.stack_info.start
* | Thread |
* | stack |
* | |
* +............|
* | TLS | } thread.stack_info.delta
* +------------+ <- thread.stack_info.start + thread.stack_info.size
*/
#define ARCH_THREAD_STACK_RESERVED CONFIG_PRIVILEGED_STACK_SIZE
#define ARCH_THREAD_STACK_SIZE_ADJUST(size) \
(ROUND_UP((size), Z_RISCV_PMP_ALIGN))
#define ARCH_THREAD_STACK_OBJ_ALIGN(size) Z_RISCV_PMP_ALIGN
#endif /* CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT */
#endif /* CONFIG_PMP_STACK_GUARD */
#else /* !CONFIG_USERSPACE */
#ifdef CONFIG_PMP_STACK_GUARD
/* Reserve some memory for the stack guard.
* This is just a minimally-sized region at the beginning of the stack
* object, which is programmed to produce an exception if written to.
*
* +------------+ <- thread.stack_obj
* | Guard | } Z_RISCV_STACK_GUARD_SIZE
* +------------+ <- thread.stack_info.start
* | Thread |
* | stack |
* | |
* +............|
* | TLS | } thread.stack_info.delta
* +------------+ <- thread.stack_info.start + thread.stack_info.size
*/
#define ARCH_THREAD_STACK_RESERVED Z_RISCV_STACK_GUARD_SIZE
#define ARCH_THREAD_STACK_OBJ_ALIGN(size) Z_RISCV_PMP_ALIGN
/* Default for ARCH_THREAD_STACK_SIZE_ADJUST */
#else /* !CONFIG_PMP_STACK_GUARD */
/* No stack guard, no userspace, Use defaults for everything. */
#endif /* CONFIG_PMP_STACK_GUARD */
#endif /* CONFIG_USERSPACE */
#ifdef CONFIG_64BIT
#define RV_OP_LOADREG ld
#define RV_OP_STOREREG sd
@ -87,6 +244,40 @@ extern "C" {
#define DO_CONCAT(x, y) x ## y
#define CONCAT(x, y) DO_CONCAT(x, y)
/* Kernel macros for memory attribution
* (access permissions and cache-ability).
*
* The macros are to be stored in k_mem_partition_attr_t
* objects. The format of a k_mem_partition_attr_t object
* is an uint8_t composed by configuration register flags
* located in arch/riscv/include/core_pmp.h
*/
/* Read-Write access permission attributes */
#define K_MEM_PARTITION_P_RW_U_RW ((k_mem_partition_attr_t) \
{PMP_R | PMP_W})
#define K_MEM_PARTITION_P_RW_U_RO ((k_mem_partition_attr_t) \
{PMP_R})
#define K_MEM_PARTITION_P_RW_U_NA ((k_mem_partition_attr_t) \
{0})
#define K_MEM_PARTITION_P_RO_U_RO ((k_mem_partition_attr_t) \
{PMP_R})
#define K_MEM_PARTITION_P_RO_U_NA ((k_mem_partition_attr_t) \
{0})
#define K_MEM_PARTITION_P_NA_U_NA ((k_mem_partition_attr_t) \
{0})
/* Execution-allowed attributes */
#define K_MEM_PARTITION_P_RWX_U_RWX ((k_mem_partition_attr_t) \
{PMP_R | PMP_W | PMP_X})
#define K_MEM_PARTITION_P_RX_U_RX ((k_mem_partition_attr_t) \
{PMP_R | PMP_X})
/* Typedef for the k_mem_partition attribute */
typedef struct {
uint8_t pmp_attr;
} k_mem_partition_attr_t;
/*
* SOC-specific function to get the IRQ number generating the interrupt.
* __soc_get_irq returns a bitfield of pending IRQs.
@ -168,6 +359,10 @@ static inline uint32_t arch_k_cycle_get_32(void)
return z_timer_cycle_get_32();
}
#ifdef CONFIG_USERSPACE
#include <arch/riscv/error.h>
#endif /* CONFIG_USERSPACE */
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2020 BayLibre, SAS
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief RISCV public error handling
*
* RISCV-specific kernel error handling interface. Included by riscv/arch.h.
*/
#ifndef ZEPHYR_INCLUDE_ARCH_RISCV_ERROR_H_
#define ZEPHYR_INCLUDE_ARCH_RISCV_ERROR_H_
#include <arch/riscv/syscall.h>
#include <arch/riscv/exp.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef CONFIG_USERSPACE
/*
* Kernel features like canary (software stack guard) are built
* with an argument to bypass the test before syscall (test if CPU
* is running in user or kernel) and directly execute the function.
* Then if this kind of code wishes to trigger a CPU exception,
* the implemented syscall is useless because the function is directly
* called even if the CPU is running in user (which happens during
* sanity check). To fix that, I bypass the generated test code by writing
* the test myself to remove the bypass ability.
*/
#define ARCH_EXCEPT(reason_p) do { \
if (_is_user_context()) { \
arch_syscall_invoke1(reason_p, \
K_SYSCALL_USER_FAULT); \
} else { \
compiler_barrier(); \
z_impl_user_fault(reason_p); \
} \
CODE_UNREACHABLE; \
} while (false)
#else
#define ARCH_EXCEPT(reason_p) do { \
z_impl_user_fault(reason_p); \
} while (false)
#endif
__syscall void user_fault(unsigned int reason);
#include <syscalls/error.h>
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_ARCH_RISCV_ERROR_H_ */

View file

@ -0,0 +1,164 @@
/*
* Copyright (c) 2020 BayLibre, SAS
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief RISCV specific syscall header
*
* This header contains the RISCV specific syscall interface. It is
* included by the syscall interface architecture-abstraction header
* (include/arch/syscall.h)
*/
#ifndef ZEPHYR_INCLUDE_ARCH_RISCV_SYSCALL_H_
#define ZEPHYR_INCLUDE_ARCH_RISCV_SYSCALL_H_
#define _SVC_CALL_CONTEXT_SWITCH 0
#define _SVC_CALL_IRQ_OFFLOAD 1
#define _SVC_CALL_RUNTIME_EXCEPT 2
#define _SVC_CALL_SYSTEM_CALL 3
#define FORCE_SYSCALL_ID -1
#ifdef CONFIG_USERSPACE
#ifndef _ASMLANGUAGE
#include <zephyr/types.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* Syscall invocation macros. riscv-specific machine constraints used to ensure
* args land in the proper registers.
*/
static inline uintptr_t arch_syscall_invoke6(uintptr_t arg1, uintptr_t arg2,
uintptr_t arg3, uintptr_t arg4,
uintptr_t arg5, uintptr_t arg6,
uintptr_t call_id)
{
register uint32_t a0 __asm__ ("a0") = arg1;
register uint32_t a1 __asm__ ("a1") = arg2;
register uint32_t a2 __asm__ ("a2") = arg3;
register uint32_t a3 __asm__ ("a3") = arg4;
register uint32_t a4 __asm__ ("a4") = arg5;
register uint32_t a5 __asm__ ("a5") = arg6;
register uint32_t a7 __asm__ ("a7") = call_id;
__asm__ volatile ("ecall"
: "+r" (a0)
: "r" (a1), "r" (a2), "r" (a3), "r" (a4), "r" (a5),
"r" (a7)
: "memory");
return a0;
}
static inline uintptr_t arch_syscall_invoke5(uintptr_t arg1, uintptr_t arg2,
uintptr_t arg3, uintptr_t arg4,
uintptr_t arg5,
uintptr_t call_id)
{
register uint32_t a0 __asm__ ("a0") = arg1;
register uint32_t a1 __asm__ ("a1") = arg2;
register uint32_t a2 __asm__ ("a2") = arg3;
register uint32_t a3 __asm__ ("a3") = arg4;
register uint32_t a4 __asm__ ("a4") = arg5;
register uint32_t a7 __asm__ ("a7") = call_id;
__asm__ volatile ("ecall"
: "+r" (a0)
: "r" (a1), "r" (a2), "r" (a3), "r" (a4), "r" (a7)
: "memory");
return a0;
}
static inline uintptr_t arch_syscall_invoke4(uintptr_t arg1, uintptr_t arg2,
uintptr_t arg3, uintptr_t arg4,
uintptr_t call_id)
{
register uint32_t a0 __asm__ ("a0") = arg1;
register uint32_t a1 __asm__ ("a1") = arg2;
register uint32_t a2 __asm__ ("a2") = arg3;
register uint32_t a3 __asm__ ("a3") = arg4;
register uint32_t a7 __asm__ ("a7") = call_id;
__asm__ volatile ("ecall"
: "+r" (a0)
: "r" (a1), "r" (a2), "r" (a3), "r" (a7)
: "memory");
return a0;
}
static inline uintptr_t arch_syscall_invoke3(uintptr_t arg1, uintptr_t arg2,
uintptr_t arg3,
uintptr_t call_id)
{
register uint32_t a0 __asm__ ("a0") = arg1;
register uint32_t a1 __asm__ ("a1") = arg2;
register uint32_t a2 __asm__ ("a2") = arg3;
register uint32_t a7 __asm__ ("a7") = call_id;
__asm__ volatile ("ecall"
: "+r" (a0)
: "r" (a1), "r" (a2), "r" (a7)
: "memory");
return a0;
}
static inline uintptr_t arch_syscall_invoke2(uintptr_t arg1, uintptr_t arg2,
uintptr_t call_id)
{
register uint32_t a0 __asm__ ("a0") = arg1;
register uint32_t a1 __asm__ ("a1") = arg2;
register uint32_t a7 __asm__ ("a7") = call_id;
__asm__ volatile ("ecall"
: "+r" (a0)
: "r" (a1), "r" (a7)
: "memory");
return a0;
}
static inline uintptr_t arch_syscall_invoke1(uintptr_t arg1, uintptr_t call_id)
{
register uint32_t a0 __asm__ ("a0") = arg1;
register uint32_t a7 __asm__ ("a7") = call_id;
__asm__ volatile ("ecall"
: "+r" (a0)
: "r" (a7)
: "memory");
return a0;
}
static inline uintptr_t arch_syscall_invoke0(uintptr_t call_id)
{
register uint32_t a0 __asm__ ("a0");
register uint32_t a7 __asm__ ("a7") = call_id;
__asm__ volatile ("ecall"
: "+r" (a0)
: "r" (a7)
: "memory");
return a0;
}
static inline bool arch_is_user_context(void)
{
/* Defined in arch/riscv/core/thread.c */
extern ulong_t is_user_mode;
return is_user_mode;
}
#ifdef __cplusplus
}
#endif
#endif /* _ASMLANGUAGE */
#endif /* CONFIG_USERSPACE */
#endif /* ZEPHYR_INCLUDE_ARCH_RISCV_SYSCALL_H_ */

View file

@ -30,6 +30,71 @@
#endif
#endif
#ifdef CONFIG_RISCV_PMP
#ifdef CONFIG_64BIT
#define RISCV_PMP_CFG_NUM (CONFIG_PMP_SLOT >> 3)
#else
#define RISCV_PMP_CFG_NUM (CONFIG_PMP_SLOT >> 2)
#endif
#endif
#ifdef CONFIG_PMP_STACK_GUARD
/*
* PMP entries:
* (1 for interrupt stack guard: None)
* 4 for stacks guard: None
* 1 for RAM: RW
* 1 for other address space: RWX
*/
#define PMP_REGION_NUM_FOR_STACK_GUARD 6
#define PMP_CFG_CSR_NUM_FOR_STACK_GUARD 2
#endif /* CONFIG_PMP_STACK_GUARD */
#ifdef CONFIG_PMP_POWER_OF_TWO_ALIGNMENT
#ifdef CONFIG_USERSPACE
#ifdef CONFIG_PMP_STACK_GUARD
/*
* 1 for interrupt stack guard: None
* 1 for core state: R
* 1 for program and read only data: RX
* 1 for user thread stack: RW
*/
#define PMP_REGION_NUM_FOR_U_THREAD 4
#else /* CONFIG_PMP_STACK_GUARD */
/*
* 1 for core state: R
* 1 for program and read only data: RX
* 1 for user thread stack: RW
*/
#define PMP_REGION_NUM_FOR_U_THREAD 3
#endif /* CONFIG_PMP_STACK_GUARD */
#define PMP_MAX_DYNAMIC_REGION (CONFIG_PMP_SLOT - PMP_REGION_NUM_FOR_U_THREAD)
#endif /* CONFIG_USERSPACE */
#else /* CONFIG_PMP_POWER_OF_TWO_ALIGNMENT */
#ifdef CONFIG_USERSPACE
#ifdef CONFIG_PMP_STACK_GUARD
/*
* 1 for interrupt stack guard: None
* 1 for core state: R
* 2 for program and read only data: RX
* 2 for user thread stack: RW
*/
#define PMP_REGION_NUM_FOR_U_THREAD 6
#else /* CONFIG_PMP_STACK_GUARD */
/*
* 1 for core state: R
* 2 for program and read only data: RX
* 2 for user thread stack: RW
*/
#define PMP_REGION_NUM_FOR_U_THREAD 5
#endif /* CONFIG_PMP_STACK_GUARD */
#define PMP_MAX_DYNAMIC_REGION ((CONFIG_PMP_SLOT - \
PMP_REGION_NUM_FOR_U_THREAD) >> 1)
#endif /* CONFIG_USERSPACE */
#endif /* CONFIG_PMP_POWER_OF_TWO_ALIGNMENT */
/*
* The following structure defines the list of registers that need to be
* saved/restored when a cooperative context switch occurs.
@ -70,6 +135,19 @@ typedef struct _callee_saved _callee_saved_t;
struct _thread_arch {
uint32_t swap_return_value; /* Return value of z_swap() */
#ifdef CONFIG_PMP_STACK_GUARD
ulong_t s_pmpcfg[PMP_CFG_CSR_NUM_FOR_STACK_GUARD];
ulong_t s_pmpaddr[PMP_REGION_NUM_FOR_STACK_GUARD];
#endif
#ifdef CONFIG_USERSPACE
ulong_t priv_stack_start;
ulong_t user_sp;
ulong_t unfinished_syscall;
ulong_t u_pmpcfg[RISCV_PMP_CFG_NUM];
ulong_t u_pmpaddr[CONFIG_PMP_SLOT];
#endif
};
typedef struct _thread_arch _thread_arch_t;

View file

@ -19,6 +19,8 @@
#include <arch/arm/aarch32/syscall.h>
#elif defined(CONFIG_ARC)
#include <arch/arc/syscall.h>
#elif defined(CONFIG_RISCV)
#include <arch/riscv/syscall.h>
#endif
#endif /* ZEPHYR_INCLUDE_ARCH_SYSCALL_H_ */