blob: 381786185f1e82393ff5fa29528cc05a21ebc646 [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 Mutai4ce43b92024-11-28 14:31:41 +0000179 ERROR("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 Mutai4ce43b92024-11-28 14:31:41 +0000184 ERROR("Bad transfer list max size 0x%x\n",
Raymond Mao98983392023-07-25 07:53:35 -0700185 tl->max_size);
186 return TL_OPS_NON;
187 }
188
189 if (tl->size > tl->max_size) {
Harrison Mutai4ce43b92024-11-28 14:31:41 +0000190 ERROR("Bad transfer list size 0x%x\n", tl->size);
Raymond Mao98983392023-07-25 07:53:35 -0700191 return TL_OPS_NON;
192 }
193
194 if (tl->hdr_size != sizeof(struct transfer_list_header)) {
Harrison Mutai4ce43b92024-11-28 14:31:41 +0000195 ERROR("Bad transfer list header size 0x%x\n",
Raymond Mao60c3c972023-10-04 09:19:16 -0700196 tl->hdr_size);
Raymond Mao98983392023-07-25 07:53:35 -0700197 return TL_OPS_NON;
198 }
199
200 if (!transfer_list_verify_checksum(tl)) {
Harrison Mutai4ce43b92024-11-28 14:31:41 +0000201 ERROR("Bad transfer list checksum 0x%x\n",
Raymond Mao60c3c972023-10-04 09:19:16 -0700202 tl->checksum);
Raymond Mao98983392023-07-25 07:53:35 -0700203 return TL_OPS_NON;
204 }
205
206 if (tl->version == 0) {
207 ERROR("Transfer list version is invalid\n");
208 return TL_OPS_NON;
209 } else if (tl->version == TRANSFER_LIST_VERSION) {
210 INFO("Transfer list version is valid for all operations\n");
211 return TL_OPS_ALL;
212 } else if (tl->version > TRANSFER_LIST_VERSION) {
213 INFO("Transfer list version is valid for read-only\n");
214 return TL_OPS_RO;
215 }
216
217 INFO("Old transfer list version is detected\n");
218 return TL_OPS_CUS;
219}
220
221/*******************************************************************************
222 * Enumerate the next transfer entry
223 * Return pointer to the next transfer entry or NULL on error
224 ******************************************************************************/
225struct transfer_list_entry *transfer_list_next(struct transfer_list_header *tl,
226 struct transfer_list_entry *last)
227{
228 struct transfer_list_entry *te = NULL;
229 uintptr_t tl_ev = 0;
230 uintptr_t va = 0;
231 uintptr_t ev = 0;
232 size_t sz = 0;
233
234 if (!tl) {
235 return NULL;
236 }
237
238 tl_ev = (uintptr_t)tl + tl->size;
239
240 if (last) {
241 va = (uintptr_t)last;
Raymond Mao60c3c972023-10-04 09:19:16 -0700242 /* check if the total size overflow */
243 if (add_overflow(last->hdr_size, last->data_size, &sz)) {
Raymond Mao98983392023-07-25 07:53:35 -0700244 return NULL;
245 }
Raymond Mao60c3c972023-10-04 09:19:16 -0700246 /* roundup to the next entry */
247 if (add_with_round_up_overflow(va, sz, TRANSFER_LIST_GRANULE,
248 &va)) {
Raymond Mao98983392023-07-25 07:53:35 -0700249 return NULL;
250 }
251 } else {
252 va = (uintptr_t)tl + tl->hdr_size;
253 }
254
255 te = (struct transfer_list_entry *)va;
256
257 if (va + sizeof(*te) > tl_ev || te->hdr_size < sizeof(*te) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700258 add_overflow(te->hdr_size, te->data_size, &sz) ||
259 add_overflow(va, sz, &ev) || ev > tl_ev) {
Raymond Mao98983392023-07-25 07:53:35 -0700260 return NULL;
261 }
262
263 return te;
264}
265
266/*******************************************************************************
267 * Calculate the byte sum of a transfer list
268 * Return byte sum of the transfer list
269 ******************************************************************************/
270static uint8_t calc_byte_sum(const struct transfer_list_header *tl)
271{
272 uint8_t *b = (uint8_t *)tl;
273 uint8_t cs = 0;
274 size_t n = 0;
275
Raymond Mao98983392023-07-25 07:53:35 -0700276 for (n = 0; n < tl->size; n++) {
277 cs += b[n];
278 }
279
280 return cs;
281}
282
283/*******************************************************************************
284 * Update the checksum of a transfer list
285 * Return updated checksum of the transfer list
286 ******************************************************************************/
287void transfer_list_update_checksum(struct transfer_list_header *tl)
288{
289 uint8_t cs;
290
Raymond Mao60c3c972023-10-04 09:19:16 -0700291 if (!tl || !(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
Raymond Mao98983392023-07-25 07:53:35 -0700292 return;
293 }
294
295 cs = calc_byte_sum(tl);
296 cs -= tl->checksum;
297 cs = 256 - cs;
298 tl->checksum = cs;
299 assert(transfer_list_verify_checksum(tl));
300}
301
302/*******************************************************************************
303 * Verify the checksum of a transfer list
304 * Return true if verified or false if not
305 ******************************************************************************/
306bool transfer_list_verify_checksum(const struct transfer_list_header *tl)
307{
Raymond Mao60c3c972023-10-04 09:19:16 -0700308 if (!tl) {
309 return false;
310 }
311
312 if (!(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
313 return true;
314 }
315
Raymond Mao98983392023-07-25 07:53:35 -0700316 return !calc_byte_sum(tl);
317}
318
319/*******************************************************************************
320 * Update the data size of a transfer entry
321 * Return true on success or false on error
322 ******************************************************************************/
323bool transfer_list_set_data_size(struct transfer_list_header *tl,
324 struct transfer_list_entry *te,
325 uint32_t new_data_size)
326{
327 uintptr_t tl_old_ev, new_ev = 0, old_ev = 0, ru_new_ev;
328 struct transfer_list_entry *dummy_te = NULL;
329 size_t gap = 0;
330 size_t mov_dis = 0;
331 size_t sz = 0;
332
333 if (!tl || !te) {
334 return false;
335 }
336 tl_old_ev = (uintptr_t)tl + tl->size;
337
Raymond Mao60c3c972023-10-04 09:19:16 -0700338 /*
339 * calculate the old and new end of TE
340 * both must be roundup to align with TRANSFER_LIST_GRANULE
341 */
Raymond Mao98983392023-07-25 07:53:35 -0700342 if (add_overflow(te->hdr_size, te->data_size, &sz) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700343 add_with_round_up_overflow((uintptr_t)te, sz, TRANSFER_LIST_GRANULE,
344 &old_ev)) {
Raymond Mao98983392023-07-25 07:53:35 -0700345 return false;
346 }
347 if (add_overflow(te->hdr_size, new_data_size, &sz) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700348 add_with_round_up_overflow((uintptr_t)te, sz, TRANSFER_LIST_GRANULE,
349 &new_ev)) {
Raymond Mao98983392023-07-25 07:53:35 -0700350 return false;
351 }
352
353 if (new_ev > old_ev) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700354 /*
355 * move distance should be roundup
356 * to meet the requirement of TE data max alignment
357 * ensure that the increased size doesn't exceed
358 * the max size of TL
359 */
Raymond Mao98983392023-07-25 07:53:35 -0700360 mov_dis = new_ev - old_ev;
Raymond Mao60c3c972023-10-04 09:19:16 -0700361 if (round_up_overflow(mov_dis, 1 << tl->alignment, &mov_dis) ||
362 tl->size + mov_dis > tl->max_size) {
Raymond Mao98983392023-07-25 07:53:35 -0700363 return false;
364 }
365 ru_new_ev = old_ev + mov_dis;
366 memmove((void *)ru_new_ev, (void *)old_ev, tl_old_ev - old_ev);
367 tl->size += mov_dis;
368 gap = ru_new_ev - new_ev;
369 } else {
370 gap = old_ev - new_ev;
371 }
372
373 if (gap >= sizeof(*dummy_te)) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700374 /* create a dummy TE to fill up the gap */
Raymond Mao98983392023-07-25 07:53:35 -0700375 dummy_te = (struct transfer_list_entry *)new_ev;
376 dummy_te->tag_id = TL_TAG_EMPTY;
Raymond Mao98983392023-07-25 07:53:35 -0700377 dummy_te->hdr_size = sizeof(*dummy_te);
378 dummy_te->data_size = gap - sizeof(*dummy_te);
379 }
380
381 te->data_size = new_data_size;
382
383 transfer_list_update_checksum(tl);
384 return true;
385}
386
387/*******************************************************************************
388 * Remove a specified transfer entry from a transfer list
389 * Return true on success or false on error
390 ******************************************************************************/
391bool transfer_list_rem(struct transfer_list_header *tl,
Raymond Mao60c3c972023-10-04 09:19:16 -0700392 struct transfer_list_entry *te)
Raymond Mao98983392023-07-25 07:53:35 -0700393{
394 if (!tl || !te || (uintptr_t)te > (uintptr_t)tl + tl->size) {
395 return false;
396 }
397 te->tag_id = TL_TAG_EMPTY;
Raymond Mao98983392023-07-25 07:53:35 -0700398 transfer_list_update_checksum(tl);
399 return true;
400}
401
402/*******************************************************************************
403 * Add a new transfer entry into a transfer list
404 * Compliant to 2.4.3 of Firmware handoff specification (v0.9)
405 * Return pointer to the added transfer entry or NULL on error
406 ******************************************************************************/
407struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl,
Harrison Mutai4490cd02024-03-20 14:37:51 +0000408 uint32_t tag_id,
Raymond Mao98983392023-07-25 07:53:35 -0700409 uint32_t data_size,
410 const void *data)
411{
412 uintptr_t max_tl_ev, tl_ev, ev;
413 struct transfer_list_entry *te = NULL;
414 uint8_t *te_data = NULL;
415 size_t sz = 0;
416
417 if (!tl) {
418 return NULL;
419 }
420
421 max_tl_ev = (uintptr_t)tl + tl->max_size;
422 tl_ev = (uintptr_t)tl + tl->size;
423 ev = tl_ev;
424
Raymond Mao60c3c972023-10-04 09:19:16 -0700425 /*
426 * skip the step 1 (optional step)
427 * new TE will be added into the tail
428 */
Raymond Mao98983392023-07-25 07:53:35 -0700429 if (add_overflow(sizeof(*te), data_size, &sz) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700430 add_with_round_up_overflow(ev, sz, TRANSFER_LIST_GRANULE, &ev) ||
431 ev > max_tl_ev) {
Raymond Mao98983392023-07-25 07:53:35 -0700432 return NULL;
433 }
434
435 te = (struct transfer_list_entry *)tl_ev;
436 te->tag_id = tag_id;
Raymond Mao98983392023-07-25 07:53:35 -0700437 te->hdr_size = sizeof(*te);
438 te->data_size = data_size;
439 tl->size += ev - tl_ev;
440
441 if (data) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700442 /* get TE data pointer */
Raymond Mao98983392023-07-25 07:53:35 -0700443 te_data = transfer_list_entry_data(te);
444 if (!te_data) {
445 return NULL;
446 }
447 memmove(te_data, data, data_size);
448 }
449
450 transfer_list_update_checksum(tl);
451
452 return te;
453}
454
455/*******************************************************************************
456 * Add a new transfer entry into a transfer list with specified new data
457 * alignment requirement
458 * Compliant to 2.4.4 of Firmware handoff specification (v0.9)
459 * Return pointer to the added transfer entry or NULL on error
460 ******************************************************************************/
Raymond Mao60c3c972023-10-04 09:19:16 -0700461struct transfer_list_entry *
Harrison Mutai4490cd02024-03-20 14:37:51 +0000462transfer_list_add_with_align(struct transfer_list_header *tl, uint32_t tag_id,
Raymond Mao60c3c972023-10-04 09:19:16 -0700463 uint32_t data_size, const void *data,
464 uint8_t alignment)
Raymond Mao98983392023-07-25 07:53:35 -0700465{
466 struct transfer_list_entry *te = NULL;
467 uintptr_t tl_ev, ev, new_tl_ev;
468 size_t dummy_te_data_sz = 0;
469
470 if (!tl) {
471 return NULL;
472 }
473
474 tl_ev = (uintptr_t)tl + tl->size;
475 ev = tl_ev + sizeof(struct transfer_list_entry);
476
477 if (!is_aligned(ev, 1 << alignment)) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700478 /*
479 * TE data address is not aligned to the new alignment
480 * fill the gap with an empty TE as a placeholder before
481 * adding the desire TE
482 */
Raymond Mao98983392023-07-25 07:53:35 -0700483 new_tl_ev = round_up(ev, 1 << alignment) -
Raymond Mao60c3c972023-10-04 09:19:16 -0700484 sizeof(struct transfer_list_entry);
485 dummy_te_data_sz =
486 new_tl_ev - tl_ev - sizeof(struct transfer_list_entry);
Raymond Mao98983392023-07-25 07:53:35 -0700487 if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz,
Raymond Mao60c3c972023-10-04 09:19:16 -0700488 NULL)) {
Raymond Mao98983392023-07-25 07:53:35 -0700489 return NULL;
490 }
491 }
492
493 te = transfer_list_add(tl, tag_id, data_size, data);
494
495 if (alignment > tl->alignment) {
496 tl->alignment = alignment;
497 transfer_list_update_checksum(tl);
498 }
499
500 return te;
501}
502
503/*******************************************************************************
504 * Search for an existing transfer entry with the specified tag id from a
505 * transfer list
506 * Return pointer to the found transfer entry or NULL on error
507 ******************************************************************************/
508struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl,
Harrison Mutai4490cd02024-03-20 14:37:51 +0000509 uint32_t tag_id)
Raymond Mao98983392023-07-25 07:53:35 -0700510{
511 struct transfer_list_entry *te = NULL;
512
513 do {
514 te = transfer_list_next(tl, te);
Harrison Mutai4490cd02024-03-20 14:37:51 +0000515 } while (te && (te->tag_id != tag_id));
Raymond Mao98983392023-07-25 07:53:35 -0700516
517 return te;
518}
519
520/*******************************************************************************
521 * Retrieve the data pointer of a specified transfer entry
522 * Return pointer to the transfer entry data or NULL on error
523 ******************************************************************************/
524void *transfer_list_entry_data(struct transfer_list_entry *entry)
525{
526 if (!entry) {
527 return NULL;
528 }
529 return (uint8_t *)entry + entry->hdr_size;
530}
Harrison Mutaiae1f7802024-11-06 10:03:51 +0000531
532/*******************************************************************************
533 * Verifies that the transfer list has not already been initialized, then
534 * initializes it at the specified memory location.
535 *
536 * Return pointer to the transfer list or NULL on error
537 * *****************************************************************************/
538struct transfer_list_header *transfer_list_ensure(void *addr, size_t size)
539{
540 struct transfer_list_header *tl = NULL;
541
542 if (transfer_list_check_header(addr) == TL_OPS_ALL) {
543 return (struct transfer_list_header *)addr;
544 }
545
546 tl = transfer_list_init((void *)addr, size);
547
548 return tl;
549}