/* * Copyright (c) 2019 Synopsys. * * SPDX-License-Identifier: Apache-2.0 */ #ifndef ZEPHYR_ARCH_ARC_CORE_MPU_ARC_MPU_V4_INTERNAL_H_ #define ZEPHYR_ARCH_ARC_CORE_MPU_ARC_MPU_V4_INTERNAL_H_ #define AUX_MPU_RPER_SID1 0x10000 /* valid mask: SID1+secure+valid */ #define AUX_MPU_RPER_VALID_MASK ((0x1) | AUX_MPU_RPER_SID1 | AUX_MPU_ATTR_S) #define AUX_MPU_RPER_ATTR_MASK (0x1FF) /* For MPU version 4, the minimum protection region size is 32 bytes */ #define ARC_FEATURE_MPU_ALIGNMENT_BITS 5 #define CALC_REGION_END_ADDR(start, size) \ (start + size - (1 << ARC_FEATURE_MPU_ALIGNMENT_BITS)) /* ARC MPU version 4 does not support mpu region overlap in hardware * so if we want to allocate MPU region dynamically, e.g. thread stack, * memory domain from a background region, a dynamic region splitting * approach is designed. pls see comments in * _dynamic_region_allocate_and_init * But this approach has an impact on performance of thread switch. * As a trade off, we can use the default mpu region as the background region * to avoid the dynamic region splitting. This will give more privilege to * codes in kernel mode which can access the memory region not covered by * explicit mpu entry. Considering memory protection is mainly used to * isolate malicious codes in user mode, it makes sense to get better * thread switch performance through default mpu region. * CONFIG_MPU_GAP_FILLING is used to turn this on/off. * */ #if defined(CONFIG_MPU_GAP_FILLING) #if defined(CONFIG_USERSPACE) && defined(CONFIG_MPU_STACK_GUARD) /* 1 for stack guard , 1 for user thread, 1 for split */ #define MPU_REGION_NUM_FOR_THREAD 3 #elif defined(CONFIG_USERSPACE) || defined(CONFIG_MPU_STACK_GUARD) /* 1 for stack guard or user thread stack , 1 for split */ #define MPU_REGION_NUM_FOR_THREAD 2 #else #define MPU_REGION_NUM_FOR_THREAD 0 #endif #define MPU_DYNAMIC_REGION_AREAS_NUM 2 /** * @brief internal structure holding information of * memory areas where dynamic MPU programming is allowed. */ struct dynamic_region_info { uint8_t index; uint32_t base; uint32_t size; uint32_t attr; }; static uint8_t dynamic_regions_num; static uint8_t dynamic_region_index; /** * Global array, holding the MPU region index of * the memory region inside which dynamic memory * regions may be configured. */ static struct dynamic_region_info dyn_reg_info[MPU_DYNAMIC_REGION_AREAS_NUM]; #endif /* CONFIG_MPU_GAP_FILLING */ static uint8_t static_regions_num; #ifdef CONFIG_ARC_NORMAL_FIRMWARE /* \todo through secure service to access mpu */ static inline void _region_init(uint32_t index, uint32_t region_addr, uint32_t size, uint32_t region_attr) { } static inline void _region_set_attr(uint32_t index, uint32_t attr) { } static inline uint32_t _region_get_attr(uint32_t index) { return 0; } static inline uint32_t _region_get_start(uint32_t index) { return 0; } static inline void _region_set_start(uint32_t index, uint32_t start) { } static inline uint32_t _region_get_end(uint32_t index) { return 0; } static inline void _region_set_end(uint32_t index, uint32_t end) { } /** * This internal function probes the given addr's MPU index.if not * in MPU, returns error */ static inline int _mpu_probe(uint32_t addr) { return -EINVAL; } /** * This internal function checks if MPU region is enabled or not */ static inline bool _is_enabled_region(uint32_t r_index) { return false; } /** * This internal function check if the region is user accessible or not */ static inline bool _is_user_accessible_region(uint32_t r_index, int write) { return false; } #else /* CONFIG_ARC_NORMAL_FIRMWARE */ /* the following functions are prepared for SECURE_FIRMWARE */ static inline void _region_init(uint32_t index, uint32_t region_addr, uint32_t size, uint32_t region_attr) { if (size < (1 << ARC_FEATURE_MPU_ALIGNMENT_BITS)) { size = (1 << ARC_FEATURE_MPU_ALIGNMENT_BITS); } if (region_attr) { region_attr &= AUX_MPU_RPER_ATTR_MASK; region_attr |= AUX_MPU_RPER_VALID_MASK; } z_arc_v2_aux_reg_write(_ARC_V2_MPU_INDEX, index); z_arc_v2_aux_reg_write(_ARC_V2_MPU_RSTART, region_addr); z_arc_v2_aux_reg_write(_ARC_V2_MPU_REND, CALC_REGION_END_ADDR(region_addr, size)); z_arc_v2_aux_reg_write(_ARC_V2_MPU_RPER, region_attr); } static inline void _region_set_attr(uint32_t index, uint32_t attr) { z_arc_v2_aux_reg_write(_ARC_V2_MPU_INDEX, index); z_arc_v2_aux_reg_write(_ARC_V2_MPU_RPER, attr | AUX_MPU_RPER_VALID_MASK); } static inline uint32_t _region_get_attr(uint32_t index) { z_arc_v2_aux_reg_write(_ARC_V2_MPU_INDEX, index); return z_arc_v2_aux_reg_read(_ARC_V2_MPU_RPER); } static inline uint32_t _region_get_start(uint32_t index) { z_arc_v2_aux_reg_write(_ARC_V2_MPU_INDEX, index); return z_arc_v2_aux_reg_read(_ARC_V2_MPU_RSTART); } static inline void _region_set_start(uint32_t index, uint32_t start) { z_arc_v2_aux_reg_write(_ARC_V2_MPU_INDEX, index); z_arc_v2_aux_reg_write(_ARC_V2_MPU_RSTART, start); } static inline uint32_t _region_get_end(uint32_t index) { z_arc_v2_aux_reg_write(_ARC_V2_MPU_INDEX, index); return z_arc_v2_aux_reg_read(_ARC_V2_MPU_REND) + (1 << ARC_FEATURE_MPU_ALIGNMENT_BITS); } static inline void _region_set_end(uint32_t index, uint32_t end) { z_arc_v2_aux_reg_write(_ARC_V2_MPU_INDEX, index); z_arc_v2_aux_reg_write(_ARC_V2_MPU_REND, end - (1 << ARC_FEATURE_MPU_ALIGNMENT_BITS)); } /** * This internal function probes the given addr's MPU index.if not * in MPU, returns error */ static inline int _mpu_probe(uint32_t addr) { uint32_t val; z_arc_v2_aux_reg_write(_ARC_V2_MPU_PROBE, addr); val = z_arc_v2_aux_reg_read(_ARC_V2_MPU_INDEX); /* if no match or multiple regions match, return error */ if (val & 0xC0000000) { return -EINVAL; } else { return val; } } /** * This internal function checks if MPU region is enabled or not */ static inline bool _is_enabled_region(uint32_t r_index) { z_arc_v2_aux_reg_write(_ARC_V2_MPU_INDEX, r_index); return ((z_arc_v2_aux_reg_read(_ARC_V2_MPU_RPER) & AUX_MPU_RPER_VALID_MASK) == AUX_MPU_RPER_VALID_MASK); } /** * This internal function check if the region is user accessible or not */ static inline bool _is_user_accessible_region(uint32_t r_index, int write) { uint32_t r_ap; z_arc_v2_aux_reg_write(_ARC_V2_MPU_INDEX, r_index); r_ap = z_arc_v2_aux_reg_read(_ARC_V2_MPU_RPER); r_ap &= AUX_MPU_RPER_ATTR_MASK; if (write) { return ((r_ap & (AUX_MPU_ATTR_UW | AUX_MPU_ATTR_KW)) == (AUX_MPU_ATTR_UW | AUX_MPU_ATTR_KW)); } return ((r_ap & (AUX_MPU_ATTR_UR | AUX_MPU_ATTR_KR)) == (AUX_MPU_ATTR_UR | AUX_MPU_ATTR_KR)); } #endif /* CONFIG_ARC_NORMAL_FIRMWARE */ /** * This internal function checks the area given by (start, size) * and returns the index if the area match one MPU entry */ static inline int _get_region_index(uint32_t start, uint32_t size) { int index = _mpu_probe(start); if (index > 0 && index == _mpu_probe(start + size - 1)) { return index; } return -EINVAL; } #if defined(CONFIG_MPU_GAP_FILLING) /** * This internal function allocates a dynamic MPU region and returns * the index or error */ static inline int _dynamic_region_allocate_index(void) { if (dynamic_region_index >= get_num_regions()) { LOG_ERR("no enough mpu entries %d", dynamic_region_index); return -EINVAL; } return dynamic_region_index++; } /* @brief allocate and init a dynamic MPU region * * This internal function performs the allocation and initialization of * a dynamic MPU region * * @param base region base * @param size region size * @param attr region attribute * @return <0 failure, >0 allocated dynamic region index */ static int _dynamic_region_allocate_and_init(uint32_t base, uint32_t size, uint32_t attr) { int u_region_index = _get_region_index(base, size); int region_index; LOG_DBG("Region info: base 0x%x size 0x%x attr 0x%x", base, size, attr); if (u_region_index == -EINVAL) { /* no underlying region */ region_index = _dynamic_region_allocate_index(); if (region_index > 0) { /* a new region */ _region_init(region_index, base, size, attr); } return region_index; } /* * The new memory region is to be placed inside the underlying * region, possibly splitting the underlying region into two. */ uint32_t u_region_start = _region_get_start(u_region_index); uint32_t u_region_end = _region_get_end(u_region_index); uint32_t u_region_attr = _region_get_attr(u_region_index); uint32_t end = base + size; if ((base == u_region_start) && (end == u_region_end)) { /* The new region overlaps entirely with the * underlying region. In this case we simply * update the partition attributes of the * underlying region with those of the new * region. */ _region_init(u_region_index, base, size, attr); region_index = u_region_index; } else if (base == u_region_start) { /* The new region starts exactly at the start of the * underlying region; the start of the underlying * region needs to be set to the end of the new region. */ _region_set_start(u_region_index, base + size); _region_set_attr(u_region_index, u_region_attr); region_index = _dynamic_region_allocate_index(); if (region_index > 0) { _region_init(region_index, base, size, attr); } } else if (end == u_region_end) { /* The new region ends exactly at the end of the * underlying region; the end of the underlying * region needs to be set to the start of the * new region. */ _region_set_end(u_region_index, base); _region_set_attr(u_region_index, u_region_attr); region_index = _dynamic_region_allocate_index(); if (region_index > 0) { _region_init(region_index, base, size, attr); } } else { /* The new region lies strictly inside the * underlying region, which needs to split * into two regions. */ _region_set_end(u_region_index, base); _region_set_attr(u_region_index, u_region_attr); region_index = _dynamic_region_allocate_index(); if (region_index > 0) { _region_init(region_index, base, size, attr); region_index = _dynamic_region_allocate_index(); if (region_index > 0) { _region_init(region_index, base + size, u_region_end - end, u_region_attr); } } } return region_index; } /* @brief reset the dynamic MPU regions * * This internal function performs the reset of dynamic MPU regions */ static void _mpu_reset_dynamic_regions(void) { uint32_t i; uint32_t num_regions = get_num_regions(); for (i = static_regions_num; i < num_regions; i++) { _region_init(i, 0, 0, 0); } for (i = 0U; i < dynamic_regions_num; i++) { _region_init( dyn_reg_info[i].index, dyn_reg_info[i].base, dyn_reg_info[i].size, dyn_reg_info[i].attr); } /* dynamic regions are after static regions */ dynamic_region_index = static_regions_num; } /** * @brief configure the base address and size for an MPU region * * @param type MPU region type * @param base base address in RAM * @param size size of the region */ static inline int _mpu_configure(uint8_t type, uint32_t base, uint32_t size) { uint32_t region_attr = get_region_attr_by_type(type); return _dynamic_region_allocate_and_init(base, size, region_attr); } #else /** * 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 int get_region_index_by_type(uint32_t type) { /* * The new MPU regions are allocated per type after the statically * configured regions. The type is one-indexed rather than * zero-indexed. * * For ARC MPU v2, the smaller index has higher priority, so the * index is allocated in reverse order. Static regions start from * the biggest index, then thread related regions. * */ switch (type) { case THREAD_STACK_USER_REGION: return static_regions_num + THREAD_STACK_REGION; case THREAD_STACK_REGION: case THREAD_APP_DATA_REGION: case THREAD_STACK_GUARD_REGION: return static_regions_num + type; case THREAD_DOMAIN_PARTITION_REGION: #if defined(CONFIG_MPU_STACK_GUARD) return static_regions_num + type; #else /* * Start domain partition region from stack guard region * since stack guard is not enabled. */ return static_regions_num + type - 1; #endif default: __ASSERT(0, "Unsupported type"); return -EINVAL; } } /** * @brief configure the base address and size for an MPU region * * @param type MPU region type * @param base base address in RAM * @param size size of the region */ static inline int _mpu_configure(uint8_t type, uint32_t base, uint32_t size) { int region_index = get_region_index_by_type(type); uint32_t region_attr = get_region_attr_by_type(type); LOG_DBG("Region info: 0x%x 0x%x", base, size); if (region_attr == 0U || region_index < 0) { return -EINVAL; } _region_init(region_index, base, size, region_attr); return 0; } #endif /* ARC Core MPU Driver API Implementation for ARC MPUv3 */ /** * @brief enable the MPU */ void arc_core_mpu_enable(void) { #ifdef CONFIG_ARC_SECURE_FIRMWARE /* the default region: * secure:0x8000, SID:0x10000, KW:0x100 KR:0x80 */ #define MPU_ENABLE_ATTR 0x18180 #else #define MPU_ENABLE_ATTR 0 #endif arc_core_mpu_default(MPU_ENABLE_ATTR); } /** * @brief disable the MPU */ void arc_core_mpu_disable(void) { /* MPU is always enabled, use default region to * simulate MPU disable */ arc_core_mpu_default(REGION_ALL_ATTR | AUX_MPU_ATTR_S | AUX_MPU_RPER_SID1); } /** * @brief configure the thread's mpu regions * * @param thread the target thread */ void arc_core_mpu_configure_thread(struct k_thread *thread) { #if defined(CONFIG_MPU_GAP_FILLING) /* the mpu entries of ARC MPUv4 are divided into 2 parts: * static entries: global mpu entries, not changed in context switch * dynamic entries: MPU entries changed in context switch and * memory domain configure, including: * MPU entries for user thread stack * MPU entries for stack guard * MPU entries for mem domain * MPU entries for other thread specific regions * before configuring thread specific mpu entries, need to reset dynamic * entries */ _mpu_reset_dynamic_regions(); #endif #if defined(CONFIG_MPU_STACK_GUARD) uint32_t guard_start; /* Set location of guard area when the thread is running in * supervisor mode. For a supervisor thread, this is just low * memory in the stack buffer. For a user thread, it only runs * in supervisor mode when handling a system call on the privilege * elevation stack. */ #if defined(CONFIG_USERSPACE) if ((thread->base.user_options & K_USER) != 0U) { guard_start = thread->arch.priv_stack_start; } else #endif { guard_start = thread->stack_info.start; } guard_start -= Z_ARC_STACK_GUARD_SIZE; if (_mpu_configure(THREAD_STACK_GUARD_REGION, guard_start, Z_ARC_STACK_GUARD_SIZE) < 0) { LOG_ERR("thread %p's stack guard failed", thread); return; } #endif /* CONFIG_MPU_STACK_GUARD */ #if defined(CONFIG_USERSPACE) /* configure stack region of user thread */ if (thread->base.user_options & K_USER) { LOG_DBG("configure user thread %p's stack", thread); if (_mpu_configure(THREAD_STACK_USER_REGION, (uint32_t)thread->stack_info.start, thread->stack_info.size) < 0) { LOG_ERR("thread %p's stack failed", thread); return; } } #if defined(CONFIG_MPU_GAP_FILLING) uint32_t num_partitions; struct k_mem_partition *pparts; struct k_mem_domain *mem_domain = thread->mem_domain_info.mem_domain; /* configure thread's memory domain */ if (mem_domain) { LOG_DBG("configure thread %p's domain: %p", thread, mem_domain); num_partitions = mem_domain->num_partitions; pparts = mem_domain->partitions; } else { num_partitions = 0U; pparts = NULL; } for (uint32_t i = 0; i < num_partitions; i++) { if (pparts->size) { if (_dynamic_region_allocate_and_init(pparts->start, pparts->size, pparts->attr) < 0) { LOG_ERR( "thread %p's mem region: %p failed", thread, pparts); return; } } pparts++; } #else arc_core_mpu_configure_mem_domain(thread); #endif #endif } /** * @brief configure the default region * * @param region_attr region attribute of default region */ void arc_core_mpu_default(uint32_t region_attr) { #ifdef CONFIG_ARC_NORMAL_FIRMWARE /* \todo through secure service to access mpu */ #else z_arc_v2_aux_reg_write(_ARC_V2_MPU_EN, region_attr); #endif } /** * @brief configure the MPU region * * @param index MPU region index * @param base base address * @param size region size * @param region_attr region attribute */ int arc_core_mpu_region(uint32_t index, uint32_t base, uint32_t size, uint32_t region_attr) { if (index >= get_num_regions()) { return -EINVAL; } region_attr &= AUX_MPU_RPER_ATTR_MASK; _region_init(index, base, size, region_attr); return 0; } #if defined(CONFIG_USERSPACE) /** * @brief configure MPU regions for the memory partitions of the memory domain * * @param thread the thread which has memory domain */ #if defined(CONFIG_MPU_GAP_FILLING) void arc_core_mpu_configure_mem_domain(struct k_thread *thread) { arc_core_mpu_configure_thread(thread); } #else void arc_core_mpu_configure_mem_domain(struct k_thread *thread) { uint32_t region_index; uint32_t num_partitions; uint32_t num_regions; struct k_mem_partition *pparts; struct k_mem_domain *mem_domain = NULL; if (thread) { mem_domain = thread->mem_domain_info.mem_domain; } if (mem_domain) { LOG_DBG("configure domain: %p", mem_domain); num_partitions = mem_domain->num_partitions; pparts = mem_domain->partitions; } else { LOG_DBG("disable domain partition regions"); num_partitions = 0U; pparts = NULL; } num_regions = get_num_regions(); region_index = get_region_index_by_type(THREAD_DOMAIN_PARTITION_REGION); while (num_partitions && region_index < num_regions) { if (pparts->size > 0) { LOG_DBG("set region 0x%x 0x%lx 0x%x", region_index, pparts->start, pparts->size); _region_init(region_index, pparts->start, pparts->size, pparts->attr); region_index++; } pparts++; num_partitions--; } while (region_index < num_regions) { /* clear the left mpu entries */ _region_init(region_index, 0, 0, 0); region_index++; } } #endif /** * @brief remove MPU regions for the memory partitions of the memory domain * * @param mem_domain the target memory domain */ void arc_core_mpu_remove_mem_domain(struct k_mem_domain *mem_domain) { uint32_t num_partitions; struct k_mem_partition *pparts; int index; if (mem_domain) { LOG_DBG("configure domain: %p", mem_domain); num_partitions = mem_domain->num_partitions; pparts = mem_domain->partitions; } else { LOG_DBG("disable domain partition regions"); num_partitions = 0U; pparts = NULL; } for (uint32_t i = 0; i < num_partitions; i++) { if (pparts->size) { index = _get_region_index(pparts->start, pparts->size); if (index > 0) { #if defined(CONFIG_MPU_GAP_FILLING) _region_set_attr(index, REGION_KERNEL_RAM_ATTR); #else _region_init(index, 0, 0, 0); #endif } } pparts++; } } /** * @brief reset MPU region for a single memory partition * * @param partition_id memory partition id */ void arc_core_mpu_remove_mem_partition(struct k_mem_domain *domain, uint32_t partition_id) { struct k_mem_partition *partition = &domain->partitions[partition_id]; int region_index = _get_region_index(partition->start, partition->size); if (region_index < 0) { return; } LOG_DBG("remove region 0x%x", region_index); #if defined(CONFIG_MPU_GAP_FILLING) _region_set_attr(region_index, REGION_KERNEL_RAM_ATTR); #else _region_init(region_index, 0, 0, 0); #endif } /** * @brief get the maximum number of free regions for memory domain partitions */ int arc_core_mpu_get_max_domain_partition_regions(void) { #if defined(CONFIG_MPU_GAP_FILLING) /* consider the worst case: each partition requires split */ return (get_num_regions() - MPU_REGION_NUM_FOR_THREAD) / 2; #else return get_num_regions() - get_region_index_by_type(THREAD_DOMAIN_PARTITION_REGION) - 1; #endif } /** * @brief validate the given buffer is user accessible or not */ int arc_core_mpu_buffer_validate(const void *addr, size_t size, int write) { int r_index; int key = arch_irq_lock(); /* * For ARC MPU v4, overlapping is not supported. * we can stop the iteration immediately once we find the * matched region that grants permission or denies access. */ r_index = _mpu_probe((uint32_t)addr); /* match and the area is in one region */ if (r_index >= 0 && r_index == _mpu_probe((uint32_t)addr + (size - 1))) { if (_is_user_accessible_region(r_index, write)) { r_index = 0; } else { r_index = -EPERM; } } else { r_index = -EPERM; } arch_irq_unlock(key); return r_index; } #endif /* CONFIG_USERSPACE */ /* ARC MPU Driver Initial Setup */ /* * @brief MPU default initialization and configuration * * This function provides the default configuration mechanism for the Memory * Protection Unit (MPU). */ static int arc_mpu_init(void) { uint32_t num_regions; uint32_t i; num_regions = get_num_regions(); /* ARC MPU supports up to 16 Regions */ if (mpu_config.num_regions > num_regions) { __ASSERT(0, "Request to configure: %u regions (supported: %u)\n", mpu_config.num_regions, num_regions); return -EINVAL; } static_regions_num = 0U; /* Disable MPU */ arc_core_mpu_disable(); for (i = 0U; i < mpu_config.num_regions; i++) { /* skip empty region */ if (mpu_config.mpu_regions[i].size == 0) { continue; } #if defined(CONFIG_MPU_GAP_FILLING) _region_init(static_regions_num, mpu_config.mpu_regions[i].base, mpu_config.mpu_regions[i].size, mpu_config.mpu_regions[i].attr); /* record the static region which can be split */ if (mpu_config.mpu_regions[i].attr & REGION_DYNAMIC) { if (dynamic_regions_num >= MPU_DYNAMIC_REGION_AREAS_NUM) { LOG_ERR("not enough dynamic regions %d", dynamic_regions_num); return -EINVAL; } dyn_reg_info[dynamic_regions_num].index = i; dyn_reg_info[dynamic_regions_num].base = mpu_config.mpu_regions[i].base; dyn_reg_info[dynamic_regions_num].size = mpu_config.mpu_regions[i].size; dyn_reg_info[dynamic_regions_num].attr = mpu_config.mpu_regions[i].attr; dynamic_regions_num++; } static_regions_num++; #else /* dynamic region will be covered by default mpu setting * no need to configure */ if (!(mpu_config.mpu_regions[i].attr & REGION_DYNAMIC)) { _region_init(static_regions_num, mpu_config.mpu_regions[i].base, mpu_config.mpu_regions[i].size, mpu_config.mpu_regions[i].attr); static_regions_num++; } #endif } for (i = static_regions_num; i < num_regions; i++) { _region_init(i, 0, 0, 0); } /* Enable MPU */ arc_core_mpu_enable(); return 0; } SYS_INIT(arc_mpu_init, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); #endif /* ZEPHYR_ARCH_ARC_CORE_MPU_ARC_MPU_V4_INTERNAL_H_ */