1 /*
2 * Copyright (c) 2017 Linaro Limited
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/init.h>
8 #include <zephyr/kernel.h>
9 #include <zephyr/kernel_structs.h>
10 #include <kernel_internal.h>
11 #include <zephyr/sys/__assert.h>
12 #include <stdbool.h>
13 #include <zephyr/spinlock.h>
14 #include <zephyr/sys/check.h>
15 #include <zephyr/sys/libc-hooks.h>
16 #include <zephyr/logging/log.h>
17 LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL);
18
19 struct k_spinlock z_mem_domain_lock;
20 static uint8_t max_partitions;
21
22 struct k_mem_domain k_mem_domain_default;
23
check_add_partition(struct k_mem_domain * domain,struct k_mem_partition * part)24 static bool check_add_partition(struct k_mem_domain *domain,
25 struct k_mem_partition *part)
26 {
27
28 int i;
29 uintptr_t pstart, pend, dstart, dend;
30
31 if (part == NULL) {
32 LOG_ERR("NULL k_mem_partition provided");
33 return false;
34 }
35
36 #ifdef CONFIG_EXECUTE_XOR_WRITE
37 /* Arches where execution cannot be disabled should always return
38 * false to this check
39 */
40 if (K_MEM_PARTITION_IS_EXECUTABLE(part->attr) &&
41 K_MEM_PARTITION_IS_WRITABLE(part->attr)) {
42 LOG_ERR("partition is writable and executable <start %lx>",
43 part->start);
44 return false;
45 }
46 #endif /* CONFIG_EXECUTE_XOR_WRITE */
47
48 if (part->size == 0U) {
49 LOG_ERR("zero sized partition at %p with base 0x%lx",
50 part, part->start);
51 return false;
52 }
53
54 pstart = part->start;
55 pend = part->start + part->size;
56
57 if (pend <= pstart) {
58 LOG_ERR("invalid partition %p, wraparound detected. base 0x%lx size %zu",
59 part, part->start, part->size);
60 return false;
61 }
62
63 /* Check that this partition doesn't overlap any existing ones already
64 * in the domain
65 */
66 for (i = 0; i < domain->num_partitions; i++) {
67 struct k_mem_partition *dpart = &domain->partitions[i];
68
69 if (dpart->size == 0U) {
70 /* Unused slot */
71 continue;
72 }
73
74 dstart = dpart->start;
75 dend = dstart + dpart->size;
76
77 if (pend > dstart && dend > pstart) {
78 LOG_ERR("partition %p base %lx (size %zu) overlaps existing base %lx (size %zu)",
79 part, part->start, part->size,
80 dpart->start, dpart->size);
81 return false;
82 }
83 }
84
85 return true;
86 }
87
k_mem_domain_init(struct k_mem_domain * domain,uint8_t num_parts,struct k_mem_partition * parts[])88 int k_mem_domain_init(struct k_mem_domain *domain, uint8_t num_parts,
89 struct k_mem_partition *parts[])
90 {
91 k_spinlock_key_t key;
92 int ret = 0;
93
94 CHECKIF(domain == NULL) {
95 ret = -EINVAL;
96 goto out;
97 }
98
99 CHECKIF(!(num_parts == 0U || parts != NULL)) {
100 LOG_ERR("parts array is NULL and num_parts is nonzero");
101 ret = -EINVAL;
102 goto out;
103 }
104
105 CHECKIF(!(num_parts <= max_partitions)) {
106 LOG_ERR("num_parts of %d exceeds maximum allowable partitions (%d)",
107 num_parts, max_partitions);
108 ret = -EINVAL;
109 goto out;
110 }
111
112 key = k_spin_lock(&z_mem_domain_lock);
113
114 domain->num_partitions = 0U;
115 (void)memset(domain->partitions, 0, sizeof(domain->partitions));
116 sys_dlist_init(&domain->mem_domain_q);
117
118 #ifdef CONFIG_ARCH_MEM_DOMAIN_DATA
119 ret = arch_mem_domain_init(domain);
120
121 if (ret != 0) {
122 LOG_ERR("architecture-specific initialization failed for domain %p with %d",
123 domain, ret);
124 ret = -ENOMEM;
125 goto unlock_out;
126 }
127 #endif /* CONFIG_ARCH_MEM_DOMAIN_DATA */
128 if (num_parts != 0U) {
129 uint32_t i;
130
131 for (i = 0U; i < num_parts; i++) {
132 CHECKIF(!check_add_partition(domain, parts[i])) {
133 LOG_ERR("invalid partition index %d (%p)",
134 i, parts[i]);
135 ret = -EINVAL;
136 goto unlock_out;
137 }
138
139 domain->partitions[i] = *parts[i];
140 domain->num_partitions++;
141 #ifdef CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API
142 int ret2 = arch_mem_domain_partition_add(domain, i);
143
144 ARG_UNUSED(ret2);
145 CHECKIF(ret2 != 0) {
146 ret = ret2;
147 }
148 #endif /* CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API */
149 }
150 }
151
152 unlock_out:
153 k_spin_unlock(&z_mem_domain_lock, key);
154
155 out:
156 return ret;
157 }
158
k_mem_domain_add_partition(struct k_mem_domain * domain,struct k_mem_partition * part)159 int k_mem_domain_add_partition(struct k_mem_domain *domain,
160 struct k_mem_partition *part)
161 {
162 int p_idx;
163 k_spinlock_key_t key;
164 int ret = 0;
165
166 CHECKIF(domain == NULL) {
167 ret = -EINVAL;
168 goto out;
169 }
170
171 CHECKIF(!check_add_partition(domain, part)) {
172 LOG_ERR("invalid partition %p", part);
173 ret = -EINVAL;
174 goto out;
175 }
176
177 key = k_spin_lock(&z_mem_domain_lock);
178
179 for (p_idx = 0; p_idx < max_partitions; p_idx++) {
180 /* A zero-sized partition denotes it's a free partition */
181 if (domain->partitions[p_idx].size == 0U) {
182 break;
183 }
184 }
185
186 CHECKIF(!(p_idx < max_partitions)) {
187 LOG_ERR("no free partition slots available");
188 ret = -ENOSPC;
189 goto unlock_out;
190 }
191
192 LOG_DBG("add partition base %lx size %zu to domain %p\n",
193 part->start, part->size, domain);
194
195 domain->partitions[p_idx].start = part->start;
196 domain->partitions[p_idx].size = part->size;
197 domain->partitions[p_idx].attr = part->attr;
198
199 domain->num_partitions++;
200
201 #ifdef CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API
202 ret = arch_mem_domain_partition_add(domain, p_idx);
203 #endif /* CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API */
204
205 unlock_out:
206 k_spin_unlock(&z_mem_domain_lock, key);
207
208 out:
209 return ret;
210 }
211
k_mem_domain_remove_partition(struct k_mem_domain * domain,struct k_mem_partition * part)212 int k_mem_domain_remove_partition(struct k_mem_domain *domain,
213 struct k_mem_partition *part)
214 {
215 int p_idx;
216 k_spinlock_key_t key;
217 int ret = 0;
218
219 CHECKIF((domain == NULL) || (part == NULL)) {
220 ret = -EINVAL;
221 goto out;
222 }
223
224 key = k_spin_lock(&z_mem_domain_lock);
225
226 /* find a partition that matches the given start and size */
227 for (p_idx = 0; p_idx < max_partitions; p_idx++) {
228 if ((domain->partitions[p_idx].start == part->start) &&
229 (domain->partitions[p_idx].size == part->size)) {
230 break;
231 }
232 }
233
234 CHECKIF(!(p_idx < max_partitions)) {
235 LOG_ERR("no matching partition found");
236 ret = -ENOENT;
237 goto unlock_out;
238 }
239
240 LOG_DBG("remove partition base %lx size %zu from domain %p\n",
241 part->start, part->size, domain);
242
243 #ifdef CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API
244 ret = arch_mem_domain_partition_remove(domain, p_idx);
245 #endif /* CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API */
246
247 /* A zero-sized partition denotes it's a free partition */
248 domain->partitions[p_idx].size = 0U;
249
250 domain->num_partitions--;
251
252 unlock_out:
253 k_spin_unlock(&z_mem_domain_lock, key);
254
255 out:
256 return ret;
257 }
258
add_thread_locked(struct k_mem_domain * domain,k_tid_t thread)259 static int add_thread_locked(struct k_mem_domain *domain,
260 k_tid_t thread)
261 {
262 int ret = 0;
263
264 __ASSERT_NO_MSG(domain != NULL);
265 __ASSERT_NO_MSG(thread != NULL);
266
267 LOG_DBG("add thread %p to domain %p\n", thread, domain);
268 sys_dlist_append(&domain->mem_domain_q,
269 &thread->mem_domain_info.mem_domain_q_node);
270 thread->mem_domain_info.mem_domain = domain;
271
272 #ifdef CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API
273 ret = arch_mem_domain_thread_add(thread);
274 #endif /* CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API */
275
276 return ret;
277 }
278
remove_thread_locked(struct k_thread * thread)279 static int remove_thread_locked(struct k_thread *thread)
280 {
281 int ret = 0;
282
283 __ASSERT_NO_MSG(thread != NULL);
284 LOG_DBG("remove thread %p from memory domain %p\n",
285 thread, thread->mem_domain_info.mem_domain);
286 sys_dlist_remove(&thread->mem_domain_info.mem_domain_q_node);
287
288 #ifdef CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API
289 ret = arch_mem_domain_thread_remove(thread);
290 #endif /* CONFIG_ARCH_MEM_DOMAIN_SYNCHRONOUS_API */
291
292 return ret;
293 }
294
295 /* Called from thread object initialization */
z_mem_domain_init_thread(struct k_thread * thread)296 void z_mem_domain_init_thread(struct k_thread *thread)
297 {
298 int ret;
299 k_spinlock_key_t key = k_spin_lock(&z_mem_domain_lock);
300
301 /* New threads inherit memory domain configuration from parent */
302 ret = add_thread_locked(_current->mem_domain_info.mem_domain, thread);
303 __ASSERT_NO_MSG(ret == 0);
304 ARG_UNUSED(ret);
305
306 k_spin_unlock(&z_mem_domain_lock, key);
307 }
308
309 /* Called when thread aborts during teardown tasks. _sched_spinlock is held */
z_mem_domain_exit_thread(struct k_thread * thread)310 void z_mem_domain_exit_thread(struct k_thread *thread)
311 {
312 int ret;
313
314 k_spinlock_key_t key = k_spin_lock(&z_mem_domain_lock);
315
316 ret = remove_thread_locked(thread);
317 __ASSERT_NO_MSG(ret == 0);
318 ARG_UNUSED(ret);
319
320 k_spin_unlock(&z_mem_domain_lock, key);
321 }
322
k_mem_domain_add_thread(struct k_mem_domain * domain,k_tid_t thread)323 int k_mem_domain_add_thread(struct k_mem_domain *domain, k_tid_t thread)
324 {
325 int ret = 0;
326 k_spinlock_key_t key;
327
328 key = k_spin_lock(&z_mem_domain_lock);
329 if (thread->mem_domain_info.mem_domain != domain) {
330 ret = remove_thread_locked(thread);
331
332 if (ret == 0) {
333 ret = add_thread_locked(domain, thread);
334 }
335 }
336 k_spin_unlock(&z_mem_domain_lock, key);
337
338 return ret;
339 }
340
init_mem_domain_module(void)341 static int init_mem_domain_module(void)
342 {
343 int ret;
344
345 ARG_UNUSED(ret);
346
347 max_partitions = arch_mem_domain_max_partitions_get();
348 /*
349 * max_partitions must be less than or equal to
350 * CONFIG_MAX_DOMAIN_PARTITIONS, or would encounter array index
351 * out of bounds error.
352 */
353 __ASSERT(max_partitions <= CONFIG_MAX_DOMAIN_PARTITIONS, "");
354
355 ret = k_mem_domain_init(&k_mem_domain_default, 0, NULL);
356 __ASSERT(ret == 0, "failed to init default mem domain");
357
358 #ifdef Z_LIBC_PARTITION_EXISTS
359 ret = k_mem_domain_add_partition(&k_mem_domain_default,
360 &z_libc_partition);
361 __ASSERT(ret == 0, "failed to add default libc mem partition");
362 #endif /* Z_LIBC_PARTITION_EXISTS */
363
364 return 0;
365 }
366
367 SYS_INIT(init_mem_domain_module, PRE_KERNEL_1,
368 CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
369