From b3c32e036bd548e70ee8e3efd86da9f9db1b07c9 Mon Sep 17 00:00:00 2001 From: Andy Gross Date: Fri, 8 Dec 2017 12:13:43 -0600 Subject: [PATCH 1/6] arm: mpu: Add user context API placeholder This patch adds a configure_mpu_user_context API and implements the required function placeholders in the NXP and ARM MPU files. Signed-off-by: Andy Gross --- arch/arm/core/cortex_m/mpu/arm_core_mpu.c | 16 ++++++++++++++++ arch/arm/core/cortex_m/mpu/arm_mpu.c | 5 +++++ arch/arm/core/cortex_m/mpu/nxp_mpu.c | 5 +++++ include/arch/arm/cortex_m/mpu/arm_core_mpu.h | 11 +++++++++++ include/arch/arm/cortex_m/mpu/arm_core_mpu_dev.h | 8 ++++++++ 5 files changed, 45 insertions(+) diff --git a/arch/arm/core/cortex_m/mpu/arm_core_mpu.c b/arch/arm/core/cortex_m/mpu/arm_core_mpu.c index f167df341f175..3ab20ff52e5d0 100644 --- a/arch/arm/core/cortex_m/mpu/arm_core_mpu.c +++ b/arch/arm/core/cortex_m/mpu/arm_core_mpu.c @@ -32,6 +32,22 @@ void configure_mpu_stack_guard(struct k_thread *thread) #endif #if defined(CONFIG_USERSPACE) +/* + * @brief Configure MPU user context + * + * This function configures the thread's user context. + * The functionality is meant to be used during context switch. + * + * @param thread thread info data structure. + */ +void configure_mpu_user_context(struct k_thread *thread) +{ + SYS_LOG_DBG("configure user thread %p's context", thread); + arm_core_mpu_disable(); + arm_core_mpu_configure_user_context(thread); + arm_core_mpu_enable(); +} + /* * @brief Configure MPU memory domain * diff --git a/arch/arm/core/cortex_m/mpu/arm_mpu.c b/arch/arm/core/cortex_m/mpu/arm_mpu.c index c59ffca2e32bb..b5a3251ad4ae1 100644 --- a/arch/arm/core/cortex_m/mpu/arm_mpu.c +++ b/arch/arm/core/cortex_m/mpu/arm_mpu.c @@ -240,6 +240,11 @@ void arm_core_mpu_configure(u8_t type, u32_t base, u32_t size) } #if defined(CONFIG_USERSPACE) +void arm_core_mpu_configure_user_context(struct k_thread *thread) +{ + return 0; +} + /** * @brief configure MPU regions for the memory partitions of the memory domain * diff --git a/arch/arm/core/cortex_m/mpu/nxp_mpu.c b/arch/arm/core/cortex_m/mpu/nxp_mpu.c index b7f32e3b8bfb8..c99be9c05c0a8 100644 --- a/arch/arm/core/cortex_m/mpu/nxp_mpu.c +++ b/arch/arm/core/cortex_m/mpu/nxp_mpu.c @@ -249,6 +249,11 @@ void arm_core_mpu_configure(u8_t type, u32_t base, u32_t size) } #if defined(CONFIG_USERSPACE) +void arm_core_mpu_configure_user_context(struct k_thread *thread) +{ + return 0; +} + /** * @brief configure MPU regions for the memory partitions of the memory domain * diff --git a/include/arch/arm/cortex_m/mpu/arm_core_mpu.h b/include/arch/arm/cortex_m/mpu/arm_core_mpu.h index bf72df37a6bbc..64ed35d646556 100644 --- a/include/arch/arm/cortex_m/mpu/arm_core_mpu.h +++ b/include/arch/arm/cortex_m/mpu/arm_core_mpu.h @@ -42,6 +42,17 @@ void configure_mpu_stack_guard(struct k_thread *thread); * @param thread thread info data structure. */ void configure_mpu_mem_domain(struct k_thread *thread); + +/* + * @brief Configure MPU user context + * + * This function configures the stack and application data regions + * for user mode threads + * + * @param thread thread info data structure. + */ +void configure_mpu_user_context(struct k_thread *thread); + #endif #ifdef __cplusplus diff --git a/include/arch/arm/cortex_m/mpu/arm_core_mpu_dev.h b/include/arch/arm/cortex_m/mpu/arm_core_mpu_dev.h index 6d9588d15d2f4..1dd4efa1b0f09 100644 --- a/include/arch/arm/cortex_m/mpu/arm_core_mpu_dev.h +++ b/include/arch/arm/cortex_m/mpu/arm_core_mpu_dev.h @@ -37,6 +37,7 @@ extern "C" { #if defined(CONFIG_ARM_CORE_MPU) struct k_mem_domain; struct k_mem_partition; +struct k_thread; /* ARM Core MPU Driver API */ @@ -71,6 +72,13 @@ void arm_core_mpu_configure(u8_t type, u32_t base, u32_t size); */ void arm_core_mpu_configure_mem_domain(struct k_mem_domain *mem_domain); +/** + * @brief configure MPU regions for a user thread's context + * + * @param thread thread to configure + */ +void arm_core_mpu_configure_user_context(struct k_thread *thread); + /** * @brief configure MPU region for a single memory partition * From 66eff456a36855bab5217de9e144755d4450c7a6 Mon Sep 17 00:00:00 2001 From: Andy Gross Date: Fri, 8 Dec 2017 12:22:49 -0600 Subject: [PATCH 2/6] arm: userspace: Add ARM userspace infrastructure This patch adds support for userspace on ARM architectures. Arch specific calls for transitioning threads to user mode, system calls, and associated handlers. Signed-off-by: Andy Gross --- arch/arm/core/CMakeLists.txt | 1 + arch/arm/core/Kconfig | 10 +- arch/arm/core/cortex_m/mpu/arm_core_mpu.c | 14 +- arch/arm/core/fatal.c | 11 ++ arch/arm/core/offsets/offsets.c | 1 + arch/arm/core/swap.S | 79 ++++++++- arch/arm/core/thread.c | 53 +++++- arch/arm/core/userspace.S | 182 +++++++++++++++++++ arch/arm/include/kernel_arch_func.h | 6 + arch/arm/include/kernel_arch_thread.h | 6 + arch/arm/include/offsets_short_arch.h | 3 + include/arch/arm/arch.h | 207 +++++++++++++++++++--- include/arch/arm/cortex_m/error.h | 1 + kernel/thread.c | 12 +- 14 files changed, 542 insertions(+), 44 deletions(-) create mode 100644 arch/arm/core/userspace.S diff --git a/arch/arm/core/CMakeLists.txt b/arch/arm/core/CMakeLists.txt index d32114c1d675f..c3e3bb57509d0 100644 --- a/arch/arm/core/CMakeLists.txt +++ b/arch/arm/core/CMakeLists.txt @@ -16,6 +16,7 @@ zephyr_sources_ifdef(CONFIG_GEN_SW_ISR_TABLE isr_wrapper.S) zephyr_sources_ifdef(CONFIG_CPLUSPLUS __aeabi_atexit.c) zephyr_sources_ifdef(CONFIG_IRQ_OFFLOAD irq_offload.c) zephyr_sources_ifdef(CONFIG_CPU_CORTEX_M0 irq_relay.S) +zephyr_sources_ifdef(CONFIG_USERSPACE userspace.S) add_subdirectory_ifdef(CONFIG_CPU_CORTEX_M cortex_m) add_subdirectory_ifdef(CONFIG_CPU_HAS_MPU cortex_m/mpu) diff --git a/arch/arm/core/Kconfig b/arch/arm/core/Kconfig index 1dcb94e2c7256..1c9490db85bbb 100644 --- a/arch/arm/core/Kconfig +++ b/arch/arm/core/Kconfig @@ -23,7 +23,7 @@ config CPU_CORTEX_M select HAS_FLASH_LOAD_OFFSET select HAS_DTS select ARCH_HAS_STACK_PROTECTION if ARM_CORE_MPU - select ARCH_HAS_USERSPACE if ARM_USERSPACE + select ARCH_HAS_USERSPACE if ARM_CORE_MPU help This option signifies the use of a CPU of the Cortex-M family. @@ -42,14 +42,6 @@ config ARM_STACK_PROTECTION This option enables MPU stack guard to cause a system fatal error if the bounds of the current process stack are overflowed. -config ARM_USERSPACE - bool - default n - help - This option enables APIs to drop a thread's privileges, supporting - user-level threads that are protected from each other and from - crashing the kernel. - menu "Architectue Floating Point Options" depends on CPU_HAS_FPU diff --git a/arch/arm/core/cortex_m/mpu/arm_core_mpu.c b/arch/arm/core/cortex_m/mpu/arm_core_mpu.c index 3ab20ff52e5d0..2b3e971e33f26 100644 --- a/arch/arm/core/cortex_m/mpu/arm_core_mpu.c +++ b/arch/arm/core/cortex_m/mpu/arm_core_mpu.c @@ -23,10 +23,18 @@ */ void configure_mpu_stack_guard(struct k_thread *thread) { + u32_t guard_size = MPU_GUARD_ALIGN_AND_SIZE; +#if defined(CONFIG_USERSPACE) + u32_t guard_start = thread->arch.priv_stack_start ? + (u32_t)thread->arch.priv_stack_start : + (u32_t)thread->stack_obj; +#else + u32_t guard_start = thread->stack_info.start; +#endif + arm_core_mpu_disable(); - arm_core_mpu_configure(THREAD_STACK_GUARD_REGION, - thread->stack_info.start - MPU_GUARD_ALIGN_AND_SIZE, - thread->stack_info.size); + arm_core_mpu_configure(THREAD_STACK_GUARD_REGION, guard_start, + guard_size); arm_core_mpu_enable(); } #endif diff --git a/arch/arm/core/fatal.c b/arch/arm/core/fatal.c index fa46a43f0b15d..7f1b32b4a6db8 100644 --- a/arch/arm/core/fatal.c +++ b/arch/arm/core/fatal.c @@ -87,3 +87,14 @@ void _do_kernel_oops(const NANO_ESF *esf) { _NanoFatalErrorHandler(esf->r0, esf); } + +FUNC_NORETURN void _arch_syscall_oops(void *ssf_ptr) +{ + u32_t *ssf_contents = ssf_ptr; + NANO_ESF oops_esf = { 0 }; + + oops_esf.pc = ssf_contents[3]; + + _do_kernel_oops(&oops_esf); + CODE_UNREACHABLE; +} diff --git a/arch/arm/core/offsets/offsets.c b/arch/arm/core/offsets/offsets.c index 4386e885c3a47..75e07d98aa1c1 100644 --- a/arch/arm/core/offsets/offsets.c +++ b/arch/arm/core/offsets/offsets.c @@ -30,6 +30,7 @@ GEN_OFFSET_SYM(_thread_arch_t, basepri); GEN_OFFSET_SYM(_thread_arch_t, swap_return_value); #ifdef CONFIG_USERSPACE +GEN_OFFSET_SYM(_thread_arch_t, mode); GEN_OFFSET_SYM(_thread_arch_t, priv_stack_start); #endif diff --git a/arch/arm/core/swap.S b/arch/arm/core/swap.S index f4a575d129655..781a22255d1cb 100644 --- a/arch/arm/core/swap.S +++ b/arch/arm/core/swap.S @@ -23,6 +23,7 @@ GTEXT(__swap) GTEXT(__svc) GTEXT(__pendsv) GTEXT(_do_kernel_oops) +GTEXT(_arm_do_syscall) GDATA(_k_neg_eagain) GDATA(_kernel) @@ -176,12 +177,24 @@ _thread_irq_disabled: #endif /* CONFIG_MPU_STACK_GUARD */ #ifdef CONFIG_USERSPACE + /* restore mode */ + ldr r0, [r2, #_thread_offset_to_mode] + mrs r3, CONTROL + bic r3, #1 + orr r3, r0 + msr CONTROL, r3 + /* r2 contains k_thread */ add r0, r2, #0 push {r2, lr} blx configure_mpu_mem_domain pop {r2, lr} -#endif /* CONFIG_USERSPACE */ + + add r0, r2, #0 + push {r2, lr} + blx configure_mpu_user_context + pop {r2, lr} +#endif /* load callee-saved + psp from thread */ add r0, r2, #_thread_offset_to_callee_saved @@ -268,7 +281,6 @@ _oops: */ SECTION_FUNC(TEXT, __svc) - tst lr, #0x4 /* did we come from thread mode ? */ ite eq /* if zero (equal), came from handler mode */ mrseq r0, MSP /* handler mode, stack frame is on MSP */ @@ -283,10 +295,26 @@ SECTION_FUNC(TEXT, __svc) * 0: context switch * 1: irq_offload (if configured) * 2: kernel panic or oops (software generated fatal exception) + * 3: System call * Planned implementation of system calls for memory protection will * expand this case. */ ands r1, #0xff +#if CONFIG_USERSPACE + mrs r2, CONTROL + + cmp r1, #3 + beq _do_syscall + + /* + * check that we are privileged before invoking other SVCs + * oops if we are unprivileged + */ + tst r2, #0x1 + bne _oops + + cmp r1, #0 +#endif beq _context_switch cmp r1, #2 @@ -324,6 +352,46 @@ _oops: blx _do_kernel_oops pop {pc} +#if CONFIG_USERSPACE + /* + * System call will setup a jump to the _do_arm_syscall function + * when the SVC returns via the bx lr. + * + * There is some trickery involved here because we have to preserve + * the original LR value so that we can return back to the caller of + * the SVC. + * + * On SVC exeption, the stack looks like the following: + * r0 - r1 - r2 - r3 - r12 - LR - PC - PSR + * r5 - r6 - call id - saved LR + * + */ +_do_syscall: + ldr r1, [r0, #24] /* grab address of PC from stack frame */ + str r1, [r0, #44] /* store address to use for LR after syscall */ + ldr r1, =_arm_do_syscall + str r1, [r0, #24] /* overwrite the LR to point to _arm_do_syscall */ + + /* validate syscall limit, only set priv mode if valid */ + ldr ip, =_SYSCALL_LIMIT + ldr r1, [r0, #40] + cmp r1, ip + blt valid_syscall_id + + /* bad syscall id. Set arg0 to bad id and set call_id to SYSCALL_BAD */ + str r1, [r0, #0] + ldr r1, =_SYSCALL_BAD + str r1, [r0, #40] + +valid_syscall_id: + /* set mode to privileged, r2 still contains value from CONTROL */ + bic r2, #1 + msr CONTROL, r2 + + /* return from SVC to the modified LR - _arm_do_syscall */ + bx lr +#endif + #else #error Unknown ARM architecture #endif /* CONFIG_ARMV6_M_ARMV8_M_BASELINE */ @@ -381,6 +449,13 @@ SECTION_FUNC(TEXT, __swap) ldr r2, [r1, #_kernel_offset_to_current] str r0, [r2, #_thread_offset_to_basepri] +#ifdef CONFIG_USERSPACE + mrs r0, CONTROL + movs r3, #1 + ands r0, r3 + str r0, [r2, #_thread_offset_to_mode] +#endif + /* * Set __swap()'s default return code to -EAGAIN. This eliminates the need * for the timeout code to set it itself. diff --git a/arch/arm/core/thread.c b/arch/arm/core/thread.c index ff8a9cafe304d..81211d0a510ff 100644 --- a/arch/arm/core/thread.c +++ b/arch/arm/core/thread.c @@ -19,6 +19,10 @@ #include #endif /* CONFIG_INIT_STACKS */ +#ifdef CONFIG_USERSPACE +extern u8_t *_k_priv_stack_find(void *obj); +#endif + /** * * @brief Initialize a new thread from its stack space @@ -58,16 +62,33 @@ void _new_thread(struct k_thread *thread, k_thread_stack_t *stack, _ASSERT_VALID_PRIO(priority, pEntry); +#if CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT + char *stackEnd = pStackMem + stackSize - MPU_GUARD_ALIGN_AND_SIZE; +#else char *stackEnd = pStackMem + stackSize; +#endif struct __esf *pInitCtx; - _new_thread_init(thread, pStackMem, stackSize, priority, options); - /* carve the thread entry struct from the "base" of the stack */ + _new_thread_init(thread, pStackMem, stackEnd - pStackMem, priority, + options); + /* carve the thread entry struct from the "base" of the stack */ pInitCtx = (struct __esf *)(STACK_ROUND_DOWN(stackEnd - sizeof(struct __esf))); - pInitCtx->pc = ((u32_t)_thread_entry) & 0xfffffffe; +#if CONFIG_USERSPACE + if (options & K_USER) { + pInitCtx->pc = (u32_t)_arch_user_mode_enter; + } else { + pInitCtx->pc = (u32_t)_thread_entry; + } +#else + pInitCtx->pc = (u32_t)_thread_entry; +#endif + + /* force ARM mode by clearing LSB of address */ + pInitCtx->pc &= 0xfffffffe; + pInitCtx->a1 = (u32_t)pEntry; pInitCtx->a2 = (u32_t)parameter1; pInitCtx->a3 = (u32_t)parameter2; @@ -78,6 +99,12 @@ void _new_thread(struct k_thread *thread, k_thread_stack_t *stack, thread->callee_saved.psp = (u32_t)pInitCtx; thread->arch.basepri = 0; +#if CONFIG_USERSPACE + thread->arch.mode = 0; + thread->arch.priv_stack_start = 0; + thread->arch.priv_stack_size = 0; +#endif + /* swap_return_value can contain garbage */ /* @@ -94,3 +121,23 @@ void _new_thread(struct k_thread *thread, k_thread_stack_t *stack, thread_monitor_init(thread); #endif } + +#ifdef CONFIG_USERSPACE + +FUNC_NORETURN void _arch_user_mode_enter(k_thread_entry_t user_entry, + void *p1, void *p2, void *p3) +{ + + /* Set up privileged stack before entering user mode */ + _current->arch.priv_stack_start = + (u32_t)_k_priv_stack_find(_current->stack_obj); + _current->arch.priv_stack_size = + (u32_t)CONFIG_PRIVILEGED_STACK_SIZE; + + _arm_userspace_enter(user_entry, p1, p2, p3, + (u32_t)_current->stack_info.start, + _current->stack_info.size); + CODE_UNREACHABLE; +} + +#endif diff --git a/arch/arm/core/userspace.S b/arch/arm/core/userspace.S new file mode 100644 index 0000000000000..3f5030befb376 --- /dev/null +++ b/arch/arm/core/userspace.S @@ -0,0 +1,182 @@ +/* + * Userspace and service handler hooks + * + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#include +#include +#include +#include +#include +#include + +_ASM_FILE_PROLOGUE + +GTEXT(_arm_userspace_enter) +GTEXT(_arm_do_syscall) +GDATA(_kernel) + +/* Imports */ +GTEXT(_k_syscall_table) + +/** + * + * User space entry function + * + * This function is the entry point to user mode from privileged execution. + * The conversion is one way, and threads which transition to user mode do + * not transition back later, unless they are doing system calls. + * + */ +SECTION_FUNC(TEXT,_arm_userspace_enter) + /* move user_entry to lr */ + mov lr, r0 + + /* set stack to priviliged stack */ + ldr r0, =_kernel + ldr r0, [r0, #_kernel_offset_to_current] + ldr r0, [r0, #_thread_offset_to_priv_stack_start] /* priv stack ptr */ + ldr ip, =CONFIG_PRIVILEGED_STACK_SIZE + add r0, r0, ip + + mov ip, sp + msr PSP, r0 + + /* load up stack info from user stack */ + ldr r0, [ip] + ldr ip, [ip, #4] + +#ifdef CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT + /* Guard is taken out of size, so adjust beginning and size of stack */ + subs ip, #MPU_GUARD_ALIGN_AND_SIZE +#endif + + /* push args to stack */ + push {r0,r1,r2,r3,ip,lr} + + /* clear the user stack area to clean out privileged data */ + /* from right past the guard right up to the end */ + mov r2, ip +#ifdef CONFIG_INIT_STACKS + ldr r1,=0xaaaaaaaa +#else + eors.n r1, r1 +#endif + bl memset + + /* setup arguments to configure_mpu_mem_domain */ + ldr r0, =_kernel + ldr r0, [r0, #_kernel_offset_to_current] + bl configure_mpu_mem_domain + + /* setup arguments configure_mpu_user_context */ + ldr r0, =_kernel + ldr r0, [r0, #_kernel_offset_to_current] + bl configure_mpu_user_context + + pop {r0,r1,r2,r3,ip,lr} + + /* r0 contains user stack start, ip contains user stack size */ + add r0, r0, ip /* calculate top of stack */ + + /* set stack to user stack */ + msr PSP, r0 + + /* restore r0 */ + mov r0, lr + + /* change processor mode to unprivileged */ + mrs ip, CONTROL + orrs ip, ip, #1 + msr CONTROL, ip + + /* jump to _thread_entry entry */ + ldr ip, =_thread_entry + bx ip + +/** + * + * Userspace system call function + * + * This function is used to do system calls from unprivileged code. This + * function is responsible for the following: + * 1) Fixing up bad syscalls + * 2) Configuring privileged stack and loading up stack arguments + * 3) Dispatching the system call + * 4) Restoring stack and calling back to the caller of the SVC + * + */ +SECTION_FUNC(TEXT, _arm_do_syscall) + /* + * r0-r3 are values from pre-SVC from stack frame stored during SVC + * 16 bytes of storage reside on the stack: + * arg5, arg6, call_id, and LR from SVC frame + */ + push {r4,r5,r6,lr} + + ldr ip, =_k_syscall_table + ldr r4, [sp, #24] /* load call_id from stack */ + lsl r4, #2 + add ip, r4 + ldr ip, [ip] /* load table address */ + ldr r5, =_SYSCALL_BAD + lsl r5, #2 /* shift to match the shift we did on the call_id */ + cmp r4, r5 + bne valid_syscall + + /* BAD SYSCALL path */ + /* fixup stack frame on unprivileged stack, adding ssf */ + /* pop registers and lr as this is a one way jump */ + mov r4, sp + str r4, [sp, #24] + pop {r4,r5,r6,lr} + b dispatch_syscall + +valid_syscall: + /* setup priviliged stack */ + ldr r4, =_kernel + ldr r4, [r4, #_kernel_offset_to_current] + ldr r5, [r4, #_thread_offset_to_priv_stack_start] /* priv stack ptr */ + ldr r6, =CONFIG_PRIVILEGED_STACK_SIZE + add r5, r6 + + /* setup privileged stack frame */ + /* 16 bytes: arg5, arg6, ssf, 4 bytes padding */ + sub r5, #16 + ldr r6, [sp, #16] + str r6, [r5, #0] + ldr r6, [sp, #20] + str r6, [r5, #4] + mov r6, sp + str r6, [r5, #8] /* store ssf of unprivileged stack */ + ldr r6, =0 + str r6, [r5, #12] /* store zeroed padding */ + + /* switch to privileged stack */ + msr PSP, r5 +dispatch_syscall: + /* execute function from dispatch table */ + blx ip + + /* set stack back to unprivileged stack */ + ldr ip, [sp,#8] + msr PSP, ip + + pop {r4,r5,r6,lr} + + /* drop privileges by setting bit 0 in CONTROL */ + mrs ip, CONTROL + orrs ip, ip, #1 + msr CONTROL, ip + + /* + * return back to original function that called SVC, add 1 to force thumb + * mode + */ + ldr ip, [sp, #12] + orrs ip, ip, #1 + bx ip diff --git a/arch/arm/include/kernel_arch_func.h b/arch/arm/include/kernel_arch_func.h index c352005a03fac..ba53da34ba826 100644 --- a/arch/arm/include/kernel_arch_func.h +++ b/arch/arm/include/kernel_arch_func.h @@ -96,6 +96,12 @@ extern void k_cpu_atomic_idle(unsigned int key); extern void _IntLibInit(void); + +extern FUNC_NORETURN void _arm_userspace_enter(k_thread_entry_t user_entry, + void *p1, void *p2, void *p3, + u32_t stack_end, + u32_t stack_start); + #endif /* _ASMLANGUAGE */ #ifdef __cplusplus diff --git a/arch/arm/include/kernel_arch_thread.h b/arch/arm/include/kernel_arch_thread.h index 819fc0c289abc..86fffed027f1c 100644 --- a/arch/arm/include/kernel_arch_thread.h +++ b/arch/arm/include/kernel_arch_thread.h @@ -94,6 +94,12 @@ struct _thread_arch { */ struct _preempt_float preempt_float; #endif + +#ifdef CONFIG_USERSPACE + u32_t mode; + u32_t priv_stack_start; + u32_t priv_stack_size; +#endif }; typedef struct _thread_arch _thread_arch_t; diff --git a/arch/arm/include/offsets_short_arch.h b/arch/arm/include/offsets_short_arch.h index 73cb03e2b6f5c..14d86c6e2e95f 100644 --- a/arch/arm/include/offsets_short_arch.h +++ b/arch/arm/include/offsets_short_arch.h @@ -27,6 +27,9 @@ (___thread_t_arch_OFFSET + ___thread_arch_t_preempt_float_OFFSET) #ifdef CONFIG_USERSPACE +#define _thread_offset_to_mode \ + (___thread_t_arch_OFFSET + ___thread_arch_t_mode_OFFSET) + #define _thread_offset_to_priv_stack_start \ (___thread_t_arch_OFFSET + ___thread_arch_t_priv_stack_start_OFFSET) #endif diff --git a/include/arch/arm/arch.h b/include/arch/arm/arch.h index 947b70713ef20..a1dde6ad75231 100644 --- a/include/arch/arm/arch.h +++ b/include/arch/arm/arch.h @@ -103,17 +103,6 @@ extern "C" { #define MPU_GUARD_ALIGN_AND_SIZE 0 #endif -/** - * @brief Define alignment of a stack buffer - * - * This is used for two different things: - * 1) Used in checks for stack size to be a multiple of the stack buffer - * alignment - * 2) Used to determine the alignment of a stack buffer - * - */ -#define STACK_ALIGN max(STACK_ALIGN_SIZE, MPU_GUARD_ALIGN_AND_SIZE) - /** * @brief Declare a toplevel thread stack memory region * @@ -134,9 +123,40 @@ extern "C" { * @param sym Thread stack symbol name * @param size Size of the stack memory region */ + +/** + * @brief Define alignment of a stack buffer + * + * This is used for two different things: + * 1) Used in checks for stack size to be a multiple of the stack buffer + * alignment + * 2) Used to determine the alignment of a stack buffer + * + */ +#if defined(CONFIG_USERSPACE) +#define STACK_ALIGN 32 +#else +#define STACK_ALIGN max(STACK_ALIGN_SIZE, MPU_GUARD_ALIGN_AND_SIZE) +#endif + +/** + * @brief Calculate power of two ceiling for a buffer size input + * + */ +#define POW2_CEIL(x) ((1 << (31 - __builtin_clz(x))) < x ? \ + 1 << (31 - __builtin_clz(x) + 1) : \ + 1 << (31 - __builtin_clz(x))) + +#if defined(CONFIG_USERSPACE) && \ + defined(CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT) +#define _ARCH_THREAD_STACK_DEFINE(sym, size) \ + struct _k_thread_stack_element __kernel_noinit \ + __aligned(POW2_CEIL(size)) sym[POW2_CEIL(size)] +#else #define _ARCH_THREAD_STACK_DEFINE(sym, size) \ struct _k_thread_stack_element __kernel_noinit __aligned(STACK_ALIGN) \ sym[size+MPU_GUARD_ALIGN_AND_SIZE] +#endif /** * @brief Declare a toplevel array of thread stack memory regions @@ -151,9 +171,18 @@ extern "C" { * @param nmemb Number of stacks to declare * @param size Size of the stack memory region */ +#if defined(CONFIG_USERSPACE) && \ + defined(CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT) #define _ARCH_THREAD_STACK_ARRAY_DEFINE(sym, nmemb, size) \ - struct _k_thread_stack_element __kernel_noinit __aligned(STACK_ALIGN) \ + struct _k_thread_stack_element __kernel_noinit \ + __aligned(POW2_CEIL(size)) \ + sym[nmemb][POW2_CEIL(size)] +#else +#define _ARCH_THREAD_STACK_ARRAY_DEFINE(sym, nmemb, size) \ + struct _k_thread_stack_element __kernel_noinit \ + __aligned(STACK_ALIGN) \ sym[nmemb][size+MPU_GUARD_ALIGN_AND_SIZE] +#endif /** * @brief Declare an embedded stack memory region @@ -167,9 +196,16 @@ extern "C" { * @param sym Thread stack symbol name * @param size Size of the stack memory region */ +#if defined(CONFIG_USERSPACE) && \ + defined(CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT) +#define _ARCH_THREAD_STACK_MEMBER(sym, size) \ + struct _k_thread_stack_element __aligned(POW2_CEIL(size)) \ + sym[POW2_CEIL(size)] +#else #define _ARCH_THREAD_STACK_MEMBER(sym, size) \ struct _k_thread_stack_element __aligned(STACK_ALIGN) \ sym[size+MPU_GUARD_ALIGN_AND_SIZE] +#endif /** * @brief Return the size in bytes of a stack memory region @@ -178,9 +214,15 @@ extern "C" { * since the underlying implementation may actually create something larger * (for instance a guard area). * - * The value returned here is guaranteed to match the 'size' parameter + * The value returned here is NOT guaranteed to match the 'size' parameter * passed to K_THREAD_STACK_DEFINE and related macros. * + * In the case of CONFIG_USERSPACE=y and + * CONFIG_MPU_REQUIRES_POWER_OF_2_ALIGNMENT, the size will be larger than the + * requested size. + * + * In all other configurations, the size will be correct. + * * @param sym Stack memory symbol * @return Size of the stack */ @@ -304,59 +346,172 @@ extern "C" { typedef u32_t k_mem_partition_attr_t; #endif /* _ASMLANGUAGE */ -#ifdef CONFIG_ARM_USERSPACE +#ifdef CONFIG_USERSPACE #ifndef _ASMLANGUAGE + /* Syscall invocation macros. arm-specific machine constraints used to ensure - * args land in the proper registers. Currently, they are all stub functions - * just for enabling CONFIG_USERSPACE on arm w/o errors. + * args land in the proper registers. */ - static inline u32_t _arch_syscall_invoke6(u32_t arg1, u32_t arg2, u32_t arg3, u32_t arg4, u32_t arg5, u32_t arg6, u32_t call_id) { - return 0; + register u32_t ret __asm__("r0") = arg1; + register u32_t r1 __asm__("r1") = arg2; + register u32_t r2 __asm__("r2") = arg3; + register u32_t r3 __asm__("r3") = arg4; + + __asm__ volatile("sub sp, #16\n" + "str %[a5], [sp, #0]\n" + "str %[a6], [sp, #4]\n" + "str %[cid], [sp, #8]\n" + "svc %[svid]\n" + "add sp, #16\n" + : "=r"(ret) + : [cid] "r" (call_id), + [svid] "i" (_SVC_CALL_SYSTEM_CALL), + "r" (ret), "r" (r1), "r" (r2), "r" (r3), + [a5] "r" (arg5), [a6] "r" (arg6) + : "ip", "memory"); + + return ret; } static inline u32_t _arch_syscall_invoke5(u32_t arg1, u32_t arg2, u32_t arg3, u32_t arg4, u32_t arg5, u32_t call_id) { - return 0; + register u32_t ret __asm__("r0") = arg1; + register u32_t r1 __asm__("r1") = arg2; + register u32_t r2 __asm__("r2") = arg3; + register u32_t r3 __asm__("r3") = arg4; + + __asm__ volatile("sub sp, #16\n" + "str %[a5], [sp, #0]\n" + "str %[cid], [sp, #8]\n" + "svc %[svid]\n" + "add sp, #16\n" + : "=r"(ret) + : [cid] "r" (call_id), + [svid] "i" (_SVC_CALL_SYSTEM_CALL), + "r" (ret), "r" (r1), "r" (r2), "r" (r3), + [a5] "r" (arg5) + : "ip", "memory"); + + return ret; } static inline u32_t _arch_syscall_invoke4(u32_t arg1, u32_t arg2, u32_t arg3, u32_t arg4, u32_t call_id) { - return 0; + register u32_t ret __asm__("r0") = arg1; + register u32_t r1 __asm__("r1") = arg2; + register u32_t r2 __asm__("r2") = arg3; + register u32_t r3 __asm__("r3") = arg4; + + __asm__ volatile("sub sp, #16\n" + "str %[cid], [sp,#8]\n" + "svc %[svid]\n" + "add sp, #16\n" + : "=r"(ret) + : [cid] "r" (call_id), + [svid] "i" (_SVC_CALL_SYSTEM_CALL), + "r" (ret), "r" (r1), "r" (r2), "r" (r3) + : "ip", "memory"); + + return ret; } static inline u32_t _arch_syscall_invoke3(u32_t arg1, u32_t arg2, u32_t arg3, u32_t call_id) { - return 0; + register u32_t ret __asm__("r0") = arg1; + register u32_t r1 __asm__("r1") = arg2; + register u32_t r2 __asm__("r2") = arg3; + + __asm__ volatile("sub sp, #16\n" + "str %[cid], [sp,#8]\n" + "svc %[svid]\n" + "add sp, #16\n" + : "=r"(ret) + : [cid] "r" (call_id), + [svid] "i" (_SVC_CALL_SYSTEM_CALL), + "r" (ret), "r" (r1), "r" (r2) + : "r3", "ip", "memory"); + + return ret; } static inline u32_t _arch_syscall_invoke2(u32_t arg1, u32_t arg2, u32_t call_id) { - return 0; + register u32_t ret __asm__("r0") = arg1; + register u32_t r1 __asm__("r1") = arg2; + + __asm__ volatile( + "sub sp, #16\n" + "str %[cid], [sp,#8]\n" + "svc %[svid]\n" + "add sp, #16\n" + : "=r"(ret) + : [cid] "r" (call_id), + [svid] "i" (_SVC_CALL_SYSTEM_CALL), + "r" (ret), "r" (r1) + : "r2", "r3", "ip", "memory"); + + return ret; } static inline u32_t _arch_syscall_invoke1(u32_t arg1, u32_t call_id) { - return 0; + register u32_t ret __asm__("r0") = arg1; + + __asm__ volatile( + "sub sp, #16\n" + "str %[cid], [sp,#8]\n" + "svc %[svid]\n" + "add sp, #16\n" + : "=r"(ret) + : [cid] "r" (call_id), + [svid] "i" (_SVC_CALL_SYSTEM_CALL), + "r" (ret) + : "r1", "r2", "r3", "ip", "memory"); + return ret; } static inline u32_t _arch_syscall_invoke0(u32_t call_id) { - return 0; + register u32_t ret __asm__("r0"); + + __asm__ volatile( + "sub sp, #16\n" + "str %[cid], [sp,#8]\n" + "svc %[svid]\n" + "add sp, #16\n" + : "=r"(ret) + : [cid] "r" (call_id), + [svid] "i" (_SVC_CALL_SYSTEM_CALL), + "r" (ret) + : "r1", "r2", "r3", "ip", "memory"); + + return ret; } static inline int _arch_is_user_context(void) { - return 0; + u32_t value; + + /* check for handler mode */ + __asm__ volatile("mrs %0, IPSR\n\t" : "=r"(value)); + if (value) { + return 0; + } + + /* if not handler mode, return mode information */ + __asm__ volatile("mrs %0, CONTROL\n\t" : "=r"(value)); + return value & 0x1; } + #endif /* _ASMLANGUAGE */ -#endif /* CONFIG_ARM_USERSPACE */ +#endif /* CONFIG_USERSPACE */ #ifdef __cplusplus } diff --git a/include/arch/arm/cortex_m/error.h b/include/arch/arm/cortex_m/error.h index d245b543cf615..020e8cab3eca7 100644 --- a/include/arch/arm/cortex_m/error.h +++ b/include/arch/arm/cortex_m/error.h @@ -33,6 +33,7 @@ extern void _SysFatalErrorHandler(unsigned int reason, const NANO_ESF *esf); #define _SVC_CALL_IRQ_OFFLOAD 1 #define _SVC_CALL_RUNTIME_EXCEPT 2 +#define _SVC_CALL_SYSTEM_CALL 3 #if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE) /* ARMv6 will hard-fault if SVC is called with interrupts locked. Just diff --git a/kernel/thread.c b/kernel/thread.c index 18c004e4ffdf3..b2d1c9c45b517 100644 --- a/kernel/thread.c +++ b/kernel/thread.c @@ -319,7 +319,10 @@ _SYSCALL_HANDLER(k_thread_create, new_thread_p, stack_p, stack_size, entry, p1, more_args) { int prio; - u32_t options, delay, guard_size, total_size; + u32_t options, delay; +#ifndef CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT + u32_t guard_size, total_size; +#endif struct _k_object *stack_object; struct k_thread *new_thread = (struct k_thread *)new_thread_p; volatile struct _syscall_10_args *margs = @@ -334,18 +337,25 @@ _SYSCALL_HANDLER(k_thread_create, _OBJ_INIT_FALSE), "bad stack object"); +#ifndef CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT /* Verify that the stack size passed in is OK by computing the total * size and comparing it with the size value in the object metadata + * + * We skip this check for SoCs which utilize MPUs with power of two + * alignment requirements as the guard is allocated out of the stack + * size and not allocated in addition to the stack size */ guard_size = (u32_t)K_THREAD_STACK_BUFFER(stack) - (u32_t)stack; _SYSCALL_VERIFY_MSG(!__builtin_uadd_overflow(guard_size, stack_size, &total_size), "stack size overflow (%u+%u)", stack_size, guard_size); + /* They really ought to be equal, make this more strict? */ _SYSCALL_VERIFY_MSG(total_size <= stack_object->data, "stack size %u is too big, max is %u", total_size, stack_object->data); +#endif /* Verify the struct containing args 6-10 */ _SYSCALL_MEMORY_READ(margs, sizeof(*margs)); From 75282f6fe79e12bec462b1d4db2b19290f84255f Mon Sep 17 00:00:00 2001 From: Andy Gross Date: Fri, 8 Dec 2017 12:26:38 -0600 Subject: [PATCH 3/6] arm: mpu: Enable userspace support for NXP and ARM This patch set implements the APIs and changed required to support the user mode thread support. Signed-off-by: Andy Gross --- arch/arm/core/cortex_m/mpu/Kconfig | 1 + arch/arm/core/cortex_m/mpu/arm_mpu.c | 119 +++++++++------ arch/arm/core/cortex_m/mpu/nxp_mpu.c | 136 +++++++++++------- .../arch/arm/cortex_m/mpu/arm_core_mpu_dev.h | 6 +- include/arch/arm/cortex_m/mpu/arm_mpu.h | 5 +- include/arch/arm/cortex_m/mpu/nxp_mpu.h | 25 ++-- 6 files changed, 179 insertions(+), 113 deletions(-) diff --git a/arch/arm/core/cortex_m/mpu/Kconfig b/arch/arm/core/cortex_m/mpu/Kconfig index b575accf034b0..ddc98a4eb8137 100644 --- a/arch/arm/core/cortex_m/mpu/Kconfig +++ b/arch/arm/core/cortex_m/mpu/Kconfig @@ -27,6 +27,7 @@ config ARM_MPU depends on SOC_FAMILY_ARM || SOC_FAMILY_STM32 || SOC_FAMILY_NRF5 || SOC_FAMILY_IMX select ARM_CORE_MPU select ARCH_HAS_EXECUTABLE_PAGE_BIT + select MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT default n help MCU has ARM MPU diff --git a/arch/arm/core/cortex_m/mpu/arm_mpu.c b/arch/arm/core/cortex_m/mpu/arm_mpu.c index b5a3251ad4ae1..8cc3cc2992251 100644 --- a/arch/arm/core/cortex_m/mpu/arm_mpu.c +++ b/arch/arm/core/cortex_m/mpu/arm_mpu.c @@ -12,6 +12,7 @@ #include #include #include +#include #define ARM_MPU_DEV ((volatile struct arm_mpu *) ARM_MPU_BASE) @@ -31,18 +32,52 @@ static inline u32_t _get_region_attr(u32_t xn, u32_t ap, u32_t tex, | (c << 17) | (b << 16) | (srd << 5) | (size)); } +/** + * This internal function converts the region size to + * the SIZE field value of MPU_RASR. + */ +static inline u32_t _size_to_mpu_rasr_size(u32_t size) +{ + /* The minimal supported region size is 32 bytes */ + if (size <= 32) { + return REGION_32B; + } + + /* + * A size value greater than 2^31 could not be handled by + * round_up_to_next_power_of_two() properly. We handle + * it separately here. + */ + if (size > (1 << 31)) { + return REGION_4G; + } + + size = 1 << (32 - __builtin_clz(size - 1)); + return (32 - __builtin_clz(size) - 2) << 1; +} + + /** * This internal function is utilized by the MPU driver to parse the intent * type (i.e. THREAD_STACK_REGION) and return the correct parameter set. */ static inline u32_t _get_region_attr_by_type(u32_t type, u32_t size) { + int region_size = _size_to_mpu_rasr_size(size); + switch (type) { + case THREAD_STACK_USER_REGION: + return _get_region_attr(1, P_RW_U_RW, 0, 1, 0, + 1, 0, region_size); case THREAD_STACK_REGION: - return 0; + return _get_region_attr(1, P_RW_U_RW, 0, 1, 0, + 1, 0, region_size); case THREAD_STACK_GUARD_REGION: - return _get_region_attr(1, P_RO_U_RO, 0, 1, 0, - 1, 0, REGION_32B); + return _get_region_attr(1, P_RO_U_NA, 0, 1, 0, + 1, 0, region_size); + case THREAD_APP_DATA_REGION: + return _get_region_attr(1, P_RW_U_RW, 0, 1, 0, + 1, 0, region_size); default: /* Size 0 region */ return 0; @@ -67,6 +102,7 @@ static void _region_init(u32_t index, u32_t region_addr, ARM_MPU_DEV->rbar = (region_addr & REGION_BASE_ADDR_MASK) | REGION_VALID | index; ARM_MPU_DEV->rasr = region_attr | REGION_ENABLE; + SYS_LOG_DBG("[%d] 0x%08x 0x%08x", index, region_addr, region_attr); } /** @@ -82,19 +118,23 @@ static inline u32_t _get_region_index_by_type(u32_t type) * index. */ switch (type) { + case THREAD_STACK_USER_REGION: + return mpu_config.num_regions + THREAD_STACK_REGION - 1; case THREAD_STACK_REGION: - return mpu_config.num_regions + type - 1; case THREAD_STACK_GUARD_REGION: + case THREAD_APP_DATA_REGION: return mpu_config.num_regions + type - 1; case THREAD_DOMAIN_PARTITION_REGION: -#if defined(CONFIG_MPU_STACK_GUARD) +#if defined(CONFIG_USERSPACE) return mpu_config.num_regions + type - 1; +#elif defined(CONFIG_MPU_STACK_GUARD) + return mpu_config.num_regions + type - 2; #else /* * Start domain partition region from stack guard region * since stack guard is not enabled. */ - return mpu_config.num_regions + type - 2; + return mpu_config.num_regions + type - 3; #endif default: __ASSERT(0, "Unsupported type"); @@ -102,44 +142,6 @@ static inline u32_t _get_region_index_by_type(u32_t type) } } -static inline u32_t round_up_to_next_power_of_two(u32_t v) -{ - v--; - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - v++; - - return v; -} - -/** - * This internal function converts the region size to - * the SIZE field value of MPU_RASR. - */ -static inline u32_t _size_to_mpu_rasr_size(u32_t size) -{ - /* The minimal supported region size is 32 bytes */ - if (size <= 32) { - return REGION_32B; - } - - /* - * A size value greater than 2^31 could not be handled by - * round_up_to_next_power_of_two() properly. We handle - * it separately here. - */ - if (size > (1 << 31)) { - return REGION_4G; - } - - size = round_up_to_next_power_of_two(size); - - return (find_msb_set(size) - 2) << 1; -} - /** * This internal function check if region is enabled or not */ @@ -182,6 +184,11 @@ static inline int _is_user_accessible_region(u32_t r_index, int write) ARM_MPU_DEV->rnr = r_index; r_ap = ARM_MPU_DEV->rasr & ACCESS_PERMS_MASK; + /* always return true if this is the thread stack region */ + if (_get_region_index_by_type(THREAD_STACK_REGION) == r_index) { + return 1; + } + if (write) { return r_ap == P_RW_U_RW; } @@ -242,7 +249,29 @@ void arm_core_mpu_configure(u8_t type, u32_t base, u32_t size) #if defined(CONFIG_USERSPACE) void arm_core_mpu_configure_user_context(struct k_thread *thread) { - return 0; + u32_t base = (u32_t)thread->stack_obj; + u32_t size = thread->stack_info.size; + u32_t index = _get_region_index_by_type(THREAD_STACK_USER_REGION); + u32_t region_attr = _get_region_attr_by_type(THREAD_STACK_USER_REGION, + size); + + if (!thread->arch.priv_stack_start) { + ARM_MPU_DEV->rnr = index; + ARM_MPU_DEV->rbar = 0; + ARM_MPU_DEV->rasr = 0; + return; + } + /* configure stack */ + _region_init(index, base, region_attr); + +#if defined(CONFIG_APPLICATION_MEMORY) + /* configure app data portion */ + index = _get_region_index_by_type(THREAD_APP_DATA_REGION); + size = (u32_t)&__app_ram_end - (u32_t)&__app_ram_start; + region_attr = _get_region_attr_by_type(THREAD_APP_DATA_REGION, size); + if (size > 0) + _region_init(index, (u32_t)&__app_ram_start, region_attr); +#endif } /** diff --git a/arch/arm/core/cortex_m/mpu/nxp_mpu.c b/arch/arm/core/cortex_m/mpu/nxp_mpu.c index c99be9c05c0a8..12897b5c6a0cd 100644 --- a/arch/arm/core/cortex_m/mpu/nxp_mpu.c +++ b/arch/arm/core/cortex_m/mpu/nxp_mpu.c @@ -12,8 +12,7 @@ #include #include #include - -#define STACK_GUARD_REGION_SIZE 32 +#include /* NXP MPU Enabled state */ static u8_t nxp_mpu_enabled; @@ -25,11 +24,15 @@ static u8_t nxp_mpu_enabled; static inline u32_t _get_region_attr_by_type(u32_t type) { switch (type) { + case THREAD_STACK_USER_REGION: + return REGION_USER_MODE_ATTR; case THREAD_STACK_REGION: - return 0; + return REGION_RAM_ATTR; case THREAD_STACK_GUARD_REGION: /* The stack guard region has to be not accessible */ return REGION_RO_ATTR; + case THREAD_APP_DATA_REGION: + return REGION_USER_MODE_ATTR; default: /* Size 0 region */ return 0; @@ -92,19 +95,23 @@ static inline u32_t _get_region_index_by_type(u32_t type) * index. */ switch (type) { + case THREAD_STACK_USER_REGION: + return mpu_config.num_regions + THREAD_STACK_REGION - 1; case THREAD_STACK_REGION: - return mpu_config.num_regions + type - 1; case THREAD_STACK_GUARD_REGION: + case THREAD_APP_DATA_REGION: return mpu_config.num_regions + type - 1; case THREAD_DOMAIN_PARTITION_REGION: -#if defined(CONFIG_MPU_STACK_GUARD) +#if defined(CONFIG_USERSPACE) return mpu_config.num_regions + type - 1; +#elif defined(CONFIG_MPU_STACK_GUARD) + return mpu_config.num_regions + type - 2; #else /* * Start domain partition region from stack guard region * since stack guard is not enabled. */ - return mpu_config.num_regions + type - 2; + return mpu_config.num_regions + type - 3; #endif default: __ASSERT(0, "Unsupported type"); @@ -145,6 +152,11 @@ static inline int _is_user_accessible_region(u32_t r_index, int write) { u32_t r_ap = SYSMPU->WORD[r_index][2]; + /* always return true if this is the thread stack region */ + if (_get_region_index_by_type(THREAD_STACK_REGION) == r_index) { + return 1; + } + if (write) { return (r_ap & MPU_REGION_WRITE) == MPU_REGION_WRITE; } @@ -152,6 +164,44 @@ static inline int _is_user_accessible_region(u32_t r_index, int write) return (r_ap & MPU_REGION_READ) == MPU_REGION_READ; } +static void nxp_mpu_setup_sram_region(u32_t base, u32_t size) +{ + u32_t last_region = _get_num_regions() - 1; + + /* + * The NXP MPU manages the permissions of the overlapping regions + * doing the logic OR in between them, hence they can't be used + * for stack/stack guard protection. For this reason the last region of + * the MPU will be reserved. + * + * A consequence of this is that the SRAM is split in different + * regions. In example if THREAD_STACK_GUARD_REGION is selected: + * - SRAM before THREAD_STACK_GUARD_REGION: RW + * - SRAM THREAD_STACK_GUARD_REGION: RO + * - SRAM after THREAD_STACK_GUARD_REGION: RW + */ + + /* Configure SRAM_0 region + * + * The mpu_config.sram_region contains the index of the region in + * the mpu_config.mpu_regions array but the region 0 on the NXP MPU + * is the background region, so on this MPU the regions are mapped + * starting from 1, hence the mpu_config.sram_region on the data + * structure is mapped on the mpu_config.sram_region + 1 region of + * the MPU. + */ + _region_init(mpu_config.sram_region, + mpu_config.mpu_regions[mpu_config.sram_region].base, + ENDADDR_ROUND(base), + mpu_config.mpu_regions[mpu_config.sram_region].attr); + + /* Configure SRAM_1 region */ + _region_init(last_region, base + size, + ENDADDR_ROUND(mpu_config.mpu_regions[mpu_config.sram_region].end), + mpu_config.mpu_regions[mpu_config.sram_region].attr); + +} + /* ARM Core MPU Driver API Implementation for NXP MPU */ /** @@ -194,64 +244,46 @@ void arm_core_mpu_configure(u8_t type, u32_t base, u32_t size) SYS_LOG_DBG("Region info: 0x%x 0x%x", base, size); u32_t region_index = _get_region_index_by_type(type); u32_t region_attr = _get_region_attr_by_type(type); - u32_t last_region = _get_num_regions() - 1; - /* - * The NXP MPU manages the permissions of the overlapping regions - * doing the logic OR in between them, hence they can't be used - * for stack/stack guard protection. For this reason the last region of - * the MPU will be reserved. - * - * A consequence of this is that the SRAM is splitted in different - * regions. In example if THREAD_STACK_GUARD_REGION is selected: - * - SRAM before THREAD_STACK_GUARD_REGION: RW - * - SRAM THREAD_STACK_GUARD_REGION: RO - * - SRAM after THREAD_STACK_GUARD_REGION: RW - */ /* NXP MPU supports up to 16 Regions */ if (region_index > _get_num_regions() - 2) { return; } - /* Configure SRAM_0 region */ - /* - * The mpu_config.sram_region contains the index of the region in - * the mpu_config.mpu_regions array but the region 0 on the NXP MPU - * is the background region, so on this MPU the regions are mapped - * starting from 1, hence the mpu_config.sram_region on the data - * structure is mapped on the mpu_config.sram_region + 1 region of - * the MPU. - */ - _region_init(mpu_config.sram_region, - mpu_config.mpu_regions[mpu_config.sram_region].base, - ENDADDR_ROUND(base), - mpu_config.mpu_regions[mpu_config.sram_region].attr); - - switch (type) { - case THREAD_STACK_REGION: - break; - case THREAD_STACK_GUARD_REGION: - _region_init(region_index, base, - ENDADDR_ROUND(base + STACK_GUARD_REGION_SIZE), - region_attr); - break; - default: - break; + _region_init(region_index, base, + ENDADDR_ROUND(base + size), + region_attr); + if (type == THREAD_STACK_GUARD_REGION) { + nxp_mpu_setup_sram_region(base, size); } - - /* Configure SRAM_1 region */ - _region_init(last_region, - base + STACK_GUARD_REGION_SIZE, - ENDADDR_ROUND( - mpu_config.mpu_regions[mpu_config.sram_region].end), - mpu_config.mpu_regions[mpu_config.sram_region].attr); - } #if defined(CONFIG_USERSPACE) void arm_core_mpu_configure_user_context(struct k_thread *thread) { - return 0; + u32_t base = (u32_t)thread->stack_info.start; + u32_t size = thread->stack_info.size; + u32_t index = _get_region_index_by_type(THREAD_STACK_USER_REGION); + u32_t region_attr = _get_region_attr_by_type(THREAD_STACK_USER_REGION); + + /* configure stack */ + _region_init(index, base, ENDADDR_ROUND(base + size), region_attr); + +#if defined(CONFIG_APPLICATION_MEMORY) + /* configure app data portion */ + index = _get_region_index_by_type(THREAD_APP_DATA_REGION); + region_attr = _get_region_attr_by_type(THREAD_APP_DATA_REGION); + base = (u32_t)&__app_ram_start; + size = (u32_t)&__app_ram_end - (u32_t)&__app_ram_start; + + /* set up app data region if exists, otherwise disable */ + if (size > 0) { + _region_init(index, base, ENDADDR_ROUND(base + size), + region_attr); + } else { + SYSMPU->WORD[index][3] = 0; + } +#endif } /** diff --git a/include/arch/arm/cortex_m/mpu/arm_core_mpu_dev.h b/include/arch/arm/cortex_m/mpu/arm_core_mpu_dev.h index 1dd4efa1b0f09..c4601c8d30ce6 100644 --- a/include/arch/arm/cortex_m/mpu/arm_core_mpu_dev.h +++ b/include/arch/arm/cortex_m/mpu/arm_core_mpu_dev.h @@ -30,9 +30,11 @@ extern "C" { * be managed inside the MPU driver and not escalated. */ /* Thread Stack Region Intent Type */ +#define THREAD_STACK_USER_REGION 0x0 /* fake region for user mode stack */ #define THREAD_STACK_REGION 0x1 -#define THREAD_STACK_GUARD_REGION 0x2 -#define THREAD_DOMAIN_PARTITION_REGION 0x3 +#define THREAD_APP_DATA_REGION 0x2 +#define THREAD_STACK_GUARD_REGION 0x3 +#define THREAD_DOMAIN_PARTITION_REGION 0x4 #if defined(CONFIG_ARM_CORE_MPU) struct k_mem_domain; diff --git a/include/arch/arm/cortex_m/mpu/arm_mpu.h b/include/arch/arm/cortex_m/mpu/arm_mpu.h index 668efb539b654..a35ecf8200eeb 100644 --- a/include/arch/arm/cortex_m/mpu/arm_mpu.h +++ b/include/arch/arm/cortex_m/mpu/arm_mpu.h @@ -101,9 +101,12 @@ struct arm_mpu { #define DEVICE_NON_SHAREABLE (2 << 19) /* Some helper defines for common regions */ -#define REGION_RAM_ATTR(size) \ +#define REGION_USER_RAM_ATTR(size) \ (NORMAL_OUTER_INNER_NON_CACHEABLE_NON_SHAREABLE | \ NOT_EXEC | size | FULL_ACCESS) +#define REGION_RAM_ATTR(size) \ + (NORMAL_OUTER_INNER_NON_CACHEABLE_NON_SHAREABLE | \ + NOT_EXEC | size | P_RW_U_NA) #if defined(CONFIG_MPU_ALLOW_FLASH_WRITE) #define REGION_FLASH_ATTR(size) \ (NORMAL_OUTER_INNER_NON_CACHEABLE_NON_SHAREABLE | size | \ diff --git a/include/arch/arm/cortex_m/mpu/nxp_mpu.h b/include/arch/arm/cortex_m/mpu/nxp_mpu.h index 8403aafb8554e..9e65ce3cdd865 100644 --- a/include/arch/arm/cortex_m/mpu/nxp_mpu.h +++ b/include/arch/arm/cortex_m/mpu/nxp_mpu.h @@ -76,21 +76,18 @@ /* The ENDADDR field has the last 5 bit reserved and set to 1 */ #define ENDADDR_ROUND(x) (x - 0x1F) +#define REGION_USER_MODE_ATTR (MPU_REGION_READ | \ + MPU_REGION_WRITE | \ + MPU_REGION_SU) + /* Some helper defines for common regions */ #if defined(CONFIG_MPU_ALLOW_FLASH_WRITE) -#define REGION_RAM_ATTR (MPU_REGION_READ | \ - MPU_REGION_WRITE | \ - MPU_REGION_EXEC | \ - MPU_REGION_SU) +#define REGION_RAM_ATTR (MPU_REGION_SU_RWX) + +#define REGION_FLASH_ATTR (MPU_REGION_SU_RWX) -#define REGION_FLASH_ATTR (MPU_REGION_READ | \ - MPU_REGION_WRITE | \ - MPU_REGION_EXEC | \ - MPU_REGION_SU) #else -#define REGION_RAM_ATTR (MPU_REGION_READ | \ - MPU_REGION_WRITE | \ - MPU_REGION_SU) +#define REGION_RAM_ATTR (MPU_REGION_SU_RW) #define REGION_FLASH_ATTR (MPU_REGION_READ | \ MPU_REGION_EXEC | \ @@ -102,8 +99,10 @@ MPU_REGION_EXEC | \ MPU_REGION_SU) -#define REGION_RO_ATTR (MPU_REGION_READ | \ - MPU_REGION_SU) +#define REGION_RO_ATTR (MPU_REGION_READ | MPU_REGION_SU) + +#define REGION_USER_RO_ATTR (MPU_REGION_READ | \ + MPU_REGION_SU) #define REGION_DEBUG_ATTR MPU_REGION_SU From 0bb8c481329eda80ec0771438974cb5bf1ce905d Mon Sep 17 00:00:00 2001 From: Andy Gross Date: Fri, 8 Dec 2017 14:11:39 -0600 Subject: [PATCH 4/6] tests: mem_protect: userspace: Adjust kernel stack tests This patch adjusts the calculation of the overflow size for the kernel stack tests which read/write to areas below the current user stack. Signed-off-by: Andy Gross --- tests/kernel/mem_protect/userspace/src/main.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/kernel/mem_protect/userspace/src/main.c b/tests/kernel/mem_protect/userspace/src/main.c index 02359a64ca090..dbef1f2b400be 100644 --- a/tests/kernel/mem_protect/userspace/src/main.c +++ b/tests/kernel/mem_protect/userspace/src/main.c @@ -222,10 +222,14 @@ static void write_kernel_data(void) volatile int *ptr = NULL; #if defined(CONFIG_X86) volatile size_t size = MMU_PAGE_SIZE; -#elif defined(CONFIG_ARM) && defined(CONFIG_PRIVILEGED_STACK_SIZE) -volatile size_t size = CONFIG_ZTEST_STACKSIZE - CONFIG_PRIVILEGED_STACK_SIZE; +#elif defined(CONFIG_ARM) +#if defined(CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT) +volatile size_t size = POW2_CEIL(CONFIG_ZTEST_STACKSIZE); +#else +volatile size_t size = CONFIG_ZTEST_STACKSIZE + MPU_GUARD_ALIGN_AND_SIZE; +#endif #else -volatile size_t size = 512; +#error "Not implemented for this architecture" #endif static void read_kernel_stack(void) From c614a90b8706d12456fe504db86b35995560f361 Mon Sep 17 00:00:00 2001 From: Andy Gross Date: Tue, 30 Jan 2018 00:33:52 -0600 Subject: [PATCH 5/6] arch: arm: Fixup stack end calculations This patch fixes calculations for the top of the interrupt and main stacks. Due to power of two alignment requirements for certain MPUs, the guard size must be taken into account due to the guard being counted against the initial stack size. Signed-off-by: Andy Gross --- arch/arm/include/cortex_m/stack.h | 5 +++++ arch/arm/include/kernel_arch_func.h | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/arch/arm/include/cortex_m/stack.h b/arch/arm/include/cortex_m/stack.h index c7e87125893ba..1ef95f4bd3893 100644 --- a/arch/arm/include/cortex_m/stack.h +++ b/arch/arm/include/cortex_m/stack.h @@ -40,8 +40,13 @@ extern K_THREAD_STACK_DEFINE(_interrupt_stack, CONFIG_ISR_STACK_SIZE); */ static ALWAYS_INLINE void _InterruptStackSetup(void) { +#ifdef CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT + u32_t msp = (u32_t)(K_THREAD_STACK_BUFFER(_interrupt_stack) + + CONFIG_ISR_STACK_SIZE - MPU_GUARD_ALIGN_AND_SIZE); +#else u32_t msp = (u32_t)(K_THREAD_STACK_BUFFER(_interrupt_stack) + CONFIG_ISR_STACK_SIZE); +#endif _MspSet(msp); } diff --git a/arch/arm/include/kernel_arch_func.h b/arch/arm/include/kernel_arch_func.h index ba53da34ba826..03355dbac650c 100644 --- a/arch/arm/include/kernel_arch_func.h +++ b/arch/arm/include/kernel_arch_func.h @@ -45,8 +45,14 @@ _arch_switch_to_main_thread(struct k_thread *main_thread, /* get high address of the stack, i.e. its start (stack grows down) */ char *start_of_main_stack; +#ifdef CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT + start_of_main_stack = + K_THREAD_STACK_BUFFER(main_stack) + main_stack_size - + MPU_GUARD_ALIGN_AND_SIZE; +#else start_of_main_stack = K_THREAD_STACK_BUFFER(main_stack) + main_stack_size; +#endif start_of_main_stack = (void *)STACK_ROUND_DOWN(start_of_main_stack); _current = main_thread; From 4c59c8570f86f0e544ef37582e555f1e2efaf2e6 Mon Sep 17 00:00:00 2001 From: Andy Gross Date: Tue, 30 Jan 2018 16:58:22 -0600 Subject: [PATCH 6/6] doc: kernel: usermode: Add MPU stack and userspace documentation This patch adds documentation on the design and implementation of stack objects for architectures which utilize MPU backed stack and memory protection. Signed-off-by: Andy Gross --- doc/kernel/usermode/mpu_stack_objects.rst | 65 +++++++++++++++++++++++ doc/kernel/usermode/mpu_userspace.rst | 26 +++++++++ doc/kernel/usermode/usermode.rst | 2 + 3 files changed, 93 insertions(+) create mode 100644 doc/kernel/usermode/mpu_stack_objects.rst create mode 100644 doc/kernel/usermode/mpu_userspace.rst diff --git a/doc/kernel/usermode/mpu_stack_objects.rst b/doc/kernel/usermode/mpu_stack_objects.rst new file mode 100644 index 0000000000000..843be44fc72d3 --- /dev/null +++ b/doc/kernel/usermode/mpu_stack_objects.rst @@ -0,0 +1,65 @@ +.. _mpu_stack_objects: + +MPU Stack Objects +################# + +Thread Stack Creation +********************* + +Thread stacks are declared statically with :c:macro:`K_THREAD_STACK_DEFINE()` +or embedded within structures using c:macro:`K_THREAD_STACK_MEMBER()` + +For architectures which utilize memory protection unit (MPU) hardware, +stacks are physically contiguous allocations. This contiguous allocation +has implications for the placement of stacks in memory, as well as the +implementation of other features such as stack protection and userspace. The +implications for placement are directly attributed to the alignment +requirements for MPU regions. This is discussed in the memory placement +section below. + +Stack Guards +************ + +Stack protection mechanisms require hardware support that can restrict access +to memory. Memory protection units can provide this kind of support. +The MPU provides a fixed number of regions. Each region contains information +about the start, end, size, and access attributes to be enforced on that +particular region. + +Stack guards are implemented by using a single MPU region and setting the +attributes for that region to not allow write access. If invalid accesses +occur, a fault ensues. The stack guard is defined at the bottom (the lowest +address) of the stack. + +Memory Placement +**************** + +During stack creation, a set of constraints are enforced on the allocation of +memory. These constraints include determining the alignment of the stack and +the correct sizing of the stack. During linking of the binary, these +constraints are used to place the stacks properly. + +The main source of the memory constraints is the MPU design for the SoC. The +MPU design may require specific constraints on the region definition. These +can include alignment of beginning and end addresses, sizes of allocations, +or even interactions between overlapping regions. + +Some MPUs require that each region be aligned to a power of two. These SoCs +will have :option:`CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT` defined. +This means that a 1500 byte stack should be aligned to a 2kB boundary and the +stack size should also be adjusted to 2kB to ensure that nothing else is +placed in the remainder of the region. SoCs which include the unmodified ARM +v7m MPU will have these constraints. + +Some ARM MPUs use start and end addresses to define MPU regions and both the +start and end addresses require 32 byte alignment. An example of this kind of +MPU is found in the NXP FRDM K64F. + +MPUs may have a region priority mechanisms that use the highest priority region +that covers the memory access to determine the enforcement policy. Others may +logically OR regions to determine enforcement policy. + +Size and alignment constraints may result in stack allocations being larger +than the requested size. Region priority mechanisms may result in +some added complexity when implementing stack guards. + diff --git a/doc/kernel/usermode/mpu_userspace.rst b/doc/kernel/usermode/mpu_userspace.rst new file mode 100644 index 0000000000000..66c357da4339a --- /dev/null +++ b/doc/kernel/usermode/mpu_userspace.rst @@ -0,0 +1,26 @@ +.. _mpu_userspace: + +MPU Backed Userspace +#################### + +The MPU backed userspace implementation requires the creation of a secondary +set of stacks. These stacks exist in a 1:1 relationship with each thread stack +defined in the system. The privileged stacks are created as a part of the +build process. + +A post-build script ``gen_priv_stacks.py`` scans the generated +ELF file and finds all of the thread stack objects. A set of priviliged +stacks, a lookup table, and a set of helper functions are created and added +to the image. + +During the process of dropping a thread to user mode, the privileged stack +information is filled in and later used by the swap and system call +infrastructure to configure the MPU regions properly for the thread stack and +guard (if applicable). + +During system calls, the user mode thread's access to the system call and the +passed-in parameters are all validated. The user mode thread is then elevated +to privileged mode, the stack is switched to use the privileged stack, and the +call is made to the specified kernel API. On return from the kernel API, the +thread is set back to user mode and the stack is restored to the user stack. + diff --git a/doc/kernel/usermode/usermode.rst b/doc/kernel/usermode/usermode.rst index 2b2e4f088b746..b3f04d65f1863 100644 --- a/doc/kernel/usermode/usermode.rst +++ b/doc/kernel/usermode/usermode.rst @@ -188,3 +188,5 @@ for execution after the kernel starts: kernelobjects.rst syscalls.rst memory_domain.rst + mpu_stack_objects.rst + mpu_userspace.rst