You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
368 lines
8.5 KiB
368 lines
8.5 KiB
/* |
|
* Copyright (c) 2017 Linaro Limited |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <zephyr/init.h> |
|
#include <zephyr/kernel.h> |
|
#include <zephyr/kernel_structs.h> |
|
#include <kernel_internal.h> |
|
#include <zephyr/sys/__assert.h> |
|
#include <stdbool.h> |
|
#include <zephyr/spinlock.h> |
|
#include <zephyr/sys/check.h> |
|
#include <zephyr/sys/libc-hooks.h> |
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL); |
|
|
|
struct k_spinlock z_mem_domain_lock; |
|
static uint8_t max_partitions; |
|
|
|
struct k_mem_domain k_mem_domain_default; |
|
|
|
static bool check_add_partition(struct k_mem_domain *domain, |
|
struct k_mem_partition *part) |
|
{ |
|
|
|
int i; |
|
uintptr_t pstart, pend, dstart, dend; |
|
|
|
if (part == NULL) { |
|
LOG_ERR("NULL k_mem_partition provided"); |
|
return false; |
|
} |
|
|
|
#ifdef CONFIG_EXECUTE_XOR_WRITE |
|
/* Arches where execution cannot be disabled should always return |
|
* false to this check |
|
*/ |
|
if (K_MEM_PARTITION_IS_EXECUTABLE(part->attr) && |
|
K_MEM_PARTITION_IS_WRITABLE(part->attr)) { |
|
LOG_ERR("partition is writable and executable <start %lx>", |
|
part->start); |
|
return false; |
|
} |
|
#endif /* CONFIG_EXECUTE_XOR_WRITE */ |
|
|
|
if (part->size == 0U) { |
|
LOG_ERR("zero sized partition at %p with base 0x%lx", |
|
part, part->start); |
|
return false; |
|
} |
|
|
|
pstart = part->start; |
|
pend = part->start + part->size; |
|
|
|
if (pend <= pstart) { |
|
LOG_ERR("invalid partition %p, wraparound detected. base 0x%lx size %zu", |
|
part, part->start, part->size); |
|
return false; |
|
} |
|
|
|
/* Check that this partition doesn't overlap any existing ones already |
|
* in the domain |
|
*/ |
|
for (i = 0; i < domain->num_partitions; i++) { |
|
struct k_mem_partition *dpart = &domain->partitions[i]; |
|
|
|
if (dpart->size == 0U) { |
|
/* Unused slot */ |
|
continue; |
|
} |
|
|
|
dstart = dpart->start; |
|
dend = dstart + dpart->size; |
|
|
|
if (pend > dstart && dend > pstart) { |
|
LOG_ERR("partition %p base %lx (size %zu) overlaps existing base %lx (size %zu)", |
|
part, part->start, part->size, |
|
dpart->start, dpart->size); |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
int k_mem_domain_init(struct k_mem_domain *domain, uint8_t num_parts, |
|
struct k_mem_partition *parts[]) |
|
{ |
|
k_spinlock_key_t key; |
|
int ret = 0; |
|
|
|
CHECKIF(domain == NULL) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
CHECKIF(!(num_parts == 0U || parts != NULL)) { |
|
LOG_ERR("parts array is NULL and num_parts is nonzero"); |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
CHECKIF(!(num_parts <= max_partitions)) { |
|
LOG_ERR("num_parts of %d exceeds maximum allowable partitions (%d)", |
|
num_parts, max_partitions); |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
key = k_spin_lock(&z_mem_domain_lock); |
|
|
|
domain->num_partitions = 0U; |
|
(void)memset(domain->partitions, 0, sizeof(domain->partitions)); |
|
sys_dlist_init(&domain->mem_domain_q); |
|
|
|
#ifdef CONFIG_ARCH_MEM_DOMAIN_DATA |
|
ret = arch_mem_domain_init(domain); |
|
|
|
if (ret != 0) { |
|
LOG_ERR("architecture-specific initialization failed for domain %p with %d", |
|
domain, ret); |
|
ret = -ENOMEM; |
|
goto unlock_out; |
|
} |
|
#endif /* CONFIG_ARCH_MEM_DOMAIN_DATA */ |
|
if (num_parts != 0U) { |
|
uint32_t i; |
|
|
|
for (i = 0U; i < num_parts; i++) { |
|
CHECKIF(!check_add_partition(domain, parts[i])) { |
|
LOG_ERR("invalid partition index %d (%p)", |
|
i, parts[i]); |
|
ret = -EINVAL; |
|
goto unlock_out; |
|
} |
|
|
|
domain->partitions[i] = *parts[i]; |
|
domain->num_partitions++; |
|
#ifdef CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API |
|
int ret2 = arch_mem_domain_partition_add(domain, i); |
|
|
|
ARG_UNUSED(ret2); |
|
CHECKIF(ret2 != 0) { |
|
ret = ret2; |
|
} |
|
#endif /* CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API */ |
|
} |
|
} |
|
|
|
unlock_out: |
|
k_spin_unlock(&z_mem_domain_lock, key); |
|
|
|
out: |
|
return ret; |
|
} |
|
|
|
int k_mem_domain_add_partition(struct k_mem_domain *domain, |
|
struct k_mem_partition *part) |
|
{ |
|
int p_idx; |
|
k_spinlock_key_t key; |
|
int ret = 0; |
|
|
|
CHECKIF(domain == NULL) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
CHECKIF(!check_add_partition(domain, part)) { |
|
LOG_ERR("invalid partition %p", part); |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
key = k_spin_lock(&z_mem_domain_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; |
|
} |
|
} |
|
|
|
CHECKIF(!(p_idx < max_partitions)) { |
|
LOG_ERR("no free partition slots available"); |
|
ret = -ENOSPC; |
|
goto unlock_out; |
|
} |
|
|
|
LOG_DBG("add partition base %lx size %zu to domain %p\n", |
|
part->start, part->size, domain); |
|
|
|
domain->partitions[p_idx].start = part->start; |
|
domain->partitions[p_idx].size = part->size; |
|
domain->partitions[p_idx].attr = part->attr; |
|
|
|
domain->num_partitions++; |
|
|
|
#ifdef CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API |
|
ret = arch_mem_domain_partition_add(domain, p_idx); |
|
#endif /* CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API */ |
|
|
|
unlock_out: |
|
k_spin_unlock(&z_mem_domain_lock, key); |
|
|
|
out: |
|
return ret; |
|
} |
|
|
|
int k_mem_domain_remove_partition(struct k_mem_domain *domain, |
|
struct k_mem_partition *part) |
|
{ |
|
int p_idx; |
|
k_spinlock_key_t key; |
|
int ret = 0; |
|
|
|
CHECKIF((domain == NULL) || (part == NULL)) { |
|
ret = -EINVAL; |
|
goto out; |
|
} |
|
|
|
key = k_spin_lock(&z_mem_domain_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; |
|
} |
|
} |
|
|
|
CHECKIF(!(p_idx < max_partitions)) { |
|
LOG_ERR("no matching partition found"); |
|
ret = -ENOENT; |
|
goto unlock_out; |
|
} |
|
|
|
LOG_DBG("remove partition base %lx size %zu from domain %p\n", |
|
part->start, part->size, domain); |
|
|
|
#ifdef CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API |
|
ret = arch_mem_domain_partition_remove(domain, p_idx); |
|
#endif /* CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API */ |
|
|
|
/* A zero-sized partition denotes it's a free partition */ |
|
domain->partitions[p_idx].size = 0U; |
|
|
|
domain->num_partitions--; |
|
|
|
unlock_out: |
|
k_spin_unlock(&z_mem_domain_lock, key); |
|
|
|
out: |
|
return ret; |
|
} |
|
|
|
static int add_thread_locked(struct k_mem_domain *domain, |
|
k_tid_t thread) |
|
{ |
|
int ret = 0; |
|
|
|
__ASSERT_NO_MSG(domain != NULL); |
|
__ASSERT_NO_MSG(thread != NULL); |
|
|
|
LOG_DBG("add thread %p to domain %p\n", thread, domain); |
|
sys_dlist_append(&domain->mem_domain_q, |
|
&thread->mem_domain_info.mem_domain_q_node); |
|
thread->mem_domain_info.mem_domain = domain; |
|
|
|
#ifdef CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API |
|
ret = arch_mem_domain_thread_add(thread); |
|
#endif /* CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API */ |
|
|
|
return ret; |
|
} |
|
|
|
static int remove_thread_locked(struct k_thread *thread) |
|
{ |
|
int ret = 0; |
|
|
|
__ASSERT_NO_MSG(thread != NULL); |
|
LOG_DBG("remove thread %p from memory domain %p\n", |
|
thread, thread->mem_domain_info.mem_domain); |
|
sys_dlist_remove(&thread->mem_domain_info.mem_domain_q_node); |
|
|
|
#ifdef CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API |
|
ret = arch_mem_domain_thread_remove(thread); |
|
#endif /* CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API */ |
|
|
|
return ret; |
|
} |
|
|
|
/* Called from thread object initialization */ |
|
void z_mem_domain_init_thread(struct k_thread *thread) |
|
{ |
|
int ret; |
|
k_spinlock_key_t key = k_spin_lock(&z_mem_domain_lock); |
|
|
|
/* New threads inherit memory domain configuration from parent */ |
|
ret = add_thread_locked(_current->mem_domain_info.mem_domain, thread); |
|
__ASSERT_NO_MSG(ret == 0); |
|
ARG_UNUSED(ret); |
|
|
|
k_spin_unlock(&z_mem_domain_lock, key); |
|
} |
|
|
|
/* Called when thread aborts during teardown tasks. _sched_spinlock is held */ |
|
void z_mem_domain_exit_thread(struct k_thread *thread) |
|
{ |
|
int ret; |
|
|
|
k_spinlock_key_t key = k_spin_lock(&z_mem_domain_lock); |
|
|
|
ret = remove_thread_locked(thread); |
|
__ASSERT_NO_MSG(ret == 0); |
|
ARG_UNUSED(ret); |
|
|
|
k_spin_unlock(&z_mem_domain_lock, key); |
|
} |
|
|
|
int k_mem_domain_add_thread(struct k_mem_domain *domain, k_tid_t thread) |
|
{ |
|
int ret = 0; |
|
k_spinlock_key_t key; |
|
|
|
key = k_spin_lock(&z_mem_domain_lock); |
|
if (thread->mem_domain_info.mem_domain != domain) { |
|
ret = remove_thread_locked(thread); |
|
|
|
if (ret == 0) { |
|
ret = add_thread_locked(domain, thread); |
|
} |
|
} |
|
k_spin_unlock(&z_mem_domain_lock, key); |
|
|
|
return ret; |
|
} |
|
|
|
static int init_mem_domain_module(void) |
|
{ |
|
int ret; |
|
|
|
ARG_UNUSED(ret); |
|
|
|
max_partitions = 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, ""); |
|
|
|
ret = k_mem_domain_init(&k_mem_domain_default, 0, NULL); |
|
__ASSERT(ret == 0, "failed to init default mem domain"); |
|
|
|
#ifdef Z_LIBC_PARTITION_EXISTS |
|
ret = k_mem_domain_add_partition(&k_mem_domain_default, |
|
&z_libc_partition); |
|
__ASSERT(ret == 0, "failed to add default libc mem partition"); |
|
#endif /* Z_LIBC_PARTITION_EXISTS */ |
|
|
|
return 0; |
|
} |
|
|
|
SYS_INIT(init_mem_domain_module, PRE_KERNEL_1, |
|
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
|
|