blob: 2e78677127b9a0e19f38ba71c6afa64b260b3e34 [file] [log] [blame]
/*
* Copyright (c) 2017 Linaro Limited.
* Copyright (c) 2018 Nordic Semiconductor ASA.
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_ARCH_ARM_CORE_AARCH32_MPU_ARM_MPU_V8_INTERNAL_H_
#define ZEPHYR_ARCH_ARM_CORE_AARCH32_MPU_ARM_MPU_V8_INTERNAL_H_
#include <aarch32/cortex_m/cmse.h>
#define LOG_LEVEL CONFIG_MPU_LOG_LEVEL
#include <zephyr/logging/log.h>
#include <zephyr/sys/math_extras.h>
/**
* @brief internal structure holding information of
* memory areas where dynamic MPU programming
* is allowed.
*/
struct dynamic_region_info {
int index;
struct arm_mpu_region region_conf;
};
/**
* 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];
#if defined(CONFIG_CPU_CORTEX_M23) || defined(CONFIG_CPU_CORTEX_M33) || \
defined(CONFIG_CPU_CORTEX_M55)
static inline void mpu_set_mair0(uint32_t mair0)
{
MPU->MAIR0 = mair0;
}
static inline void mpu_set_rnr(uint32_t rnr)
{
MPU->RNR = rnr;
}
static inline void mpu_set_rbar(uint32_t rbar)
{
MPU->RBAR = rbar;
}
static inline uint32_t mpu_get_rbar(void)
{
return MPU->RBAR;
}
static inline void mpu_set_rlar(uint32_t rlar)
{
MPU->RLAR = rlar;
}
static inline uint32_t mpu_get_rlar(void)
{
return MPU->RLAR;
}
static inline uint8_t mpu_get_num_regions(void)
{
uint32_t type = MPU->TYPE;
type = (type & MPU_TYPE_DREGION_Msk) >> MPU_TYPE_DREGION_Pos;
return (uint8_t)type;
}
static inline void mpu_clear_region(uint32_t rnr)
{
ARM_MPU_ClrRegion(rnr);
}
#elif defined(CONFIG_AARCH32_ARMV8_R)
static inline void mpu_set_mair0(uint32_t mair0)
{
write_mair0(mair0);
__DSB();
__ISB();
}
static inline void mpu_set_rnr(uint32_t rnr)
{
write_prselr(rnr);
__DSB();
}
static inline void mpu_set_rbar(uint32_t rbar)
{
write_prbar(rbar);
__DSB();
__ISB();
}
static inline uint32_t mpu_get_rbar(void)
{
return read_prbar();
}
static inline void mpu_set_rlar(uint32_t rlar)
{
write_prlar(rlar);
__DSB();
__ISB();
}
static inline uint32_t mpu_get_rlar(void)
{
return read_prlar();
}
static inline uint8_t mpu_get_num_regions(void)
{
uint32_t type = read_mpuir();
type = (type >> MPU_IR_REGION_Pos) & MPU_IR_REGION_Msk;
return (uint8_t)type;
}
static inline void mpu_clear_region(uint32_t rnr)
{
mpu_set_rnr(rnr);
mpu_set_rbar(0);
mpu_set_rlar(0);
}
#else
#error "Unsupported ARM CPU"
#endif
/* Global MPU configuration at system initialization. */
static void mpu_init(void)
{
/* Configure the cache-ability attributes for all the
* different types of memory regions.
*/
mpu_set_mair0(MPU_MAIR_ATTRS);
}
static void mpu_set_region(uint32_t rnr, uint32_t rbar, uint32_t rlar)
{
mpu_set_rnr(rnr);
mpu_set_rbar(rbar);
mpu_set_rlar(rlar);
}
/* 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 arm_mpu_region *region_conf)
{
mpu_set_region(
/* RNR */
index,
/* RBAR */
(region_conf->base & MPU_RBAR_BASE_Msk)
| (region_conf->attr.rbar &
(MPU_RBAR_XN_Msk | MPU_RBAR_AP_Msk | MPU_RBAR_SH_Msk)),
/* RLAR */
(region_conf->attr.r_limit & MPU_RLAR_LIMIT_Msk)
| ((region_conf->attr.mair_idx << MPU_RLAR_AttrIndx_Pos)
& MPU_RLAR_AttrIndx_Msk)
| MPU_RLAR_EN_Msk
);
LOG_DBG("[%d] 0x%08x 0x%08x 0x%08x 0x%08x",
index, region_conf->base, region_conf->attr.rbar,
region_conf->attr.mair_idx, region_conf->attr.r_limit);
}
/* @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 >= CONFIG_ARM_MPU_REGION_MIN_ALIGN_AND_SIZE)
&&
((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 returns the MPU region, in which a
* buffer, specified by its start address and size, lies. If
* a valid MPU region cannot be derived the function returns
* -EINVAL.
*
* Note that, for the function to work properly, the ARM MPU
* needs to be enabled.
*
*/
#if defined(CONFIG_AARCH32_ARMV8_R)
static inline int get_region_index(uint32_t start, uint32_t size)
{
uint32_t limit = (start + size - 1) & MPU_RLAR_LIMIT_Msk;
for (uint8_t idx = 0; idx < mpu_get_num_regions(); idx++) {
mpu_set_rnr(idx);
if (start >= (mpu_get_rbar() & MPU_RBAR_BASE_Msk) &&
limit <= (mpu_get_rlar() & MPU_RLAR_LIMIT_Msk)) {
return idx;
}
}
return -EINVAL;
}
#else
static inline int get_region_index(uint32_t start, uint32_t size)
{
uint32_t region_start_addr = arm_cmse_mpu_region_get(start);
uint32_t region_end_addr = arm_cmse_mpu_region_get(start + size - 1);
/* MPU regions are contiguous so return the region number,
* if both start and end address are in the same region.
*/
if (region_start_addr == region_end_addr) {
return region_start_addr;
}
return -EINVAL;
}
#endif
static inline uint32_t mpu_region_get_base(const uint32_t index)
{
mpu_set_rnr(index);
return mpu_get_rbar() & MPU_RBAR_BASE_Msk;
}
static inline void mpu_region_set_base(const uint32_t index, const uint32_t base)
{
mpu_set_rnr(index);
mpu_set_rbar((mpu_get_rbar() & (~MPU_RBAR_BASE_Msk))
| (base & MPU_RBAR_BASE_Msk));
}
static inline uint32_t mpu_region_get_last_addr(const uint32_t index)
{
mpu_set_rnr(index);
return (mpu_get_rlar() & MPU_RLAR_LIMIT_Msk) | (~MPU_RLAR_LIMIT_Msk);
}
static inline void mpu_region_set_limit(const uint32_t index, const uint32_t limit)
{
mpu_set_rnr(index);
mpu_set_rlar((mpu_get_rlar() & (~MPU_RLAR_LIMIT_Msk))
| (limit & MPU_RLAR_LIMIT_Msk));
}
static inline void mpu_region_get_access_attr(const uint32_t index,
arm_mpu_region_attr_t *attr)
{
mpu_set_rnr(index);
attr->rbar = mpu_get_rbar() &
(MPU_RBAR_XN_Msk | MPU_RBAR_AP_Msk | MPU_RBAR_SH_Msk);
attr->mair_idx = (mpu_get_rlar() & MPU_RLAR_AttrIndx_Msk) >>
MPU_RLAR_AttrIndx_Pos;
}
static inline void mpu_region_get_conf(const uint32_t index,
struct arm_mpu_region *region_conf)
{
mpu_set_rnr(index);
/* Region attribution:
* - Cache-ability
* - Share-ability
* - Access Permissions
*/
mpu_region_get_access_attr(index, &region_conf->attr);
/* Region base address */
region_conf->base = mpu_get_rbar() & MPU_RBAR_BASE_Msk;
/* Region limit address */
region_conf->attr.r_limit = mpu_get_rlar() & MPU_RLAR_LIMIT_Msk;
}
/**
* 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 configuration.
*/
static inline void get_region_attr_from_mpu_partition_info(
arm_mpu_region_attr_t *p_attr,
const k_mem_partition_attr_t *attr, uint32_t base, uint32_t size)
{
p_attr->rbar = attr->rbar &
(MPU_RBAR_XN_Msk | MPU_RBAR_AP_Msk | MPU_RBAR_SH_Msk);
p_attr->mair_idx = attr->mair_idx;
p_attr->r_limit = REGION_LIMIT_ADDR(base, size);
}
#if defined(CONFIG_USERSPACE)
/**
* This internal function returns the minimum HW MPU region index
* that may hold the configuration of a dynamic memory region.
*
* Browse through the memory areas marked for dynamic MPU programming,
* pick the one with the minimum MPU region index. Return that index.
*
* The function is optimized for the (most common) use-case of a single
* marked area for dynamic memory regions.
*/
static inline int get_dyn_region_min_index(void)
{
int dyn_reg_min_index = dyn_reg_info[0].index;
#if MPU_DYNAMIC_REGION_AREAS_NUM > 1
for (int i = 1; i < MPU_DYNAMIC_REGION_AREAS_NUM; i++) {
if ((dyn_reg_info[i].index != -EINVAL) &&
(dyn_reg_info[i].index < dyn_reg_min_index)
) {
dyn_reg_min_index = dyn_reg_info[i].index;
}
}
#endif
return dyn_reg_min_index;
}
static inline uint32_t mpu_region_get_size(uint32_t index)
{
return mpu_region_get_last_addr(index) + 1
- mpu_region_get_base(index);
}
/**
* 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 index)
{
mpu_set_rnr(index);
return (mpu_get_rlar() & MPU_RLAR_EN_Msk) ? 1 : 0;
}
#if defined(CONFIG_AARCH32_ARMV8_R)
/**
* 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 rnr, uint32_t start, uint32_t size)
{
uint32_t r_addr_start;
uint32_t r_addr_end;
uint32_t end;
r_addr_start = mpu_region_get_base(rnr);
r_addr_end = mpu_region_get_last_addr(rnr);
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;
}
static inline int is_user_accessible_region(uint32_t rnr, int write)
{
uint32_t r_ap;
mpu_set_rnr(rnr);
r_ap = (mpu_get_rbar() & MPU_RBAR_AP_Msk) >> MPU_RBAR_AP_Pos;
if (write != 0) {
return r_ap == P_RW_U_RW;
}
return ((r_ap == P_RW_U_RW) || (r_ap == P_RO_U_RO));
}
/**
* This internal function validates whether a given memory buffer
* is user accessible or not.
*/
static inline int mpu_buffer_validate(void *addr, size_t size, int write)
{
int32_t rnr;
int rc = -EPERM;
int key = arch_irq_lock();
/* Iterate all mpu regions in reversed order */
for (rnr = 0; rnr < mpu_get_num_regions(); rnr++) {
if (!is_enabled_region(rnr) ||
!is_in_region(rnr, (uint32_t)addr, size)) {
continue;
}
if (is_user_accessible_region(rnr, write)) {
rc = 0;
}
}
arch_irq_unlock(key);
return rc;
}
#else
/**
* This internal function validates whether a given memory buffer
* is user accessible or not.
*
* Note: [Doc. number: ARM-ECM-0359818]
* "Some SAU, IDAU, and MPU configurations block the efficient implementation
* of an address range check. The CMSE intrinsic operates under the assumption
* that the configuration of the SAU, IDAU, and MPU is constrained as follows:
* - An object is allocated in a single MPU/SAU/IDAU region.
* - A stack is allocated in a single region.
*
* These points imply that the memory buffer does not span across multiple MPU,
* SAU, or IDAU regions."
*
* MPU regions are configurable, however, some platforms might have fixed-size
* SAU or IDAU regions. So, even if a buffer is allocated inside a single MPU
* region, it might span across multiple SAU/IDAU regions, which will make the
* TT-based address range check fail.
*
* Therefore, the function performs a second check, which is based on MPU only,
* in case the fast address range check fails.
*
*/
static inline int mpu_buffer_validate(void *addr, size_t size, int write)
{
uint32_t _addr = (uint32_t)addr;
uint32_t _size = (uint32_t)size;
if (write) {
if (arm_cmse_addr_range_readwrite_ok(_addr, _size, 1)) {
return 0;
}
} else {
if (arm_cmse_addr_range_read_ok(_addr, _size, 1)) {
return 0;
}
}
#if defined(CONFIG_CPU_HAS_TEE)
/*
* Validation failure may be due to SAU/IDAU presence.
* We re-check user accessibility based on MPU only.
*/
int32_t r_index_base = arm_cmse_mpu_region_get(_addr);
int32_t r_index_last = arm_cmse_mpu_region_get(_addr + _size - 1);
if ((r_index_base != -EINVAL) && (r_index_base == r_index_last)) {
/* Valid MPU region, check permissions on base address only. */
if (write) {
if (arm_cmse_addr_readwrite_ok(_addr, 1)) {
return 0;
}
} else {
if (arm_cmse_addr_read_ok(_addr, 1)) {
return 0;
}
}
}
#endif /* CONFIG_CPU_HAS_TEE */
return -EPERM;
}
#endif /* CONFIG_AARCH32_ARMV8_R */
#endif /* CONFIG_USERSPACE */
static int region_allocate_and_init(const uint8_t index,
const struct arm_mpu_region *region_conf);
static int mpu_configure_region(const uint8_t index,
const struct z_arm_mpu_partition *new_region);
#if !defined(CONFIG_MPU_GAP_FILLING)
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);
#endif
/* 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.
*
* The function performs a full partition of the background memory
* area, effectively, leaving no space in this area uncovered by MPU.
*/
static int mpu_configure_regions_and_partition(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(&regions[i]))) {
LOG_ERR("Partition %u: sanity check failed.", i);
return -EINVAL;
}
/* Derive the index of the underlying MPU region,
* inside which the new region will be configured.
*/
int u_reg_index =
get_region_index(regions[i].start, regions[i].size);
if ((u_reg_index == -EINVAL) ||
(u_reg_index > (reg_index - 1))) {
LOG_ERR("Invalid underlying region index %u",
u_reg_index);
return -EINVAL;
}
/*
* The new memory region is to be placed inside the underlying
* region, possibly splitting the underlying region into two.
*/
uint32_t u_reg_base = mpu_region_get_base(u_reg_index);
uint32_t u_reg_last = mpu_region_get_last_addr(u_reg_index);
uint32_t reg_last = regions[i].start + regions[i].size - 1;
if ((regions[i].start == u_reg_base) &&
(reg_last == u_reg_last)) {
/* 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.
*/
mpu_configure_region(u_reg_index, &regions[i]);
} else if (regions[i].start == u_reg_base) {
/* 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.
*/
mpu_region_set_base(u_reg_index,
regions[i].start + regions[i].size);
reg_index =
mpu_configure_region(reg_index, &regions[i]);
if (reg_index == -EINVAL) {
return reg_index;
}
reg_index++;
} else if (reg_last == u_reg_last) {
/* 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.
*/
mpu_region_set_limit(u_reg_index,
regions[i].start - 1);
reg_index =
mpu_configure_region(reg_index, &regions[i]);
if (reg_index == -EINVAL) {
return reg_index;
}
reg_index++;
} else {
/* The new regions lies strictly inside the
* underlying region, which needs to split
* into two regions.
*/
mpu_region_set_limit(u_reg_index,
regions[i].start - 1);
reg_index =
mpu_configure_region(reg_index, &regions[i]);
if (reg_index == -EINVAL) {
return reg_index;
}
reg_index++;
/* The additional region shall have the same
* access attributes as the initial underlying
* region.
*/
struct arm_mpu_region fill_region;
mpu_region_get_access_attr(u_reg_index,
&fill_region.attr);
fill_region.base = regions[i].start +
regions[i].size;
fill_region.attr.r_limit =
REGION_LIMIT_ADDR((regions[i].start +
regions[i].size), (u_reg_last - reg_last));
reg_index =
region_allocate_and_init(reg_index,
(const struct arm_mpu_region *)
&fill_region);
if (reg_index == -EINVAL) {
return reg_index;
}
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 ARMv8-M architecture the static regions are programmed on SRAM,
* forming a full partition of the background area, specified by the
* given boundaries.
*/
ARG_UNUSED(background_area_base);
ARG_UNUSED(background_area_end);
mpu_reg_index = mpu_configure_regions_and_partition(static_regions,
regions_num, mpu_reg_index, true);
static_regions_num = mpu_reg_index;
return mpu_reg_index;
}
/* This internal function marks and stores the configuration of memory areas
* where dynamic region programming is allowed. Return zero on success, or
* -EINVAL on error.
*/
static int mpu_mark_areas_for_dynamic_regions(
const struct z_arm_mpu_partition dyn_region_areas[],
const uint8_t dyn_region_areas_num)
{
/* In ARMv8-M architecture we need to store the index values
* and the default configuration of the MPU regions, inside
* which dynamic memory regions may be programmed at run-time.
*/
for (int i = 0; i < dyn_region_areas_num; i++) {
if (dyn_region_areas[i].size == 0U) {
continue;
}
/* Non-empty area */
/* Retrieve HW MPU region index */
dyn_reg_info[i].index =
get_region_index(dyn_region_areas[i].start,
dyn_region_areas[i].size);
if (dyn_reg_info[i].index == -EINVAL) {
return -EINVAL;
}
if (dyn_reg_info[i].index >= static_regions_num) {
return -EINVAL;
}
/* Store default configuration */
mpu_region_get_conf(dyn_reg_info[i].index,
&dyn_reg_info[i].region_conf);
}
return 0;
}
/**
* Get the number of supported MPU regions.
*/
static inline uint8_t get_num_regions(void)
{
#if defined(NUM_MPU_REGIONS)
/* Retrieve the number of regions from DTS configuration. */
return NUM_MPU_REGIONS;
#else
return mpu_get_num_regions();
#endif /* NUM_MPU_REGIONS */
}
/* 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)
{
int mpu_reg_index = static_regions_num;
/* Disable all MPU regions except for the static ones. */
for (int i = mpu_reg_index; i < get_num_regions(); i++) {
mpu_clear_region(i);
}
#if defined(CONFIG_MPU_GAP_FILLING)
/* Reset MPU regions inside which dynamic memory regions may
* be programmed.
*/
for (int i = 0; i < MPU_DYNAMIC_REGION_AREAS_NUM; i++) {
region_init(dyn_reg_info[i].index,
&dyn_reg_info[i].region_conf);
}
/* In ARMv8-M architecture the dynamic regions are programmed on SRAM,
* forming a full partition of the background area, specified by the
* given boundaries.
*/
mpu_reg_index = mpu_configure_regions_and_partition(dynamic_regions,
regions_num, mpu_reg_index, true);
#else
/* We are going to skip the full partition of the background areas.
* So we can disable MPU regions inside which dynamic memory regions
* may be programmed.
*/
for (int i = 0; i < MPU_DYNAMIC_REGION_AREAS_NUM; i++) {
mpu_clear_region(dyn_reg_info[i].index);
}
/* The dynamic regions are now programmed on top of
* existing SRAM region configuration.
*/
mpu_reg_index = mpu_configure_regions(dynamic_regions,
regions_num, mpu_reg_index, true);
#endif /* CONFIG_MPU_GAP_FILLING */
return mpu_reg_index;
}
#endif /* ZEPHYR_ARCH_ARM_CORE_AARCH32_MPU_ARM_MPU_V8_INTERNAL_H_ */