blob: 2e681dc482db29c742d3318f213bf9c7d7655edc [file] [log] [blame]
Manish Pandey65fe3642025-03-21 12:44:42 +00001/*
2 * Copyright The Transfer List Library Contributors
3 *
4 * SPDX-License-Identifier: MIT OR GPL-2.0-or-later
5 */
6
7#include <assert.h>
8#include <inttypes.h>
9#include <stdio.h>
10#include <string.h>
11
Harrison Mutaibd05e572025-04-16 14:20:35 +000012#include <private/math_utils.h>
Manish Pandey65fe3642025-03-21 12:44:42 +000013#include <transfer_list.h>
14
Govindraj Raja442c09a2025-04-10 16:30:22 +020015void transfer_list_dump(struct transfer_list_header *tl)
16{
Manish Pandey65fe3642025-03-21 12:44:42 +000017 struct transfer_list_entry *te = NULL;
18 int i = 0;
19
20 if (!tl) {
21 return;
22 }
23 printf("Dump transfer list:\n");
24 printf("signature 0x%x\n", tl->signature);
25 printf("checksum 0x%x\n", tl->checksum);
26 printf("version 0x%x\n", tl->version);
27 printf("hdr_size 0x%x\n", tl->hdr_size);
28 printf("alignment 0x%x\n", tl->alignment);
29 printf("size 0x%x\n", tl->size);
30 printf("max_size 0x%x\n", tl->max_size);
31 printf("flags 0x%x\n", tl->flags);
32 while (true) {
33 te = transfer_list_next(tl, te);
34 if (!te) {
35 break;
36 }
Harrison Mutaidb83bfa2025-03-21 15:24:55 +000037
Manish Pandey65fe3642025-03-21 12:44:42 +000038 printf("Entry %d:\n", i++);
Harrison Mutaidb83bfa2025-03-21 15:24:55 +000039 transfer_entry_dump(te);
40 }
41}
42
43void transfer_entry_dump(struct transfer_list_entry *te)
44{
45 if (te) {
Manish Pandey65fe3642025-03-21 12:44:42 +000046 printf("tag_id 0x%x\n", te->tag_id);
47 printf("hdr_size 0x%x\n", te->hdr_size);
48 printf("data_size 0x%x\n", te->data_size);
49 printf("data_addr 0x%lx\n",
Harrison Mutaie0bc2c82025-05-08 15:36:08 +000050 (unsigned long)transfer_list_entry_data(te));
Manish Pandey65fe3642025-03-21 12:44:42 +000051 }
52}
53
54/*******************************************************************************
55 * Creating a transfer list in a reserved memory region specified
56 * Compliant to 2.4.5 of Firmware handoff specification (v0.9)
57 * Return pointer to the created transfer list or NULL on error
58 ******************************************************************************/
59struct transfer_list_header *transfer_list_init(void *addr, size_t max_size)
60{
61 struct transfer_list_header *tl = addr;
62
63 if (!addr || max_size == 0) {
64 return NULL;
65 }
66
Harrison Mutaibd05e572025-04-16 14:20:35 +000067 if (!libtl_is_aligned((uintptr_t)addr,
68 1 << TRANSFER_LIST_INIT_MAX_ALIGN) ||
69 !libtl_is_aligned(max_size, 1 << TRANSFER_LIST_INIT_MAX_ALIGN) ||
Manish Pandey65fe3642025-03-21 12:44:42 +000070 max_size < sizeof(*tl)) {
71 return NULL;
72 }
73
74 memset(tl, 0, max_size);
75 tl->signature = TRANSFER_LIST_SIGNATURE;
76 tl->version = TRANSFER_LIST_VERSION;
77 tl->hdr_size = sizeof(*tl);
78 tl->alignment = TRANSFER_LIST_INIT_MAX_ALIGN; /* initial max align */
79 tl->size = sizeof(*tl); /* initial size is the size of header */
80 tl->max_size = max_size;
81 tl->flags = TL_FLAGS_HAS_CHECKSUM;
82
83 transfer_list_update_checksum(tl);
84
85 return tl;
86}
87
88/*******************************************************************************
89 * Relocating a transfer list to a reserved memory region specified
90 * Compliant to 2.4.6 of Firmware handoff specification (v0.9)
91 * Return pointer to the relocated transfer list or NULL on error
92 ******************************************************************************/
93struct transfer_list_header *
94transfer_list_relocate(struct transfer_list_header *tl, void *addr,
95 size_t max_size)
96{
97 uintptr_t new_addr, align_mask, align_off;
98 struct transfer_list_header *new_tl;
99 uint32_t new_max_size;
100
101 if (!tl || !addr || max_size == 0) {
102 return NULL;
103 }
104
105 align_mask = (1 << tl->alignment) - 1;
106 align_off = (uintptr_t)tl & align_mask;
107 new_addr = ((uintptr_t)addr & ~align_mask) + align_off;
108
109 if (new_addr < (uintptr_t)addr) {
110 new_addr += (1 << tl->alignment);
111 }
112
113 new_max_size = max_size - (new_addr - (uintptr_t)addr);
114
115 /* the new space is not sufficient for the tl */
116 if (tl->size > new_max_size) {
117 return NULL;
118 }
119
120 new_tl = (struct transfer_list_header *)new_addr;
121 memmove(new_tl, tl, tl->size);
122 new_tl->max_size = new_max_size;
123
124 transfer_list_update_checksum(new_tl);
125
126 return new_tl;
127}
128
129/*******************************************************************************
130 * Verifying the header of a transfer list
131 * Compliant to 2.4.1 of Firmware handoff specification (v0.9)
132 * Return transfer list operation status code
133 ******************************************************************************/
134enum transfer_list_ops
135transfer_list_check_header(const struct transfer_list_header *tl)
136{
137 if (!tl) {
138 return TL_OPS_NON;
139 }
140
141 if (tl->signature != TRANSFER_LIST_SIGNATURE) {
142 printf("Bad transfer list signature %#" PRIx32 "\n",
143 tl->signature);
144 return TL_OPS_NON;
145 }
146
147 if (!tl->max_size) {
148 printf("Bad transfer list max size %#" PRIx32 "\n",
149 tl->max_size);
150 return TL_OPS_NON;
151 }
152
153 if (tl->size > tl->max_size) {
154 printf("Bad transfer list size %#" PRIx32 "\n", tl->size);
155 return TL_OPS_NON;
156 }
157
158 if (tl->hdr_size != sizeof(struct transfer_list_header)) {
159 printf("Bad transfer list header size %#" PRIx32 "\n",
160 tl->hdr_size);
161 return TL_OPS_NON;
162 }
163
164 if (!transfer_list_verify_checksum(tl)) {
165 printf("Bad transfer list checksum %#" PRIx32 "\n",
166 tl->checksum);
167 return TL_OPS_NON;
168 }
169
170 if (tl->version == 0) {
171 printf("Transfer list version is invalid\n");
172 return TL_OPS_NON;
173 } else if (tl->version == TRANSFER_LIST_VERSION) {
174 printf("Transfer list version is valid for all operations\n");
175 return TL_OPS_ALL;
176 } else if (tl->version > TRANSFER_LIST_VERSION) {
177 printf("Transfer list version is valid for read-only\n");
178 return TL_OPS_RO;
179 }
180
181 printf("Old transfer list version is detected\n");
182 return TL_OPS_CUS;
183}
184
185/*******************************************************************************
186 * Enumerate the next transfer entry
187 * Return pointer to the next transfer entry or NULL on error
188 ******************************************************************************/
189struct transfer_list_entry *transfer_list_next(struct transfer_list_header *tl,
190 struct transfer_list_entry *last)
191{
192 struct transfer_list_entry *te = NULL;
193 uintptr_t tl_ev = 0;
194 uintptr_t va = 0;
195 uintptr_t ev = 0;
196 size_t sz = 0;
197
198 if (!tl) {
199 return NULL;
200 }
201
202 tl_ev = (uintptr_t)tl + tl->size;
203
204 if (last) {
205 va = (uintptr_t)last;
206 /* check if the total size overflow */
Harrison Mutaibd05e572025-04-16 14:20:35 +0000207 if (libtl_add_overflow(last->hdr_size, last->data_size, &sz)) {
Manish Pandey65fe3642025-03-21 12:44:42 +0000208 return NULL;
209 }
210 /* roundup to the next entry */
Harrison Mutaibd05e572025-04-16 14:20:35 +0000211 if (libtl_add_with_round_up_overflow(
212 va, sz, TRANSFER_LIST_GRANULE, &va)) {
Manish Pandey65fe3642025-03-21 12:44:42 +0000213 return NULL;
214 }
215 } else {
216 va = (uintptr_t)tl + tl->hdr_size;
217 }
218
219 te = (struct transfer_list_entry *)va;
220
221 if (va + sizeof(*te) > tl_ev || te->hdr_size < sizeof(*te) ||
Harrison Mutaibd05e572025-04-16 14:20:35 +0000222 libtl_add_overflow(te->hdr_size, te->data_size, &sz) ||
223 libtl_add_overflow(va, sz, &ev) || ev > tl_ev) {
Manish Pandey65fe3642025-03-21 12:44:42 +0000224 return NULL;
225 }
226
227 return te;
228}
229
230/*******************************************************************************
Yeoreum Yunb7dc0de2025-06-02 20:38:19 +0100231 * Enumerate the prev transfer entry
232 * Return pointer to the prev transfer entry or NULL on error
233 ******************************************************************************/
234struct transfer_list_entry *transfer_list_prev(struct transfer_list_header *tl,
235 struct transfer_list_entry *last)
236{
237 struct transfer_list_entry *prev;
238 struct transfer_list_entry *te = NULL;
239
240 if (!last || !tl || (tl + tl->hdr_size == (void *)last)) {
241 return NULL;
242 }
243
244 do {
245 prev = te;
246 te = transfer_list_next(tl, te);
247 } while (te && te != last);
248
249 return (te != NULL) ? prev : NULL;
250}
251
252/*******************************************************************************
Manish Pandey65fe3642025-03-21 12:44:42 +0000253 * Calculate the byte sum of a transfer list
254 * Return byte sum of the transfer list
255 ******************************************************************************/
256static uint8_t calc_byte_sum(const struct transfer_list_header *tl)
257{
258 uint8_t *b = (uint8_t *)tl;
259 uint8_t cs = 0;
260 size_t n = 0;
261
262 for (n = 0; n < tl->size; n++) {
263 cs += b[n];
264 }
265
266 return cs;
267}
268
269/*******************************************************************************
270 * Update the checksum of a transfer list
271 * Return updated checksum of the transfer list
272 ******************************************************************************/
273void transfer_list_update_checksum(struct transfer_list_header *tl)
274{
275 uint8_t cs;
276
277 if (!tl || !(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
278 return;
279 }
280
281 cs = calc_byte_sum(tl);
282 cs -= tl->checksum;
283 cs = 256 - cs;
284 tl->checksum = cs;
285 assert(transfer_list_verify_checksum(tl));
286}
287
288/*******************************************************************************
289 * Verify the checksum of a transfer list
290 * Return true if verified or false if not
291 ******************************************************************************/
292bool transfer_list_verify_checksum(const struct transfer_list_header *tl)
293{
294 if (!tl) {
295 return false;
296 }
297
298 if (!(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
299 return true;
300 }
301
302 return !calc_byte_sum(tl);
303}
304
305/*******************************************************************************
306 * Update the data size of a transfer entry
307 * Return true on success or false on error
308 ******************************************************************************/
309bool transfer_list_set_data_size(struct transfer_list_header *tl,
310 struct transfer_list_entry *te,
311 uint32_t new_data_size)
312{
Yeoreum Yunb7dc0de2025-06-02 20:38:19 +0100313 uintptr_t tl_old_ev, new_ev = 0, old_ev = 0, merge_ev, ru_new_ev;
Manish Pandey65fe3642025-03-21 12:44:42 +0000314 struct transfer_list_entry *dummy_te = NULL;
315 size_t gap = 0;
316 size_t mov_dis = 0;
317 size_t sz = 0;
318
319 if (!tl || !te) {
320 return false;
321 }
322 tl_old_ev = (uintptr_t)tl + tl->size;
323
324 /*
325 * calculate the old and new end of TE
326 * both must be roundup to align with TRANSFER_LIST_GRANULE
327 */
Harrison Mutaibd05e572025-04-16 14:20:35 +0000328 if (libtl_add_overflow(te->hdr_size, te->data_size, &sz) ||
329 libtl_add_with_round_up_overflow((uintptr_t)te, sz,
330 TRANSFER_LIST_GRANULE, &old_ev)) {
Manish Pandey65fe3642025-03-21 12:44:42 +0000331 return false;
332 }
Harrison Mutaibd05e572025-04-16 14:20:35 +0000333 if (libtl_add_overflow(te->hdr_size, new_data_size, &sz) ||
334 libtl_add_with_round_up_overflow((uintptr_t)te, sz,
335 TRANSFER_LIST_GRANULE, &new_ev)) {
Manish Pandey65fe3642025-03-21 12:44:42 +0000336 return false;
337 }
338
339 if (new_ev > old_ev) {
340 /*
Yeoreum Yunb7dc0de2025-06-02 20:38:19 +0100341 * When next transfer list is dummy,
342 * - if te can be extended in boundary of dummy entry,
343 * extend it to dummy entry and set new dummy entry.
344 *
345 * - otherwise, merge dummy entry with existing te and
346 * extend transfer list as much as it requires.
347 */
348 dummy_te = transfer_list_next(tl, te);
349 if (dummy_te && (dummy_te->tag_id == TL_TAG_EMPTY)) {
Harrison Mutaibd05e572025-04-16 14:20:35 +0000350 merge_ev = libtl_align_up(old_ev + dummy_te->hdr_size +
351 dummy_te->data_size,
352 TRANSFER_LIST_GRANULE);
Yeoreum Yunb7dc0de2025-06-02 20:38:19 +0100353 if (merge_ev >= new_ev) {
354 gap = merge_ev - new_ev;
355 goto set_dummy;
356 } else {
357 old_ev = merge_ev;
358 }
359 }
360
361 /*
Manish Pandey65fe3642025-03-21 12:44:42 +0000362 * move distance should be roundup
363 * to meet the requirement of TE data max alignment
364 * ensure that the increased size doesn't exceed
365 * the max size of TL
366 */
367 mov_dis = new_ev - old_ev;
Harrison Mutaibd05e572025-04-16 14:20:35 +0000368 if (libtl_round_up_overflow(mov_dis, 1 << tl->alignment,
369 &mov_dis) ||
Manish Pandey65fe3642025-03-21 12:44:42 +0000370 tl->size + mov_dis > tl->max_size) {
371 return false;
372 }
373 ru_new_ev = old_ev + mov_dis;
374 memmove((void *)ru_new_ev, (void *)old_ev, tl_old_ev - old_ev);
375 tl->size += mov_dis;
376 gap = ru_new_ev - new_ev;
377 } else {
378 gap = old_ev - new_ev;
379 }
380
Yeoreum Yunb7dc0de2025-06-02 20:38:19 +0100381set_dummy:
Manish Pandey65fe3642025-03-21 12:44:42 +0000382 if (gap >= sizeof(*dummy_te)) {
383 /* create a dummy TE to fill up the gap */
384 dummy_te = (struct transfer_list_entry *)new_ev;
385 dummy_te->tag_id = TL_TAG_EMPTY;
386 dummy_te->hdr_size = sizeof(*dummy_te);
387 dummy_te->data_size = gap - sizeof(*dummy_te);
388 }
389
390 te->data_size = new_data_size;
391
392 transfer_list_update_checksum(tl);
393 return true;
394}
395
396/*******************************************************************************
397 * Remove a specified transfer entry from a transfer list
398 * Return true on success or false on error
399 ******************************************************************************/
400bool transfer_list_rem(struct transfer_list_header *tl,
401 struct transfer_list_entry *te)
402{
Yeoreum Yunb7dc0de2025-06-02 20:38:19 +0100403 struct transfer_list_entry *prev;
404 struct transfer_list_entry *next;
405
Manish Pandey65fe3642025-03-21 12:44:42 +0000406 if (!tl || !te || (uintptr_t)te > (uintptr_t)tl + tl->size) {
407 return false;
408 }
Yeoreum Yunb7dc0de2025-06-02 20:38:19 +0100409
410 prev = transfer_list_prev(tl, te);
411 next = transfer_list_next(tl, te);
412
413 if (prev && prev->tag_id == TL_TAG_EMPTY) {
Harrison Mutaibd05e572025-04-16 14:20:35 +0000414 prev->data_size += libtl_align_up(te->hdr_size + te->data_size,
415 TRANSFER_LIST_GRANULE);
Yeoreum Yunb7dc0de2025-06-02 20:38:19 +0100416 te = prev;
417 }
418
419 if (next && next->tag_id == TL_TAG_EMPTY) {
Harrison Mutaibd05e572025-04-16 14:20:35 +0000420 te->data_size +=
421 libtl_align_up(next->hdr_size + next->data_size,
422 TRANSFER_LIST_GRANULE);
Yeoreum Yunb7dc0de2025-06-02 20:38:19 +0100423 }
424
Manish Pandey65fe3642025-03-21 12:44:42 +0000425 te->tag_id = TL_TAG_EMPTY;
426 transfer_list_update_checksum(tl);
427 return true;
428}
429
430/*******************************************************************************
431 * Add a new transfer entry into a transfer list
432 * Compliant to 2.4.3 of Firmware handoff specification (v0.9)
433 * Return pointer to the added transfer entry or NULL on error
434 ******************************************************************************/
435struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl,
436 uint32_t tag_id,
437 uint32_t data_size,
438 const void *data)
439{
Harrison Mutai7fb41222025-04-16 14:18:46 +0000440 uintptr_t tl_ev;
Manish Pandey65fe3642025-03-21 12:44:42 +0000441 struct transfer_list_entry *te = NULL;
442 uint8_t *te_data = NULL;
443 uintptr_t te_end;
Manish Pandey65fe3642025-03-21 12:44:42 +0000444
445 if (!tl || (tag_id & (1 << 24))) {
446 return NULL;
447 }
448
449 /*
450 * skip the step 1 (optional step)
451 * new TE will be added into the tail
452 */
453 tl_ev = (uintptr_t)tl + tl->size;
Harrison Mutaibd05e572025-04-16 14:20:35 +0000454 te = (struct transfer_list_entry *)libtl_align_up(
455 tl_ev, TRANSFER_LIST_GRANULE);
Manish Pandey65fe3642025-03-21 12:44:42 +0000456
Harrison Mutaibd05e572025-04-16 14:20:35 +0000457 te_end = libtl_align_up((uintptr_t)te + sizeof(*te) + data_size,
458 TRANSFER_LIST_GRANULE);
Manish Pandey65fe3642025-03-21 12:44:42 +0000459
460 if (te_end > (uintptr_t)tl + tl->max_size) {
461 return NULL;
462 }
463
464 te->tag_id = tag_id;
465 te->hdr_size = sizeof(*te);
466 te->data_size = data_size;
467 tl->size += te_end - tl_ev;
468
469 if (data) {
470 /* get TE data pointer */
471 te_data = transfer_list_entry_data(te);
472 if (!te_data) {
473 return NULL;
474 }
475 memmove(te_data, data, data_size);
476 }
477
478 transfer_list_update_checksum(tl);
479
480 return te;
481}
482
483/*******************************************************************************
484 * Add a new transfer entry into a transfer list with specified new data
485 * alignment requirement
486 * Compliant to 2.4.4 of Firmware handoff specification (v0.9)
487 * Return pointer to the added transfer entry or NULL on error
488 ******************************************************************************/
489struct transfer_list_entry *
490transfer_list_add_with_align(struct transfer_list_header *tl, uint32_t tag_id,
491 uint32_t data_size, const void *data,
492 uint8_t alignment)
493{
494 struct transfer_list_entry *te = NULL;
495 uintptr_t tl_ev, ev, new_tl_ev;
496 size_t dummy_te_data_sz = 0;
497
498 if (!tl) {
499 return NULL;
500 }
501
502 tl_ev = (uintptr_t)tl + tl->size;
503 ev = tl_ev + sizeof(struct transfer_list_entry);
504
Harrison Mutaibd05e572025-04-16 14:20:35 +0000505 if (!libtl_is_aligned(ev, 1 << alignment)) {
Manish Pandey65fe3642025-03-21 12:44:42 +0000506 /*
507 * TE data address is not aligned to the new alignment
508 * fill the gap with an empty TE as a placeholder before
509 * adding the desire TE
510 */
Harrison Mutaibd05e572025-04-16 14:20:35 +0000511 new_tl_ev = libtl_align_up(ev, 1 << alignment) -
Manish Pandey65fe3642025-03-21 12:44:42 +0000512 sizeof(struct transfer_list_entry);
513 dummy_te_data_sz =
514 new_tl_ev - tl_ev - sizeof(struct transfer_list_entry);
515 if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz,
516 NULL)) {
517 return NULL;
518 }
519 }
520
521 te = transfer_list_add(tl, tag_id, data_size, data);
522
523 if (alignment > tl->alignment) {
524 tl->alignment = alignment;
525 transfer_list_update_checksum(tl);
526 }
527
528 return te;
529}
530
531/*******************************************************************************
532 * Search for an existing transfer entry with the specified tag id from a
533 * transfer list
534 * Return pointer to the found transfer entry or NULL on error
535 ******************************************************************************/
536struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl,
537 uint32_t tag_id)
538{
539 struct transfer_list_entry *te = NULL;
540
541 do {
542 te = transfer_list_next(tl, te);
543 } while (te && (te->tag_id != tag_id));
544
545 return te;
546}
547
548/*******************************************************************************
549 * Retrieve the data pointer of a specified transfer entry
550 * Return pointer to the transfer entry data or NULL on error
551 ******************************************************************************/
552void *transfer_list_entry_data(struct transfer_list_entry *entry)
553{
554 if (!entry) {
555 return NULL;
556 }
557 return (uint8_t *)entry + entry->hdr_size;
558}
Harrison Mutaidb83bfa2025-03-21 15:24:55 +0000559
560/*******************************************************************************
561 * Verifies that the transfer list has not already been initialized, then
562 * initializes it at the specified memory location.
563 *
564 * Return pointer to the transfer list or NULL on error
565 * *****************************************************************************/
566struct transfer_list_header *transfer_list_ensure(void *addr, size_t size)
567{
568 struct transfer_list_header *tl = NULL;
569
570 if (transfer_list_check_header(addr) == TL_OPS_ALL) {
571 return (struct transfer_list_header *)addr;
572 }
573
574 tl = transfer_list_init((void *)addr, size);
575
576 return tl;
577}