From e9c9702818afa68bdf60568fcc4b87ac06bcd25d Mon Sep 17 00:00:00 2001 From: Chunlin Han Date: Fri, 7 Jul 2017 20:29:30 +0800 Subject: [PATCH] kernel: add memory domain APIs Add the following application-facing memory domain APIs: k_mem_domain_init() - to initialize a memory domain k_mem_domain_destroy() - to destroy a memory domain k_mem_domain_add_partition() - to add a partition into a domain k_mem_domain_remove_partition() - to remove a partition from a domain k_mem_domain_add_thread() - to add a thread into a domain k_mem_domain_remove_thread() - to remove a thread from a domain A memory domain would contain some number of memory partitions. A memory partition is a memory region (might be RAM, peripheral registers, flash...) with specific attributes (access permission, e.g. privileged read/write, unprivileged read-only, execute never...). Memory partitions would be defined by set of MPU regions or MMU tables underneath. A thread could only belong to a single memory domain any point in time but a memory domain could contain multiple threads. Threads in the same memory domain would have the same access permission to the memory partitions belong to the memory domain. The memory domain APIs are used by unprivileged threads to share data to the threads in the same memory and protect sensitive data from threads outside their domain. It is not only for improving the security but also useful for debugging (unexpected access would cause exception). Jira: ZEP-2281 Signed-off-by: Chunlin Han --- arch/arm/core/cortex_m/mpu/arm_core_mpu.c | 24 +++ arch/arm/core/cortex_m/mpu/arm_mpu.c | 167 ++++++++++++++++- arch/arm/core/cortex_m/mpu/nxp_mpu.c | 139 +++++++++++++- arch/arm/core/swap.S | 8 + doc/api/kernel_api.rst | 14 ++ doc/kernel/other/memory_domain.rst | 177 ++++++++++++++++++ doc/kernel/other/other.rst | 1 + include/arch/arm/arch.h | 40 ++++ include/arch/arm/cortex_m/mpu/arm_core_mpu.h | 12 ++ .../arch/arm/cortex_m/mpu/arm_core_mpu_dev.h | 23 +++ include/arch/arm/cortex_m/mpu/nxp_mpu.h | 10 + include/kernel.h | 143 ++++++++++++++ kernel/Kconfig | 9 + kernel/Makefile | 2 +- kernel/include/kernel_structs.h | 4 + kernel/include/nano_internal.h | 2 + kernel/mem_domain.c | 168 +++++++++++++++++ 17 files changed, 928 insertions(+), 15 deletions(-) create mode 100644 doc/kernel/other/memory_domain.rst create mode 100644 kernel/mem_domain.c 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 73b224c4034..c58a3b5571c 100644 --- a/arch/arm/core/cortex_m/mpu/arm_core_mpu.c +++ b/arch/arm/core/cortex_m/mpu/arm_core_mpu.c @@ -10,6 +10,7 @@ #include #include #include +#include #if defined(CONFIG_MPU_STACK_GUARD) /* @@ -29,3 +30,26 @@ void configure_mpu_stack_guard(struct k_thread *thread) arm_core_mpu_enable(); } #endif + +#if defined(CONFIG_USERSPACE) +/* + * @brief Configure MPU memory domain + * + * This function configures per thread memory domain reprogramming the MPU. + * The functionality is meant to be used during context switch. + * + * @param thread thread info data structure. + */ +void configure_mpu_mem_domain(struct k_thread *thread) +{ + SYS_LOG_DBG("configure thread %p's domain", thread); + arm_core_mpu_disable(); + arm_core_mpu_configure_mem_domain(thread->mem_domain_info.mem_domain); + arm_core_mpu_enable(); +} + +int _arch_mem_domain_max_partitions_get(void) +{ + return arm_core_mpu_get_max_domain_partition_regions(); +} +#endif diff --git a/arch/arm/core/cortex_m/mpu/arm_mpu.c b/arch/arm/core/cortex_m/mpu/arm_mpu.c index dda44660ef7..92aedadcc20 100644 --- a/arch/arm/core/cortex_m/mpu/arm_mpu.c +++ b/arch/arm/core/cortex_m/mpu/arm_mpu.c @@ -69,6 +69,77 @@ static void _region_init(u32_t index, u32_t region_addr, ARM_MPU_DEV->rasr = region_attr | REGION_ENABLE; } +/** + * This internal function is utilized by the MPU driver to parse the intent + * type (i.e. THREAD_STACK_REGION) and return the correct region index. + */ +static inline u32_t _get_region_index_by_type(u32_t type) +{ + /* + * The new MPU regions are allocated per type after the statically + * configured regions. The type is one-indexed rather than + * zero-indexed, therefore we need to subtract by one to get the region + * index. + */ + switch (type) { + case THREAD_STACK_REGION: + return mpu_config.num_regions + type - 1; + case THREAD_STACK_GUARD_REGION: + return mpu_config.num_regions + type - 1; + case THREAD_DOMAIN_PARTITION_REGION: +#if defined(CONFIG_MPU_STACK_GUARD) + return mpu_config.num_regions + type - 1; +#else + /* + * Start domain partition region from stack guard region + * since stack guard is not enabled. + */ + return mpu_config.num_regions + type - 2; +#endif + default: + __ASSERT(0, "Unsupported type"); + return 0; + } +} + +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; +} + /* ARM Core MPU Driver API Implementation for ARM MPU */ /** @@ -107,13 +178,7 @@ void arm_core_mpu_disable(void) 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); - /* - * The new MPU regions are allocated per type after the statically - * configured regions. The type is one-indexed rather than - * zero-indexed, therefore we need to subtract by one to get the region - * index. - */ - u32_t region_index = mpu_config.num_regions + type - 1; + u32_t region_index = _get_region_index_by_type(type); u32_t region_attr = _get_region_attr_by_type(type, size); /* ARM MPU supports up to 16 Regions */ @@ -124,6 +189,94 @@ void arm_core_mpu_configure(u8_t type, u32_t base, u32_t size) _region_init(region_index, base, region_attr); } +#if defined(CONFIG_USERSPACE) +/** + * @brief configure MPU regions for the memory partitions of the memory domain + * + * @param mem_domain memory domain that thread belongs to + */ +void arm_core_mpu_configure_mem_domain(struct k_mem_domain *mem_domain) +{ + u32_t region_index = + _get_region_index_by_type(THREAD_DOMAIN_PARTITION_REGION); + u32_t region_attr; + u32_t num_partitions; + struct k_mem_partition *pparts; + + if (mem_domain) { + SYS_LOG_DBG("configure domain: %p", mem_domain); + num_partitions = mem_domain->num_partitions; + pparts = mem_domain->partitions; + } else { + SYS_LOG_DBG("disable domain partition regions"); + num_partitions = 0; + pparts = NULL; + } + + for (; region_index < _get_num_regions(); region_index++) { + if (num_partitions && pparts->size) { + SYS_LOG_DBG("set region 0x%x 0x%x 0x%x", + region_index, pparts->start, pparts->size); + region_attr = pparts->attr | + _size_to_mpu_rasr_size(pparts->size); + _region_init(region_index, pparts->start, region_attr); + num_partitions--; + } else { + SYS_LOG_DBG("disable region 0x%x", region_index); + /* Disable region */ + ARM_MPU_DEV->rnr = region_index; + ARM_MPU_DEV->rbar = 0; + ARM_MPU_DEV->rasr = 0; + } + pparts++; + } +} + +/** + * @brief configure MPU region for a single memory partition + * + * @param part_index memory partition index + * @param part memory partition info + */ +void arm_core_mpu_configure_mem_partition(u32_t part_index, + struct k_mem_partition *part) +{ + u32_t region_index = + _get_region_index_by_type(THREAD_DOMAIN_PARTITION_REGION); + u32_t region_attr; + + SYS_LOG_DBG("configure partition index: %u", part_index); + + if (part) { + SYS_LOG_DBG("set region 0x%x 0x%x 0x%x", + region_index + part_index, part->start, part->size); + region_attr = part->attr | _size_to_mpu_rasr_size(part->size); + _region_init(region_index + part_index, part->start, + region_attr); + } else { + SYS_LOG_DBG("disable region 0x%x", region_index + part_index); + /* Disable region */ + ARM_MPU_DEV->rnr = region_index + part_index; + ARM_MPU_DEV->rbar = 0; + ARM_MPU_DEV->rasr = 0; + } +} + +/** + * @brief get the maximum number of free regions for memory domain partitions + */ +int arm_core_mpu_get_max_domain_partition_regions(void) +{ + /* + * Subtract the start of domain partition regions from total regions + * will get the maximum number of free regions for memory domain + * partitions. + */ + return _get_num_regions() - + _get_region_index_by_type(THREAD_DOMAIN_PARTITION_REGION); +} +#endif /* CONFIG_USERSPACE */ + /* ARM MPU Driver Initial Setup */ /* diff --git a/arch/arm/core/cortex_m/mpu/nxp_mpu.c b/arch/arm/core/cortex_m/mpu/nxp_mpu.c index 54a638abc81..602c3e212de 100644 --- a/arch/arm/core/cortex_m/mpu/nxp_mpu.c +++ b/arch/arm/core/cortex_m/mpu/nxp_mpu.c @@ -79,6 +79,39 @@ static void _region_init(u32_t index, u32_t region_base, SYSMPU->WORD[index][3]); } +/** + * This internal function is utilized by the MPU driver to parse the intent + * type (i.e. THREAD_STACK_REGION) and return the correct region index. + */ +static inline u32_t _get_region_index_by_type(u32_t type) +{ + /* + * The new MPU regions are allocated per type after the statically + * configured regions. The type is one-indexed rather than + * zero-indexed, therefore we need to subtract by one to get the region + * index. + */ + switch (type) { + case THREAD_STACK_REGION: + return mpu_config.num_regions + type - 1; + case THREAD_STACK_GUARD_REGION: + return mpu_config.num_regions + type - 1; + case THREAD_DOMAIN_PARTITION_REGION: +#if defined(CONFIG_MPU_STACK_GUARD) + return mpu_config.num_regions + type - 1; +#else + /* + * Start domain partition region from stack guard region + * since stack guard is not enabled. + */ + return mpu_config.num_regions + type - 2; +#endif + default: + __ASSERT(0, "Unsupported type"); + return 0; + } +} + /* ARM Core MPU Driver API Implementation for NXP MPU */ /** @@ -119,13 +152,7 @@ void arm_core_mpu_disable(void) 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); - /* - * The new MPU regions are allocated per type after the statically - * configured regions. The type is one-indexed rather than - * zero-indexed, therefore we need to subtract by one to get the region - * index. - */ - u32_t region_index = mpu_config.num_regions + type - 1; + 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; @@ -181,6 +208,104 @@ void arm_core_mpu_configure(u8_t type, u32_t base, u32_t size) } +#if defined(CONFIG_USERSPACE) +/** + * @brief configure MPU regions for the memory partitions of the memory domain + * + * @param mem_domain memory domain that thread belongs to + */ +void arm_core_mpu_configure_mem_domain(struct k_mem_domain *mem_domain) +{ + u32_t region_index = + _get_region_index_by_type(THREAD_DOMAIN_PARTITION_REGION); + u32_t region_attr; + u32_t num_partitions; + struct k_mem_partition *pparts; + + if (mem_domain) { + SYS_LOG_DBG("configure domain: %p", mem_domain); + num_partitions = mem_domain->num_partitions; + pparts = mem_domain->partitions; + } else { + SYS_LOG_DBG("disable domain partition regions"); + num_partitions = 0; + pparts = NULL; + } + + /* + * Don't touch the last region, it is reserved for SRAM_1 region. + * See comments in arm_core_mpu_configure(). + */ + for (; region_index < _get_num_regions() - 1; region_index++) { + if (num_partitions && pparts->size) { + SYS_LOG_DBG("set region 0x%x 0x%x 0x%x", + region_index, pparts->start, pparts->size); + region_attr = pparts->attr; + _region_init(region_index, pparts->start, + ENDADDR_ROUND(pparts->start+pparts->size), + region_attr); + num_partitions--; + } else { + SYS_LOG_DBG("disable region 0x%x", region_index); + /* Disable region */ + SYSMPU->WORD[region_index][0] = 0; + SYSMPU->WORD[region_index][1] = 0; + SYSMPU->WORD[region_index][2] = 0; + SYSMPU->WORD[region_index][3] = 0; + } + pparts++; + } +} + +/** + * @brief configure MPU region for a single memory partition + * + * @param part_index memory partition index + * @param part memory partition info + */ +void arm_core_mpu_configure_mem_partition(u32_t part_index, + struct k_mem_partition *part) +{ + u32_t region_index = + _get_region_index_by_type(THREAD_DOMAIN_PARTITION_REGION); + u32_t region_attr; + + SYS_LOG_DBG("configure partition index: %u", part_index); + + if (part) { + SYS_LOG_DBG("set region 0x%x 0x%x 0x%x", + region_index + part_index, part->start, part->size); + region_attr = part->attr; + _region_init(region_index + part_index, part->start, + ENDADDR_ROUND(part->start + part->size), + region_attr); + } else { + SYS_LOG_DBG("disable region 0x%x", region_index); + /* Disable region */ + SYSMPU->WORD[region_index + part_index][0] = 0; + SYSMPU->WORD[region_index + part_index][1] = 0; + SYSMPU->WORD[region_index + part_index][2] = 0; + SYSMPU->WORD[region_index + part_index][3] = 0; + } +} + +/** + * @brief get the maximum number of free regions for memory domain partitions + */ +int arm_core_mpu_get_max_domain_partition_regions(void) +{ + /* + * Subtract the start of domain partition regions from total regions + * should get the maximum number of free regions for memory domain + * partitions. But we need to consume an extra 1 region to make + * stack/stack guard protection work properly. + * See the comments in arm_core_mpu_configure(). + */ + return _get_num_regions() - + _get_region_index_by_type(THREAD_DOMAIN_PARTITION_REGION) - 1; +} +#endif /* CONFIG_USERSPACE */ + /* NXP MPU Driver Initial Setup */ /* diff --git a/arch/arm/core/swap.S b/arch/arm/core/swap.S index 22d5bbac1c8..d90dd24d67c 100644 --- a/arch/arm/core/swap.S +++ b/arch/arm/core/swap.S @@ -175,6 +175,14 @@ _thread_irq_disabled: pop {r2, lr} #endif /* CONFIG_MPU_STACK_GUARD */ +#ifdef CONFIG_USERSPACE + /* r2 contains k_thread */ + add r0, r2, #0 + push {r2, lr} + blx configure_mpu_mem_domain + pop {r2, lr} +#endif /* CONFIG_USERSPACE */ + /* load callee-saved + psp from thread */ add r0, r2, #_thread_offset_to_callee_saved ldmia r0, {v1-v8, ip} diff --git a/doc/api/kernel_api.rst b/doc/api/kernel_api.rst index 1c353b3bb96..367e4b0ca06 100644 --- a/doc/api/kernel_api.rst +++ b/doc/api/kernel_api.rst @@ -217,3 +217,17 @@ of variable-size data items. .. doxygengroup:: ring_buffer_apis :project: Zephyr + :content-only: + +Memory Domain +************* + +A memory domain contains some number of memory partitions. Threads can +specify the range and attribute (access permission) for memory partitions +in a memory domain. Threads in the same memory domain have the +same access permissions to the memory partitions belong to the +memory domain. +(See :ref:`memory_domain`.) + +.. doxygengroup:: mem_domain_apis + :project: Zephyr diff --git a/doc/kernel/other/memory_domain.rst b/doc/kernel/other/memory_domain.rst new file mode 100644 index 00000000000..f89e1c222eb --- /dev/null +++ b/doc/kernel/other/memory_domain.rst @@ -0,0 +1,177 @@ +.. _memory_domain: + +Memory Domain +############# + +The memory domain APIs are used by unprivileged threads to share data to +the threads in the same memory domain and protect sensitive data from threads +outside their domain. Memory domains are not only used for improving security, +but are also useful for debugging (unexpected access would cause exception). + +.. contents:: + :local: + :depth: 2 + +Concepts +******** + +A memory domain contains some number of memory partitions. +A memory partition is a memory region (might be RAM, peripheral registers, +or flash, for example) with specific attributes (access permission, e.g. +privileged read/write, unprivileged read-only, or execute never). +Memory partitions are defined by a set of underlying MPU regions +or MMU tables. A thread belongs to a single memory domain at +any point in time but a memory domain may contain multiple threads. +Threads in the same memory domain have the same access permissions +to the memory partitions belong to the memory domain. + +Implementation +************** + +Create a Memory Domain +====================== + +A memory domain is defined using a variable of type +:c:type:`struct k_mem_domain`. It must then be initialized by calling +:cpp:func:`k_mem_domain_init()`. + +The following code defines and initializes an empty memory domain. + +.. code-block:: c + + struct k_mem_domain app0_domain; + + k_mem_domain_init(&app0_domain, "app0_domain", 0, NULL); + +Add Memory Partitions into a Memory Domain +========================================== + +There are two ways to add memory partitions into a memory domain. + +This first code sample shows how to add memory partitions while creating +a memory domain. + +.. code-block:: c + + /* the start address of the MPU region needs to align with its size */ + u8_t __aligned(32) app0_buf[32]; + u8_t __aligned(32) app1_buf[32]; + + K_MEM_PARTITION_DEFINE(app0_part0, app0_buf, sizeof(app0_buf), + K_MEM_PARTITION_P_RW_U_RW); + + K_MEM_PARTITION_DEFINE(app0_part1, app1_buf, sizeof(app1_buf), + K_MEM_PARTITION_P_RW_U_RO); + + struct k_mem_partition *app0_parts[] = { + app0_part0, + app0_part1 + }; + + k_mem_domain_init(&app0_domain, ARRAY_SIZE(app0_parts), app0_parts); + +This second code sample shows how to add memory partitions into an initialized +memory domain one by one. + +.. code-block:: c + + /* the start address of the MPU region needs to align with its size */ + u8_t __aligned(32) app0_buf[32]; + u8_t __aligned(32) app1_buf[32]; + + K_MEM_PARTITION_DEFINE(app0_part0, app0_buf, sizeof(app0_buf), + K_MEM_PARTITION_P_RW_U_RW); + + K_MEM_PARTITION_DEFINE(app0_part1, app1_buf, sizeof(app1_buf), + K_MEM_PARTITION_P_RW_U_RO); + + k_mem_domain_add_partition(&app0_domain, &app0_part0); + k_mem_domain_add_partition(&app0_domain, &app0_part1); + +.. note:: + The maximum number of memory partitions is limited by the maximum + number of MPU regions or the maximum number of MMU tables. + +Add Threads into a Memory Domain +================================ + +Adding threads into a memory domain grants threads permission to access +the memory partitions in the memory domain. + +The following code shows how to add threads into a memory domain. + +.. code-block:: c + + k_mem_domain_add_thread(&app0_domain, &app_thread_id); + +Remove a Memory Partition from a Memory Domain +============================================== + +The following code shows how to remove a memory partition from a memory +domain. + +.. code-block:: c + + k_mem_domain_remove_partition(&app0_domain, &app0_part1); + +The k_mem_domain_remove_partition() API finds the memory partition +that matches the given parameter and removes that partition from the +memory domain. + +Remove a Thread from the Memory Domain +====================================== + +The following code shows how to remove a thread from the memory domain. + +.. code-block:: c + + k_mem_domain_remove_thread(&app0_domain, &app_thread_id); + +Destroy a Memory Domain +======================= + +The following code shows how to destroy a memory domain. + +.. code-block:: c + + k_mem_domain_destroy(&app0_domain); + +Available Partition Attributes +============================== + +When defining a partition, we need to set access permission attributes +to the partition. Since the access control of memory partitions relies on +either an MPU or MMU, the available partition attributes would be architecture +dependent. + +The complete list of available partition attributes for a specific architecture +is found in the architecture-specific include file +``include/arch//arch.h``, (for example, ``include/arch/arm/arch.h``.) +Some examples of partition attributes are: + +.. code-block:: c + + /* Denote partition is privileged read/write, unprivileged read/write */ + K_MEM_PARTITION_P_RW_U_RW + /* Denote partition is privileged read/write, unprivileged read-only */ + K_MEM_PARTITION_P_RW_U_RO + +Configuration Options +********************* + +Related configuration options: + +* :option:`CONFIG_MAX_DOMAIN_PARTITIONS` + +APIs +**** + +The following memory domain APIs are provided by :file:`kernel.h`: + +* :c:macro:`K_MEM_PARTITION_DEFINE` +* :cpp:func:`k_mem_domain_init()` +* :cpp:func:`k_mem_domain_destroy()` +* :cpp:func:`k_mem_domain_add_partition()` +* :cpp:func:`k_mem_domain_remove_partition()` +* :cpp:func:`k_mem_domain_add_thread()` +* :cpp:func:`k_mem_domain_remove_thread()` diff --git a/doc/kernel/other/other.rst b/doc/kernel/other/other.rst index 8bb3e433f6e..79f50bf8157 100644 --- a/doc/kernel/other/other.rst +++ b/doc/kernel/other/other.rst @@ -15,3 +15,4 @@ This section describes other services provided by the kernel. float.rst cxx_support.rst cpu_idle.rst + memory_domain.rst diff --git a/include/arch/arm/arch.h b/include/arch/arm/arch.h index 54bfd502258..66e485e244b 100644 --- a/include/arch/arm/arch.h +++ b/include/arch/arm/arch.h @@ -200,6 +200,46 @@ extern "C" { #define _ARCH_THREAD_STACK_BUFFER(sym) \ ((char *)(sym) + MPU_GUARD_ALIGN_AND_SIZE) +#ifdef CONFIG_USERSPACE +#ifdef CONFIG_ARM_MPU +#ifndef _ASMLANGUAGE +#include + +#define K_MEM_PARTITION_P_NA_U_NA (NO_ACCESS | NOT_EXEC) +#define K_MEM_PARTITION_P_RW_U_RW (P_RW_U_RW | NOT_EXEC) +#define K_MEM_PARTITION_P_RW_U_RO (P_RW_U_RO | NOT_EXEC) +#define K_MEM_PARTITION_P_RW_U_NA (P_RW_U_NA | NOT_EXEC) +#define K_MEM_PARTITION_P_RO_U_RO (P_RO_U_RO | NOT_EXEC) +#define K_MEM_PARTITION_P_RO_U_NA (P_RO_U_NA | NOT_EXEC) +#endif /* _ASMLANGUAGE */ +#define _ARCH_MEM_PARTITION_ALIGN_CHECK(start, size) \ + BUILD_ASSERT_MSG(!(((size) & ((size) - 1))) && (size) >= 32 && \ + !((u32_t)(start) & ((size) - 1)), \ + "the size of the partition must be power of 2" \ + " and greater than or equal to 32." \ + "start address of the partition must align with size.") +#endif /* CONFIG_ARM_MPU*/ +#ifdef CONFIG_NXP_MPU +#ifndef _ASMLANGUAGE +#include + +#define K_MEM_PARTITION_P_NA_U_NA (MPU_REGION_SU) +#define K_MEM_PARTITION_P_RW_U_RW (MPU_REGION_READ | MPU_REGION_WRITE | \ + MPU_REGION_SU) +#define K_MEM_PARTITION_P_RW_U_RO (MPU_REGION_READ | MPU_REGION_SU_RW) +#define K_MEM_PARTITION_P_RW_U_NA (MPU_REGION_SU_RW) +#define K_MEM_PARTITION_P_RO_U_RO (MPU_REGION_READ | MPU_REGION_SU) +#define K_MEM_PARTITION_P_RO_U_NA (MPU_REGION_SU_RX) +#endif /* _ASMLANGUAGE */ +#define _ARCH_MEM_PARTITION_ALIGN_CHECK(start, size) \ + BUILD_ASSERT_MSG((size) % 32 == 0 && (size) >= 32 && \ + (u32_t)(start) % 32 == 0, \ + "the size of the partition must align with 32" \ + " and greater than or equal to 32." \ + "start address of the partition must align with 32.") +#endif /* CONFIG_NXP_MPU */ +#endif /* CONFIG_USERSPACE */ + #ifdef CONFIG_ARM_USERSPACE #ifndef _ASMLANGUAGE /* Syscall invocation macros. arm-specific machine constraints used to ensure 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 d21c2e5eac0..bf72df37a6b 100644 --- a/include/arch/arm/cortex_m/mpu/arm_core_mpu.h +++ b/include/arch/arm/cortex_m/mpu/arm_core_mpu.h @@ -32,6 +32,18 @@ extern "C" { void configure_mpu_stack_guard(struct k_thread *thread); #endif +#if defined(CONFIG_USERSPACE) +/* + * @brief Configure MPU memory domain + * + * This function configures per thread memory domain reprogramming the MPU. + * The functionality is meant to be used during context switch. + * + * @param thread thread info data structure. + */ +void configure_mpu_mem_domain(struct k_thread *thread); +#endif + #ifdef __cplusplus } #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 6cd9f7686ff..c81a983bd05 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,6 +30,7 @@ extern "C" { /* Thread Stack Region Intent Type */ #define THREAD_STACK_REGION 0x1 #define THREAD_STACK_GUARD_REGION 0x2 +#define THREAD_DOMAIN_PARTITION_REGION 0x3 #if defined(CONFIG_ARM_CORE_MPU) /* ARM Core MPU Driver API */ @@ -57,6 +58,28 @@ void arm_core_mpu_disable(void); * @param size size of the region */ void arm_core_mpu_configure(u8_t type, u32_t base, u32_t size); + +/** + * @brief configure MPU regions for the memory partitions of the memory domain + * + * @param mem_domain memory domain that thread belongs to + */ +void arm_core_mpu_configure_mem_domain(struct k_mem_domain *mem_domain); + +/** + * @brief configure MPU region for a single memory partition + * + * @param part_index memory partition index + * @param part memory partition info + */ +void arm_core_mpu_configure_mem_partition(u32_t part_index, + struct k_mem_partition *part); + +/** + * @brief get the maximum number of free regions for memory domain partitions + */ +int arm_core_mpu_get_max_domain_partition_regions(void); + #endif /* CONFIG_ARM_CORE_MPU */ #ifdef __cplusplus diff --git a/include/arch/arm/cortex_m/mpu/nxp_mpu.h b/include/arch/arm/cortex_m/mpu/nxp_mpu.h index 8cbdc6abb20..84953badc12 100644 --- a/include/arch/arm/cortex_m/mpu/nxp_mpu.h +++ b/include/arch/arm/cortex_m/mpu/nxp_mpu.h @@ -58,6 +58,16 @@ (SM_SAME_AS_UM << BM2_SM_SHIFT) | \ (SM_SAME_AS_UM << BM3_SM_SHIFT)) +#define MPU_REGION_SU_RX ((SM_RX_ALLOW << BM0_SM_SHIFT) | \ + (SM_RX_ALLOW << BM1_SM_SHIFT) | \ + (SM_RX_ALLOW << BM2_SM_SHIFT) | \ + (SM_RX_ALLOW << BM3_SM_SHIFT)) + +#define MPU_REGION_SU_RW ((SM_RW_ALLOW << BM0_SM_SHIFT) | \ + (SM_RW_ALLOW << BM1_SM_SHIFT) | \ + (SM_RW_ALLOW << BM2_SM_SHIFT) | \ + (SM_RW_ALLOW << BM3_SM_SHIFT)) + /* The ENDADDR field has the last 5 bit reserved and set to 1 */ #define ENDADDR_ROUND(x) (x - 0x1F) diff --git a/include/kernel.h b/include/kernel.h index c400ab1bc0d..d2125d3a357 100644 --- a/include/kernel.h +++ b/include/kernel.h @@ -123,6 +123,8 @@ struct k_mem_pool; struct k_timer; struct k_poll_event; struct k_poll_signal; +struct k_mem_domain; +struct k_mem_partition; /* This enumeration needs to be kept in sync with the lists of kernel objects * and subsystems in scripts/gen_kobject_list.py, as well as the otype_to_str() @@ -360,6 +362,16 @@ struct _thread_stack_info { typedef struct _thread_stack_info _thread_stack_info_t; #endif /* CONFIG_THREAD_STACK_INFO */ +#if defined(CONFIG_USERSPACE) +struct _mem_domain_info { + /* memory domain queue node */ + sys_dnode_t mem_domain_q_node; + /* memory domain of the thread */ + struct k_mem_domain *mem_domain; +}; + +#endif /* CONFIG_USERSPACE */ + struct k_thread { struct _thread_base base; @@ -397,6 +409,11 @@ struct k_thread { struct _thread_stack_info stack_info; #endif /* CONFIG_THREAD_STACK_INFO */ +#if defined(CONFIG_USERSPACE) + /* memory domain info of the thread */ + struct _mem_domain_info mem_domain_info; +#endif /* CONFIG_USERSPACE */ + /* arch-specifics: must always be at the end */ struct _thread_arch arch; }; @@ -4093,6 +4110,132 @@ static inline char *K_THREAD_STACK_BUFFER(k_thread_stack_t sym) #endif /* _ARCH_DECLARE_STACK */ +/** + * @defgroup mem_domain_apis Memory domain APIs + * @ingroup kernel_apis + * @{ + */ + +/** @def MEM_PARTITION_ENTRY + * @brief Used to declare a memory partition entry + */ +#define MEM_PARTITION_ENTRY(_start, _size, _attr) \ + {\ + .start = _start, \ + .size = _size, \ + .attr = _attr, \ + } + +/** @def K_MEM_PARTITION_DEFINE + * @brief Used to declare a memory partition + */ +#ifdef _ARCH_MEM_PARTITION_ALIGN_CHECK +#define K_MEM_PARTITION_DEFINE(name, start, size, attr) \ + _ARCH_MEM_PARTITION_ALIGN_CHECK(start, size); \ + struct k_mem_partition name = \ + MEM_PARTITION_ENTRY((u32_t)start, size, attr) +#else +#define K_MEM_PARTITION_DEFINE(name, start, size, attr) \ + struct k_mem_partition name = \ + MEM_PARTITION_ENTRY((u32_t)start, size, attr) +#endif /* _ARCH_MEM_PARTITION_ALIGN_CHECK */ + + +/* memory partition */ +struct k_mem_partition { + /* start address of memory partition */ + u32_t start; + /* size of memory partition */ + u32_t size; + /* attribute of memory partition */ + u32_t attr; +}; + +#if defined(CONFIG_USERSPACE) +/* memory domian */ +struct k_mem_domain { + /* number of partitions in the domain */ + u32_t num_partitions; + /* partitions in the domain */ + struct k_mem_partition partitions[CONFIG_MAX_DOMAIN_PARTITIONS]; + /* domain q */ + sys_dlist_t mem_domain_q; +}; +#endif /* CONFIG_USERSPACE */ + +/** + * @brief Initialize a memory domain. + * + * Initialize a memory domain with given name and memory partitions. + * + * @param domain The memory domain to be initialized. + * @param num_parts The number of array items of "parts" parameter. + * @param parts An array of pointers to the memory partitions. Can be NULL + * if num_parts is zero. + */ + +extern void k_mem_domain_init(struct k_mem_domain *domain, u32_t num_parts, + struct k_mem_partition *parts[]); +/** + * @brief Destroy a memory domain. + * + * Destroy a memory domain. + * + * @param domain The memory domain to be destroyed. + */ + +extern void k_mem_domain_destroy(struct k_mem_domain *domain); + +/** + * @brief Add a memory partition into a memory domain. + * + * Add a memory partition into a memory domain. + * + * @param domain The memory domain to be added a memory partition. + * @param part The memory partition to be added + */ + +extern void k_mem_domain_add_partition(struct k_mem_domain *domain, + struct k_mem_partition *part); + +/** + * @brief Remove a memory partition from a memory domain. + * + * Remove a memory partition from a memory domain. + * + * @param domain The memory domain to be removed a memory partition. + * @param part The memory partition to be removed + */ + +extern void k_mem_domain_remove_partition(struct k_mem_domain *domain, + struct k_mem_partition *part); + +/** + * @brief Add a thread into a memory domain. + * + * Add a thread into a memory domain. + * + * @param domain The memory domain that the thread is going to be added into. + * @param thread ID of thread going to be added into the memory domain. + */ + +extern void k_mem_domain_add_thread(struct k_mem_domain *domain, + k_tid_t thread); + +/** + * @brief Remove a thread from its memory domain. + * + * Remove a thread from its memory domain. + * + * @param thread ID of thread going to be removed from its memory domain. + */ + +extern void k_mem_domain_remove_thread(k_tid_t thread); + +/** + * @} end defgroup mem_domain_apis + */ + #ifdef __cplusplus } #endif diff --git a/kernel/Kconfig b/kernel/Kconfig index 356210e7388..db07c398eef 100644 --- a/kernel/Kconfig +++ b/kernel/Kconfig @@ -557,6 +557,15 @@ config STACK_CANARIES option has no effect. endmenu +config MAX_DOMAIN_PARTITIONS + int + prompt "Maximum number of partitions per memory domain" + default 16 + range 0 255 + depends on USERSPACE + help + Configure the maximum number of partitions per memory domain. + source "kernel/Kconfig.event_logger" source "kernel/Kconfig.power_mgmt" diff --git a/kernel/Makefile b/kernel/Makefile index 882c26b076e..c39c3ca3cf5 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -37,4 +37,4 @@ lib-$(CONFIG_SYS_CLOCK_EXISTS) += timer.o lib-$(CONFIG_ATOMIC_OPERATIONS_C) += atomic_c.o lib-$(CONFIG_POLL) += poll.o lib-$(CONFIG_PTHREAD_IPC) += pthread.o -lib-$(CONFIG_USERSPACE) += userspace.o +lib-$(CONFIG_USERSPACE) += userspace.o mem_domain.o diff --git a/kernel/include/kernel_structs.h b/kernel/include/kernel_structs.h index 616ee318804..c27e6294898 100644 --- a/kernel/include/kernel_structs.h +++ b/kernel/include/kernel_structs.h @@ -177,6 +177,10 @@ static ALWAYS_INLINE void _new_thread_init(struct k_thread *thread, thread->custom_data = NULL; #endif +#if defined(CONFIG_USERSPACE) + thread->mem_domain_info.mem_domain = NULL; +#endif /* CONFIG_USERSPACE */ + #if defined(CONFIG_THREAD_STACK_INFO) thread->stack_info.start = (u32_t)pStack; thread->stack_info.size = (u32_t)stackSize; diff --git a/kernel/include/nano_internal.h b/kernel/include/nano_internal.h index 051166e26af..09cb016e5b0 100644 --- a/kernel/include/nano_internal.h +++ b/kernel/include/nano_internal.h @@ -95,7 +95,9 @@ static inline unsigned int _Swap(unsigned int key) * @return Max number of free regions, or -1 if there is no limit */ extern int _arch_mem_domain_max_partitions_get(void); +#endif +#ifdef CONFIG_USERSPACE /** * @brief Check memory region permissions * diff --git a/kernel/mem_domain.c b/kernel/mem_domain.c new file mode 100644 index 00000000000..e64e4decb77 --- /dev/null +++ b/kernel/mem_domain.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + + +static u8_t max_partitions; + + +void k_mem_domain_init(struct k_mem_domain *domain, u32_t num_parts, + struct k_mem_partition *parts[]) +{ + unsigned int key; + + __ASSERT(domain && !num_parts || parts, ""); + __ASSERT(num_parts <= max_partitions, ""); + + key = irq_lock(); + + domain->num_partitions = num_parts; + memset(domain->partitions, 0, sizeof(domain->partitions)); + + if (num_parts) { + u32_t i; + + for (i = 0; i < num_parts; i++) { + __ASSERT(parts[i], ""); + domain->partitions[i] = *parts[i]; + } + } + + sys_dlist_init(&domain->mem_domain_q); + + irq_unlock(key); +} + +void k_mem_domain_destroy(struct k_mem_domain *domain) +{ + unsigned int key; + sys_dnode_t *node, *next_node; + + __ASSERT(domain, ""); + + key = irq_lock(); + + SYS_DLIST_FOR_EACH_NODE_SAFE(&domain->mem_domain_q, node, next_node) { + struct k_thread *thread = + CONTAINER_OF(node, struct k_thread, mem_domain_info); + + sys_dlist_remove(&thread->mem_domain_info.mem_domain_q_node); + thread->mem_domain_info.mem_domain = NULL; + } + + irq_unlock(key); +} + +void k_mem_domain_add_partition(struct k_mem_domain *domain, + struct k_mem_partition *part) +{ + int p_idx; + unsigned int key; + + __ASSERT(domain && part, ""); + __ASSERT(part->start + part->size > part->start, ""); + + key = irq_lock(); + + for (p_idx = 0; p_idx < max_partitions; p_idx++) { + /* A zero-sized partition denotes it's a free partition */ + if (domain->partitions[p_idx].size == 0) { + break; + } + } + + /* Assert if there is no free partition */ + __ASSERT(p_idx < max_partitions, ""); + + domain->partitions[p_idx].start = part->start; + domain->partitions[p_idx].size = part->size; + domain->partitions[p_idx].attr = part->attr; + + domain->num_partitions++; + + irq_unlock(key); +} + +void k_mem_domain_remove_partition(struct k_mem_domain *domain, + struct k_mem_partition *part) +{ + int p_idx; + unsigned int key; + + __ASSERT(domain && part, ""); + + key = irq_lock(); + + /* find a partition that matches the given start and size */ + for (p_idx = 0; p_idx < max_partitions; p_idx++) { + if (domain->partitions[p_idx].start == part->start && + domain->partitions[p_idx].size == part->size) { + break; + } + } + + /* Assert if not found */ + __ASSERT(p_idx < max_partitions, ""); + + domain->partitions[p_idx].start = 0; + domain->partitions[p_idx].size = 0; + domain->partitions[p_idx].attr = 0; + + domain->num_partitions--; + + irq_unlock(key); +} + +void k_mem_domain_add_thread(struct k_mem_domain *domain, k_tid_t thread) +{ + unsigned int key; + + __ASSERT(domain && thread && !thread->mem_domain_info.mem_domain, ""); + + key = irq_lock(); + + sys_dlist_append(&domain->mem_domain_q, + &thread->mem_domain_info.mem_domain_q_node); + thread->mem_domain_info.mem_domain = domain; + + irq_unlock(key); +} + +void k_mem_domain_remove_thread(k_tid_t thread) +{ + unsigned int key; + + __ASSERT(thread && thread->mem_domain_info.mem_domain, ""); + + key = irq_lock(); + + sys_dlist_remove(&thread->mem_domain_info.mem_domain_q_node); + thread->mem_domain_info.mem_domain = NULL; + + irq_unlock(key); +} + +static int init_mem_domain_module(struct device *arg) +{ + ARG_UNUSED(arg); + + max_partitions = _arch_mem_domain_max_partitions_get(); + /* + * max_partitions must be less than or equal to + * CONFIG_MAX_DOMAIN_PARTITIONS, or would encounter array index + * out of bounds error. + */ + __ASSERT(max_partitions <= CONFIG_MAX_DOMAIN_PARTITIONS, ""); + + return 0; +} + +SYS_INIT(init_mem_domain_module, PRE_KERNEL_1, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);