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