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/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_core_mpu.c b/arch/arm/core/cortex_m/mpu/arm_core_mpu.c index f167df341f175..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,15 +23,39 @@ */ 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 #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..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; } @@ -240,6 +247,33 @@ 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) +{ + 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 +} + /** * @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..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,61 +244,48 @@ 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); + _region_init(region_index, base, + ENDADDR_ROUND(base + size), + region_attr); + if (type == THREAD_STACK_GUARD_REGION) { + nxp_mpu_setup_sram_region(base, size); + } +} - switch (type) { - case THREAD_STACK_REGION: - break; - case THREAD_STACK_GUARD_REGION: - _region_init(region_index, base, - ENDADDR_ROUND(base + STACK_GUARD_REGION_SIZE), +#if defined(CONFIG_USERSPACE) +void arm_core_mpu_configure_user_context(struct k_thread *thread) +{ + 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); - break; - default: - break; + } else { + SYSMPU->WORD[index][3] = 0; } - - /* 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); - +#endif } -#if defined(CONFIG_USERSPACE) /** * @brief configure MPU regions for the memory partitions of the memory domain * 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/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 c352005a03fac..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; @@ -96,6 +102,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/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 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/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..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,13 +30,16 @@ 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; struct k_mem_partition; +struct k_thread; /* ARM Core MPU Driver API */ @@ -71,6 +74,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 * 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 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)); 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)