8915e41b7b
The current API was assuming too much, in that it expected that arch-specific memory domain configuration is only maintained in some global area, and updates to domains that are not currently active have no effect. This was true when all memory domain state was tracked in page tables or MPU registers, but no longer works when arch-specific memory management information is stored in thread-specific areas. This is needed for: #13441 #13074 #15135 Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
267 lines
6.4 KiB
C
267 lines
6.4 KiB
C
/*
|
|
* Copyright (c) 2017 Linaro Limited
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <init.h>
|
|
#include <kernel.h>
|
|
#include <kernel_structs.h>
|
|
#include <kernel_internal.h>
|
|
#include <sys/__assert.h>
|
|
#include <stdbool.h>
|
|
#include <spinlock.h>
|
|
|
|
static struct k_spinlock lock;
|
|
static u8_t max_partitions;
|
|
|
|
#if (defined(CONFIG_EXECUTE_XOR_WRITE) || \
|
|
defined(CONFIG_MPU_REQUIRES_NON_OVERLAPPING_REGIONS)) && __ASSERT_ON
|
|
static bool sane_partition(const struct k_mem_partition *part,
|
|
const struct k_mem_partition *parts,
|
|
u32_t num_parts)
|
|
{
|
|
bool exec, write;
|
|
u32_t last;
|
|
u32_t i;
|
|
|
|
last = part->start + part->size - 1;
|
|
exec = K_MEM_PARTITION_IS_EXECUTABLE(part->attr);
|
|
write = K_MEM_PARTITION_IS_WRITABLE(part->attr);
|
|
|
|
if (exec && write) {
|
|
__ASSERT(false,
|
|
"partition is writable and executable <start %lx>",
|
|
part->start);
|
|
return false;
|
|
}
|
|
|
|
for (i = 0U; i < num_parts; i++) {
|
|
bool cur_write, cur_exec;
|
|
u32_t cur_last;
|
|
|
|
cur_last = parts[i].start + parts[i].size - 1;
|
|
|
|
if (last < parts[i].start || cur_last < part->start) {
|
|
continue;
|
|
}
|
|
#if defined(CONFIG_MPU_REQUIRES_NON_OVERLAPPING_REGIONS)
|
|
/* Partitions overlap */
|
|
__ASSERT(false, "overlapping partitions <%lx...%x>, <%lx...%x>",
|
|
part->start, last,
|
|
parts[i].start, cur_last);
|
|
return false;
|
|
#endif
|
|
|
|
cur_write = K_MEM_PARTITION_IS_WRITABLE(parts[i].attr);
|
|
cur_exec = K_MEM_PARTITION_IS_EXECUTABLE(parts[i].attr);
|
|
|
|
if ((cur_write && exec) || (cur_exec && write)) {
|
|
__ASSERT(false, "overlapping partitions are "
|
|
"writable and executable "
|
|
"<%lx...%x>, <%lx...%x>",
|
|
part->start, last,
|
|
parts[i].start, cur_last);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline bool sane_partition_domain(const struct k_mem_domain *domain,
|
|
const struct k_mem_partition *part)
|
|
{
|
|
return sane_partition(part, domain->partitions,
|
|
domain->num_partitions);
|
|
}
|
|
#else
|
|
#define sane_partition(...) (true)
|
|
#define sane_partition_domain(...) (true)
|
|
#endif
|
|
|
|
void k_mem_domain_init(struct k_mem_domain *domain, u8_t num_parts,
|
|
struct k_mem_partition *parts[])
|
|
{
|
|
k_spinlock_key_t key;
|
|
|
|
__ASSERT(domain != NULL, "");
|
|
__ASSERT(num_parts == 0U || parts != NULL, "");
|
|
__ASSERT(num_parts <= max_partitions, "");
|
|
|
|
key = k_spin_lock(&lock);
|
|
|
|
domain->num_partitions = 0U;
|
|
(void)memset(domain->partitions, 0, sizeof(domain->partitions));
|
|
|
|
if (num_parts != 0U) {
|
|
u32_t i;
|
|
|
|
for (i = 0U; i < num_parts; i++) {
|
|
__ASSERT(parts[i] != NULL, "");
|
|
__ASSERT((parts[i]->start + parts[i]->size) >
|
|
parts[i]->start,
|
|
"invalid partition %p size %d",
|
|
parts[i], parts[i]->size);
|
|
|
|
#if defined(CONFIG_EXECUTE_XOR_WRITE) || \
|
|
defined(CONFIG_MPU_REQUIRES_NON_OVERLAPPING_REGIONS)
|
|
__ASSERT(sane_partition_domain(domain,
|
|
parts[i]),
|
|
"");
|
|
#endif
|
|
domain->partitions[i] = *parts[i];
|
|
domain->num_partitions++;
|
|
}
|
|
}
|
|
|
|
sys_dlist_init(&domain->mem_domain_q);
|
|
|
|
k_spin_unlock(&lock, key);
|
|
}
|
|
|
|
void k_mem_domain_destroy(struct k_mem_domain *domain)
|
|
{
|
|
k_spinlock_key_t key;
|
|
sys_dnode_t *node, *next_node;
|
|
|
|
__ASSERT(domain != NULL, "");
|
|
|
|
key = k_spin_lock(&lock);
|
|
|
|
z_arch_mem_domain_destroy(domain);
|
|
|
|
SYS_DLIST_FOR_EACH_NODE_SAFE(&domain->mem_domain_q, node, next_node) {
|
|
struct k_thread *thread =
|
|
CONTAINER_OF(node, struct k_thread, mem_domain_info);
|
|
|
|
sys_dlist_remove(&thread->mem_domain_info.mem_domain_q_node);
|
|
thread->mem_domain_info.mem_domain = NULL;
|
|
}
|
|
|
|
k_spin_unlock(&lock, key);
|
|
}
|
|
|
|
void k_mem_domain_add_partition(struct k_mem_domain *domain,
|
|
struct k_mem_partition *part)
|
|
{
|
|
int p_idx;
|
|
k_spinlock_key_t key;
|
|
|
|
__ASSERT(domain != NULL, "");
|
|
__ASSERT(part != NULL, "");
|
|
__ASSERT((part->start + part->size) > part->start,
|
|
"invalid partition %p size %d", part, part->size);
|
|
|
|
#if defined(CONFIG_EXECUTE_XOR_WRITE) || \
|
|
defined(CONFIG_MPU_REQUIRES_NON_OVERLAPPING_REGIONS)
|
|
__ASSERT(sane_partition_domain(domain, part), "");
|
|
#endif
|
|
|
|
key = k_spin_lock(&lock);
|
|
|
|
for (p_idx = 0; p_idx < max_partitions; p_idx++) {
|
|
/* A zero-sized partition denotes it's a free partition */
|
|
if (domain->partitions[p_idx].size == 0U) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Assert if there is no free partition */
|
|
__ASSERT(p_idx < max_partitions, "");
|
|
|
|
domain->partitions[p_idx].start = part->start;
|
|
domain->partitions[p_idx].size = part->size;
|
|
domain->partitions[p_idx].attr = part->attr;
|
|
|
|
domain->num_partitions++;
|
|
|
|
z_arch_mem_domain_partition_add(domain, p_idx);
|
|
k_spin_unlock(&lock, key);
|
|
}
|
|
|
|
void k_mem_domain_remove_partition(struct k_mem_domain *domain,
|
|
struct k_mem_partition *part)
|
|
{
|
|
int p_idx;
|
|
k_spinlock_key_t key;
|
|
|
|
__ASSERT(domain != NULL, "");
|
|
__ASSERT(part != NULL, "");
|
|
|
|
key = k_spin_lock(&lock);
|
|
|
|
/* find a partition that matches the given start and size */
|
|
for (p_idx = 0; p_idx < max_partitions; p_idx++) {
|
|
if (domain->partitions[p_idx].start == part->start &&
|
|
domain->partitions[p_idx].size == part->size) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Assert if not found */
|
|
__ASSERT(p_idx < max_partitions, "no matching partition found");
|
|
|
|
z_arch_mem_domain_partition_remove(domain, p_idx);
|
|
|
|
/* A zero-sized partition denotes it's a free partition */
|
|
domain->partitions[p_idx].size = 0U;
|
|
|
|
domain->num_partitions--;
|
|
|
|
k_spin_unlock(&lock, key);
|
|
}
|
|
|
|
void k_mem_domain_add_thread(struct k_mem_domain *domain, k_tid_t thread)
|
|
{
|
|
k_spinlock_key_t key;
|
|
|
|
__ASSERT(domain != NULL, "");
|
|
__ASSERT(thread != NULL, "");
|
|
__ASSERT(thread->mem_domain_info.mem_domain == NULL,
|
|
"mem domain unset");
|
|
|
|
key = k_spin_lock(&lock);
|
|
|
|
sys_dlist_append(&domain->mem_domain_q,
|
|
&thread->mem_domain_info.mem_domain_q_node);
|
|
thread->mem_domain_info.mem_domain = domain;
|
|
|
|
z_arch_mem_domain_thread_add(thread);
|
|
|
|
k_spin_unlock(&lock, key);
|
|
}
|
|
|
|
void k_mem_domain_remove_thread(k_tid_t thread)
|
|
{
|
|
k_spinlock_key_t key;
|
|
|
|
__ASSERT(thread != NULL, "");
|
|
__ASSERT(thread->mem_domain_info.mem_domain != NULL, "mem domain set");
|
|
|
|
key = k_spin_lock(&lock);
|
|
z_arch_mem_domain_thread_remove(thread);
|
|
|
|
sys_dlist_remove(&thread->mem_domain_info.mem_domain_q_node);
|
|
thread->mem_domain_info.mem_domain = NULL;
|
|
k_spin_unlock(&lock, key);
|
|
}
|
|
|
|
static int init_mem_domain_module(struct device *arg)
|
|
{
|
|
ARG_UNUSED(arg);
|
|
|
|
max_partitions = z_arch_mem_domain_max_partitions_get();
|
|
/*
|
|
* max_partitions must be less than or equal to
|
|
* CONFIG_MAX_DOMAIN_PARTITIONS, or would encounter array index
|
|
* out of bounds error.
|
|
*/
|
|
__ASSERT(max_partitions <= CONFIG_MAX_DOMAIN_PARTITIONS, "");
|
|
|
|
return 0;
|
|
}
|
|
|
|
SYS_INIT(init_mem_domain_module, PRE_KERNEL_1,
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|