| /* |
| * Copyright (c) 2017 Linaro Limited. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <device.h> |
| #include <init.h> |
| #include <kernel.h> |
| #include <soc.h> |
| #include <kernel_structs.h> |
| |
| #include "arm_core_mpu_dev.h" |
| #include <linker/linker-defs.h> |
| |
| #define LOG_LEVEL CONFIG_MPU_LOG_LEVEL |
| #include <logging/log.h> |
| LOG_MODULE_REGISTER(mpu); |
| |
| /* |
| * Maximum number of dynamic memory partitions that may be supplied to the MPU |
| * driver for programming during run-time. Note that the actual number of the |
| * available MPU regions for dynamic programming depends on the number of the |
| * static MPU regions currently being programmed, and the total number of HW- |
| * available MPU regions. This macro is only used internally in function |
| * z_arch_configure_dynamic_mpu_regions(), to reserve sufficient area for the |
| * array of dynamic regions passed to the underlying driver. |
| */ |
| #if defined(CONFIG_USERSPACE) |
| #define _MAX_DYNAMIC_MPU_REGIONS_NUM \ |
| CONFIG_MAX_DOMAIN_PARTITIONS + /* User thread stack */ 1 + \ |
| (IS_ENABLED(CONFIG_MPU_STACK_GUARD) ? 1 : 0) |
| #else |
| #define _MAX_DYNAMIC_MPU_REGIONS_NUM \ |
| (IS_ENABLED(CONFIG_MPU_STACK_GUARD) ? 1 : 0) |
| #endif /* CONFIG_USERSPACE */ |
| |
| /* Convenience macros to denote the start address and the size of the system |
| * memory area, where dynamic memory regions may be programmed at run-time. |
| */ |
| #if defined(CONFIG_USERSPACE) |
| #define _MPU_DYNAMIC_REGIONS_AREA_START ((u32_t)&_app_smem_start) |
| #else |
| #define _MPU_DYNAMIC_REGIONS_AREA_START ((u32_t)&__kernel_ram_start) |
| #endif /* CONFIG_USERSPACE */ |
| #define _MPU_DYNAMIC_REGIONS_AREA_SIZE ((u32_t)&__kernel_ram_end - \ |
| _MPU_DYNAMIC_REGIONS_AREA_START) |
| |
| /** |
| * @brief Use the HW-specific MPU driver to program |
| * the static MPU regions. |
| * |
| * Program the static MPU regions using the HW-specific MPU driver. The |
| * function is meant to be invoked only once upon system initialization. |
| * |
| * If the function attempts to configure a number of regions beyond the |
| * MPU HW limitations, the system behavior will be undefined. |
| * |
| * For some MPU architectures, such as the unmodified ARMv8-M MPU, |
| * the function must execute with MPU enabled. |
| */ |
| void z_arch_configure_static_mpu_regions(void) |
| { |
| #if defined(CONFIG_COVERAGE_GCOV) && defined(CONFIG_USERSPACE) |
| const struct k_mem_partition gcov_region = |
| { |
| .start = (u32_t)&__gcov_bss_start, |
| .size = (u32_t)&__gcov_bss_size, |
| .attr = K_MEM_PARTITION_P_RW_U_RW, |
| }; |
| #endif /* CONFIG_COVERAGE_GCOV && CONFIG_USERSPACE */ |
| #if defined(CONFIG_NOCACHE_MEMORY) |
| const struct k_mem_partition nocache_region = |
| { |
| .start = (u32_t)&_nocache_ram_start, |
| .size = (u32_t)&_nocache_ram_size, |
| .attr = K_MEM_PARTITION_P_RW_U_NA_NOCACHE, |
| }; |
| #endif /* CONFIG_NOCACHE_MEMORY */ |
| #if defined(CONFIG_ARCH_HAS_RAMFUNC_SUPPORT) |
| const struct k_mem_partition ramfunc_region = |
| { |
| .start = (u32_t)&_ramfunc_ram_start, |
| .size = (u32_t)&_ramfunc_ram_size, |
| .attr = K_MEM_PARTITION_P_RX_U_RX, |
| }; |
| #endif /* CONFIG_ARCH_HAS_RAMFUNC_SUPPORT */ |
| |
| /* Define a constant array of k_mem_partition objects |
| * to hold the configuration of the respective static |
| * MPU regions. |
| */ |
| const struct k_mem_partition *static_regions[] = { |
| #if defined(CONFIG_COVERAGE_GCOV) && defined(CONFIG_USERSPACE) |
| &gcov_region, |
| #endif /* CONFIG_COVERAGE_GCOV && CONFIG_USERSPACE */ |
| #if defined(CONFIG_NOCACHE_MEMORY) |
| &nocache_region, |
| #endif /* CONFIG_NOCACHE_MEMORY */ |
| #if defined(CONFIG_ARCH_HAS_RAMFUNC_SUPPORT) |
| &ramfunc_region |
| #endif /* CONFIG_ARCH_HAS_RAMFUNC_SUPPORT */ |
| }; |
| |
| /* Configure the static MPU regions within firmware SRAM boundaries. |
| * Start address of the image is given by _image_ram_start. The end |
| * of the firmware SRAM area is marked by __kernel_ram_end, taking |
| * into account the unused SRAM area, as well. |
| */ |
| arm_core_mpu_configure_static_mpu_regions(static_regions, |
| ARRAY_SIZE(static_regions), |
| (u32_t)&_image_ram_start, |
| (u32_t)&__kernel_ram_end); |
| |
| #if defined(CONFIG_MPU_REQUIRES_NON_OVERLAPPING_REGIONS) |
| /* Define a constant array of k_mem_partition objects that holds the |
| * boundaries of the areas, inside which dynamic region programming |
| * is allowed. The information is passed to the underlying driver at |
| * initialization. |
| */ |
| const struct k_mem_partition dyn_region_areas[] = { |
| { |
| .start = _MPU_DYNAMIC_REGIONS_AREA_START, |
| .size = _MPU_DYNAMIC_REGIONS_AREA_SIZE, |
| } |
| }; |
| |
| arm_core_mpu_mark_areas_for_dynamic_regions(dyn_region_areas, |
| ARRAY_SIZE(dyn_region_areas)); |
| #endif /* CONFIG_MPU_REQUIRES_NON_OVERLAPPING_REGIONS */ |
| } |
| |
| /** |
| * @brief Use the HW-specific MPU driver to program |
| * the dynamic MPU regions. |
| * |
| * Program the dynamic MPU regions using the HW-specific MPU |
| * driver. This function is meant to be invoked every time the |
| * memory map is to be re-programmed, e.g during thread context |
| * switch, entering user mode, reconfiguring memory domain, etc. |
| * |
| * For some MPU architectures, such as the unmodified ARMv8-M MPU, |
| * the function must execute with MPU enabled. |
| */ |
| void z_arch_configure_dynamic_mpu_regions(struct k_thread *thread) |
| { |
| /* Define an array of k_mem_partition objects to hold the configuration |
| * of the respective dynamic MPU regions to be programmed for |
| * the given thread. The array of partitions (along with its |
| * actual size) will be supplied to the underlying MPU driver. |
| */ |
| struct k_mem_partition *dynamic_regions[_MAX_DYNAMIC_MPU_REGIONS_NUM]; |
| |
| u8_t region_num = 0U; |
| |
| #if defined(CONFIG_USERSPACE) |
| struct k_mem_partition thread_stack; |
| |
| /* Memory domain */ |
| LOG_DBG("configure thread %p's domain", thread); |
| struct k_mem_domain *mem_domain = thread->mem_domain_info.mem_domain; |
| |
| if (mem_domain) { |
| LOG_DBG("configure domain: %p", mem_domain); |
| u32_t num_partitions = mem_domain->num_partitions; |
| struct k_mem_partition partition; |
| int i; |
| |
| LOG_DBG("configure domain: %p", mem_domain); |
| |
| for (i = 0; i < CONFIG_MAX_DOMAIN_PARTITIONS; i++) { |
| partition = mem_domain->partitions[i]; |
| if (partition.size == 0) { |
| /* Zero size indicates a non-existing |
| * memory partition. |
| */ |
| continue; |
| } |
| LOG_DBG("set region 0x%lx 0x%x", |
| partition.start, partition.size); |
| __ASSERT(region_num < _MAX_DYNAMIC_MPU_REGIONS_NUM, |
| "Out-of-bounds error for dynamic region map."); |
| dynamic_regions[region_num] = |
| &mem_domain->partitions[i]; |
| |
| region_num++; |
| num_partitions--; |
| if (num_partitions == 0U) { |
| break; |
| } |
| } |
| } |
| /* Thread user stack */ |
| LOG_DBG("configure user thread %p's context", thread); |
| if (thread->arch.priv_stack_start) { |
| u32_t base = (u32_t)thread->stack_obj; |
| u32_t size = thread->stack_info.size + |
| (thread->stack_info.start - base); |
| |
| __ASSERT(region_num < _MAX_DYNAMIC_MPU_REGIONS_NUM, |
| "Out-of-bounds error for dynamic region map."); |
| thread_stack = (const struct k_mem_partition) |
| {base, size, K_MEM_PARTITION_P_RW_U_RW}; |
| |
| dynamic_regions[region_num] = &thread_stack; |
| |
| region_num++; |
| } |
| #endif /* CONFIG_USERSPACE */ |
| |
| #if defined(CONFIG_MPU_STACK_GUARD) |
| struct k_mem_partition guard; |
| |
| /* Privileged stack guard */ |
| u32_t guard_start; |
| u32_t guard_size = MPU_GUARD_ALIGN_AND_SIZE; |
| |
| #if defined(CONFIG_FLOAT) && defined(CONFIG_FP_SHARING) |
| if ((thread->base.user_options & K_FP_REGS) != 0) { |
| guard_size = MPU_GUARD_ALIGN_AND_SIZE_FLOAT; |
| } |
| #endif |
| |
| #if defined(CONFIG_USERSPACE) |
| if (thread->arch.priv_stack_start) { |
| guard_start = thread->arch.priv_stack_start - guard_size; |
| |
| __ASSERT((u32_t)&z_priv_stacks_ram_start <= guard_start, |
| "Guard start: (0x%x) below privilege stacks boundary: (0x%x)", |
| guard_start, (u32_t)&z_priv_stacks_ram_start); |
| } else { |
| guard_start = thread->stack_info.start - guard_size; |
| |
| __ASSERT((u32_t)thread->stack_obj == guard_start, |
| "Guard start (0x%x) not beginning at stack object (0x%x)\n", |
| guard_start, (u32_t)thread->stack_obj); |
| } |
| #else |
| guard_start = thread->stack_info.start - guard_size; |
| #endif /* CONFIG_USERSPACE */ |
| |
| __ASSERT(region_num < _MAX_DYNAMIC_MPU_REGIONS_NUM, |
| "Out-of-bounds error for dynamic region map."); |
| guard = (const struct k_mem_partition) |
| { |
| guard_start, |
| guard_size, |
| K_MEM_PARTITION_P_RO_U_NA |
| }; |
| dynamic_regions[region_num] = &guard; |
| |
| region_num++; |
| #endif /* CONFIG_MPU_STACK_GUARD */ |
| |
| /* Configure the dynamic MPU regions */ |
| arm_core_mpu_configure_dynamic_mpu_regions( |
| (const struct k_mem_partition **)dynamic_regions, |
| region_num); |
| } |
| |
| #if defined(CONFIG_USERSPACE) |
| |
| /** |
| * @brief Get the maximum number of partitions for a memory domain |
| * that is supported by the MPU hardware, and with respect |
| * to the current static memory region configuration. |
| */ |
| int z_arch_mem_domain_max_partitions_get(void) |
| { |
| int available_regions = arm_core_mpu_get_max_available_dyn_regions(); |
| |
| available_regions -= |
| ARM_CORE_MPU_NUM_MPU_REGIONS_FOR_THREAD_STACK; |
| |
| if (IS_ENABLED(CONFIG_MPU_STACK_GUARD)) { |
| available_regions -= |
| ARM_CORE_MPU_NUM_MPU_REGIONS_FOR_MPU_STACK_GUARD; |
| } |
| |
| return ARM_CORE_MPU_MAX_DOMAIN_PARTITIONS_GET(available_regions); |
| } |
| |
| /** |
| * @brief Configure the memory domain of the thread. |
| */ |
| void z_arch_mem_domain_thread_add(struct k_thread *thread) |
| { |
| if (_current != thread) { |
| return; |
| } |
| |
| /* Request to configure memory domain for a thread. |
| * This triggers re-programming of the entire dynamic |
| * memory map. |
| */ |
| z_arch_configure_dynamic_mpu_regions(thread); |
| } |
| |
| /* |
| * @brief Reset the MPU configuration related to the memory domain |
| * partitions |
| * |
| * @param domain pointer to the memory domain (must be valid) |
| */ |
| void z_arch_mem_domain_destroy(struct k_mem_domain *domain) |
| { |
| /* This function will reset the access permission configuration |
| * of the active partitions of the memory domain. |
| */ |
| int i; |
| struct k_mem_partition partition; |
| |
| if (_current->mem_domain_info.mem_domain != domain) { |
| return; |
| } |
| |
| /* Partitions belonging to the memory domain will be reset |
| * to default (Privileged RW, Unprivileged NA) permissions. |
| */ |
| k_mem_partition_attr_t reset_attr = K_MEM_PARTITION_P_RW_U_NA; |
| |
| for (i = 0; i < CONFIG_MAX_DOMAIN_PARTITIONS; i++) { |
| partition = domain->partitions[i]; |
| if (partition.size == 0U) { |
| /* Zero size indicates a non-existing |
| * memory partition. |
| */ |
| continue; |
| } |
| arm_core_mpu_mem_partition_config_update(&partition, |
| &reset_attr); |
| } |
| } |
| |
| /* |
| * @brief Remove a partition from the memory domain |
| * |
| * @param domain pointer to the memory domain (must be valid |
| * @param partition_id the ID (sequence) number of the memory domain |
| * partition (must be a valid partition). |
| */ |
| void z_arch_mem_domain_partition_remove(struct k_mem_domain *domain, |
| u32_t partition_id) |
| { |
| /* Request to remove a partition from a memory domain. |
| * This resets the access permissions of the partition |
| * to default (Privileged RW, Unprivileged NA). |
| */ |
| k_mem_partition_attr_t reset_attr = K_MEM_PARTITION_P_RW_U_NA; |
| |
| if (_current->mem_domain_info.mem_domain != domain) { |
| return; |
| } |
| |
| arm_core_mpu_mem_partition_config_update( |
| &domain->partitions[partition_id], &reset_attr); |
| } |
| |
| void z_arch_mem_domain_partition_add(struct k_mem_domain *domain, |
| u32_t partition_id) |
| { |
| /* No-op on this architecture */ |
| } |
| |
| void z_arch_mem_domain_thread_remove(struct k_thread *thread) |
| { |
| if (_current != thread) { |
| return; |
| } |
| |
| z_arch_mem_domain_destroy(thread->mem_domain_info.mem_domain); |
| } |
| |
| /* |
| * Validate the given buffer is user accessible or not |
| */ |
| int z_arch_buffer_validate(void *addr, size_t size, int write) |
| { |
| return arm_core_mpu_buffer_validate(addr, size, write); |
| } |
| |
| #endif /* CONFIG_USERSPACE */ |