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