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);