blob: 12897b5c6a0cd28c5e7d8f170e7b0abd068643f3 [file] [log] [blame]
/*
* Copyright (c) 2017 Linaro Limited.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <device.h>
#include <init.h>
#include <kernel.h>
#include <soc.h>
#include <arch/arm/cortex_m/cmsis.h>
#include <arch/arm/cortex_m/mpu/nxp_mpu.h>
#include <logging/sys_log.h>
#include <misc/__assert.h>
#include <linker/linker-defs.h>
/* NXP MPU Enabled state */
static u8_t nxp_mpu_enabled;
/**
* This internal function is utilized by the MPU driver to parse the intent
* type (i.e. THREAD_STACK_REGION) and return the correct parameter set.
*/
static inline u32_t _get_region_attr_by_type(u32_t type)
{
switch (type) {
case THREAD_STACK_USER_REGION:
return REGION_USER_MODE_ATTR;
case THREAD_STACK_REGION:
return REGION_RAM_ATTR;
case THREAD_STACK_GUARD_REGION:
/* The stack guard region has to be not accessible */
return REGION_RO_ATTR;
case THREAD_APP_DATA_REGION:
return REGION_USER_MODE_ATTR;
default:
/* Size 0 region */
return 0;
}
}
static inline u8_t _get_num_regions(void)
{
return FSL_FEATURE_SYSMPU_DESCRIPTOR_COUNT;
}
static void _region_init(u32_t index, u32_t region_base,
u32_t region_end, u32_t region_attr)
{
if (index == 0) {
/* 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, (u32_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, (u32_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;
}
SYS_LOG_DBG("[%d] 0x%08x 0x%08x 0x%08x 0x%08x", index,
SYSMPU->WORD[index][0],
SYSMPU->WORD[index][1],
SYSMPU->WORD[index][2],
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_USER_REGION:
return mpu_config.num_regions + THREAD_STACK_REGION - 1;
case THREAD_STACK_REGION:
case THREAD_STACK_GUARD_REGION:
case THREAD_APP_DATA_REGION:
return mpu_config.num_regions + type - 1;
case THREAD_DOMAIN_PARTITION_REGION:
#if defined(CONFIG_USERSPACE)
return mpu_config.num_regions + type - 1;
#elif defined(CONFIG_MPU_STACK_GUARD)
return mpu_config.num_regions + type - 2;
#else
/*
* Start domain partition region from stack guard region
* since stack guard is not enabled.
*/
return mpu_config.num_regions + type - 3;
#endif
default:
__ASSERT(0, "Unsupported type");
return 0;
}
}
/**
* This internal function check if region is enabled or not
*/
static inline int _is_enabled_region(u32_t r_index)
{
return SYSMPU->WORD[r_index][3] & SYSMPU_WORD_VLD_MASK;
}
/**
* This internal function check if the given buffer in in the region
*/
static inline int _is_in_region(u32_t r_index, u32_t start, u32_t size)
{
u32_t r_addr_start;
u32_t r_addr_end;
r_addr_start = SYSMPU->WORD[r_index][0];
r_addr_end = SYSMPU->WORD[r_index][1];
if (start >= r_addr_start && (start + size - 1) <= r_addr_end) {
return 1;
}
return 0;
}
/**
* This internal function check if the region is user accessible or not
*/
static inline int _is_user_accessible_region(u32_t r_index, int write)
{
u32_t r_ap = SYSMPU->WORD[r_index][2];
/* always return true if this is the thread stack region */
if (_get_region_index_by_type(THREAD_STACK_REGION) == r_index) {
return 1;
}
if (write) {
return (r_ap & MPU_REGION_WRITE) == MPU_REGION_WRITE;
}
return (r_ap & MPU_REGION_READ) == MPU_REGION_READ;
}
static void nxp_mpu_setup_sram_region(u32_t base, u32_t size)
{
u32_t last_region = _get_num_regions() - 1;
/*
* The NXP MPU manages the permissions of the overlapping regions
* doing the logic OR in between them, hence they can't be used
* for stack/stack guard protection. For this reason the last region of
* the MPU will be reserved.
*
* A consequence of this is that the SRAM is split in different
* regions. In example if THREAD_STACK_GUARD_REGION is selected:
* - SRAM before THREAD_STACK_GUARD_REGION: RW
* - SRAM THREAD_STACK_GUARD_REGION: RO
* - SRAM after THREAD_STACK_GUARD_REGION: RW
*/
/* Configure SRAM_0 region
*
* The mpu_config.sram_region contains the index of the region in
* the mpu_config.mpu_regions array but the region 0 on the NXP MPU
* is the background region, so on this MPU the regions are mapped
* starting from 1, hence the mpu_config.sram_region on the data
* structure is mapped on the mpu_config.sram_region + 1 region of
* the MPU.
*/
_region_init(mpu_config.sram_region,
mpu_config.mpu_regions[mpu_config.sram_region].base,
ENDADDR_ROUND(base),
mpu_config.mpu_regions[mpu_config.sram_region].attr);
/* Configure SRAM_1 region */
_region_init(last_region, base + size,
ENDADDR_ROUND(mpu_config.mpu_regions[mpu_config.sram_region].end),
mpu_config.mpu_regions[mpu_config.sram_region].attr);
}
/* ARM Core MPU Driver API Implementation for NXP MPU */
/**
* @brief enable the MPU
*/
void arm_core_mpu_enable(void)
{
if (nxp_mpu_enabled == 0) {
/* Enable MPU */
SYSMPU->CESR |= SYSMPU_CESR_VLD_MASK;
nxp_mpu_enabled = 1;
}
}
/**
* @brief disable the MPU
*/
void arm_core_mpu_disable(void)
{
if (nxp_mpu_enabled == 1) {
/* Disable MPU */
SYSMPU->CESR &= ~SYSMPU_CESR_VLD_MASK;
/* Clear Interrupts */
SYSMPU->CESR |= SYSMPU_CESR_SPERR_MASK;
nxp_mpu_enabled = 0;
}
}
/**
* @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
*/
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);
u32_t region_index = _get_region_index_by_type(type);
u32_t region_attr = _get_region_attr_by_type(type);
/* NXP MPU supports up to 16 Regions */
if (region_index > _get_num_regions() - 2) {
return;
}
_region_init(region_index, base,
ENDADDR_ROUND(base + size),
region_attr);
if (type == THREAD_STACK_GUARD_REGION) {
nxp_mpu_setup_sram_region(base, size);
}
}
#if defined(CONFIG_USERSPACE)
void arm_core_mpu_configure_user_context(struct k_thread *thread)
{
u32_t base = (u32_t)thread->stack_info.start;
u32_t size = thread->stack_info.size;
u32_t index = _get_region_index_by_type(THREAD_STACK_USER_REGION);
u32_t region_attr = _get_region_attr_by_type(THREAD_STACK_USER_REGION);
/* configure stack */
_region_init(index, base, ENDADDR_ROUND(base + size), region_attr);
#if defined(CONFIG_APPLICATION_MEMORY)
/* configure app data portion */
index = _get_region_index_by_type(THREAD_APP_DATA_REGION);
region_attr = _get_region_attr_by_type(THREAD_APP_DATA_REGION);
base = (u32_t)&__app_ram_start;
size = (u32_t)&__app_ram_end - (u32_t)&__app_ram_start;
/* set up app data region if exists, otherwise disable */
if (size > 0) {
_region_init(index, base, ENDADDR_ROUND(base + size),
region_attr);
} else {
SYSMPU->WORD[index][3] = 0;
}
#endif
}
/**
* @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 Reset MPU region for a single memory partition
*
* @param part_index memory partition index
*/
void arm_core_mpu_mem_partition_remove(u32_t part_index)
{
u32_t region_index =
_get_region_index_by_type(THREAD_DOMAIN_PARTITION_REGION);
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;
}
/**
* @brief validate the given buffer is user accessible or not
*/
int arm_core_mpu_buffer_validate(void *addr, size_t size, int write)
{
u32_t r_index;
/* Iterate all mpu regions */
for (r_index = 0; r_index < _get_num_regions(); r_index++) {
if (!_is_enabled_region(r_index) ||
!_is_in_region(r_index, (u32_t)addr, size)) {
continue;
}
/* For NXP MPU, priority 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 */
/* NXP MPU Driver Initial Setup */
/*
* @brief MPU default configuration
*
* This function provides the default configuration mechanism for the Memory
* Protection Unit (MPU).
*/
static void _nxp_mpu_config(void)
{
u32_t r_index;
SYS_LOG_DBG("region number: %d", _get_num_regions());
/* NXP MPU supports up to 16 Regions */
if (mpu_config.num_regions > _get_num_regions()) {
return;
}
/* Disable MPU */
SYSMPU->CESR &= ~SYSMPU_CESR_VLD_MASK;
/* Clear Interrupts */
SYSMPU->CESR |= SYSMPU_CESR_SPERR_MASK;
/* MPU Configuration */
/* Configure regions */
for (r_index = 0; r_index < mpu_config.num_regions; r_index++) {
_region_init(r_index,
mpu_config.mpu_regions[r_index].base,
mpu_config.mpu_regions[r_index].end,
mpu_config.mpu_regions[r_index].attr);
}
/* Enable MPU */
SYSMPU->CESR |= SYSMPU_CESR_VLD_MASK;
nxp_mpu_enabled = 1;
/* Make sure that all the registers are set before proceeding */
__DSB();
__ISB();
}
/*
* @brief MPU clock configuration
*
* This function provides the clock configuration for the Memory Protection
* Unit (MPU).
*/
static void _nxp_mpu_clock_cfg(void)
{
/* Enable Clock */
CLOCK_EnableClock(kCLOCK_Sysmpu0);
}
static int nxp_mpu_init(struct device *arg)
{
ARG_UNUSED(arg);
_nxp_mpu_clock_cfg();
_nxp_mpu_config();
return 0;
}
#if defined(CONFIG_SYS_LOG)
/* To have logging the driver needs to be initialized later */
SYS_INIT(nxp_mpu_init, POST_KERNEL,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
#else
SYS_INIT(nxp_mpu_init, PRE_KERNEL_1,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
#endif