xtensa: mpu: enable userspace support
This extends the Xtensa MPU to support userspace. Signed-off-by: Daniel Leung <daniel.leung@intel.com>
This commit is contained in:
parent
aa09b41f29
commit
57d591700b
12 changed files with 535 additions and 8 deletions
|
@ -221,6 +221,8 @@ menuconfig XTENSA_MPU
|
||||||
select MPU
|
select MPU
|
||||||
select SRAM_REGION_PERMISSIONS
|
select SRAM_REGION_PERMISSIONS
|
||||||
select XTENSA_SMALL_VECTOR_TABLE_ENTRY
|
select XTENSA_SMALL_VECTOR_TABLE_ENTRY
|
||||||
|
select ARCH_MEM_DOMAIN_SYNCHRONOUS_API if USERSPACE
|
||||||
|
select CURRENT_THREAD_USE_NO_TLS if USERSPACE
|
||||||
select EXPERIMENTAL
|
select EXPERIMENTAL
|
||||||
# TODO: the target the MPU code developed on (basically sample_controller
|
# TODO: the target the MPU code developed on (basically sample_controller
|
||||||
# plus MPU minus s32c1i) does not have cache or SMP capability.
|
# plus MPU minus s32c1i) does not have cache or SMP capability.
|
||||||
|
@ -238,6 +240,9 @@ config XTENSA_MPU_DEFAULT_MEM_TYPE
|
||||||
Default memory type for memory regions: non-cacheable memory,
|
Default memory type for memory regions: non-cacheable memory,
|
||||||
non-shareable, non-bufferable and interruptible.
|
non-shareable, non-bufferable and interruptible.
|
||||||
|
|
||||||
|
If userspace is enabled, it will be used to restore the memory type of
|
||||||
|
the region being removed from a memory domain.
|
||||||
|
|
||||||
endif # XTENSA_MPU
|
endif # XTENSA_MPU
|
||||||
|
|
||||||
endif # CPU_HAS_MPU
|
endif # CPU_HAS_MPU
|
||||||
|
@ -245,7 +250,7 @@ endif # CPU_HAS_MPU
|
||||||
config XTENSA_SYSCALL_USE_HELPER
|
config XTENSA_SYSCALL_USE_HELPER
|
||||||
bool "Use userspace syscall helper"
|
bool "Use userspace syscall helper"
|
||||||
default y if "$(ZEPHYR_TOOLCHAIN_VARIANT)" = "xt-clang"
|
default y if "$(ZEPHYR_TOOLCHAIN_VARIANT)" = "xt-clang"
|
||||||
depends on XTENSA_MMU && USERSPACE
|
depends on (XTENSA_MMU || XTENSA_MPU) && USERSPACE
|
||||||
help
|
help
|
||||||
Use syscall helpers for passing more then 3 arguments.
|
Use syscall helpers for passing more then 3 arguments.
|
||||||
This is a workaround for toolchains where they have
|
This is a workaround for toolchains where they have
|
||||||
|
@ -254,6 +259,6 @@ config XTENSA_SYSCALL_USE_HELPER
|
||||||
config XTENSA_INSECURE_USERSPACE
|
config XTENSA_INSECURE_USERSPACE
|
||||||
bool
|
bool
|
||||||
default y
|
default y
|
||||||
depends on XTENSA_MMU && USERSPACE
|
depends on (XTENSA_MMU || XTENSA_MPU) && USERSPACE
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include <zephyr/kernel.h>
|
#include <zephyr/kernel.h>
|
||||||
#include <zephyr/spinlock.h>
|
#include <zephyr/spinlock.h>
|
||||||
|
@ -21,6 +22,11 @@
|
||||||
#include <xtensa/config/core-isa.h>
|
#include <xtensa/config/core-isa.h>
|
||||||
#include <xtensa_mpu_priv.h>
|
#include <xtensa_mpu_priv.h>
|
||||||
|
|
||||||
|
#ifdef CONFIG_USERSPACE
|
||||||
|
BUILD_ASSERT((CONFIG_PRIVILEGED_STACK_SIZE > 0) &&
|
||||||
|
(CONFIG_PRIVILEGED_STACK_SIZE % XCHAL_MPU_ALIGN) == 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
extern char _heap_end[];
|
extern char _heap_end[];
|
||||||
extern char _heap_start[];
|
extern char _heap_start[];
|
||||||
|
|
||||||
|
@ -602,10 +608,25 @@ out:
|
||||||
*
|
*
|
||||||
* @param map Pointer to foreground MPU map.
|
* @param map Pointer to foreground MPU map.
|
||||||
*/
|
*/
|
||||||
|
#ifdef CONFIG_USERSPACE
|
||||||
|
/* With userspace enabled, the pointer to per memory domain MPU map is stashed
|
||||||
|
* inside the thread struct. If we still only take struct xtensa_mpu_map as
|
||||||
|
* argument, a wrapper function is needed. To avoid the cost associated with
|
||||||
|
* calling that wrapper function, takes thread pointer directly as argument
|
||||||
|
* when userspace is enabled. Not to mention that writing the map to hardware
|
||||||
|
* is already a costly operation per context switch. So every little bit helps.
|
||||||
|
*/
|
||||||
|
void xtensa_mpu_map_write(struct k_thread *thread)
|
||||||
|
#else
|
||||||
void xtensa_mpu_map_write(struct xtensa_mpu_map *map)
|
void xtensa_mpu_map_write(struct xtensa_mpu_map *map)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
int entry;
|
int entry;
|
||||||
|
|
||||||
|
#ifdef CONFIG_USERSPACE
|
||||||
|
struct xtensa_mpu_map *map = thread->arch.mpu_map;
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Clear MPU entries first, then write MPU entries in reverse order.
|
* Clear MPU entries first, then write MPU entries in reverse order.
|
||||||
*
|
*
|
||||||
|
@ -698,5 +719,342 @@ void xtensa_mpu_init(void)
|
||||||
consolidate_entries(xtensa_mpu_map_fg_kernel.entries, first_enabled_idx);
|
consolidate_entries(xtensa_mpu_map_fg_kernel.entries, first_enabled_idx);
|
||||||
|
|
||||||
/* Write the map into hardware. There is no turning back now. */
|
/* Write the map into hardware. There is no turning back now. */
|
||||||
|
#ifdef CONFIG_USERSPACE
|
||||||
|
struct k_thread dummy_map_thread;
|
||||||
|
|
||||||
|
dummy_map_thread.arch.mpu_map = &xtensa_mpu_map_fg_kernel;
|
||||||
|
xtensa_mpu_map_write(&dummy_map_thread);
|
||||||
|
#else
|
||||||
xtensa_mpu_map_write(&xtensa_mpu_map_fg_kernel);
|
xtensa_mpu_map_write(&xtensa_mpu_map_fg_kernel);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_USERSPACE
|
||||||
|
int arch_mem_domain_init(struct k_mem_domain *domain)
|
||||||
|
{
|
||||||
|
domain->arch.mpu_map = xtensa_mpu_map_fg_kernel;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int arch_mem_domain_max_partitions_get(void)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Due to each memory region requiring 2 MPU entries to describe,
|
||||||
|
* it is hard to figure out how many partitions are available.
|
||||||
|
* For example, if all those partitions are contiguous, it only
|
||||||
|
* needs 2 entries (1 if the end of region already has an entry).
|
||||||
|
* If they are all disjoint, it will need (2 * n) entries to
|
||||||
|
* describe all of them. So just use CONFIG_MAX_DOMAIN_PARTITIONS
|
||||||
|
* here and let the application set this instead.
|
||||||
|
*/
|
||||||
|
return CONFIG_MAX_DOMAIN_PARTITIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int arch_mem_domain_partition_remove(struct k_mem_domain *domain,
|
||||||
|
uint32_t partition_id)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
uint32_t perm;
|
||||||
|
struct xtensa_mpu_map *map = &domain->arch.mpu_map;
|
||||||
|
struct k_mem_partition *partition = &domain->partitions[partition_id];
|
||||||
|
uintptr_t end_addr = partition->start + partition->size;
|
||||||
|
|
||||||
|
if (end_addr <= partition->start) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is simply to get rid of the user permissions and retain
|
||||||
|
* whatever the kernel permissions are. So that we won't be
|
||||||
|
* setting the memory region permission incorrectly, for example,
|
||||||
|
* marking read only region writable.
|
||||||
|
*
|
||||||
|
* Note that Zephyr does not do RWX partitions so we can treat it
|
||||||
|
* as invalid.
|
||||||
|
*/
|
||||||
|
switch (partition->attr) {
|
||||||
|
case XTENSA_MPU_ACCESS_P_RO_U_NA:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RX_U_NA:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RO_U_RO:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RX_U_RX:
|
||||||
|
perm = XTENSA_MPU_ACCESS_P_RO_U_NA;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case XTENSA_MPU_ACCESS_P_RW_U_NA:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RWX_U_NA:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RW_U_RWX:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RW_U_RO:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RWX_U_RX:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RW_U_RW:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RWX_U_RWX:
|
||||||
|
perm = XTENSA_MPU_ACCESS_P_RW_U_NA;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
/* _P_X_U_NA is not a valid permission for userspace, so ignore.
|
||||||
|
* _P_NA_U_X becomes _P_NA_U_NA when removing user permissions.
|
||||||
|
* _P_WO_U_WO has not kernel only counterpart so just force no access.
|
||||||
|
* If we get here with _P_NA_P_NA, there is something seriously
|
||||||
|
* wrong with the userspace and/or application code.
|
||||||
|
*/
|
||||||
|
perm = XTENSA_MPU_ACCESS_P_NA_U_NA;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reset the memory region attributes by simply "adding"
|
||||||
|
* a region with default attributes. If entries already
|
||||||
|
* exist for the region, the corresponding entries will
|
||||||
|
* be updated with the default attributes. Or new entries
|
||||||
|
* will be added to carve a hole in existing regions.
|
||||||
|
*/
|
||||||
|
ret = mpu_map_region_add(map, partition->start, end_addr,
|
||||||
|
perm,
|
||||||
|
CONFIG_XTENSA_MPU_DEFAULT_MEM_TYPE,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int arch_mem_domain_partition_add(struct k_mem_domain *domain,
|
||||||
|
uint32_t partition_id)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct xtensa_mpu_map *map = &domain->arch.mpu_map;
|
||||||
|
struct k_mem_partition *partition = &domain->partitions[partition_id];
|
||||||
|
uintptr_t end_addr = partition->start + partition->size;
|
||||||
|
|
||||||
|
if (end_addr <= partition->start) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = mpu_map_region_add(map, partition->start, end_addr,
|
||||||
|
(uint8_t)partition->attr,
|
||||||
|
CONFIG_XTENSA_MPU_DEFAULT_MEM_TYPE,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int arch_mem_domain_thread_add(struct k_thread *thread)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
/* New memory domain we are being added to */
|
||||||
|
struct k_mem_domain *domain = thread->mem_domain_info.mem_domain;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* this is only set for threads that were migrating from some other
|
||||||
|
* memory domain; new threads this is NULL.
|
||||||
|
*/
|
||||||
|
struct xtensa_mpu_map *old_map = thread->arch.mpu_map;
|
||||||
|
|
||||||
|
bool is_user = (thread->base.user_options & K_USER) != 0;
|
||||||
|
bool is_migration = (old_map != NULL) && is_user;
|
||||||
|
|
||||||
|
uintptr_t stack_end_addr = thread->stack_info.start + thread->stack_info.size;
|
||||||
|
|
||||||
|
if (stack_end_addr < thread->stack_info.start) {
|
||||||
|
/* Account for wrapping around back to 0. */
|
||||||
|
stack_end_addr = 0xFFFFFFFFU;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allow USER access to the thread's stack in its new domain if
|
||||||
|
* we are migrating. If we are not migrating this is done in
|
||||||
|
* xtensa_user_stack_perms().
|
||||||
|
*/
|
||||||
|
if (is_migration) {
|
||||||
|
/* Add stack to new domain's MPU map. */
|
||||||
|
ret = mpu_map_region_add(&domain->arch.mpu_map,
|
||||||
|
thread->stack_info.start, stack_end_addr,
|
||||||
|
XTENSA_MPU_ACCESS_P_RW_U_RW,
|
||||||
|
CONFIG_XTENSA_MPU_DEFAULT_MEM_TYPE,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
/* Probably this fails due to no more available slots in MPU map. */
|
||||||
|
__ASSERT_NO_MSG(ret == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
thread->arch.mpu_map = &domain->arch.mpu_map;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove thread stack from old memory domain if we are
|
||||||
|
* migrating away from old memory domain. This is done
|
||||||
|
* by simply remove USER access from the region.
|
||||||
|
*/
|
||||||
|
if (is_migration) {
|
||||||
|
/*
|
||||||
|
* Remove stack from old MPU map by...
|
||||||
|
* "adding" a new memory region to the map
|
||||||
|
* as this carves a hole in the existing map.
|
||||||
|
*/
|
||||||
|
ret = mpu_map_region_add(old_map,
|
||||||
|
thread->stack_info.start, stack_end_addr,
|
||||||
|
XTENSA_MPU_ACCESS_P_RW_U_NA,
|
||||||
|
CONFIG_XTENSA_MPU_DEFAULT_MEM_TYPE,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Need to switch to new MPU map if this is the current
|
||||||
|
* running thread.
|
||||||
|
*/
|
||||||
|
if (thread == _current_cpu->current) {
|
||||||
|
xtensa_mpu_map_write(thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int arch_mem_domain_thread_remove(struct k_thread *thread)
|
||||||
|
{
|
||||||
|
uintptr_t stack_end_addr;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
struct k_mem_domain *domain = thread->mem_domain_info.mem_domain;
|
||||||
|
|
||||||
|
if ((thread->base.user_options & K_USER) == 0) {
|
||||||
|
ret = 0;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((thread->base.thread_state & _THREAD_DEAD) == 0) {
|
||||||
|
/* Thread is migrating to another memory domain and not
|
||||||
|
* exiting for good; we weren't called from
|
||||||
|
* z_thread_abort(). Resetting the stack region will
|
||||||
|
* take place in the forthcoming thread_add() call.
|
||||||
|
*/
|
||||||
|
ret = 0;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
stack_end_addr = thread->stack_info.start + thread->stack_info.size;
|
||||||
|
if (stack_end_addr < thread->stack_info.start) {
|
||||||
|
/* Account for wrapping around back to 0. */
|
||||||
|
stack_end_addr = 0xFFFFFFFFU;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Restore permissions on the thread's stack area since it is no
|
||||||
|
* longer a member of the domain.
|
||||||
|
*/
|
||||||
|
ret = mpu_map_region_add(&domain->arch.mpu_map,
|
||||||
|
thread->stack_info.start, stack_end_addr,
|
||||||
|
XTENSA_MPU_ACCESS_P_RW_U_NA,
|
||||||
|
CONFIG_XTENSA_MPU_DEFAULT_MEM_TYPE,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
xtensa_mpu_map_write(thread);
|
||||||
|
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int arch_buffer_validate(void *addr, size_t size, int write)
|
||||||
|
{
|
||||||
|
uintptr_t aligned_addr;
|
||||||
|
size_t aligned_size, addr_offset;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
/* addr/size arbitrary, fix this up into an aligned region */
|
||||||
|
aligned_addr = ROUND_DOWN((uintptr_t)addr, XCHAL_MPU_ALIGN);
|
||||||
|
addr_offset = (uintptr_t)addr - aligned_addr;
|
||||||
|
aligned_size = ROUND_UP(size + addr_offset, XCHAL_MPU_ALIGN);
|
||||||
|
|
||||||
|
for (size_t offset = 0; offset < aligned_size;
|
||||||
|
offset += XCHAL_MPU_ALIGN) {
|
||||||
|
uint32_t probed = xtensa_pptlb_probe(aligned_addr + offset);
|
||||||
|
|
||||||
|
uint8_t access_rights = (probed & XTENSA_MPU_PPTLB_ACCESS_RIGHTS_MASK)
|
||||||
|
>> XTENSA_MPU_PPTLB_ACCESS_RIGHTS_SHIFT;
|
||||||
|
|
||||||
|
if (write) {
|
||||||
|
/* Need to check write permission. */
|
||||||
|
switch (access_rights) {
|
||||||
|
case XTENSA_MPU_ACCESS_P_WO_U_WO:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RW_U_RWX:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RW_U_RW:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RWX_U_RWX:
|
||||||
|
/* These permissions are okay. */
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -EPERM;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Only check read permission. */
|
||||||
|
switch (access_rights) {
|
||||||
|
case XTENSA_MPU_ACCESS_P_RW_U_RWX:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RW_U_RO:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RWX_U_RX:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RO_U_RO:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RX_U_RX:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RW_U_RW:
|
||||||
|
__fallthrough;
|
||||||
|
case XTENSA_MPU_ACCESS_P_RWX_U_RWX:
|
||||||
|
/* These permissions are okay. */
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -EPERM;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void xtensa_user_stack_perms(struct k_thread *thread)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
uintptr_t stack_end_addr = thread->stack_info.start + thread->stack_info.size;
|
||||||
|
|
||||||
|
if (stack_end_addr < thread->stack_info.start) {
|
||||||
|
/* Account for wrapping around back to 0. */
|
||||||
|
stack_end_addr = 0xFFFFFFFFU;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)memset((void *)thread->stack_info.start,
|
||||||
|
(IS_ENABLED(CONFIG_INIT_STACKS)) ? 0xAA : 0x00,
|
||||||
|
thread->stack_info.size - thread->stack_info.delta);
|
||||||
|
|
||||||
|
/* Add stack to new domain's MPU map. */
|
||||||
|
ret = mpu_map_region_add(thread->arch.mpu_map,
|
||||||
|
thread->stack_info.start, stack_end_addr,
|
||||||
|
XTENSA_MPU_ACCESS_P_RW_U_RW,
|
||||||
|
CONFIG_XTENSA_MPU_DEFAULT_MEM_TYPE,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
xtensa_mpu_map_write(thread);
|
||||||
|
|
||||||
|
/* Probably this fails due to no more available slots in MPU map. */
|
||||||
|
ARG_UNUSED(ret);
|
||||||
|
__ASSERT_NO_MSG(ret == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CONFIG_USERSPACE */
|
||||||
|
|
|
@ -67,8 +67,13 @@ GEN_OFFSET_SYM(_xtensa_irq_bsa_t, hifi);
|
||||||
|
|
||||||
#ifdef CONFIG_USERSPACE
|
#ifdef CONFIG_USERSPACE
|
||||||
GEN_OFFSET_SYM(_thread_arch_t, psp);
|
GEN_OFFSET_SYM(_thread_arch_t, psp);
|
||||||
|
#ifdef CONFIG_XTENSA_MMU
|
||||||
GEN_OFFSET_SYM(_thread_arch_t, ptables);
|
GEN_OFFSET_SYM(_thread_arch_t, ptables);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef CONFIG_XTENSA_MPU
|
||||||
|
GEN_OFFSET_SYM(_thread_arch_t, mpu_map);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
GEN_ABS_SYM_END
|
GEN_ABS_SYM_END
|
||||||
|
|
|
@ -300,7 +300,12 @@ xtensa_userspace_enter:
|
||||||
call4 xtensa_user_stack_perms
|
call4 xtensa_user_stack_perms
|
||||||
|
|
||||||
l32i a6, a1, 24
|
l32i a6, a1, 24
|
||||||
|
#ifdef CONFIG_XTENSA_MMU
|
||||||
call4 xtensa_swap_update_page_tables
|
call4 xtensa_swap_update_page_tables
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_XTENSA_MPU
|
||||||
|
call4 xtensa_mpu_map_write
|
||||||
|
#endif
|
||||||
|
|
||||||
#if XCHAL_HAVE_THREADPTR
|
#if XCHAL_HAVE_THREADPTR
|
||||||
#ifdef CONFIG_THREAD_LOCAL_STORAGE
|
#ifdef CONFIG_THREAD_LOCAL_STORAGE
|
||||||
|
@ -337,7 +342,14 @@ xtensa_userspace_enter:
|
||||||
* We have to set callinc as well, since the called
|
* We have to set callinc as well, since the called
|
||||||
* function will do "entry"
|
* function will do "entry"
|
||||||
*/
|
*/
|
||||||
|
#ifdef CONFIG_XTENSA_MMU
|
||||||
movi a0, PS_WOE|PS_CALLINC(1)|PS_UM|PS_RING(2)
|
movi a0, PS_WOE|PS_CALLINC(1)|PS_UM|PS_RING(2)
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_XTENSA_MPU
|
||||||
|
/* MPU only has RING 0 and 1. */
|
||||||
|
movi a0, PS_WOE|PS_CALLINC(1)|PS_UM|PS_RING(1)
|
||||||
|
#endif
|
||||||
|
|
||||||
wsr a0, EPS2
|
wsr a0, EPS2
|
||||||
|
|
||||||
movi a0, 0
|
movi a0, 0
|
||||||
|
|
|
@ -364,14 +364,14 @@ void *xtensa_excint1_c(int *interrupted_stack)
|
||||||
_current_cpu->nested = 1;
|
_current_cpu->nested = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_XTENSA_MMU
|
#if defined(CONFIG_XTENSA_MMU) || defined(CONFIG_XTENSA_MPU)
|
||||||
#ifdef CONFIG_USERSPACE
|
#ifdef CONFIG_USERSPACE
|
||||||
fixup_out:
|
fixup_out:
|
||||||
#endif
|
#endif
|
||||||
if (is_dblexc) {
|
if (is_dblexc) {
|
||||||
__asm__ volatile("wsr.depc %0" : : "r"(0));
|
__asm__ volatile("wsr.depc %0" : : "r"(0));
|
||||||
}
|
}
|
||||||
#endif /* CONFIG_XTENSA_MMU */
|
#endif /* CONFIG_XTENSA_MMU || CONFIG_XTENSA_MPU */
|
||||||
|
|
||||||
|
|
||||||
return return_to(interrupted_stack);
|
return return_to(interrupted_stack);
|
||||||
|
|
|
@ -286,7 +286,12 @@ noflush:
|
||||||
/* Switch page tables */
|
/* Switch page tables */
|
||||||
rsr a6, ZSR_CPU
|
rsr a6, ZSR_CPU
|
||||||
l32i a6, a6, ___cpu_t_current_OFFSET
|
l32i a6, a6, ___cpu_t_current_OFFSET
|
||||||
|
#ifdef CONFIG_XTENSA_MMU
|
||||||
call4 xtensa_swap_update_page_tables
|
call4 xtensa_swap_update_page_tables
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_XTENSA_MPU
|
||||||
|
call4 xtensa_mpu_map_write
|
||||||
|
#endif
|
||||||
|
|
||||||
l32i a2, a3, 0
|
l32i a2, a3, 0
|
||||||
l32i a2, a2, 0
|
l32i a2, a2, 0
|
||||||
|
@ -394,9 +399,11 @@ _Level1RealVector:
|
||||||
rsr.exccause a0
|
rsr.exccause a0
|
||||||
#ifdef CONFIG_XTENSA_MMU
|
#ifdef CONFIG_XTENSA_MMU
|
||||||
beqi a0, EXCCAUSE_ITLB_MISS, _handle_tlb_miss_user
|
beqi a0, EXCCAUSE_ITLB_MISS, _handle_tlb_miss_user
|
||||||
|
#endif /* CONFIG_XTENSA_MMU */
|
||||||
#ifdef CONFIG_USERSPACE
|
#ifdef CONFIG_USERSPACE
|
||||||
beqi a0, EXCCAUSE_SYSCALL, _syscall
|
beqi a0, EXCCAUSE_SYSCALL, _syscall
|
||||||
#endif /* CONFIG_USERSPACE */
|
#endif /* CONFIG_USERSPACE */
|
||||||
|
#ifdef CONFIG_XTENSA_MMU
|
||||||
addi a0, a0, -EXCCAUSE_DTLB_MISS
|
addi a0, a0, -EXCCAUSE_DTLB_MISS
|
||||||
beqz a0, _handle_tlb_miss_user
|
beqz a0, _handle_tlb_miss_user
|
||||||
rsr.exccause a0
|
rsr.exccause a0
|
||||||
|
@ -426,12 +433,12 @@ _handle_tlb_miss_user:
|
||||||
l32i a0, a0, 0
|
l32i a0, a0, 0
|
||||||
rsr a0, ZSR_A0SAVE
|
rsr a0, ZSR_A0SAVE
|
||||||
rfe
|
rfe
|
||||||
|
#endif /* CONFIG_XTENSA_MMU */
|
||||||
#ifdef CONFIG_USERSPACE
|
#ifdef CONFIG_USERSPACE
|
||||||
_syscall:
|
_syscall:
|
||||||
rsr a0, ZSR_A0SAVE
|
rsr a0, ZSR_A0SAVE
|
||||||
j xtensa_do_syscall
|
j xtensa_do_syscall
|
||||||
#endif /* CONFIG_USERSPACE */
|
#endif /* CONFIG_USERSPACE */
|
||||||
#endif /* CONFIG_XTENSA_MMU */
|
|
||||||
.popsection
|
.popsection
|
||||||
|
|
||||||
/* In theory you can have levels up to 15, but known hardware only uses 7. */
|
/* In theory you can have levels up to 15, but known hardware only uses 7. */
|
||||||
|
|
|
@ -549,7 +549,12 @@ _do_call_\@:
|
||||||
rsr a6, ZSR_CPU
|
rsr a6, ZSR_CPU
|
||||||
l32i a6, a6, ___cpu_t_current_OFFSET
|
l32i a6, a6, ___cpu_t_current_OFFSET
|
||||||
|
|
||||||
|
#ifdef CONFIG_XTENSA_MMU
|
||||||
call4 xtensa_swap_update_page_tables
|
call4 xtensa_swap_update_page_tables
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_XTENSA_MPU
|
||||||
|
call4 xtensa_mpu_map_write
|
||||||
|
#endif
|
||||||
l32i a1, a1, 0
|
l32i a1, a1, 0
|
||||||
l32i a0, a1, ___xtensa_irq_bsa_t_a0_OFFSET
|
l32i a0, a1, ___xtensa_irq_bsa_t_a0_OFFSET
|
||||||
addi a1, a1, ___xtensa_irq_bsa_t_SIZEOF
|
addi a1, a1, ___xtensa_irq_bsa_t_SIZEOF
|
||||||
|
|
|
@ -73,6 +73,23 @@
|
||||||
* @}
|
* @}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name Bit shifts and masks for MPU PPTLB return value.
|
||||||
|
*
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Bit shift for segment value. */
|
||||||
|
#define XTENSA_MPU_PPTLB_ACCESS_RIGHTS_SHIFT 8U
|
||||||
|
|
||||||
|
/** Mask for segment value. */
|
||||||
|
#define XTENSA_MPU_PPTLB_ACCESS_RIGHTS_MASK 0x00000F00U
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define one MPU entry of type struct xtensa_mpu_entry.
|
* Define one MPU entry of type struct xtensa_mpu_entry.
|
||||||
*
|
*
|
||||||
|
@ -140,6 +157,21 @@ static ALWAYS_INLINE void xtensa_mpu_mpuenb_write(uint32_t mpuenb)
|
||||||
__asm__ __volatile__("wsr.mpuenb %0" : : "a"(mpuenb));
|
__asm__ __volatile__("wsr.mpuenb %0" : : "a"(mpuenb));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Probe for protection TLB entry from an address.
|
||||||
|
*
|
||||||
|
* @param addr Probe address.
|
||||||
|
*
|
||||||
|
* @return Return of the PPTLB instruction.
|
||||||
|
*/
|
||||||
|
static ALWAYS_INLINE uint32_t xtensa_pptlb_probe(uintptr_t addr)
|
||||||
|
{
|
||||||
|
uint32_t ret;
|
||||||
|
|
||||||
|
__asm__ __volatile__("pptlb %0, %1\n\t" : "=a"(ret) : "a"(addr));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name MPU entry internal helper functions.
|
* @name MPU entry internal helper functions.
|
||||||
*
|
*
|
||||||
|
|
|
@ -65,6 +65,9 @@ struct arch_mem_domain {
|
||||||
uint32_t *ptables __aligned(CONFIG_MMU_PAGE_SIZE);
|
uint32_t *ptables __aligned(CONFIG_MMU_PAGE_SIZE);
|
||||||
uint8_t asid;
|
uint8_t asid;
|
||||||
bool dirty;
|
bool dirty;
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_XTENSA_MPU
|
||||||
|
struct xtensa_mpu_map mpu_map;
|
||||||
#endif
|
#endif
|
||||||
sys_snode_t node;
|
sys_snode_t node;
|
||||||
};
|
};
|
||||||
|
|
|
@ -190,6 +190,85 @@ struct xtensa_mpu_map {
|
||||||
struct xtensa_mpu_entry entries[XTENSA_MPU_NUM_ENTRIES];
|
struct xtensa_mpu_entry entries[XTENSA_MPU_NUM_ENTRIES];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name Memory domain and partitions
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef uint32_t k_mem_partition_attr_t;
|
||||||
|
|
||||||
|
static inline bool xtensa_mem_partition_is_executable(k_mem_partition_attr_t access_rights)
|
||||||
|
{
|
||||||
|
bool is_exec;
|
||||||
|
|
||||||
|
switch (access_rights) {
|
||||||
|
case XTENSA_MPU_ACCESS_P_X_U_NA:
|
||||||
|
case XTENSA_MPU_ACCESS_P_NA_U_X:
|
||||||
|
case XTENSA_MPU_ACCESS_P_RX_U_NA:
|
||||||
|
case XTENSA_MPU_ACCESS_P_RWX_U_NA:
|
||||||
|
case XTENSA_MPU_ACCESS_P_RW_U_RWX:
|
||||||
|
case XTENSA_MPU_ACCESS_P_RWX_U_RX:
|
||||||
|
case XTENSA_MPU_ACCESS_P_RX_U_RX:
|
||||||
|
case XTENSA_MPU_ACCESS_P_RWX_U_RWX:
|
||||||
|
is_exec = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
is_exec = false;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
return is_exec;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool xtensa_mem_partition_is_writable(k_mem_partition_attr_t access_rights)
|
||||||
|
{
|
||||||
|
bool is_writable;
|
||||||
|
|
||||||
|
switch (access_rights) {
|
||||||
|
case XTENSA_MPU_ACCESS_P_RW_U_NA:
|
||||||
|
case XTENSA_MPU_ACCESS_P_RWX_U_NA:
|
||||||
|
case XTENSA_MPU_ACCESS_P_WO_U_WO:
|
||||||
|
case XTENSA_MPU_ACCESS_P_RW_U_RWX:
|
||||||
|
case XTENSA_MPU_ACCESS_P_RW_U_RO:
|
||||||
|
case XTENSA_MPU_ACCESS_P_RWX_U_RX:
|
||||||
|
case XTENSA_MPU_ACCESS_P_RW_U_RW:
|
||||||
|
case XTENSA_MPU_ACCESS_P_RWX_U_RWX:
|
||||||
|
is_writable = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
is_writable = false;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
return is_writable;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define K_MEM_PARTITION_IS_EXECUTABLE(access_rights) \
|
||||||
|
(xtensa_mem_partition_is_executable(access_rights))
|
||||||
|
|
||||||
|
#define K_MEM_PARTITION_IS_WRITABLE(access_rights) \
|
||||||
|
(xtensa_mem_partition_is_writable(access_rights))
|
||||||
|
|
||||||
|
/* Read-Write access permission attributes */
|
||||||
|
#define K_MEM_PARTITION_P_RW_U_RW \
|
||||||
|
((k_mem_partition_attr_t) {XTENSA_MPU_ACCESS_P_RW_U_RW})
|
||||||
|
#define K_MEM_PARTITION_P_RW_U_NA \
|
||||||
|
((k_mem_partition_attr_t) {XTENSA_MPU_ACCESS_P_RW_U_NA})
|
||||||
|
#define K_MEM_PARTITION_P_RO_U_RO \
|
||||||
|
((k_mem_partition_attr_t) {XTENSA_MPU_ACCESS_P_RO_U_RO})
|
||||||
|
#define K_MEM_PARTITION_P_RO_U_NA \
|
||||||
|
((k_mem_partition_attr_t) {XTENSA_MPU_ACCESS_P_RO_U_NA})
|
||||||
|
#define K_MEM_PARTITION_P_NA_U_NA \
|
||||||
|
((k_mem_partition_attr_t) {XTENSA_MPU_ACCESS_P_NA_U_NA})
|
||||||
|
|
||||||
|
/* Execution-allowed attributes */
|
||||||
|
#define K_MEM_PARTITION_P_RX_U_RX \
|
||||||
|
((k_mem_partition_attr_t) {XTENSA_MPU_ACCESS_P_RX_U_RX})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Struct to describe a memory region [start, end).
|
* Struct to describe a memory region [start, end).
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -10,6 +10,10 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#ifndef _ASMLANGUAGE
|
#ifndef _ASMLANGUAGE
|
||||||
|
|
||||||
|
#ifdef CONFIG_XTENSA_MPU
|
||||||
|
#include <zephyr/arch/xtensa/mpu.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Xtensa doesn't use these structs, but Zephyr core requires they be
|
/* Xtensa doesn't use these structs, but Zephyr core requires they be
|
||||||
* defined so they can be included in struct _thread_base. Dummy
|
* defined so they can be included in struct _thread_base. Dummy
|
||||||
* field exists for sizeof compatibility with C++.
|
* field exists for sizeof compatibility with C++.
|
||||||
|
@ -24,7 +28,15 @@ typedef struct _callee_saved _callee_saved_t;
|
||||||
struct _thread_arch {
|
struct _thread_arch {
|
||||||
uint32_t last_cpu;
|
uint32_t last_cpu;
|
||||||
#ifdef CONFIG_USERSPACE
|
#ifdef CONFIG_USERSPACE
|
||||||
|
|
||||||
|
#ifdef CONFIG_XTENSA_MMU
|
||||||
uint32_t *ptables;
|
uint32_t *ptables;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_XTENSA_MPU
|
||||||
|
/* Pointer to the memory domain's MPU map. */
|
||||||
|
struct xtensa_mpu_map *mpu_map;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Initial privilege mode stack pointer when doing a system call.
|
/* Initial privilege mode stack pointer when doing a system call.
|
||||||
* Un-set for surpervisor threads.
|
* Un-set for surpervisor threads.
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include <xtensa/config/core-isa.h>
|
#include <xtensa/config/core-isa.h>
|
||||||
#include <zephyr/toolchain.h>
|
#include <zephyr/toolchain.h>
|
||||||
|
#include <zephyr/sys/util.h>
|
||||||
|
|
||||||
#ifdef CONFIG_KERNEL_COHERENCE
|
#ifdef CONFIG_KERNEL_COHERENCE
|
||||||
#define ARCH_STACK_PTR_ALIGN XCHAL_DCACHE_LINESIZE
|
#define ARCH_STACK_PTR_ALIGN XCHAL_DCACHE_LINESIZE
|
||||||
|
@ -17,9 +18,15 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#if CONFIG_USERSPACE
|
#ifdef CONFIG_USERSPACE
|
||||||
|
#ifdef CONFIG_XTENSA_MMU
|
||||||
#define XTENSA_STACK_BASE_ALIGN CONFIG_MMU_PAGE_SIZE
|
#define XTENSA_STACK_BASE_ALIGN CONFIG_MMU_PAGE_SIZE
|
||||||
#define XTENSA_STACK_SIZE_ALIGN CONFIG_MMU_PAGE_SIZE
|
#define XTENSA_STACK_SIZE_ALIGN CONFIG_MMU_PAGE_SIZE
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_XTENSA_MPU
|
||||||
|
#define XTENSA_STACK_BASE_ALIGN XCHAL_MPU_ALIGN
|
||||||
|
#define XTENSA_STACK_SIZE_ALIGN XCHAL_MPU_ALIGN
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
#define XTENSA_STACK_BASE_ALIGN ARCH_STACK_PTR_ALIGN
|
#define XTENSA_STACK_BASE_ALIGN ARCH_STACK_PTR_ALIGN
|
||||||
#define XTENSA_STACK_SIZE_ALIGN ARCH_STACK_PTR_ALIGN
|
#define XTENSA_STACK_SIZE_ALIGN ARCH_STACK_PTR_ALIGN
|
||||||
|
@ -45,14 +52,16 @@
|
||||||
#ifndef _ASMLANGUAGE
|
#ifndef _ASMLANGUAGE
|
||||||
|
|
||||||
/* thread stack */
|
/* thread stack */
|
||||||
#ifdef CONFIG_XTENSA_MMU
|
|
||||||
struct xtensa_thread_stack_header {
|
struct xtensa_thread_stack_header {
|
||||||
|
#if defined(CONFIG_XTENSA_MMU) || defined(CONFIG_XTENSA_MPU)
|
||||||
char privilege_stack[CONFIG_PRIVILEGED_STACK_SIZE];
|
char privilege_stack[CONFIG_PRIVILEGED_STACK_SIZE];
|
||||||
|
#endif /* CONFIG_XTENSA_MPU */
|
||||||
} __packed __aligned(XTENSA_STACK_BASE_ALIGN);
|
} __packed __aligned(XTENSA_STACK_BASE_ALIGN);
|
||||||
|
|
||||||
|
#if defined(CONFIG_XTENSA_MMU) || defined(CONFIG_XTENSA_MPU)
|
||||||
#define ARCH_THREAD_STACK_RESERVED \
|
#define ARCH_THREAD_STACK_RESERVED \
|
||||||
sizeof(struct xtensa_thread_stack_header)
|
sizeof(struct xtensa_thread_stack_header)
|
||||||
#endif /* CONFIG_XTENSA_MMU */
|
#endif /* CONFIG_XTENSA_MMU || CONFIG_XTENSA_MPU */
|
||||||
|
|
||||||
#define ARCH_THREAD_STACK_OBJ_ALIGN(size) XTENSA_STACK_BASE_ALIGN
|
#define ARCH_THREAD_STACK_OBJ_ALIGN(size) XTENSA_STACK_BASE_ALIGN
|
||||||
#define ARCH_THREAD_STACK_SIZE_ADJUST(size) \
|
#define ARCH_THREAD_STACK_SIZE_ADJUST(size) \
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue