blob: 4a380a794675c1fc97b65f47c00c30600ca6c607 [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/mpu/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%x 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;
#if defined(CONFIG_USERSPACE)
if (thread->arch.priv_stack_start) {
guard_start = thread->arch.priv_stack_start;
} else {
guard_start = thread->stack_info.start -
MPU_GUARD_ALIGN_AND_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 -
MPU_GUARD_ALIGN_AND_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,
MPU_GUARD_ALIGN_AND_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_configure(struct k_thread *thread)
{
/* 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;
/* 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;
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 */
}
/*
* 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 */