blob: 4d4a167edf898bf88824733d866f479372b5a294 [file] [log] [blame]
Raymond Mao98983392023-07-25 07:53:35 -07001/*
2 * Copyright (c) 2023, Linaro Limited and Contributors. All rights reserved.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
Raymond Mao60c3c972023-10-04 09:19:16 -07007#include <arch.h>
Raymond Mao98983392023-07-25 07:53:35 -07008#include <assert.h>
9#include <inttypes.h>
10#include <string.h>
11
12#include <common/debug.h>
13#include <lib/transfer_list.h>
14#include <lib/utils_def.h>
15
16void transfer_list_dump(struct transfer_list_header *tl)
17{
18 struct transfer_list_entry *te = NULL;
19 int i = 0;
20
21 if (!tl) {
22 return;
23 }
Raymond Mao60c3c972023-10-04 09:19:16 -070024 INFO("Dump transfer list:\n");
25 INFO("signature 0x%x\n", tl->signature);
26 INFO("checksum 0x%x\n", tl->checksum);
27 INFO("version 0x%x\n", tl->version);
28 INFO("hdr_size 0x%x\n", tl->hdr_size);
29 INFO("alignment 0x%x\n", tl->alignment);
30 INFO("size 0x%x\n", tl->size);
31 INFO("max_size 0x%x\n", tl->max_size);
32 INFO("flags 0x%x\n", tl->flags);
Raymond Mao98983392023-07-25 07:53:35 -070033 while (true) {
34 te = transfer_list_next(tl, te);
35 if (!te) {
36 break;
37 }
Harrison Mutaicebad0e2024-12-13 09:14:59 +000038
Raymond Mao60c3c972023-10-04 09:19:16 -070039 INFO("Entry %d:\n", i++);
Harrison Mutaicebad0e2024-12-13 09:14:59 +000040 transfer_entry_dump(te);
41 }
42}
43
44void transfer_entry_dump(struct transfer_list_entry *te)
45{
46 if (te) {
Raymond Mao60c3c972023-10-04 09:19:16 -070047 INFO("tag_id 0x%x\n", te->tag_id);
48 INFO("hdr_size 0x%x\n", te->hdr_size);
49 INFO("data_size 0x%x\n", te->data_size);
50 INFO("data_addr 0x%lx\n",
51 (unsigned long)transfer_list_entry_data(te));
Raymond Mao98983392023-07-25 07:53:35 -070052 }
53}
54
55/*******************************************************************************
Raymond Mao60c3c972023-10-04 09:19:16 -070056 * Set the handoff arguments according to the transfer list payload
57 * Return pointer to the entry point info if arguments are set properly
58 * or NULL if not
59 ******************************************************************************/
60entry_point_info_t *
61transfer_list_set_handoff_args(struct transfer_list_header *tl,
62 entry_point_info_t *ep_info)
63{
64 struct transfer_list_entry *te = NULL;
65 void *dt = NULL;
66
67 if (!ep_info || !tl || transfer_list_check_header(tl) == TL_OPS_NON) {
68 return NULL;
69 }
70
71 te = transfer_list_find(tl, TL_TAG_FDT);
72 dt = transfer_list_entry_data(te);
73
levi.yun010d2ae2024-05-13 10:27:17 +010074#ifdef __aarch64__
75 if (GET_RW(ep_info->spsr) == MODE_RW_64) {
Raymond Mao60c3c972023-10-04 09:19:16 -070076 ep_info->args.arg0 = (uintptr_t)dt;
levi.yun010d2ae2024-05-13 10:27:17 +010077 ep_info->args.arg1 = TRANSFER_LIST_HANDOFF_X1_VALUE(REGISTER_CONVENTION_VERSION);
Raymond Mao60c3c972023-10-04 09:19:16 -070078 ep_info->args.arg2 = 0;
levi.yun010d2ae2024-05-13 10:27:17 +010079 } else
80#endif
81 {
82 ep_info->args.arg0 = 0;
83 ep_info->args.arg1 = TRANSFER_LIST_HANDOFF_R1_VALUE(REGISTER_CONVENTION_VERSION);
84 ep_info->args.arg2 = (uintptr_t)dt;
Raymond Mao60c3c972023-10-04 09:19:16 -070085 }
86
levi.yun010d2ae2024-05-13 10:27:17 +010087 ep_info->args.arg3 = (uintptr_t)tl;
88
Raymond Mao60c3c972023-10-04 09:19:16 -070089 return ep_info;
90}
91
92/*******************************************************************************
Raymond Mao98983392023-07-25 07:53:35 -070093 * Creating a transfer list in a reserved memory region specified
94 * Compliant to 2.4.5 of Firmware handoff specification (v0.9)
95 * Return pointer to the created transfer list or NULL on error
96 ******************************************************************************/
97struct transfer_list_header *transfer_list_init(void *addr, size_t max_size)
98{
99 struct transfer_list_header *tl = addr;
100
101 if (!addr || max_size == 0) {
102 return NULL;
103 }
104
105 if (!is_aligned((uintptr_t)addr, 1 << TRANSFER_LIST_INIT_MAX_ALIGN) ||
106 !is_aligned(max_size, 1 << TRANSFER_LIST_INIT_MAX_ALIGN) ||
107 max_size < sizeof(*tl)) {
108 return NULL;
109 }
110
111 memset(tl, 0, max_size);
112 tl->signature = TRANSFER_LIST_SIGNATURE;
113 tl->version = TRANSFER_LIST_VERSION;
114 tl->hdr_size = sizeof(*tl);
Raymond Mao60c3c972023-10-04 09:19:16 -0700115 tl->alignment = TRANSFER_LIST_INIT_MAX_ALIGN; /* initial max align */
116 tl->size = sizeof(*tl); /* initial size is the size of header */
Raymond Mao98983392023-07-25 07:53:35 -0700117 tl->max_size = max_size;
Raymond Mao60c3c972023-10-04 09:19:16 -0700118 tl->flags = TL_FLAGS_HAS_CHECKSUM;
Raymond Mao98983392023-07-25 07:53:35 -0700119
120 transfer_list_update_checksum(tl);
121
122 return tl;
123}
124
125/*******************************************************************************
126 * Relocating a transfer list to a reserved memory region specified
127 * Compliant to 2.4.6 of Firmware handoff specification (v0.9)
Raymond Mao60c3c972023-10-04 09:19:16 -0700128 * Return pointer to the relocated transfer list or NULL on error
Raymond Mao98983392023-07-25 07:53:35 -0700129 ******************************************************************************/
Raymond Mao60c3c972023-10-04 09:19:16 -0700130struct transfer_list_header *
131transfer_list_relocate(struct transfer_list_header *tl, void *addr,
132 size_t max_size)
Raymond Mao98983392023-07-25 07:53:35 -0700133{
134 uintptr_t new_addr, align_mask, align_off;
135 struct transfer_list_header *new_tl;
136 uint32_t new_max_size;
137
138 if (!tl || !addr || max_size == 0) {
139 return NULL;
140 }
141
142 align_mask = (1 << tl->alignment) - 1;
143 align_off = (uintptr_t)tl & align_mask;
144 new_addr = ((uintptr_t)addr & ~align_mask) + align_off;
145
146 if (new_addr < (uintptr_t)addr) {
147 new_addr += (1 << tl->alignment);
148 }
149
150 new_max_size = max_size - (new_addr - (uintptr_t)addr);
151
Raymond Mao60c3c972023-10-04 09:19:16 -0700152 /* the new space is not sufficient for the tl */
Raymond Mao98983392023-07-25 07:53:35 -0700153 if (tl->size > new_max_size) {
154 return NULL;
155 }
156
157 new_tl = (struct transfer_list_header *)new_addr;
158 memmove(new_tl, tl, tl->size);
159 new_tl->max_size = new_max_size;
160
161 transfer_list_update_checksum(new_tl);
162
163 return new_tl;
164}
165
166/*******************************************************************************
167 * Verifying the header of a transfer list
168 * Compliant to 2.4.1 of Firmware handoff specification (v0.9)
169 * Return transfer list operation status code
170 ******************************************************************************/
Raymond Mao60c3c972023-10-04 09:19:16 -0700171enum transfer_list_ops
172transfer_list_check_header(const struct transfer_list_header *tl)
Raymond Mao98983392023-07-25 07:53:35 -0700173{
174 if (!tl) {
175 return TL_OPS_NON;
176 }
177
178 if (tl->signature != TRANSFER_LIST_SIGNATURE) {
Harrison Mutaie19293a2024-09-27 16:04:55 +0000179 VERBOSE("Bad transfer list signature 0x%x\n", tl->signature);
Raymond Mao98983392023-07-25 07:53:35 -0700180 return TL_OPS_NON;
181 }
182
183 if (!tl->max_size) {
Harrison Mutaie19293a2024-09-27 16:04:55 +0000184 VERBOSE("Bad transfer list max size 0x%x\n", tl->max_size);
Raymond Mao98983392023-07-25 07:53:35 -0700185 return TL_OPS_NON;
186 }
187
188 if (tl->size > tl->max_size) {
Harrison Mutaie19293a2024-09-27 16:04:55 +0000189 VERBOSE("Bad transfer list size 0x%x\n", tl->size);
Raymond Mao98983392023-07-25 07:53:35 -0700190 return TL_OPS_NON;
191 }
192
193 if (tl->hdr_size != sizeof(struct transfer_list_header)) {
Harrison Mutaie19293a2024-09-27 16:04:55 +0000194 VERBOSE("Bad transfer list header size 0x%x\n", tl->hdr_size);
Raymond Mao98983392023-07-25 07:53:35 -0700195 return TL_OPS_NON;
196 }
197
198 if (!transfer_list_verify_checksum(tl)) {
Harrison Mutaie19293a2024-09-27 16:04:55 +0000199 VERBOSE("Bad transfer list checksum 0x%x\n", tl->checksum);
Raymond Mao98983392023-07-25 07:53:35 -0700200 return TL_OPS_NON;
201 }
202
203 if (tl->version == 0) {
Harrison Mutaie19293a2024-09-27 16:04:55 +0000204 VERBOSE("Transfer list version is invalid\n");
Raymond Mao98983392023-07-25 07:53:35 -0700205 return TL_OPS_NON;
206 } else if (tl->version == TRANSFER_LIST_VERSION) {
207 INFO("Transfer list version is valid for all operations\n");
208 return TL_OPS_ALL;
209 } else if (tl->version > TRANSFER_LIST_VERSION) {
210 INFO("Transfer list version is valid for read-only\n");
211 return TL_OPS_RO;
212 }
213
214 INFO("Old transfer list version is detected\n");
215 return TL_OPS_CUS;
216}
217
218/*******************************************************************************
219 * Enumerate the next transfer entry
220 * Return pointer to the next transfer entry or NULL on error
221 ******************************************************************************/
222struct transfer_list_entry *transfer_list_next(struct transfer_list_header *tl,
223 struct transfer_list_entry *last)
224{
225 struct transfer_list_entry *te = NULL;
226 uintptr_t tl_ev = 0;
227 uintptr_t va = 0;
228 uintptr_t ev = 0;
229 size_t sz = 0;
230
231 if (!tl) {
232 return NULL;
233 }
234
235 tl_ev = (uintptr_t)tl + tl->size;
236
237 if (last) {
238 va = (uintptr_t)last;
Raymond Mao60c3c972023-10-04 09:19:16 -0700239 /* check if the total size overflow */
240 if (add_overflow(last->hdr_size, last->data_size, &sz)) {
Raymond Mao98983392023-07-25 07:53:35 -0700241 return NULL;
242 }
Raymond Mao60c3c972023-10-04 09:19:16 -0700243 /* roundup to the next entry */
244 if (add_with_round_up_overflow(va, sz, TRANSFER_LIST_GRANULE,
245 &va)) {
Raymond Mao98983392023-07-25 07:53:35 -0700246 return NULL;
247 }
248 } else {
249 va = (uintptr_t)tl + tl->hdr_size;
250 }
251
252 te = (struct transfer_list_entry *)va;
253
254 if (va + sizeof(*te) > tl_ev || te->hdr_size < sizeof(*te) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700255 add_overflow(te->hdr_size, te->data_size, &sz) ||
256 add_overflow(va, sz, &ev) || ev > tl_ev) {
Raymond Mao98983392023-07-25 07:53:35 -0700257 return NULL;
258 }
259
260 return te;
261}
262
263/*******************************************************************************
264 * Calculate the byte sum of a transfer list
265 * Return byte sum of the transfer list
266 ******************************************************************************/
267static uint8_t calc_byte_sum(const struct transfer_list_header *tl)
268{
269 uint8_t *b = (uint8_t *)tl;
270 uint8_t cs = 0;
271 size_t n = 0;
272
Raymond Mao98983392023-07-25 07:53:35 -0700273 for (n = 0; n < tl->size; n++) {
274 cs += b[n];
275 }
276
277 return cs;
278}
279
280/*******************************************************************************
281 * Update the checksum of a transfer list
282 * Return updated checksum of the transfer list
283 ******************************************************************************/
284void transfer_list_update_checksum(struct transfer_list_header *tl)
285{
286 uint8_t cs;
287
Raymond Mao60c3c972023-10-04 09:19:16 -0700288 if (!tl || !(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
Raymond Mao98983392023-07-25 07:53:35 -0700289 return;
290 }
291
292 cs = calc_byte_sum(tl);
293 cs -= tl->checksum;
294 cs = 256 - cs;
295 tl->checksum = cs;
296 assert(transfer_list_verify_checksum(tl));
297}
298
299/*******************************************************************************
300 * Verify the checksum of a transfer list
301 * Return true if verified or false if not
302 ******************************************************************************/
303bool transfer_list_verify_checksum(const struct transfer_list_header *tl)
304{
Raymond Mao60c3c972023-10-04 09:19:16 -0700305 if (!tl) {
306 return false;
307 }
308
309 if (!(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
310 return true;
311 }
312
Raymond Mao98983392023-07-25 07:53:35 -0700313 return !calc_byte_sum(tl);
314}
315
316/*******************************************************************************
317 * Update the data size of a transfer entry
318 * Return true on success or false on error
319 ******************************************************************************/
320bool transfer_list_set_data_size(struct transfer_list_header *tl,
321 struct transfer_list_entry *te,
322 uint32_t new_data_size)
323{
324 uintptr_t tl_old_ev, new_ev = 0, old_ev = 0, ru_new_ev;
325 struct transfer_list_entry *dummy_te = NULL;
326 size_t gap = 0;
327 size_t mov_dis = 0;
328 size_t sz = 0;
329
330 if (!tl || !te) {
331 return false;
332 }
333 tl_old_ev = (uintptr_t)tl + tl->size;
334
Raymond Mao60c3c972023-10-04 09:19:16 -0700335 /*
336 * calculate the old and new end of TE
337 * both must be roundup to align with TRANSFER_LIST_GRANULE
338 */
Raymond Mao98983392023-07-25 07:53:35 -0700339 if (add_overflow(te->hdr_size, te->data_size, &sz) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700340 add_with_round_up_overflow((uintptr_t)te, sz, TRANSFER_LIST_GRANULE,
341 &old_ev)) {
Raymond Mao98983392023-07-25 07:53:35 -0700342 return false;
343 }
344 if (add_overflow(te->hdr_size, new_data_size, &sz) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700345 add_with_round_up_overflow((uintptr_t)te, sz, TRANSFER_LIST_GRANULE,
346 &new_ev)) {
Raymond Mao98983392023-07-25 07:53:35 -0700347 return false;
348 }
349
350 if (new_ev > old_ev) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700351 /*
352 * move distance should be roundup
353 * to meet the requirement of TE data max alignment
354 * ensure that the increased size doesn't exceed
355 * the max size of TL
356 */
Raymond Mao98983392023-07-25 07:53:35 -0700357 mov_dis = new_ev - old_ev;
Raymond Mao60c3c972023-10-04 09:19:16 -0700358 if (round_up_overflow(mov_dis, 1 << tl->alignment, &mov_dis) ||
359 tl->size + mov_dis > tl->max_size) {
Raymond Mao98983392023-07-25 07:53:35 -0700360 return false;
361 }
362 ru_new_ev = old_ev + mov_dis;
363 memmove((void *)ru_new_ev, (void *)old_ev, tl_old_ev - old_ev);
364 tl->size += mov_dis;
365 gap = ru_new_ev - new_ev;
366 } else {
367 gap = old_ev - new_ev;
368 }
369
370 if (gap >= sizeof(*dummy_te)) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700371 /* create a dummy TE to fill up the gap */
Raymond Mao98983392023-07-25 07:53:35 -0700372 dummy_te = (struct transfer_list_entry *)new_ev;
373 dummy_te->tag_id = TL_TAG_EMPTY;
Raymond Mao98983392023-07-25 07:53:35 -0700374 dummy_te->hdr_size = sizeof(*dummy_te);
375 dummy_te->data_size = gap - sizeof(*dummy_te);
376 }
377
378 te->data_size = new_data_size;
379
380 transfer_list_update_checksum(tl);
381 return true;
382}
383
384/*******************************************************************************
385 * Remove a specified transfer entry from a transfer list
386 * Return true on success or false on error
387 ******************************************************************************/
388bool transfer_list_rem(struct transfer_list_header *tl,
Raymond Mao60c3c972023-10-04 09:19:16 -0700389 struct transfer_list_entry *te)
Raymond Mao98983392023-07-25 07:53:35 -0700390{
391 if (!tl || !te || (uintptr_t)te > (uintptr_t)tl + tl->size) {
392 return false;
393 }
394 te->tag_id = TL_TAG_EMPTY;
Raymond Mao98983392023-07-25 07:53:35 -0700395 transfer_list_update_checksum(tl);
396 return true;
397}
398
399/*******************************************************************************
400 * Add a new transfer entry into a transfer list
401 * Compliant to 2.4.3 of Firmware handoff specification (v0.9)
402 * Return pointer to the added transfer entry or NULL on error
403 ******************************************************************************/
404struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl,
Harrison Mutai4490cd02024-03-20 14:37:51 +0000405 uint32_t tag_id,
Raymond Mao98983392023-07-25 07:53:35 -0700406 uint32_t data_size,
407 const void *data)
408{
409 uintptr_t max_tl_ev, tl_ev, ev;
410 struct transfer_list_entry *te = NULL;
411 uint8_t *te_data = NULL;
412 size_t sz = 0;
413
414 if (!tl) {
415 return NULL;
416 }
417
418 max_tl_ev = (uintptr_t)tl + tl->max_size;
419 tl_ev = (uintptr_t)tl + tl->size;
420 ev = tl_ev;
421
Raymond Mao60c3c972023-10-04 09:19:16 -0700422 /*
423 * skip the step 1 (optional step)
424 * new TE will be added into the tail
425 */
Raymond Mao98983392023-07-25 07:53:35 -0700426 if (add_overflow(sizeof(*te), data_size, &sz) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700427 add_with_round_up_overflow(ev, sz, TRANSFER_LIST_GRANULE, &ev) ||
428 ev > max_tl_ev) {
Raymond Mao98983392023-07-25 07:53:35 -0700429 return NULL;
430 }
431
432 te = (struct transfer_list_entry *)tl_ev;
433 te->tag_id = tag_id;
Raymond Mao98983392023-07-25 07:53:35 -0700434 te->hdr_size = sizeof(*te);
435 te->data_size = data_size;
436 tl->size += ev - tl_ev;
437
438 if (data) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700439 /* get TE data pointer */
Raymond Mao98983392023-07-25 07:53:35 -0700440 te_data = transfer_list_entry_data(te);
441 if (!te_data) {
442 return NULL;
443 }
444 memmove(te_data, data, data_size);
445 }
446
447 transfer_list_update_checksum(tl);
448
449 return te;
450}
451
452/*******************************************************************************
453 * Add a new transfer entry into a transfer list with specified new data
454 * alignment requirement
455 * Compliant to 2.4.4 of Firmware handoff specification (v0.9)
456 * Return pointer to the added transfer entry or NULL on error
457 ******************************************************************************/
Raymond Mao60c3c972023-10-04 09:19:16 -0700458struct transfer_list_entry *
Harrison Mutai4490cd02024-03-20 14:37:51 +0000459transfer_list_add_with_align(struct transfer_list_header *tl, uint32_t tag_id,
Raymond Mao60c3c972023-10-04 09:19:16 -0700460 uint32_t data_size, const void *data,
461 uint8_t alignment)
Raymond Mao98983392023-07-25 07:53:35 -0700462{
463 struct transfer_list_entry *te = NULL;
464 uintptr_t tl_ev, ev, new_tl_ev;
465 size_t dummy_te_data_sz = 0;
466
467 if (!tl) {
468 return NULL;
469 }
470
471 tl_ev = (uintptr_t)tl + tl->size;
472 ev = tl_ev + sizeof(struct transfer_list_entry);
473
474 if (!is_aligned(ev, 1 << alignment)) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700475 /*
476 * TE data address is not aligned to the new alignment
477 * fill the gap with an empty TE as a placeholder before
478 * adding the desire TE
479 */
Raymond Mao98983392023-07-25 07:53:35 -0700480 new_tl_ev = round_up(ev, 1 << alignment) -
Raymond Mao60c3c972023-10-04 09:19:16 -0700481 sizeof(struct transfer_list_entry);
482 dummy_te_data_sz =
483 new_tl_ev - tl_ev - sizeof(struct transfer_list_entry);
Raymond Mao98983392023-07-25 07:53:35 -0700484 if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz,
Raymond Mao60c3c972023-10-04 09:19:16 -0700485 NULL)) {
Raymond Mao98983392023-07-25 07:53:35 -0700486 return NULL;
487 }
488 }
489
490 te = transfer_list_add(tl, tag_id, data_size, data);
491
492 if (alignment > tl->alignment) {
493 tl->alignment = alignment;
494 transfer_list_update_checksum(tl);
495 }
496
497 return te;
498}
499
500/*******************************************************************************
501 * Search for an existing transfer entry with the specified tag id from a
502 * transfer list
503 * Return pointer to the found transfer entry or NULL on error
504 ******************************************************************************/
505struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl,
Harrison Mutai4490cd02024-03-20 14:37:51 +0000506 uint32_t tag_id)
Raymond Mao98983392023-07-25 07:53:35 -0700507{
508 struct transfer_list_entry *te = NULL;
509
510 do {
511 te = transfer_list_next(tl, te);
Harrison Mutai4490cd02024-03-20 14:37:51 +0000512 } while (te && (te->tag_id != tag_id));
Raymond Mao98983392023-07-25 07:53:35 -0700513
514 return te;
515}
516
517/*******************************************************************************
518 * Retrieve the data pointer of a specified transfer entry
519 * Return pointer to the transfer entry data or NULL on error
520 ******************************************************************************/
521void *transfer_list_entry_data(struct transfer_list_entry *entry)
522{
523 if (!entry) {
524 return NULL;
525 }
526 return (uint8_t *)entry + entry->hdr_size;
527}
Harrison Mutaiae1f7802024-11-06 10:03:51 +0000528
529/*******************************************************************************
530 * Verifies that the transfer list has not already been initialized, then
531 * initializes it at the specified memory location.
532 *
533 * Return pointer to the transfer list or NULL on error
534 * *****************************************************************************/
535struct transfer_list_header *transfer_list_ensure(void *addr, size_t size)
536{
537 struct transfer_list_header *tl = NULL;
538
539 if (transfer_list_check_header(addr) == TL_OPS_ALL) {
540 return (struct transfer_list_header *)addr;
541 }
542
543 tl = transfer_list_init((void *)addr, size);
544
545 return tl;
546}