| /* |
| * Copyright (c) 2017 Linaro Limited. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/device.h> |
| #include <zephyr/init.h> |
| #include <zephyr/kernel.h> |
| #include <soc.h> |
| #include "arm_core_mpu_dev.h" |
| #include <zephyr/sys/__assert.h> |
| #include <zephyr/sys/math_extras.h> |
| #include <zephyr/sys/barrier.h> |
| #include <zephyr/linker/linker-defs.h> |
| #include <zephyr/mem_mgmt/mem_attr.h> |
| #include <zephyr/dt-bindings/memory-attr/memory-attr-arm.h> |
| |
| #define LOG_LEVEL CONFIG_MPU_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_DECLARE(mpu); |
| |
| #define NODE_HAS_PROP_AND_OR(node_id, prop) \ |
| DT_NODE_HAS_PROP(node_id, prop) || |
| |
| BUILD_ASSERT((DT_FOREACH_STATUS_OKAY_NODE_VARGS( |
| NODE_HAS_PROP_AND_OR, zephyr_memory_region_mpu) false) == false, |
| "`zephyr,memory-region-mpu` was deprecated in favor of `zephyr,memory-attr`"); |
| |
| /* |
| * Global status variable holding the number of HW MPU region indices, which |
| * have been reserved by the MPU driver to program the static (fixed) memory |
| * regions. |
| */ |
| static uint8_t static_regions_num; |
| |
| /* Global MPU configuration at system initialization. */ |
| static void mpu_init(void) |
| { |
| #if defined(CONFIG_SOC_FAMILY_KINETIS) |
| /* Enable clock for the Memory Protection Unit (MPU). */ |
| CLOCK_EnableClock(kCLOCK_Sysmpu0); |
| #endif |
| } |
| |
| /** |
| * Get the number of supported MPU regions. |
| */ |
| static inline uint8_t get_num_regions(void) |
| { |
| return FSL_FEATURE_SYSMPU_DESCRIPTOR_COUNT; |
| } |
| |
| /* @brief Partition sanity check |
| * |
| * This internal function performs run-time sanity check for |
| * MPU region start address and size. |
| * |
| * @param part Pointer to the data structure holding the partition |
| * information (must be valid). |
| */ |
| static int mpu_partition_is_valid(const struct z_arm_mpu_partition *part) |
| { |
| /* Partition size must be a multiple of the minimum MPU region |
| * size. Start address of the partition must align with the |
| * minimum MPU region size. |
| */ |
| int partition_is_valid = |
| (part->size != 0U) |
| && |
| ((part->size & |
| (~(CONFIG_ARM_MPU_REGION_MIN_ALIGN_AND_SIZE - 1))) |
| == part->size) |
| && |
| ((part->start & |
| (CONFIG_ARM_MPU_REGION_MIN_ALIGN_AND_SIZE - 1)) == 0U); |
| |
| return partition_is_valid; |
| } |
| |
| /* This internal function performs MPU region initialization. |
| * |
| * Note: |
| * The caller must provide a valid region index. |
| */ |
| static void region_init(const uint32_t index, |
| const struct nxp_mpu_region *region_conf) |
| { |
| uint32_t region_base = region_conf->base; |
| uint32_t region_end = region_conf->end; |
| uint32_t region_attr = region_conf->attr.attr; |
| |
| if (index == 0U) { |
| /* The MPU does not allow writes from the core to affect the |
| * RGD0 start or end addresses nor the permissions associated |
| * with the debugger; it can only write the permission fields |
| * associated with the other masters. These protections |
| * guarantee that the debugger always has access to the entire |
| * address space. |
| */ |
| __ASSERT(region_base == SYSMPU->WORD[index][0], |
| "Region %d base address got 0x%08x expected 0x%08x", |
| index, region_base, (uint32_t)SYSMPU->WORD[index][0]); |
| |
| __ASSERT(region_end == SYSMPU->WORD[index][1], |
| "Region %d end address got 0x%08x expected 0x%08x", |
| index, region_end, (uint32_t)SYSMPU->WORD[index][1]); |
| |
| /* Changes to the RGD0_WORD2 alterable fields should be done |
| * via a write to RGDAAC0. |
| */ |
| SYSMPU->RGDAAC[index] = region_attr; |
| |
| } else { |
| SYSMPU->WORD[index][0] = region_base; |
| SYSMPU->WORD[index][1] = region_end; |
| SYSMPU->WORD[index][2] = region_attr; |
| SYSMPU->WORD[index][3] = SYSMPU_WORD_VLD_MASK; |
| } |
| |
| LOG_DBG("[%02d] 0x%08x 0x%08x 0x%08x 0x%08x", index, |
| (uint32_t)SYSMPU->WORD[index][0], |
| (uint32_t)SYSMPU->WORD[index][1], |
| (uint32_t)SYSMPU->WORD[index][2], |
| (uint32_t)SYSMPU->WORD[index][3]); |
| |
| } |
| |
| static int region_allocate_and_init(const uint8_t index, |
| const struct nxp_mpu_region *region_conf) |
| { |
| /* Attempt to allocate new region index. */ |
| if (index > (get_num_regions() - 1)) { |
| |
| /* No available MPU region index. */ |
| LOG_ERR("Failed to allocate new MPU region %u\n", index); |
| return -EINVAL; |
| } |
| |
| LOG_DBG("Program MPU region at index 0x%x", index); |
| |
| /* Program region */ |
| region_init(index, region_conf); |
| |
| return index; |
| } |
| |
| #define _BUILD_REGION_CONF(reg, _ATTR) \ |
| (struct nxp_mpu_region) { .name = (reg).dt_name, \ |
| .base = (reg).dt_addr, \ |
| .end = (reg).dt_addr + (reg).dt_size, \ |
| .attr = _ATTR, \ |
| } |
| |
| /* This internal function programs the MPU regions defined in the DT when using |
| * the `zephyr,memory-attr = <( DT_MEM_ARM(...) )>` property. |
| */ |
| static int mpu_configure_regions_from_dt(uint8_t *reg_index) |
| { |
| const struct mem_attr_region_t *region; |
| size_t num_regions; |
| |
| num_regions = mem_attr_get_regions(®ion); |
| |
| for (size_t idx = 0; idx < num_regions; idx++) { |
| struct nxp_mpu_region region_conf; |
| |
| switch (DT_MEM_ARM_GET(region[idx].dt_attr)) { |
| case DT_MEM_ARM_MPU_RAM: |
| region_conf = _BUILD_REGION_CONF(region[idx], REGION_RAM_ATTR); |
| break; |
| #ifdef REGION_FLASH_ATTR |
| case DT_MEM_ARM_MPU_FLASH: |
| region_conf = _BUILD_REGION_CONF(region[idx], REGION_FLASH_ATTR); |
| break; |
| #endif |
| #ifdef REGION_IO_ATTR |
| case DT_MEM_ARM_MPU_IO: |
| region_conf = _BUILD_REGION_CONF(region[idx], REGION_IO_ATTR); |
| break; |
| #endif |
| default: |
| /* Either the specified `ATTR_MPU_*` attribute does not |
| * exists or the `REGION_*_ATTR` macro is not defined |
| * for that attribute. |
| */ |
| LOG_ERR("Invalid attribute for the region\n"); |
| return -EINVAL; |
| } |
| |
| if (region_allocate_and_init((*reg_index), |
| (const struct nxp_mpu_region *) ®ion_conf) < 0) { |
| return -EINVAL; |
| } |
| |
| (*reg_index)++; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * This internal function is utilized by the MPU driver to combine a given |
| * region attribute configuration and size and fill-in a driver-specific |
| * structure with the correct MPU region attribute configuration. |
| */ |
| static inline void get_region_attr_from_mpu_partition_info( |
| nxp_mpu_region_attr_t *p_attr, |
| const k_mem_partition_attr_t *attr, uint32_t base, uint32_t size) |
| { |
| /* in NXP MPU the base address and size are not required |
| * to determine region attributes |
| */ |
| (void) base; |
| (void) size; |
| |
| p_attr->attr = attr->ap_attr; |
| } |
| |
| /* This internal function programs an MPU region |
| * of a given configuration at a given MPU index. |
| */ |
| static int mpu_configure_region(const uint8_t index, |
| const struct z_arm_mpu_partition *new_region) |
| { |
| struct nxp_mpu_region region_conf; |
| |
| LOG_DBG("Configure MPU region at index 0x%x", index); |
| |
| /* Populate internal NXP MPU region configuration structure. */ |
| region_conf.base = new_region->start; |
| region_conf.end = (new_region->start + new_region->size - 1); |
| get_region_attr_from_mpu_partition_info(®ion_conf.attr, |
| &new_region->attr, new_region->start, new_region->size); |
| |
| /* Allocate and program region */ |
| return region_allocate_and_init(index, |
| (const struct nxp_mpu_region *)®ion_conf); |
| } |
| |
| #if defined(CONFIG_MPU_STACK_GUARD) |
| /* This internal function partitions the SRAM MPU region */ |
| static int mpu_sram_partitioning(uint8_t index, |
| const struct z_arm_mpu_partition *p_region) |
| { |
| /* |
| * The NXP MPU manages the permissions of the overlapping regions |
| * doing the logical OR in between them, hence they can't be used |
| * for stack/stack guard protection. For this reason we need to |
| * perform a partitioning of the SRAM area in such a way that the |
| * guard region does not overlap with the (background) SRAM regions |
| * holding the default SRAM access permission configuration. |
| * In other words, the SRAM is split in two different regions. |
| */ |
| |
| /* |
| * SRAM partitioning needs to be performed in a strict order. |
| * First, we program a new MPU region with the default SRAM |
| * access permissions for the SRAM area _after_ the stack |
| * guard. Note that the permissions are stored in the global |
| * array: |
| * 'mpu_config.mpu_regions[]', on 'sram_region' index. |
| */ |
| struct nxp_mpu_region added_sram_region; |
| |
| added_sram_region.base = p_region->start + p_region->size; |
| added_sram_region.end = |
| mpu_config.mpu_regions[mpu_config.sram_region].end; |
| added_sram_region.attr.attr = |
| mpu_config.mpu_regions[mpu_config.sram_region].attr.attr; |
| |
| if (region_allocate_and_init(index, |
| (const struct nxp_mpu_region *)&added_sram_region) < 0) { |
| return -EINVAL; |
| } |
| |
| /* Increment, as an additional region index has been consumed. */ |
| index++; |
| |
| /* Second, adjust the original SRAM region to end at the beginning |
| * of the stack guard. |
| */ |
| struct nxp_mpu_region adjusted_sram_region; |
| |
| adjusted_sram_region.base = |
| mpu_config.mpu_regions[mpu_config.sram_region].base; |
| adjusted_sram_region.end = p_region->start - 1; |
| adjusted_sram_region.attr.attr = |
| mpu_config.mpu_regions[mpu_config.sram_region].attr.attr; |
| |
| region_init(mpu_config.sram_region, |
| (const struct nxp_mpu_region *)&adjusted_sram_region); |
| |
| return index; |
| } |
| #endif /* CONFIG_MPU_STACK_GUARD */ |
| |
| /* This internal function programs a set of given MPU regions |
| * over a background memory area, optionally performing a |
| * sanity check of the memory regions to be programmed. |
| */ |
| static int mpu_configure_regions(const struct z_arm_mpu_partition regions[], |
| uint8_t regions_num, uint8_t start_reg_index, |
| bool do_sanity_check) |
| { |
| int i; |
| int reg_index = start_reg_index; |
| |
| for (i = 0; i < regions_num; i++) { |
| if (regions[i].size == 0U) { |
| continue; |
| } |
| /* Non-empty region. */ |
| |
| if (do_sanity_check && |
| (!mpu_partition_is_valid(®ions[i]))) { |
| LOG_ERR("Partition %u: sanity check failed.", i); |
| return -EINVAL; |
| } |
| |
| #if defined(CONFIG_MPU_STACK_GUARD) |
| if (regions[i].attr.ap_attr == MPU_REGION_SU_RX) { |
| unsigned int key; |
| |
| /* Attempt to configure an MPU Stack Guard region; this |
| * will require splitting of the underlying SRAM region |
| * into two SRAM regions, leaving out the guard area to |
| * be programmed afterwards. |
| */ |
| key = irq_lock(); |
| reg_index = |
| mpu_sram_partitioning(reg_index, ®ions[i]); |
| irq_unlock(key); |
| } |
| #endif /* CONFIG_MPU_STACK_GUARD */ |
| |
| if (reg_index == -EINVAL) { |
| return reg_index; |
| } |
| |
| reg_index = mpu_configure_region(reg_index, ®ions[i]); |
| |
| if (reg_index == -EINVAL) { |
| return reg_index; |
| } |
| |
| /* Increment number of programmed MPU indices. */ |
| reg_index++; |
| } |
| |
| return reg_index; |
| } |
| |
| /* This internal function programs the static MPU regions. |
| * |
| * It returns the number of MPU region indices configured. |
| * |
| * Note: |
| * If the static MPU regions configuration has not been successfully |
| * performed, the error signal is propagated to the caller of the function. |
| */ |
| static int mpu_configure_static_mpu_regions( |
| const struct z_arm_mpu_partition static_regions[], |
| const uint8_t regions_num, const uint32_t background_area_base, |
| const uint32_t background_area_end) |
| { |
| int mpu_reg_index = static_regions_num; |
| |
| /* In NXP MPU architecture the static regions are |
| * programmed on top of SRAM region configuration. |
| */ |
| ARG_UNUSED(background_area_base); |
| ARG_UNUSED(background_area_end); |
| |
| mpu_reg_index = mpu_configure_regions(static_regions, |
| regions_num, mpu_reg_index, true); |
| |
| static_regions_num = mpu_reg_index; |
| |
| return mpu_reg_index; |
| } |
| |
| /* This internal function programs the dynamic MPU regions. |
| * |
| * It returns the number of MPU region indices configured. |
| * |
| * Note: |
| * If the dynamic MPU regions configuration has not been successfully |
| * performed, the error signal is propagated to the caller of the function. |
| */ |
| static int mpu_configure_dynamic_mpu_regions( |
| const struct z_arm_mpu_partition dynamic_regions[], |
| uint8_t regions_num) |
| { |
| unsigned int key; |
| |
| /* |
| * Programming the NXP MPU has to be done with care to avoid race |
| * conditions that will cause memory faults. The NXP MPU is composed |
| * of a number of memory region descriptors. The number of descriptors |
| * varies depending on the SOC. Each descriptor has a start addr, end |
| * addr, attribute, and valid. When the MPU is enabled, access to |
| * memory space is checked for access protection errors through an |
| * OR operation of all of the valid MPU descriptors. |
| * |
| * Writing the start/end/attribute descriptor register will clear the |
| * valid bit for that descriptor. This presents a problem because if |
| * the current program stack is in that region or if an ISR occurs |
| * that switches state and uses that region a memory fault will be |
| * triggered. Note that local variable access can also cause stack |
| * accesses while programming these registers depending on the compiler |
| * optimization level. |
| * |
| * To avoid the race condition a temporary descriptor is set to enable |
| * access to all of memory before the call to mpu_configure_regions() |
| * to configure the dynamic memory regions. After, the temporary |
| * descriptor is invalidated if the mpu_configure_regions() didn't |
| * overwrite it. |
| */ |
| key = irq_lock(); |
| /* Use last descriptor region as temporary descriptor */ |
| region_init(get_num_regions()-1, (const struct nxp_mpu_region *) |
| &mpu_config.mpu_regions[mpu_config.sram_region]); |
| |
| /* Now reset the main SRAM region */ |
| region_init(mpu_config.sram_region, (const struct nxp_mpu_region *) |
| &mpu_config.mpu_regions[mpu_config.sram_region]); |
| irq_unlock(key); |
| |
| int mpu_reg_index = static_regions_num; |
| |
| /* In NXP MPU architecture the dynamic regions are |
| * programmed on top of existing SRAM region configuration. |
| */ |
| |
| mpu_reg_index = mpu_configure_regions(dynamic_regions, |
| regions_num, mpu_reg_index, false); |
| |
| if (mpu_reg_index != -EINVAL) { |
| |
| /* Disable the non-programmed MPU regions. */ |
| for (int i = mpu_reg_index; i < get_num_regions(); i++) { |
| |
| LOG_DBG("disable region 0x%x", i); |
| /* Disable region */ |
| SYSMPU->WORD[i][0] = 0; |
| SYSMPU->WORD[i][1] = 0; |
| SYSMPU->WORD[i][2] = 0; |
| SYSMPU->WORD[i][3] = 0; |
| } |
| } |
| |
| return mpu_reg_index; |
| } |
| |
| /* ARM Core MPU Driver API Implementation for NXP MPU */ |
| |
| /** |
| * @brief enable the MPU |
| */ |
| void arm_core_mpu_enable(void) |
| { |
| /* Enable MPU */ |
| SYSMPU->CESR |= SYSMPU_CESR_VLD_MASK; |
| |
| /* Make sure that all the registers are set before proceeding */ |
| barrier_dsync_fence_full(); |
| barrier_isync_fence_full(); |
| } |
| |
| /** |
| * @brief disable the MPU |
| */ |
| void arm_core_mpu_disable(void) |
| { |
| /* Force any outstanding transfers to complete before disabling MPU */ |
| barrier_dmem_fence_full(); |
| |
| /* Disable MPU */ |
| SYSMPU->CESR &= ~SYSMPU_CESR_VLD_MASK; |
| /* Clear MPU error status */ |
| SYSMPU->CESR |= SYSMPU_CESR_SPERR_MASK; |
| } |
| |
| #if defined(CONFIG_USERSPACE) |
| |
| static inline uint32_t mpu_region_get_base(uint32_t r_index) |
| { |
| return SYSMPU->WORD[r_index][0]; |
| } |
| |
| static inline uint32_t mpu_region_get_size(uint32_t r_index) |
| { |
| /* <END> + 1 - <BASE> */ |
| return (SYSMPU->WORD[r_index][1] + 1) - SYSMPU->WORD[r_index][0]; |
| } |
| |
| /** |
| * This internal function checks if region is enabled or not. |
| * |
| * Note: |
| * The caller must provide a valid region number. |
| */ |
| static inline int is_enabled_region(uint32_t r_index) |
| { |
| return SYSMPU->WORD[r_index][3] & SYSMPU_WORD_VLD_MASK; |
| } |
| |
| /** |
| * This internal function checks if the given buffer is in the region. |
| * |
| * Note: |
| * The caller must provide a valid region number. |
| */ |
| static inline int is_in_region(uint32_t r_index, uint32_t start, uint32_t size) |
| { |
| uint32_t r_addr_start; |
| uint32_t r_addr_end; |
| uint32_t end; |
| |
| r_addr_start = SYSMPU->WORD[r_index][0]; |
| r_addr_end = SYSMPU->WORD[r_index][1]; |
| |
| size = size == 0U ? 0U : size - 1U; |
| if (u32_add_overflow(start, size, &end)) { |
| return 0; |
| } |
| |
| if ((start >= r_addr_start) && (end <= r_addr_end)) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief update configuration of an active memory partition |
| */ |
| void arm_core_mpu_mem_partition_config_update( |
| struct z_arm_mpu_partition *partition, |
| k_mem_partition_attr_t *new_attr) |
| { |
| /* Find the partition. ASSERT if not found. */ |
| uint8_t i; |
| uint8_t reg_index = get_num_regions(); |
| |
| for (i = static_regions_num; i < get_num_regions(); i++) { |
| if (!is_enabled_region(i)) { |
| continue; |
| } |
| |
| uint32_t base = mpu_region_get_base(i); |
| |
| if (base != partition->start) { |
| continue; |
| } |
| |
| uint32_t size = mpu_region_get_size(i); |
| |
| if (size != partition->size) { |
| continue; |
| } |
| |
| /* Region found */ |
| reg_index = i; |
| break; |
| } |
| __ASSERT(reg_index != get_num_regions(), |
| "Memory domain partition not found\n"); |
| |
| /* Modify the permissions */ |
| partition->attr = *new_attr; |
| mpu_configure_region(reg_index, partition); |
| } |
| |
| /** |
| * @brief get the maximum number of available (free) MPU region indices |
| * for configuring dynamic MPU partitions |
| */ |
| int arm_core_mpu_get_max_available_dyn_regions(void) |
| { |
| return get_num_regions() - static_regions_num; |
| } |
| |
| /** |
| * This internal function checks if the region is user accessible or not |
| * |
| * Note: |
| * The caller must provide a valid region number. |
| */ |
| static inline int is_user_accessible_region(uint32_t r_index, int write) |
| { |
| uint32_t r_ap = SYSMPU->WORD[r_index][2]; |
| |
| if (write != 0) { |
| return (r_ap & MPU_REGION_WRITE) == MPU_REGION_WRITE; |
| } |
| |
| return (r_ap & MPU_REGION_READ) == MPU_REGION_READ; |
| } |
| |
| /** |
| * @brief validate the given buffer is user accessible or not |
| */ |
| int arm_core_mpu_buffer_validate(void *addr, size_t size, int write) |
| { |
| uint8_t r_index; |
| |
| /* Iterate through all MPU regions */ |
| for (r_index = 0U; r_index < get_num_regions(); r_index++) { |
| if (!is_enabled_region(r_index) || |
| !is_in_region(r_index, (uint32_t)addr, size)) { |
| continue; |
| } |
| |
| /* For NXP MPU, priority is given to granting permission over |
| * denying access for overlapping region. |
| * So we can stop the iteration immediately once we find the |
| * matched region that grants permission. |
| */ |
| if (is_user_accessible_region(r_index, write)) { |
| return 0; |
| } |
| } |
| |
| return -EPERM; |
| } |
| |
| #endif /* CONFIG_USERSPACE */ |
| |
| /** |
| * @brief configure fixed (static) MPU regions. |
| */ |
| void arm_core_mpu_configure_static_mpu_regions( |
| const struct z_arm_mpu_partition static_regions[], |
| const uint8_t regions_num, const uint32_t background_area_start, |
| const uint32_t background_area_end) |
| { |
| if (mpu_configure_static_mpu_regions(static_regions, regions_num, |
| background_area_start, |
| background_area_end) == -EINVAL) { |
| |
| __ASSERT(0, "Configuring %u static MPU regions failed\n", |
| regions_num); |
| } |
| } |
| |
| /** |
| * @brief configure dynamic MPU regions. |
| */ |
| void arm_core_mpu_configure_dynamic_mpu_regions( |
| const struct z_arm_mpu_partition dynamic_regions[], uint8_t regions_num) |
| { |
| if (mpu_configure_dynamic_mpu_regions(dynamic_regions, |
| regions_num) == -EINVAL) { |
| |
| __ASSERT(0, "Configuring %u dynamic MPU regions failed\n", |
| regions_num); |
| } |
| } |
| |
| /* NXP MPU Driver Initial Setup */ |
| |
| /* |
| * @brief MPU default configuration |
| * |
| * This function provides the default configuration mechanism for the Memory |
| * Protection Unit (MPU). |
| */ |
| int z_arm_mpu_init(void) |
| { |
| uint32_t r_index; |
| |
| if (mpu_config.num_regions > get_num_regions()) { |
| /* Attempt to configure more MPU regions than |
| * what is supported by hardware. As this operation |
| * may be executed during system (pre-kernel) initialization, |
| * we want to ensure we can detect an attempt to |
| * perform invalid configuration. |
| */ |
| __ASSERT(0, |
| "Request to configure: %u regions (supported: %u)\n", |
| mpu_config.num_regions, |
| get_num_regions() |
| ); |
| return -1; |
| } |
| |
| LOG_DBG("total region count: %d", get_num_regions()); |
| |
| arm_core_mpu_disable(); |
| |
| /* Architecture-specific configuration */ |
| mpu_init(); |
| |
| /* Program fixed regions configured at SOC definition. */ |
| for (r_index = 0U; r_index < mpu_config.num_regions; r_index++) { |
| region_init(r_index, &mpu_config.mpu_regions[r_index]); |
| } |
| |
| /* Update the number of programmed MPU regions. */ |
| static_regions_num = mpu_config.num_regions; |
| |
| /* DT-defined MPU regions. */ |
| if (mpu_configure_regions_from_dt(&static_regions_num) == -EINVAL) { |
| __ASSERT(0, "Failed to allocate MPU regions from DT\n"); |
| return -EINVAL; |
| } |
| |
| arm_core_mpu_enable(); |
| |
| return 0; |
| } |