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 ******************************************************************************/
get_align_base_addr(paddr_t pa,struct transfer_list_header * tl)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
unmap_list(struct transfer_list_header * tl,size_t sz)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
transfer_list_map(paddr_t pa)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
transfer_list_unmap_sync(struct transfer_list_header * tl)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
transfer_list_unmap_nosync(struct transfer_list_header * tl)83 void transfer_list_unmap_nosync(struct transfer_list_header *tl)
84 {
85 unmap_list(tl, tl->max_size);
86 }
87
transfer_list_dump(struct transfer_list_header * tl)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 ******************************************************************************/
transfer_list_init(paddr_t pa,size_t max_size)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 *
transfer_list_relocate(struct transfer_list_header * tl,paddr_t pa,size_t max_size)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 ******************************************************************************/
transfer_list_check_header(const struct transfer_list_header * tl)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 ******************************************************************************/
transfer_list_next(struct transfer_list_header * tl,struct transfer_list_entry * cur)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 ******************************************************************************/
calc_byte_sum(const struct transfer_list_header * tl)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 ******************************************************************************/
transfer_list_update_checksum(struct transfer_list_header * tl)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 ******************************************************************************/
transfer_list_verify_checksum(const struct transfer_list_header * tl)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 ******************************************************************************/
transfer_list_set_data_size(struct transfer_list_header * tl,struct transfer_list_entry * tl_e,uint32_t new_data_size)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 (ROUNDUP2_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 = ROUNDDOWN2(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 if (tl_old_ev > old_ev) {
411 memmove((void *)r_new_ev, (void *)old_ev, tl_old_ev - old_ev);
412 /*
413 * Fill the gap due to round up/down with a void entry if the
414 * size of 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
427 tl_e->data_size = new_data_size;
428
429 transfer_list_update_checksum(tl);
430 return true;
431 }
432
433 /*******************************************************************************
434 * Remove a specified transfer entry from a transfer list.
435 * @tl: Pointer to the transfer list.
436 * @tl_e: Pointer to the transfer entry.
437 * Return true on success or false on error.
438 ******************************************************************************/
transfer_list_rem(struct transfer_list_header * tl,struct transfer_list_entry * tl_e)439 bool transfer_list_rem(struct transfer_list_header *tl,
440 struct transfer_list_entry *tl_e)
441 {
442 if (!tl || !tl_e || (vaddr_t)tl_e > (vaddr_t)tl + tl->size)
443 return false;
444
445 tl_e->tag_id = TL_TAG_EMPTY;
446 tl_e->reserved0 = 0;
447 transfer_list_update_checksum(tl);
448 return true;
449 }
450
451 /*******************************************************************************
452 * Add a new transfer entry into a transfer list.
453 * Compliant with 2.4.3 of Firmware Handoff specification (v0.9).
454 * @tl: Pointer to the transfer list.
455 * @tag_id: Tag ID for the new transfer entry.
456 * @data_size: Data size of the new transfer entry.
457 * @data: Pointer to the data for the new transfer entry.
458 * NULL to skip data copying.
459 * Return pointer to the added transfer entry or NULL on error.
460 ******************************************************************************/
transfer_list_add(struct transfer_list_header * tl,uint16_t tag_id,uint32_t data_size,const void * data)461 struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl,
462 uint16_t tag_id,
463 uint32_t data_size,
464 const void *data)
465 {
466 vaddr_t max_tl_ev = 0;
467 vaddr_t tl_ev = 0;
468 vaddr_t ev = 0;
469 struct transfer_list_entry *tl_e = NULL;
470 size_t sz = 0;
471
472 if (!tl)
473 return NULL;
474
475 max_tl_ev = (vaddr_t)tl + tl->max_size;
476 tl_ev = (vaddr_t)tl + tl->size;
477 if (ROUNDUP_OVERFLOW(tl_ev, TRANSFER_LIST_GRANULE, &ev))
478 return NULL;
479
480 tl_e = (struct transfer_list_entry *)ev;
481
482 /*
483 * Skip the step 1 (optional step).
484 * New transfer entry will be added into the tail
485 */
486 if (ADD_OVERFLOW(sizeof(*tl_e), data_size, &sz) ||
487 ADD_OVERFLOW(ev, sz, &ev) || ev > max_tl_ev)
488 return NULL;
489
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(transfer_list_entry_data(tl_e), 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 *
transfer_list_add_with_align(struct transfer_list_header * tl,uint16_t tag_id,uint32_t data_size,const void * data,uint8_t alignment)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 if (ROUNDUP_OVERFLOW(tl_ev, TRANSFER_LIST_GRANULE, &tl_ev))
533 return NULL;
534
535 ev = tl_ev + sizeof(struct transfer_list_entry);
536
537 if (!IS_ALIGNED(ev, TL_ALIGNMENT_FROM_ORDER(alignment))) {
538 /*
539 * Transfer entry data address is not aligned to the new
540 * alignment. Fill the gap with an empty transfer entry as a
541 * placeholder before adding the desired transfer entry
542 */
543 new_tl_ev = ROUNDUP2(ev, TL_ALIGNMENT_FROM_ORDER(alignment)) -
544 sizeof(struct transfer_list_entry);
545 assert(new_tl_ev - tl_ev > sizeof(struct transfer_list_entry));
546 dummy_te_data_sz = new_tl_ev - tl_ev -
547 sizeof(struct transfer_list_entry);
548 if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz,
549 NULL)) {
550 return NULL;
551 }
552 }
553
554 tl_e = transfer_list_add(tl, tag_id, data_size, data);
555
556 if (alignment > tl->alignment) {
557 tl->alignment = alignment;
558 transfer_list_update_checksum(tl);
559 }
560
561 return tl_e;
562 }
563
564 /*******************************************************************************
565 * Search for an existing transfer entry with the specified tag id from a
566 * transfer list.
567 * @tl: Pointer to the transfer list.
568 * @tag_id: Tag ID to match a transfer entry.
569 * Return pointer to the found transfer entry or NULL if not found.
570 ******************************************************************************/
transfer_list_find(struct transfer_list_header * tl,uint16_t tag_id)571 struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl,
572 uint16_t tag_id)
573 {
574 struct transfer_list_entry *tl_e = NULL;
575
576 do {
577 tl_e = transfer_list_next(tl, tl_e);
578 } while (tl_e && tl_e->tag_id != tag_id);
579
580 return tl_e;
581 }
582
583 /*******************************************************************************
584 * Retrieve the data pointer of a specified transfer entry.
585 * @tl_e: Pointer to the transfer entry.
586 * Return pointer to the transfer entry data or NULL on error.
587 ******************************************************************************/
transfer_list_entry_data(struct transfer_list_entry * tl_e)588 void *transfer_list_entry_data(struct transfer_list_entry *tl_e)
589 {
590 if (!tl_e)
591 return NULL;
592
593 return (uint8_t *)tl_e + tl_e->hdr_size;
594 }
595