xref: /optee_os/core/kernel/transfer_list.c (revision 79f8990d9d28539864d8f97f9f1cb32e289e595f)
1 // SPDX-License-Identifier: BSD-2-Clause
2 /*
3  * Copyright (c) 2023, Linaro Limited
4  */
5 
6 /*******************************************************************************
7  * Transfer list library compliant with the Firmware Handoff specification at:
8  * https://github.com/FirmwareHandoff/firmware_handoff
9  ******************************************************************************/
10 
11 #include <kernel/cache_helpers.h>
12 #include <kernel/panic.h>
13 #include <kernel/transfer_list.h>
14 #include <mm/core_memprot.h>
15 #include <mm/core_mmu.h>
16 #include <string.h>
17 #include <util.h>
18 
19 /*******************************************************************************
20  * Adapt a physical address to match the maximum transfer entry data alignment
21  * required by an existing transfer list.
22  * Compliant with 2.4.6 of Firmware Handoff specification (v0.9).
23  * @pa: Physical address for adapting.
24  * @tl: Pointer to the existing transfer list.
25  * Return the adapted physical address.
26  ******************************************************************************/
27 static paddr_t get_align_base_addr(paddr_t pa,
28 				   struct transfer_list_header *tl)
29 {
30 	paddr_t align_mask = TL_ALIGNMENT_FROM_ORDER(tl->alignment) - 1;
31 	paddr_t align_off = (paddr_t)tl & align_mask;
32 	paddr_t new_addr = (pa & ~align_mask) + align_off;
33 
34 	if (new_addr < pa)
35 		new_addr += TL_ALIGNMENT_FROM_ORDER(tl->alignment);
36 
37 	return new_addr;
38 }
39 
40 static void unmap_list(struct transfer_list_header *tl, size_t sz)
41 {
42 	if (core_mmu_remove_mapping(MEM_AREA_TRANSFER_LIST, tl, sz))
43 		panic("Failed to remove transfer list mapping");
44 }
45 
46 struct transfer_list_header *transfer_list_map(paddr_t pa)
47 {
48 	struct transfer_list_header *tl = NULL;
49 	size_t sz = SMALL_PAGE_SIZE;
50 	size_t old_sz = 0;
51 
52 	while (true) {
53 		tl = core_mmu_add_mapping(MEM_AREA_TRANSFER_LIST, pa, sz);
54 		if (!tl) {
55 			EMSG("Failed to map TL with PA %#"PRIxPA", size %#zx",
56 			     pa, sz);
57 			return NULL;
58 		}
59 		old_sz = sz;
60 
61 		if (transfer_list_check_header(tl) == TL_OPS_NONE) {
62 			unmap_list(tl, sz);
63 			return NULL;
64 		}
65 
66 		if (tl->max_size <= sz)
67 			return tl;
68 
69 		sz = ROUNDUP(tl->max_size, SMALL_PAGE_SIZE);
70 		unmap_list(tl, old_sz);
71 	}
72 }
73 
74 void transfer_list_unmap_sync(struct transfer_list_header *tl)
75 {
76 	size_t sz = tl->max_size;
77 
78 	transfer_list_update_checksum(tl);
79 	dcache_cleaninv_range(tl, sz);
80 	unmap_list(tl, sz);
81 }
82 
83 void transfer_list_unmap_nosync(struct transfer_list_header *tl)
84 {
85 	unmap_list(tl, tl->max_size);
86 }
87 
88 void transfer_list_dump(struct transfer_list_header *tl)
89 {
90 	struct transfer_list_entry *tl_e = NULL;
91 	int i __maybe_unused = 0;
92 
93 	if (!tl)
94 		return;
95 
96 	DMSG("Dump transfer list:");
97 	DMSG("signature  %#"PRIx32, tl->signature);
98 	DMSG("checksum   %#"PRIx8, tl->checksum);
99 	DMSG("version    %#"PRIx8, tl->version);
100 	DMSG("hdr_size   %#"PRIx8, tl->hdr_size);
101 	DMSG("alignment  %#"PRIx8, tl->alignment);
102 	DMSG("size       %#"PRIx32, tl->size);
103 	DMSG("max_size   %#"PRIx32, tl->max_size);
104 	DMSG("flags      %#"PRIx32, tl->flags);
105 	while (true) {
106 		tl_e = transfer_list_next(tl, tl_e);
107 		if (!tl_e)
108 			break;
109 
110 		DMSG("Entry %d:", i++);
111 		DMSG("tag_id     %#"PRIx16, tl_e->tag_id);
112 		DMSG("hdr_size   %#"PRIx8, tl_e->hdr_size);
113 		DMSG("data_size  %#"PRIx32, tl_e->data_size);
114 		DMSG("data_addr  %#"PRIxVA,
115 		     (vaddr_t)transfer_list_entry_data(tl_e));
116 	}
117 }
118 
119 /*******************************************************************************
120  * Creating a transfer list in a specified reserved memory region.
121  * Compliant with 2.4.5 of Firmware Handoff specification (v0.9).
122  * @pa: Physical address for residing the new transfer list.
123  * @max_size: Maximum size of the new transfer list.
124  * Return pointer to the created transfer list or NULL on error.
125  ******************************************************************************/
126 struct transfer_list_header *transfer_list_init(paddr_t pa, size_t max_size)
127 {
128 	struct transfer_list_header *tl = NULL;
129 	int align = TL_ALIGNMENT_FROM_ORDER(TRANSFER_LIST_INIT_MAX_ALIGN);
130 
131 	if (!pa || !max_size)
132 		return NULL;
133 
134 	if (!IS_ALIGNED(pa, align) || !IS_ALIGNED(max_size, align) ||
135 	    max_size < sizeof(*tl))
136 		return NULL;
137 
138 	tl = core_mmu_add_mapping(MEM_AREA_TRANSFER_LIST, pa, max_size);
139 	if (!tl)
140 		return NULL;
141 
142 	memset(tl, 0, max_size);
143 	tl->signature = TRANSFER_LIST_SIGNATURE;
144 	tl->version = TRANSFER_LIST_VERSION;
145 	tl->hdr_size = sizeof(*tl);
146 	tl->alignment = TRANSFER_LIST_INIT_MAX_ALIGN; /* initial max align */
147 	tl->size = sizeof(*tl); /* initial size is the size of header */
148 	tl->max_size = max_size;
149 	tl->flags = TL_FLAGS_HAS_CHECKSUM;
150 
151 	transfer_list_update_checksum(tl);
152 
153 	return tl;
154 }
155 
156 /*******************************************************************************
157  * Relocating a transfer list to a specified reserved memory region.
158  * Compliant with 2.4.6 of Firmware Handoff specification (v0.9).
159  * @tl: Pointer to the transfer list for relocating.
160  * @pa: Physical address for relocating the transfer list.
161  * @max_size: Maximum size of the transfer list after relocating
162  * Return pointer to the relocated transfer list or NULL on error.
163  ******************************************************************************/
164 struct transfer_list_header *
165 transfer_list_relocate(struct transfer_list_header *tl, paddr_t pa,
166 		       size_t max_size)
167 {
168 	paddr_t new_addr = 0;
169 	struct transfer_list_header *new_tl = NULL;
170 	size_t new_max_size = 0;
171 
172 	if (!tl || !pa || !max_size)
173 		return NULL;
174 
175 	new_addr = get_align_base_addr(pa, tl);
176 	new_max_size = max_size - (new_addr - pa);
177 
178 	/* The new space is not sufficient for the TL */
179 	if (tl->size > new_max_size)
180 		return NULL;
181 
182 	new_tl = core_mmu_add_mapping(MEM_AREA_TRANSFER_LIST, new_addr,
183 				      new_max_size);
184 	if (!new_tl)
185 		return NULL;
186 
187 	memmove(new_tl, tl, tl->size);
188 	new_tl->max_size = new_max_size;
189 
190 	transfer_list_update_checksum(new_tl);
191 	transfer_list_unmap_nosync(tl);
192 
193 	return new_tl;
194 }
195 
196 /*******************************************************************************
197  * Verifying the header of a transfer list.
198  * Compliant with 2.4.1 of Firmware Handoff specification (v0.9).
199  * @tl: Pointer to the transfer list.
200  * Return transfer list operation status code.
201  ******************************************************************************/
202 int transfer_list_check_header(const struct transfer_list_header *tl)
203 {
204 	if (!tl)
205 		return TL_OPS_NONE;
206 
207 	if (tl->signature != TRANSFER_LIST_SIGNATURE) {
208 		EMSG("Bad transfer list signature %#"PRIx32, tl->signature);
209 		return TL_OPS_NONE;
210 	}
211 
212 	if (!tl->max_size) {
213 		EMSG("Bad transfer list max size %#"PRIx32, tl->max_size);
214 		return TL_OPS_NONE;
215 	}
216 
217 	if (tl->size > tl->max_size) {
218 		EMSG("Bad transfer list size %#"PRIx32, tl->size);
219 		return TL_OPS_NONE;
220 	}
221 
222 	if (tl->hdr_size != sizeof(struct transfer_list_header)) {
223 		EMSG("Bad transfer list header size %#"PRIx8, tl->hdr_size);
224 		return TL_OPS_NONE;
225 	}
226 
227 	if (!transfer_list_verify_checksum(tl)) {
228 		EMSG("Bad transfer list checksum %#"PRIx8, tl->checksum);
229 		return TL_OPS_NONE;
230 	}
231 
232 	if (tl->version == 0) {
233 		EMSG("Transfer list version is invalid");
234 		return TL_OPS_NONE;
235 	} else if (tl->version == TRANSFER_LIST_VERSION) {
236 		DMSG("Transfer list version is valid for all operations");
237 		return TL_OPS_ALL;
238 	} else if (tl->version > TRANSFER_LIST_VERSION) {
239 		DMSG("Transfer list version is valid for read-only");
240 		return TL_OPS_RO;
241 	}
242 
243 	DMSG("Old transfer list version is detected");
244 	return TL_OPS_CUS;
245 }
246 
247 /*******************************************************************************
248  * Enumerate the next transfer entry.
249  * @tl: Pointer to the transfer list.
250  * @cur: Pointer to the current transfer entry where we want to search for the
251  *       next one.
252  * Return pointer to the next transfer entry or NULL on error or if @cur is the
253  * last entry.
254  ******************************************************************************/
255 struct transfer_list_entry *transfer_list_next(struct transfer_list_header *tl,
256 					       struct transfer_list_entry *cur)
257 {
258 	struct transfer_list_entry *tl_e = NULL;
259 	vaddr_t tl_ev = 0;
260 	vaddr_t va = 0;
261 	vaddr_t ev = 0;
262 	size_t sz = 0;
263 
264 	if (!tl)
265 		return NULL;
266 
267 	tl_ev = (vaddr_t)tl + tl->size;
268 
269 	if (cur) {
270 		va = (vaddr_t)cur;
271 		/* check if the total size overflow */
272 		if (ADD_OVERFLOW(cur->hdr_size, cur->data_size, &sz))
273 			return NULL;
274 		/* roundup to the next entry */
275 		if (ADD_OVERFLOW(va, sz, &va) ||
276 		    ROUNDUP_OVERFLOW(va, TRANSFER_LIST_GRANULE, &va))
277 			return NULL;
278 	} else {
279 		va = (vaddr_t)tl + tl->hdr_size;
280 	}
281 
282 	tl_e = (struct transfer_list_entry *)va;
283 
284 	if (va + sizeof(*tl_e) > tl_ev || tl_e->hdr_size < sizeof(*tl_e) ||
285 	    ADD_OVERFLOW(tl_e->hdr_size, tl_e->data_size, &sz) ||
286 	    ADD_OVERFLOW(va, sz, &ev) || ev > tl_ev)
287 		return NULL;
288 
289 	return tl_e;
290 }
291 
292 /*******************************************************************************
293  * Calculate the byte sum (modulo 256) of a transfer list.
294  * @tl: Pointer to the transfer list.
295  * Return byte sum of the transfer list.
296  ******************************************************************************/
297 static uint8_t calc_byte_sum(const struct transfer_list_header *tl)
298 {
299 	uint8_t *b = (uint8_t *)tl;
300 	uint8_t cs = 0;
301 	size_t n = 0;
302 
303 	for (n = 0; n < tl->size; n++)
304 		cs += b[n];
305 
306 	return cs;
307 }
308 
309 /*******************************************************************************
310  * Update the checksum of a transfer list.
311  * @tl: Pointer to the transfer list.
312  * Return updated checksum of the transfer list.
313  ******************************************************************************/
314 void transfer_list_update_checksum(struct transfer_list_header *tl)
315 {
316 	uint8_t cs = 0;
317 
318 	if (!tl || !(tl->flags & TL_FLAGS_HAS_CHECKSUM))
319 		return;
320 
321 	cs = calc_byte_sum(tl);
322 	cs -= tl->checksum;
323 	cs = 256 - cs;
324 	tl->checksum = cs;
325 	assert(transfer_list_verify_checksum(tl));
326 }
327 
328 /*******************************************************************************
329  * Verify the checksum of a transfer list.
330  * @tl: Pointer to the transfer list.
331  * Return true if verified or false if not.
332  ******************************************************************************/
333 bool transfer_list_verify_checksum(const struct transfer_list_header *tl)
334 {
335 	if (!tl)
336 		return false;
337 
338 	if (!(tl->flags & TL_FLAGS_HAS_CHECKSUM))
339 		return true;
340 
341 	return !calc_byte_sum(tl);
342 }
343 
344 /*******************************************************************************
345  * Update the data size of a transfer entry.
346  * @tl: Pointer to the transfer list.
347  * @tl_e: Pointer to the transfer entry.
348  * @new_data_size: New data size of the transfer entry.
349  * Return true on success or false on error.
350  ******************************************************************************/
351 bool transfer_list_set_data_size(struct transfer_list_header *tl,
352 				 struct transfer_list_entry *tl_e,
353 				 uint32_t new_data_size)
354 {
355 	vaddr_t tl_old_ev = 0;
356 	vaddr_t new_ev = 0;
357 	vaddr_t old_ev = 0;
358 	vaddr_t r_new_ev = 0;
359 	struct transfer_list_entry *dummy_te = NULL;
360 	size_t gap = 0;
361 	size_t mov_dis = 0;
362 	size_t sz = 0;
363 
364 	if (!tl || !tl_e)
365 		return false;
366 
367 	tl_old_ev = (vaddr_t)tl + tl->size;
368 
369 	/*
370 	 * Calculate the old and new end of transfer entry
371 	 * both must be roundup to align with TRANSFER_LIST_GRANULE
372 	 */
373 	if (ADD_OVERFLOW(tl_e->hdr_size, tl_e->data_size, &sz) ||
374 	    ADD_OVERFLOW((vaddr_t)tl_e, sz, &old_ev) ||
375 	    ROUNDUP_OVERFLOW(old_ev, TRANSFER_LIST_GRANULE, &old_ev))
376 		return false;
377 
378 	if (ADD_OVERFLOW(tl_e->hdr_size, new_data_size, &sz) ||
379 	    ADD_OVERFLOW((vaddr_t)tl_e, sz, &new_ev) ||
380 	    ROUNDUP_OVERFLOW(new_ev, TRANSFER_LIST_GRANULE, &new_ev))
381 		return false;
382 
383 	if (new_ev > old_ev) {
384 		/*
385 		 * Move distance should be rounded up to match the entry data
386 		 * alignment.
387 		 * Ensure that the increased size doesn't exceed the max size
388 		 * of TL
389 		 */
390 		mov_dis = new_ev - old_ev;
391 		if (ROUNDUP_OVERFLOW(mov_dis,
392 				     TL_ALIGNMENT_FROM_ORDER(tl->alignment),
393 				     &mov_dis) ||
394 		    tl->size + mov_dis > tl->max_size) {
395 			return false;
396 		}
397 		r_new_ev = old_ev + mov_dis;
398 		tl->size += mov_dis;
399 	} else {
400 		/*
401 		 * Move distance should be rounded down to match the entry data
402 		 * alignment.
403 		 */
404 		mov_dis = ROUNDDOWN(old_ev - new_ev,
405 				    TL_ALIGNMENT_FROM_ORDER(tl->alignment));
406 		r_new_ev = old_ev - mov_dis;
407 		tl->size -= mov_dis;
408 	}
409 	/* Move all following entries to fit in the expanded or shrunk space */
410 	memmove((void *)r_new_ev, (void *)old_ev, tl_old_ev - old_ev);
411 
412 	/*
413 	 * Fill the gap due to round up/down with a void entry if the size of
414 	 * the gap is more than an entry header.
415 	 */
416 	gap = r_new_ev - new_ev;
417 	if (gap >= sizeof(*dummy_te)) {
418 		/* Create a dummy transfer entry to fill up the gap */
419 		dummy_te = (struct transfer_list_entry *)new_ev;
420 		dummy_te->tag_id = TL_TAG_EMPTY;
421 		dummy_te->reserved0 = 0;
422 		dummy_te->hdr_size = sizeof(*dummy_te);
423 		dummy_te->data_size = gap - sizeof(*dummy_te);
424 	}
425 
426 	tl_e->data_size = new_data_size;
427 
428 	transfer_list_update_checksum(tl);
429 	return true;
430 }
431 
432 /*******************************************************************************
433  * Remove a specified transfer entry from a transfer list.
434  * @tl: Pointer to the transfer list.
435  * @tl_e: Pointer to the transfer entry.
436  * Return true on success or false on error.
437  ******************************************************************************/
438 bool transfer_list_rem(struct transfer_list_header *tl,
439 		       struct transfer_list_entry *tl_e)
440 {
441 	if (!tl || !tl_e || (vaddr_t)tl_e > (vaddr_t)tl + tl->size)
442 		return false;
443 
444 	tl_e->tag_id = TL_TAG_EMPTY;
445 	tl_e->reserved0 = 0;
446 	transfer_list_update_checksum(tl);
447 	return true;
448 }
449 
450 /*******************************************************************************
451  * Add a new transfer entry into a transfer list.
452  * Compliant with 2.4.3 of Firmware Handoff specification (v0.9).
453  * @tl: Pointer to the transfer list.
454  * @tag_id: Tag ID for the new transfer entry.
455  * @data_size: Data size of the new transfer entry.
456  * @data: Pointer to the data for the new transfer entry.
457  *        NULL to skip data copying.
458  * Return pointer to the added transfer entry or NULL on error.
459  ******************************************************************************/
460 struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl,
461 					      uint16_t tag_id,
462 					      uint32_t data_size,
463 					      const void *data)
464 {
465 	vaddr_t max_tl_ev = 0;
466 	vaddr_t tl_ev = 0;
467 	vaddr_t ev = 0;
468 	struct transfer_list_entry *tl_e = NULL;
469 	size_t sz = 0;
470 
471 	if (!tl)
472 		return NULL;
473 
474 	max_tl_ev = (vaddr_t)tl + tl->max_size;
475 	tl_ev = (vaddr_t)tl + tl->size;
476 	ev = tl_ev;
477 
478 	/*
479 	 * Skip the step 1 (optional step).
480 	 * New transfer entry will be added into the tail
481 	 */
482 	if (ADD_OVERFLOW(sizeof(*tl_e), data_size, &sz) ||
483 	    ADD_OVERFLOW(ev, sz, &ev) ||
484 	    ROUNDUP_OVERFLOW(ev, TRANSFER_LIST_GRANULE, &ev) ||
485 	    ev > max_tl_ev) {
486 		return NULL;
487 	}
488 
489 	tl_e = (struct transfer_list_entry *)tl_ev;
490 	*tl_e = (struct transfer_list_entry){
491 		.tag_id = tag_id,
492 		.hdr_size = sizeof(*tl_e),
493 		.data_size = data_size,
494 	};
495 
496 	tl->size += ev - tl_ev;
497 
498 	if (data)
499 		memmove(tl_e + tl_e->hdr_size, data, data_size);
500 
501 	transfer_list_update_checksum(tl);
502 
503 	return tl_e;
504 }
505 
506 /*******************************************************************************
507  * Add a new transfer entry into a transfer list with specified new data
508  * alignment requirement.
509  * Compliant with 2.4.4 of Firmware Handoff specification (v0.9).
510  * @tl: Pointer to the transfer list.
511  * @tag_id: Tag ID for the new transfer entry.
512  * @data_size: Data size of the new transfer entry.
513  * @data: Pointer to the data for the new transfer entry.
514  * @alignment: New data alignment specified as a power of two.
515  * Return pointer to the added transfer entry or NULL on error.
516  ******************************************************************************/
517 struct transfer_list_entry *
518 transfer_list_add_with_align(struct transfer_list_header *tl, uint16_t tag_id,
519 			     uint32_t data_size, const void *data,
520 			     uint8_t alignment)
521 {
522 	struct transfer_list_entry *tl_e = NULL;
523 	vaddr_t tl_ev = 0;
524 	vaddr_t ev = 0;
525 	vaddr_t new_tl_ev = 0;
526 	size_t dummy_te_data_sz = 0;
527 
528 	if (!tl)
529 		return NULL;
530 
531 	tl_ev = (vaddr_t)tl + tl->size;
532 	ev = tl_ev + sizeof(struct transfer_list_entry);
533 
534 	if (!IS_ALIGNED(ev, TL_ALIGNMENT_FROM_ORDER(alignment))) {
535 		/*
536 		 * Transfer entry data address is not aligned to the new
537 		 * alignment. Fill the gap with an empty transfer entry as a
538 		 * placeholder before adding the desired transfer entry
539 		 */
540 		new_tl_ev = ROUNDUP(ev, TL_ALIGNMENT_FROM_ORDER(alignment)) -
541 			    sizeof(struct transfer_list_entry);
542 		assert(new_tl_ev - tl_ev > sizeof(struct transfer_list_entry));
543 		dummy_te_data_sz = new_tl_ev - tl_ev -
544 				   sizeof(struct transfer_list_entry);
545 		if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz,
546 				       NULL)) {
547 			return NULL;
548 		}
549 	}
550 
551 	tl_e = transfer_list_add(tl, tag_id, data_size, data);
552 
553 	if (alignment > tl->alignment) {
554 		tl->alignment = alignment;
555 		transfer_list_update_checksum(tl);
556 	}
557 
558 	return tl_e;
559 }
560 
561 /*******************************************************************************
562  * Search for an existing transfer entry with the specified tag id from a
563  * transfer list.
564  * @tl: Pointer to the transfer list.
565  * @tag_id: Tag ID to match a transfer entry.
566  * Return pointer to the found transfer entry or NULL if not found.
567  ******************************************************************************/
568 struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl,
569 					       uint16_t tag_id)
570 {
571 	struct transfer_list_entry *tl_e = NULL;
572 
573 	do {
574 		tl_e = transfer_list_next(tl, tl_e);
575 	} while (tl_e && tl_e->tag_id != tag_id);
576 
577 	return tl_e;
578 }
579 
580 /*******************************************************************************
581  * Retrieve the data pointer of a specified transfer entry.
582  * @tl_e: Pointer to the transfer entry.
583  * Return pointer to the transfer entry data or NULL on error.
584  ******************************************************************************/
585 void *transfer_list_entry_data(struct transfer_list_entry *tl_e)
586 {
587 	if (!tl_e)
588 		return NULL;
589 
590 	return (uint8_t *)tl_e + tl_e->hdr_size;
591 }
592