blob: 8d82d259fc0ba78fedcf9bfabe4b292b366abfe5 [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 }
Raymond Mao60c3c972023-10-04 09:19:16 -070038 INFO("Entry %d:\n", i++);
39 INFO("tag_id 0x%x\n", te->tag_id);
40 INFO("hdr_size 0x%x\n", te->hdr_size);
41 INFO("data_size 0x%x\n", te->data_size);
42 INFO("data_addr 0x%lx\n",
43 (unsigned long)transfer_list_entry_data(te));
Raymond Mao98983392023-07-25 07:53:35 -070044 }
45}
46
47/*******************************************************************************
Raymond Mao60c3c972023-10-04 09:19:16 -070048 * Set the handoff arguments according to the transfer list payload
49 * Return pointer to the entry point info if arguments are set properly
50 * or NULL if not
51 ******************************************************************************/
52entry_point_info_t *
53transfer_list_set_handoff_args(struct transfer_list_header *tl,
54 entry_point_info_t *ep_info)
55{
56 struct transfer_list_entry *te = NULL;
57 void *dt = NULL;
58
59 if (!ep_info || !tl || transfer_list_check_header(tl) == TL_OPS_NON) {
60 return NULL;
61 }
62
63 te = transfer_list_find(tl, TL_TAG_FDT);
64 dt = transfer_list_entry_data(te);
65
levi.yun010d2ae2024-05-13 10:27:17 +010066#ifdef __aarch64__
67 if (GET_RW(ep_info->spsr) == MODE_RW_64) {
Raymond Mao60c3c972023-10-04 09:19:16 -070068 ep_info->args.arg0 = (uintptr_t)dt;
levi.yun010d2ae2024-05-13 10:27:17 +010069 ep_info->args.arg1 = TRANSFER_LIST_HANDOFF_X1_VALUE(REGISTER_CONVENTION_VERSION);
Raymond Mao60c3c972023-10-04 09:19:16 -070070 ep_info->args.arg2 = 0;
levi.yun010d2ae2024-05-13 10:27:17 +010071 } else
72#endif
73 {
74 ep_info->args.arg0 = 0;
75 ep_info->args.arg1 = TRANSFER_LIST_HANDOFF_R1_VALUE(REGISTER_CONVENTION_VERSION);
76 ep_info->args.arg2 = (uintptr_t)dt;
Raymond Mao60c3c972023-10-04 09:19:16 -070077 }
78
levi.yun010d2ae2024-05-13 10:27:17 +010079 ep_info->args.arg3 = (uintptr_t)tl;
80
Raymond Mao60c3c972023-10-04 09:19:16 -070081 return ep_info;
82}
83
84/*******************************************************************************
Raymond Mao98983392023-07-25 07:53:35 -070085 * Creating a transfer list in a reserved memory region specified
86 * Compliant to 2.4.5 of Firmware handoff specification (v0.9)
87 * Return pointer to the created transfer list or NULL on error
88 ******************************************************************************/
89struct transfer_list_header *transfer_list_init(void *addr, size_t max_size)
90{
91 struct transfer_list_header *tl = addr;
92
93 if (!addr || max_size == 0) {
94 return NULL;
95 }
96
97 if (!is_aligned((uintptr_t)addr, 1 << TRANSFER_LIST_INIT_MAX_ALIGN) ||
98 !is_aligned(max_size, 1 << TRANSFER_LIST_INIT_MAX_ALIGN) ||
99 max_size < sizeof(*tl)) {
100 return NULL;
101 }
102
103 memset(tl, 0, max_size);
104 tl->signature = TRANSFER_LIST_SIGNATURE;
105 tl->version = TRANSFER_LIST_VERSION;
106 tl->hdr_size = sizeof(*tl);
Raymond Mao60c3c972023-10-04 09:19:16 -0700107 tl->alignment = TRANSFER_LIST_INIT_MAX_ALIGN; /* initial max align */
108 tl->size = sizeof(*tl); /* initial size is the size of header */
Raymond Mao98983392023-07-25 07:53:35 -0700109 tl->max_size = max_size;
Raymond Mao60c3c972023-10-04 09:19:16 -0700110 tl->flags = TL_FLAGS_HAS_CHECKSUM;
Raymond Mao98983392023-07-25 07:53:35 -0700111
112 transfer_list_update_checksum(tl);
113
114 return tl;
115}
116
117/*******************************************************************************
118 * Relocating a transfer list to a reserved memory region specified
119 * Compliant to 2.4.6 of Firmware handoff specification (v0.9)
Raymond Mao60c3c972023-10-04 09:19:16 -0700120 * Return pointer to the relocated transfer list or NULL on error
Raymond Mao98983392023-07-25 07:53:35 -0700121 ******************************************************************************/
Raymond Mao60c3c972023-10-04 09:19:16 -0700122struct transfer_list_header *
123transfer_list_relocate(struct transfer_list_header *tl, void *addr,
124 size_t max_size)
Raymond Mao98983392023-07-25 07:53:35 -0700125{
126 uintptr_t new_addr, align_mask, align_off;
127 struct transfer_list_header *new_tl;
128 uint32_t new_max_size;
129
130 if (!tl || !addr || max_size == 0) {
131 return NULL;
132 }
133
134 align_mask = (1 << tl->alignment) - 1;
135 align_off = (uintptr_t)tl & align_mask;
136 new_addr = ((uintptr_t)addr & ~align_mask) + align_off;
137
138 if (new_addr < (uintptr_t)addr) {
139 new_addr += (1 << tl->alignment);
140 }
141
142 new_max_size = max_size - (new_addr - (uintptr_t)addr);
143
Raymond Mao60c3c972023-10-04 09:19:16 -0700144 /* the new space is not sufficient for the tl */
Raymond Mao98983392023-07-25 07:53:35 -0700145 if (tl->size > new_max_size) {
146 return NULL;
147 }
148
149 new_tl = (struct transfer_list_header *)new_addr;
150 memmove(new_tl, tl, tl->size);
151 new_tl->max_size = new_max_size;
152
153 transfer_list_update_checksum(new_tl);
154
155 return new_tl;
156}
157
158/*******************************************************************************
159 * Verifying the header of a transfer list
160 * Compliant to 2.4.1 of Firmware handoff specification (v0.9)
161 * Return transfer list operation status code
162 ******************************************************************************/
Raymond Mao60c3c972023-10-04 09:19:16 -0700163enum transfer_list_ops
164transfer_list_check_header(const struct transfer_list_header *tl)
Raymond Mao98983392023-07-25 07:53:35 -0700165{
166 if (!tl) {
167 return TL_OPS_NON;
168 }
169
170 if (tl->signature != TRANSFER_LIST_SIGNATURE) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700171 ERROR("Bad transfer list signature %#" PRIx32 "\n",
Raymond Mao98983392023-07-25 07:53:35 -0700172 tl->signature);
173 return TL_OPS_NON;
174 }
175
176 if (!tl->max_size) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700177 ERROR("Bad transfer list max size %#" PRIx32 "\n",
Raymond Mao98983392023-07-25 07:53:35 -0700178 tl->max_size);
179 return TL_OPS_NON;
180 }
181
182 if (tl->size > tl->max_size) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700183 ERROR("Bad transfer list size %#" PRIx32 "\n", tl->size);
Raymond Mao98983392023-07-25 07:53:35 -0700184 return TL_OPS_NON;
185 }
186
187 if (tl->hdr_size != sizeof(struct transfer_list_header)) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700188 ERROR("Bad transfer list header size %#" PRIx32 "\n",
189 tl->hdr_size);
Raymond Mao98983392023-07-25 07:53:35 -0700190 return TL_OPS_NON;
191 }
192
193 if (!transfer_list_verify_checksum(tl)) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700194 ERROR("Bad transfer list checksum %#" PRIx32 "\n",
195 tl->checksum);
Raymond Mao98983392023-07-25 07:53:35 -0700196 return TL_OPS_NON;
197 }
198
199 if (tl->version == 0) {
200 ERROR("Transfer list version is invalid\n");
201 return TL_OPS_NON;
202 } else if (tl->version == TRANSFER_LIST_VERSION) {
203 INFO("Transfer list version is valid for all operations\n");
204 return TL_OPS_ALL;
205 } else if (tl->version > TRANSFER_LIST_VERSION) {
206 INFO("Transfer list version is valid for read-only\n");
207 return TL_OPS_RO;
208 }
209
210 INFO("Old transfer list version is detected\n");
211 return TL_OPS_CUS;
212}
213
214/*******************************************************************************
215 * Enumerate the next transfer entry
216 * Return pointer to the next transfer entry or NULL on error
217 ******************************************************************************/
218struct transfer_list_entry *transfer_list_next(struct transfer_list_header *tl,
219 struct transfer_list_entry *last)
220{
221 struct transfer_list_entry *te = NULL;
222 uintptr_t tl_ev = 0;
223 uintptr_t va = 0;
224 uintptr_t ev = 0;
225 size_t sz = 0;
226
227 if (!tl) {
228 return NULL;
229 }
230
231 tl_ev = (uintptr_t)tl + tl->size;
232
233 if (last) {
234 va = (uintptr_t)last;
Raymond Mao60c3c972023-10-04 09:19:16 -0700235 /* check if the total size overflow */
236 if (add_overflow(last->hdr_size, last->data_size, &sz)) {
Raymond Mao98983392023-07-25 07:53:35 -0700237 return NULL;
238 }
Raymond Mao60c3c972023-10-04 09:19:16 -0700239 /* roundup to the next entry */
240 if (add_with_round_up_overflow(va, sz, TRANSFER_LIST_GRANULE,
241 &va)) {
Raymond Mao98983392023-07-25 07:53:35 -0700242 return NULL;
243 }
244 } else {
245 va = (uintptr_t)tl + tl->hdr_size;
246 }
247
248 te = (struct transfer_list_entry *)va;
249
250 if (va + sizeof(*te) > tl_ev || te->hdr_size < sizeof(*te) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700251 add_overflow(te->hdr_size, te->data_size, &sz) ||
252 add_overflow(va, sz, &ev) || ev > tl_ev) {
Raymond Mao98983392023-07-25 07:53:35 -0700253 return NULL;
254 }
255
256 return te;
257}
258
259/*******************************************************************************
260 * Calculate the byte sum of a transfer list
261 * Return byte sum of the transfer list
262 ******************************************************************************/
263static uint8_t calc_byte_sum(const struct transfer_list_header *tl)
264{
265 uint8_t *b = (uint8_t *)tl;
266 uint8_t cs = 0;
267 size_t n = 0;
268
Raymond Mao98983392023-07-25 07:53:35 -0700269 for (n = 0; n < tl->size; n++) {
270 cs += b[n];
271 }
272
273 return cs;
274}
275
276/*******************************************************************************
277 * Update the checksum of a transfer list
278 * Return updated checksum of the transfer list
279 ******************************************************************************/
280void transfer_list_update_checksum(struct transfer_list_header *tl)
281{
282 uint8_t cs;
283
Raymond Mao60c3c972023-10-04 09:19:16 -0700284 if (!tl || !(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
Raymond Mao98983392023-07-25 07:53:35 -0700285 return;
286 }
287
288 cs = calc_byte_sum(tl);
289 cs -= tl->checksum;
290 cs = 256 - cs;
291 tl->checksum = cs;
292 assert(transfer_list_verify_checksum(tl));
293}
294
295/*******************************************************************************
296 * Verify the checksum of a transfer list
297 * Return true if verified or false if not
298 ******************************************************************************/
299bool transfer_list_verify_checksum(const struct transfer_list_header *tl)
300{
Raymond Mao60c3c972023-10-04 09:19:16 -0700301 if (!tl) {
302 return false;
303 }
304
305 if (!(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
306 return true;
307 }
308
Raymond Mao98983392023-07-25 07:53:35 -0700309 return !calc_byte_sum(tl);
310}
311
312/*******************************************************************************
313 * Update the data size of a transfer entry
314 * Return true on success or false on error
315 ******************************************************************************/
316bool transfer_list_set_data_size(struct transfer_list_header *tl,
317 struct transfer_list_entry *te,
318 uint32_t new_data_size)
319{
320 uintptr_t tl_old_ev, new_ev = 0, old_ev = 0, ru_new_ev;
321 struct transfer_list_entry *dummy_te = NULL;
322 size_t gap = 0;
323 size_t mov_dis = 0;
324 size_t sz = 0;
325
326 if (!tl || !te) {
327 return false;
328 }
329 tl_old_ev = (uintptr_t)tl + tl->size;
330
Raymond Mao60c3c972023-10-04 09:19:16 -0700331 /*
332 * calculate the old and new end of TE
333 * both must be roundup to align with TRANSFER_LIST_GRANULE
334 */
Raymond Mao98983392023-07-25 07:53:35 -0700335 if (add_overflow(te->hdr_size, te->data_size, &sz) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700336 add_with_round_up_overflow((uintptr_t)te, sz, TRANSFER_LIST_GRANULE,
337 &old_ev)) {
Raymond Mao98983392023-07-25 07:53:35 -0700338 return false;
339 }
340 if (add_overflow(te->hdr_size, new_data_size, &sz) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700341 add_with_round_up_overflow((uintptr_t)te, sz, TRANSFER_LIST_GRANULE,
342 &new_ev)) {
Raymond Mao98983392023-07-25 07:53:35 -0700343 return false;
344 }
345
346 if (new_ev > old_ev) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700347 /*
348 * move distance should be roundup
349 * to meet the requirement of TE data max alignment
350 * ensure that the increased size doesn't exceed
351 * the max size of TL
352 */
Raymond Mao98983392023-07-25 07:53:35 -0700353 mov_dis = new_ev - old_ev;
Raymond Mao60c3c972023-10-04 09:19:16 -0700354 if (round_up_overflow(mov_dis, 1 << tl->alignment, &mov_dis) ||
355 tl->size + mov_dis > tl->max_size) {
Raymond Mao98983392023-07-25 07:53:35 -0700356 return false;
357 }
358 ru_new_ev = old_ev + mov_dis;
359 memmove((void *)ru_new_ev, (void *)old_ev, tl_old_ev - old_ev);
360 tl->size += mov_dis;
361 gap = ru_new_ev - new_ev;
362 } else {
363 gap = old_ev - new_ev;
364 }
365
366 if (gap >= sizeof(*dummy_te)) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700367 /* create a dummy TE to fill up the gap */
Raymond Mao98983392023-07-25 07:53:35 -0700368 dummy_te = (struct transfer_list_entry *)new_ev;
369 dummy_te->tag_id = TL_TAG_EMPTY;
Raymond Mao98983392023-07-25 07:53:35 -0700370 dummy_te->hdr_size = sizeof(*dummy_te);
371 dummy_te->data_size = gap - sizeof(*dummy_te);
372 }
373
374 te->data_size = new_data_size;
375
376 transfer_list_update_checksum(tl);
377 return true;
378}
379
380/*******************************************************************************
381 * Remove a specified transfer entry from a transfer list
382 * Return true on success or false on error
383 ******************************************************************************/
384bool transfer_list_rem(struct transfer_list_header *tl,
Raymond Mao60c3c972023-10-04 09:19:16 -0700385 struct transfer_list_entry *te)
Raymond Mao98983392023-07-25 07:53:35 -0700386{
387 if (!tl || !te || (uintptr_t)te > (uintptr_t)tl + tl->size) {
388 return false;
389 }
390 te->tag_id = TL_TAG_EMPTY;
Raymond Mao98983392023-07-25 07:53:35 -0700391 transfer_list_update_checksum(tl);
392 return true;
393}
394
395/*******************************************************************************
396 * Add a new transfer entry into a transfer list
397 * Compliant to 2.4.3 of Firmware handoff specification (v0.9)
398 * Return pointer to the added transfer entry or NULL on error
399 ******************************************************************************/
400struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl,
Harrison Mutai4490cd02024-03-20 14:37:51 +0000401 uint32_t tag_id,
Raymond Mao98983392023-07-25 07:53:35 -0700402 uint32_t data_size,
403 const void *data)
404{
405 uintptr_t max_tl_ev, tl_ev, ev;
406 struct transfer_list_entry *te = NULL;
407 uint8_t *te_data = NULL;
408 size_t sz = 0;
409
410 if (!tl) {
411 return NULL;
412 }
413
414 max_tl_ev = (uintptr_t)tl + tl->max_size;
415 tl_ev = (uintptr_t)tl + tl->size;
416 ev = tl_ev;
417
Raymond Mao60c3c972023-10-04 09:19:16 -0700418 /*
419 * skip the step 1 (optional step)
420 * new TE will be added into the tail
421 */
Raymond Mao98983392023-07-25 07:53:35 -0700422 if (add_overflow(sizeof(*te), data_size, &sz) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700423 add_with_round_up_overflow(ev, sz, TRANSFER_LIST_GRANULE, &ev) ||
424 ev > max_tl_ev) {
Raymond Mao98983392023-07-25 07:53:35 -0700425 return NULL;
426 }
427
428 te = (struct transfer_list_entry *)tl_ev;
429 te->tag_id = tag_id;
Raymond Mao98983392023-07-25 07:53:35 -0700430 te->hdr_size = sizeof(*te);
431 te->data_size = data_size;
432 tl->size += ev - tl_ev;
433
434 if (data) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700435 /* get TE data pointer */
Raymond Mao98983392023-07-25 07:53:35 -0700436 te_data = transfer_list_entry_data(te);
437 if (!te_data) {
438 return NULL;
439 }
440 memmove(te_data, data, data_size);
441 }
442
443 transfer_list_update_checksum(tl);
444
445 return te;
446}
447
448/*******************************************************************************
449 * Add a new transfer entry into a transfer list with specified new data
450 * alignment requirement
451 * Compliant to 2.4.4 of Firmware handoff specification (v0.9)
452 * Return pointer to the added transfer entry or NULL on error
453 ******************************************************************************/
Raymond Mao60c3c972023-10-04 09:19:16 -0700454struct transfer_list_entry *
Harrison Mutai4490cd02024-03-20 14:37:51 +0000455transfer_list_add_with_align(struct transfer_list_header *tl, uint32_t tag_id,
Raymond Mao60c3c972023-10-04 09:19:16 -0700456 uint32_t data_size, const void *data,
457 uint8_t alignment)
Raymond Mao98983392023-07-25 07:53:35 -0700458{
459 struct transfer_list_entry *te = NULL;
460 uintptr_t tl_ev, ev, new_tl_ev;
461 size_t dummy_te_data_sz = 0;
462
463 if (!tl) {
464 return NULL;
465 }
466
467 tl_ev = (uintptr_t)tl + tl->size;
468 ev = tl_ev + sizeof(struct transfer_list_entry);
469
470 if (!is_aligned(ev, 1 << alignment)) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700471 /*
472 * TE data address is not aligned to the new alignment
473 * fill the gap with an empty TE as a placeholder before
474 * adding the desire TE
475 */
Raymond Mao98983392023-07-25 07:53:35 -0700476 new_tl_ev = round_up(ev, 1 << alignment) -
Raymond Mao60c3c972023-10-04 09:19:16 -0700477 sizeof(struct transfer_list_entry);
478 dummy_te_data_sz =
479 new_tl_ev - tl_ev - sizeof(struct transfer_list_entry);
Raymond Mao98983392023-07-25 07:53:35 -0700480 if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz,
Raymond Mao60c3c972023-10-04 09:19:16 -0700481 NULL)) {
Raymond Mao98983392023-07-25 07:53:35 -0700482 return NULL;
483 }
484 }
485
486 te = transfer_list_add(tl, tag_id, data_size, data);
487
488 if (alignment > tl->alignment) {
489 tl->alignment = alignment;
490 transfer_list_update_checksum(tl);
491 }
492
493 return te;
494}
495
496/*******************************************************************************
497 * Search for an existing transfer entry with the specified tag id from a
498 * transfer list
499 * Return pointer to the found transfer entry or NULL on error
500 ******************************************************************************/
501struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl,
Harrison Mutai4490cd02024-03-20 14:37:51 +0000502 uint32_t tag_id)
Raymond Mao98983392023-07-25 07:53:35 -0700503{
504 struct transfer_list_entry *te = NULL;
505
506 do {
507 te = transfer_list_next(tl, te);
Harrison Mutai4490cd02024-03-20 14:37:51 +0000508 } while (te && (te->tag_id != tag_id));
Raymond Mao98983392023-07-25 07:53:35 -0700509
510 return te;
511}
512
513/*******************************************************************************
514 * Retrieve the data pointer of a specified transfer entry
515 * Return pointer to the transfer entry data or NULL on error
516 ******************************************************************************/
517void *transfer_list_entry_data(struct transfer_list_entry *entry)
518{
519 if (!entry) {
520 return NULL;
521 }
522 return (uint8_t *)entry + entry->hdr_size;
523}