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