blob: 8b7d0d0a25b46994e751d476d77ebe062c8d34b9 [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
12#include <math_utils.h>
13#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 Mutaidb83bfa2025-03-21 15:24:55 +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
67 if (!is_aligned((uintptr_t)addr, 1 << TRANSFER_LIST_INIT_MAX_ALIGN) ||
68 !is_aligned(max_size, 1 << TRANSFER_LIST_INIT_MAX_ALIGN) ||
69 max_size < sizeof(*tl)) {
70 return NULL;
71 }
72
73 memset(tl, 0, max_size);
74 tl->signature = TRANSFER_LIST_SIGNATURE;
75 tl->version = TRANSFER_LIST_VERSION;
76 tl->hdr_size = sizeof(*tl);
77 tl->alignment = TRANSFER_LIST_INIT_MAX_ALIGN; /* initial max align */
78 tl->size = sizeof(*tl); /* initial size is the size of header */
79 tl->max_size = max_size;
80 tl->flags = TL_FLAGS_HAS_CHECKSUM;
81
82 transfer_list_update_checksum(tl);
83
84 return tl;
85}
86
87/*******************************************************************************
88 * Relocating a transfer list to a reserved memory region specified
89 * Compliant to 2.4.6 of Firmware handoff specification (v0.9)
90 * Return pointer to the relocated transfer list or NULL on error
91 ******************************************************************************/
92struct transfer_list_header *
93transfer_list_relocate(struct transfer_list_header *tl, void *addr,
94 size_t max_size)
95{
96 uintptr_t new_addr, align_mask, align_off;
97 struct transfer_list_header *new_tl;
98 uint32_t new_max_size;
99
100 if (!tl || !addr || max_size == 0) {
101 return NULL;
102 }
103
104 align_mask = (1 << tl->alignment) - 1;
105 align_off = (uintptr_t)tl & align_mask;
106 new_addr = ((uintptr_t)addr & ~align_mask) + align_off;
107
108 if (new_addr < (uintptr_t)addr) {
109 new_addr += (1 << tl->alignment);
110 }
111
112 new_max_size = max_size - (new_addr - (uintptr_t)addr);
113
114 /* the new space is not sufficient for the tl */
115 if (tl->size > new_max_size) {
116 return NULL;
117 }
118
119 new_tl = (struct transfer_list_header *)new_addr;
120 memmove(new_tl, tl, tl->size);
121 new_tl->max_size = new_max_size;
122
123 transfer_list_update_checksum(new_tl);
124
125 return new_tl;
126}
127
128/*******************************************************************************
129 * Verifying the header of a transfer list
130 * Compliant to 2.4.1 of Firmware handoff specification (v0.9)
131 * Return transfer list operation status code
132 ******************************************************************************/
133enum transfer_list_ops
134transfer_list_check_header(const struct transfer_list_header *tl)
135{
136 if (!tl) {
137 return TL_OPS_NON;
138 }
139
140 if (tl->signature != TRANSFER_LIST_SIGNATURE) {
141 printf("Bad transfer list signature %#" PRIx32 "\n",
142 tl->signature);
143 return TL_OPS_NON;
144 }
145
146 if (!tl->max_size) {
147 printf("Bad transfer list max size %#" PRIx32 "\n",
148 tl->max_size);
149 return TL_OPS_NON;
150 }
151
152 if (tl->size > tl->max_size) {
153 printf("Bad transfer list size %#" PRIx32 "\n", tl->size);
154 return TL_OPS_NON;
155 }
156
157 if (tl->hdr_size != sizeof(struct transfer_list_header)) {
158 printf("Bad transfer list header size %#" PRIx32 "\n",
159 tl->hdr_size);
160 return TL_OPS_NON;
161 }
162
163 if (!transfer_list_verify_checksum(tl)) {
164 printf("Bad transfer list checksum %#" PRIx32 "\n",
165 tl->checksum);
166 return TL_OPS_NON;
167 }
168
169 if (tl->version == 0) {
170 printf("Transfer list version is invalid\n");
171 return TL_OPS_NON;
172 } else if (tl->version == TRANSFER_LIST_VERSION) {
173 printf("Transfer list version is valid for all operations\n");
174 return TL_OPS_ALL;
175 } else if (tl->version > TRANSFER_LIST_VERSION) {
176 printf("Transfer list version is valid for read-only\n");
177 return TL_OPS_RO;
178 }
179
180 printf("Old transfer list version is detected\n");
181 return TL_OPS_CUS;
182}
183
184/*******************************************************************************
185 * Enumerate the next transfer entry
186 * Return pointer to the next transfer entry or NULL on error
187 ******************************************************************************/
188struct transfer_list_entry *transfer_list_next(struct transfer_list_header *tl,
189 struct transfer_list_entry *last)
190{
191 struct transfer_list_entry *te = NULL;
192 uintptr_t tl_ev = 0;
193 uintptr_t va = 0;
194 uintptr_t ev = 0;
195 size_t sz = 0;
196
197 if (!tl) {
198 return NULL;
199 }
200
201 tl_ev = (uintptr_t)tl + tl->size;
202
203 if (last) {
204 va = (uintptr_t)last;
205 /* check if the total size overflow */
206 if (add_overflow(last->hdr_size, last->data_size, &sz)) {
207 return NULL;
208 }
209 /* roundup to the next entry */
210 if (add_with_round_up_overflow(va, sz, TRANSFER_LIST_GRANULE,
211 &va)) {
212 return NULL;
213 }
214 } else {
215 va = (uintptr_t)tl + tl->hdr_size;
216 }
217
218 te = (struct transfer_list_entry *)va;
219
220 if (va + sizeof(*te) > tl_ev || te->hdr_size < sizeof(*te) ||
221 add_overflow(te->hdr_size, te->data_size, &sz) ||
222 add_overflow(va, sz, &ev) || ev > tl_ev) {
223 return NULL;
224 }
225
226 return te;
227}
228
229/*******************************************************************************
230 * Calculate the byte sum of a transfer list
231 * Return byte sum of the transfer list
232 ******************************************************************************/
233static uint8_t calc_byte_sum(const struct transfer_list_header *tl)
234{
235 uint8_t *b = (uint8_t *)tl;
236 uint8_t cs = 0;
237 size_t n = 0;
238
239 for (n = 0; n < tl->size; n++) {
240 cs += b[n];
241 }
242
243 return cs;
244}
245
246/*******************************************************************************
247 * Update the checksum of a transfer list
248 * Return updated checksum of the transfer list
249 ******************************************************************************/
250void transfer_list_update_checksum(struct transfer_list_header *tl)
251{
252 uint8_t cs;
253
254 if (!tl || !(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
255 return;
256 }
257
258 cs = calc_byte_sum(tl);
259 cs -= tl->checksum;
260 cs = 256 - cs;
261 tl->checksum = cs;
262 assert(transfer_list_verify_checksum(tl));
263}
264
265/*******************************************************************************
266 * Verify the checksum of a transfer list
267 * Return true if verified or false if not
268 ******************************************************************************/
269bool transfer_list_verify_checksum(const struct transfer_list_header *tl)
270{
271 if (!tl) {
272 return false;
273 }
274
275 if (!(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
276 return true;
277 }
278
279 return !calc_byte_sum(tl);
280}
281
282/*******************************************************************************
283 * Update the data size of a transfer entry
284 * Return true on success or false on error
285 ******************************************************************************/
286bool transfer_list_set_data_size(struct transfer_list_header *tl,
287 struct transfer_list_entry *te,
288 uint32_t new_data_size)
289{
290 uintptr_t tl_old_ev, new_ev = 0, old_ev = 0, ru_new_ev;
291 struct transfer_list_entry *dummy_te = NULL;
292 size_t gap = 0;
293 size_t mov_dis = 0;
294 size_t sz = 0;
295
296 if (!tl || !te) {
297 return false;
298 }
299 tl_old_ev = (uintptr_t)tl + tl->size;
300
301 /*
302 * calculate the old and new end of TE
303 * both must be roundup to align with TRANSFER_LIST_GRANULE
304 */
305 if (add_overflow(te->hdr_size, te->data_size, &sz) ||
306 add_with_round_up_overflow((uintptr_t)te, sz, TRANSFER_LIST_GRANULE,
307 &old_ev)) {
308 return false;
309 }
310 if (add_overflow(te->hdr_size, new_data_size, &sz) ||
311 add_with_round_up_overflow((uintptr_t)te, sz, TRANSFER_LIST_GRANULE,
312 &new_ev)) {
313 return false;
314 }
315
316 if (new_ev > old_ev) {
317 /*
318 * move distance should be roundup
319 * to meet the requirement of TE data max alignment
320 * ensure that the increased size doesn't exceed
321 * the max size of TL
322 */
323 mov_dis = new_ev - old_ev;
324 if (round_up_overflow(mov_dis, 1 << tl->alignment, &mov_dis) ||
325 tl->size + mov_dis > tl->max_size) {
326 return false;
327 }
328 ru_new_ev = old_ev + mov_dis;
329 memmove((void *)ru_new_ev, (void *)old_ev, tl_old_ev - old_ev);
330 tl->size += mov_dis;
331 gap = ru_new_ev - new_ev;
332 } else {
333 gap = old_ev - new_ev;
334 }
335
336 if (gap >= sizeof(*dummy_te)) {
337 /* create a dummy TE to fill up the gap */
338 dummy_te = (struct transfer_list_entry *)new_ev;
339 dummy_te->tag_id = TL_TAG_EMPTY;
340 dummy_te->hdr_size = sizeof(*dummy_te);
341 dummy_te->data_size = gap - sizeof(*dummy_te);
342 }
343
344 te->data_size = new_data_size;
345
346 transfer_list_update_checksum(tl);
347 return true;
348}
349
350/*******************************************************************************
351 * Remove a specified transfer entry from a transfer list
352 * Return true on success or false on error
353 ******************************************************************************/
354bool transfer_list_rem(struct transfer_list_header *tl,
355 struct transfer_list_entry *te)
356{
357 if (!tl || !te || (uintptr_t)te > (uintptr_t)tl + tl->size) {
358 return false;
359 }
360 te->tag_id = TL_TAG_EMPTY;
361 transfer_list_update_checksum(tl);
362 return true;
363}
364
365/*******************************************************************************
366 * Add a new transfer entry into a transfer list
367 * Compliant to 2.4.3 of Firmware handoff specification (v0.9)
368 * Return pointer to the added transfer entry or NULL on error
369 ******************************************************************************/
370struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl,
371 uint32_t tag_id,
372 uint32_t data_size,
373 const void *data)
374{
Govindraj Raja442c09a2025-04-10 16:30:22 +0200375 uintptr_t max_tl_ev, tl_ev, ev;
Manish Pandey65fe3642025-03-21 12:44:42 +0000376 struct transfer_list_entry *te = NULL;
377 uint8_t *te_data = NULL;
378 uintptr_t te_end;
Govindraj Raja442c09a2025-04-10 16:30:22 +0200379 size_t sz = 0;
Manish Pandey65fe3642025-03-21 12:44:42 +0000380
381 if (!tl || (tag_id & (1 << 24))) {
382 return NULL;
383 }
384
385 /*
386 * skip the step 1 (optional step)
387 * new TE will be added into the tail
388 */
389 tl_ev = (uintptr_t)tl + tl->size;
390 te = (struct transfer_list_entry *)align_up(tl_ev,
391 TRANSFER_LIST_GRANULE);
392
393 te_end = align_up((uintptr_t)te + sizeof(*te) + data_size,
394 TRANSFER_LIST_GRANULE);
395
396 if (te_end > (uintptr_t)tl + tl->max_size) {
397 return NULL;
398 }
399
400 te->tag_id = tag_id;
401 te->hdr_size = sizeof(*te);
402 te->data_size = data_size;
403 tl->size += te_end - tl_ev;
404
405 if (data) {
406 /* get TE data pointer */
407 te_data = transfer_list_entry_data(te);
408 if (!te_data) {
409 return NULL;
410 }
411 memmove(te_data, data, data_size);
412 }
413
414 transfer_list_update_checksum(tl);
415
416 return te;
417}
418
419/*******************************************************************************
420 * Add a new transfer entry into a transfer list with specified new data
421 * alignment requirement
422 * Compliant to 2.4.4 of Firmware handoff specification (v0.9)
423 * Return pointer to the added transfer entry or NULL on error
424 ******************************************************************************/
425struct transfer_list_entry *
426transfer_list_add_with_align(struct transfer_list_header *tl, uint32_t tag_id,
427 uint32_t data_size, const void *data,
428 uint8_t alignment)
429{
430 struct transfer_list_entry *te = NULL;
431 uintptr_t tl_ev, ev, new_tl_ev;
432 size_t dummy_te_data_sz = 0;
433
434 if (!tl) {
435 return NULL;
436 }
437
438 tl_ev = (uintptr_t)tl + tl->size;
439 ev = tl_ev + sizeof(struct transfer_list_entry);
440
441 if (!is_aligned(ev, 1 << alignment)) {
442 /*
443 * TE data address is not aligned to the new alignment
444 * fill the gap with an empty TE as a placeholder before
445 * adding the desire TE
446 */
447 new_tl_ev = align_up(ev, 1 << alignment) -
448 sizeof(struct transfer_list_entry);
449 dummy_te_data_sz =
450 new_tl_ev - tl_ev - sizeof(struct transfer_list_entry);
451 if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz,
452 NULL)) {
453 return NULL;
454 }
455 }
456
457 te = transfer_list_add(tl, tag_id, data_size, data);
458
459 if (alignment > tl->alignment) {
460 tl->alignment = alignment;
461 transfer_list_update_checksum(tl);
462 }
463
464 return te;
465}
466
467/*******************************************************************************
468 * Search for an existing transfer entry with the specified tag id from a
469 * transfer list
470 * Return pointer to the found transfer entry or NULL on error
471 ******************************************************************************/
472struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl,
473 uint32_t tag_id)
474{
475 struct transfer_list_entry *te = NULL;
476
477 do {
478 te = transfer_list_next(tl, te);
479 } while (te && (te->tag_id != tag_id));
480
481 return te;
482}
483
484/*******************************************************************************
485 * Retrieve the data pointer of a specified transfer entry
486 * Return pointer to the transfer entry data or NULL on error
487 ******************************************************************************/
488void *transfer_list_entry_data(struct transfer_list_entry *entry)
489{
490 if (!entry) {
491 return NULL;
492 }
493 return (uint8_t *)entry + entry->hdr_size;
494}
Harrison Mutaidb83bfa2025-03-21 15:24:55 +0000495
496/*******************************************************************************
497 * Verifies that the transfer list has not already been initialized, then
498 * initializes it at the specified memory location.
499 *
500 * Return pointer to the transfer list or NULL on error
501 * *****************************************************************************/
502struct transfer_list_header *transfer_list_ensure(void *addr, size_t size)
503{
504 struct transfer_list_header *tl = NULL;
505
506 if (transfer_list_check_header(addr) == TL_OPS_ALL) {
507 return (struct transfer_list_header *)addr;
508 }
509
510 tl = transfer_list_init((void *)addr, size);
511
512 return tl;
513}