blob: 63969e972b5418dfb0e251184381067c5c349cd8 [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
66 ep_info->args.arg1 = TRANSFER_LIST_SIGNATURE |
67 REGISTER_CONVENTION_VERSION_MASK;
68 ep_info->args.arg3 = (uintptr_t)tl;
69
70 if (GET_RW(ep_info->spsr) == MODE_RW_32) {
71 /* aarch32 */
72 ep_info->args.arg0 = 0;
73 ep_info->args.arg2 = (uintptr_t)dt;
74 } else {
75 /* aarch64 */
76 ep_info->args.arg0 = (uintptr_t)dt;
77 ep_info->args.arg2 = 0;
78 }
79
80 return ep_info;
81}
82
83/*******************************************************************************
Raymond Mao98983392023-07-25 07:53:35 -070084 * Creating a transfer list in a reserved memory region specified
85 * Compliant to 2.4.5 of Firmware handoff specification (v0.9)
86 * Return pointer to the created transfer list or NULL on error
87 ******************************************************************************/
88struct transfer_list_header *transfer_list_init(void *addr, size_t max_size)
89{
90 struct transfer_list_header *tl = addr;
91
92 if (!addr || max_size == 0) {
93 return NULL;
94 }
95
96 if (!is_aligned((uintptr_t)addr, 1 << TRANSFER_LIST_INIT_MAX_ALIGN) ||
97 !is_aligned(max_size, 1 << TRANSFER_LIST_INIT_MAX_ALIGN) ||
98 max_size < sizeof(*tl)) {
99 return NULL;
100 }
101
102 memset(tl, 0, max_size);
103 tl->signature = TRANSFER_LIST_SIGNATURE;
104 tl->version = TRANSFER_LIST_VERSION;
105 tl->hdr_size = sizeof(*tl);
Raymond Mao60c3c972023-10-04 09:19:16 -0700106 tl->alignment = TRANSFER_LIST_INIT_MAX_ALIGN; /* initial max align */
107 tl->size = sizeof(*tl); /* initial size is the size of header */
Raymond Mao98983392023-07-25 07:53:35 -0700108 tl->max_size = max_size;
Raymond Mao60c3c972023-10-04 09:19:16 -0700109 tl->flags = TL_FLAGS_HAS_CHECKSUM;
Raymond Mao98983392023-07-25 07:53:35 -0700110
111 transfer_list_update_checksum(tl);
112
113 return tl;
114}
115
116/*******************************************************************************
117 * Relocating a transfer list to a reserved memory region specified
118 * Compliant to 2.4.6 of Firmware handoff specification (v0.9)
Raymond Mao60c3c972023-10-04 09:19:16 -0700119 * Return pointer to the relocated transfer list or NULL on error
Raymond Mao98983392023-07-25 07:53:35 -0700120 ******************************************************************************/
Raymond Mao60c3c972023-10-04 09:19:16 -0700121struct transfer_list_header *
122transfer_list_relocate(struct transfer_list_header *tl, void *addr,
123 size_t max_size)
Raymond Mao98983392023-07-25 07:53:35 -0700124{
125 uintptr_t new_addr, align_mask, align_off;
126 struct transfer_list_header *new_tl;
127 uint32_t new_max_size;
128
129 if (!tl || !addr || max_size == 0) {
130 return NULL;
131 }
132
133 align_mask = (1 << tl->alignment) - 1;
134 align_off = (uintptr_t)tl & align_mask;
135 new_addr = ((uintptr_t)addr & ~align_mask) + align_off;
136
137 if (new_addr < (uintptr_t)addr) {
138 new_addr += (1 << tl->alignment);
139 }
140
141 new_max_size = max_size - (new_addr - (uintptr_t)addr);
142
Raymond Mao60c3c972023-10-04 09:19:16 -0700143 /* the new space is not sufficient for the tl */
Raymond Mao98983392023-07-25 07:53:35 -0700144 if (tl->size > new_max_size) {
145 return NULL;
146 }
147
148 new_tl = (struct transfer_list_header *)new_addr;
149 memmove(new_tl, tl, tl->size);
150 new_tl->max_size = new_max_size;
151
152 transfer_list_update_checksum(new_tl);
153
154 return new_tl;
155}
156
157/*******************************************************************************
158 * Verifying the header of a transfer list
159 * Compliant to 2.4.1 of Firmware handoff specification (v0.9)
160 * Return transfer list operation status code
161 ******************************************************************************/
Raymond Mao60c3c972023-10-04 09:19:16 -0700162enum transfer_list_ops
163transfer_list_check_header(const struct transfer_list_header *tl)
Raymond Mao98983392023-07-25 07:53:35 -0700164{
165 if (!tl) {
166 return TL_OPS_NON;
167 }
168
169 if (tl->signature != TRANSFER_LIST_SIGNATURE) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700170 ERROR("Bad transfer list signature %#" PRIx32 "\n",
Raymond Mao98983392023-07-25 07:53:35 -0700171 tl->signature);
172 return TL_OPS_NON;
173 }
174
175 if (!tl->max_size) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700176 ERROR("Bad transfer list max size %#" PRIx32 "\n",
Raymond Mao98983392023-07-25 07:53:35 -0700177 tl->max_size);
178 return TL_OPS_NON;
179 }
180
181 if (tl->size > tl->max_size) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700182 ERROR("Bad transfer list size %#" PRIx32 "\n", tl->size);
Raymond Mao98983392023-07-25 07:53:35 -0700183 return TL_OPS_NON;
184 }
185
186 if (tl->hdr_size != sizeof(struct transfer_list_header)) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700187 ERROR("Bad transfer list header size %#" PRIx32 "\n",
188 tl->hdr_size);
Raymond Mao98983392023-07-25 07:53:35 -0700189 return TL_OPS_NON;
190 }
191
192 if (!transfer_list_verify_checksum(tl)) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700193 ERROR("Bad transfer list checksum %#" PRIx32 "\n",
194 tl->checksum);
Raymond Mao98983392023-07-25 07:53:35 -0700195 return TL_OPS_NON;
196 }
197
198 if (tl->version == 0) {
199 ERROR("Transfer list version is invalid\n");
200 return TL_OPS_NON;
201 } else if (tl->version == TRANSFER_LIST_VERSION) {
202 INFO("Transfer list version is valid for all operations\n");
203 return TL_OPS_ALL;
204 } else if (tl->version > TRANSFER_LIST_VERSION) {
205 INFO("Transfer list version is valid for read-only\n");
206 return TL_OPS_RO;
207 }
208
209 INFO("Old transfer list version is detected\n");
210 return TL_OPS_CUS;
211}
212
213/*******************************************************************************
214 * Enumerate the next transfer entry
215 * Return pointer to the next transfer entry or NULL on error
216 ******************************************************************************/
217struct transfer_list_entry *transfer_list_next(struct transfer_list_header *tl,
218 struct transfer_list_entry *last)
219{
220 struct transfer_list_entry *te = NULL;
221 uintptr_t tl_ev = 0;
222 uintptr_t va = 0;
223 uintptr_t ev = 0;
224 size_t sz = 0;
225
226 if (!tl) {
227 return NULL;
228 }
229
230 tl_ev = (uintptr_t)tl + tl->size;
231
232 if (last) {
233 va = (uintptr_t)last;
Raymond Mao60c3c972023-10-04 09:19:16 -0700234 /* check if the total size overflow */
235 if (add_overflow(last->hdr_size, last->data_size, &sz)) {
Raymond Mao98983392023-07-25 07:53:35 -0700236 return NULL;
237 }
Raymond Mao60c3c972023-10-04 09:19:16 -0700238 /* roundup to the next entry */
239 if (add_with_round_up_overflow(va, sz, TRANSFER_LIST_GRANULE,
240 &va)) {
Raymond Mao98983392023-07-25 07:53:35 -0700241 return NULL;
242 }
243 } else {
244 va = (uintptr_t)tl + tl->hdr_size;
245 }
246
247 te = (struct transfer_list_entry *)va;
248
249 if (va + sizeof(*te) > tl_ev || te->hdr_size < sizeof(*te) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700250 add_overflow(te->hdr_size, te->data_size, &sz) ||
251 add_overflow(va, sz, &ev) || ev > tl_ev) {
Raymond Mao98983392023-07-25 07:53:35 -0700252 return NULL;
253 }
254
255 return te;
256}
257
258/*******************************************************************************
259 * Calculate the byte sum of a transfer list
260 * Return byte sum of the transfer list
261 ******************************************************************************/
262static uint8_t calc_byte_sum(const struct transfer_list_header *tl)
263{
264 uint8_t *b = (uint8_t *)tl;
265 uint8_t cs = 0;
266 size_t n = 0;
267
Raymond Mao98983392023-07-25 07:53:35 -0700268 for (n = 0; n < tl->size; n++) {
269 cs += b[n];
270 }
271
272 return cs;
273}
274
275/*******************************************************************************
276 * Update the checksum of a transfer list
277 * Return updated checksum of the transfer list
278 ******************************************************************************/
279void transfer_list_update_checksum(struct transfer_list_header *tl)
280{
281 uint8_t cs;
282
Raymond Mao60c3c972023-10-04 09:19:16 -0700283 if (!tl || !(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
Raymond Mao98983392023-07-25 07:53:35 -0700284 return;
285 }
286
287 cs = calc_byte_sum(tl);
288 cs -= tl->checksum;
289 cs = 256 - cs;
290 tl->checksum = cs;
291 assert(transfer_list_verify_checksum(tl));
292}
293
294/*******************************************************************************
295 * Verify the checksum of a transfer list
296 * Return true if verified or false if not
297 ******************************************************************************/
298bool transfer_list_verify_checksum(const struct transfer_list_header *tl)
299{
Raymond Mao60c3c972023-10-04 09:19:16 -0700300 if (!tl) {
301 return false;
302 }
303
304 if (!(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
305 return true;
306 }
307
Raymond Mao98983392023-07-25 07:53:35 -0700308 return !calc_byte_sum(tl);
309}
310
311/*******************************************************************************
312 * Update the data size of a transfer entry
313 * Return true on success or false on error
314 ******************************************************************************/
315bool transfer_list_set_data_size(struct transfer_list_header *tl,
316 struct transfer_list_entry *te,
317 uint32_t new_data_size)
318{
319 uintptr_t tl_old_ev, new_ev = 0, old_ev = 0, ru_new_ev;
320 struct transfer_list_entry *dummy_te = NULL;
321 size_t gap = 0;
322 size_t mov_dis = 0;
323 size_t sz = 0;
324
325 if (!tl || !te) {
326 return false;
327 }
328 tl_old_ev = (uintptr_t)tl + tl->size;
329
Raymond Mao60c3c972023-10-04 09:19:16 -0700330 /*
331 * calculate the old and new end of TE
332 * both must be roundup to align with TRANSFER_LIST_GRANULE
333 */
Raymond Mao98983392023-07-25 07:53:35 -0700334 if (add_overflow(te->hdr_size, te->data_size, &sz) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700335 add_with_round_up_overflow((uintptr_t)te, sz, TRANSFER_LIST_GRANULE,
336 &old_ev)) {
Raymond Mao98983392023-07-25 07:53:35 -0700337 return false;
338 }
339 if (add_overflow(te->hdr_size, new_data_size, &sz) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700340 add_with_round_up_overflow((uintptr_t)te, sz, TRANSFER_LIST_GRANULE,
341 &new_ev)) {
Raymond Mao98983392023-07-25 07:53:35 -0700342 return false;
343 }
344
345 if (new_ev > old_ev) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700346 /*
347 * move distance should be roundup
348 * to meet the requirement of TE data max alignment
349 * ensure that the increased size doesn't exceed
350 * the max size of TL
351 */
Raymond Mao98983392023-07-25 07:53:35 -0700352 mov_dis = new_ev - old_ev;
Raymond Mao60c3c972023-10-04 09:19:16 -0700353 if (round_up_overflow(mov_dis, 1 << tl->alignment, &mov_dis) ||
354 tl->size + mov_dis > tl->max_size) {
Raymond Mao98983392023-07-25 07:53:35 -0700355 return false;
356 }
357 ru_new_ev = old_ev + mov_dis;
358 memmove((void *)ru_new_ev, (void *)old_ev, tl_old_ev - old_ev);
359 tl->size += mov_dis;
360 gap = ru_new_ev - new_ev;
361 } else {
362 gap = old_ev - new_ev;
363 }
364
365 if (gap >= sizeof(*dummy_te)) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700366 /* create a dummy TE to fill up the gap */
Raymond Mao98983392023-07-25 07:53:35 -0700367 dummy_te = (struct transfer_list_entry *)new_ev;
368 dummy_te->tag_id = TL_TAG_EMPTY;
369 dummy_te->reserved0 = 0;
370 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;
391 te->reserved0 = 0;
392 transfer_list_update_checksum(tl);
393 return true;
394}
395
396/*******************************************************************************
397 * Add a new transfer entry into a transfer list
398 * Compliant to 2.4.3 of Firmware handoff specification (v0.9)
399 * Return pointer to the added transfer entry or NULL on error
400 ******************************************************************************/
401struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl,
402 uint16_t tag_id,
403 uint32_t data_size,
404 const void *data)
405{
406 uintptr_t max_tl_ev, tl_ev, ev;
407 struct transfer_list_entry *te = NULL;
408 uint8_t *te_data = NULL;
409 size_t sz = 0;
410
411 if (!tl) {
412 return NULL;
413 }
414
415 max_tl_ev = (uintptr_t)tl + tl->max_size;
416 tl_ev = (uintptr_t)tl + tl->size;
417 ev = tl_ev;
418
Raymond Mao60c3c972023-10-04 09:19:16 -0700419 /*
420 * skip the step 1 (optional step)
421 * new TE will be added into the tail
422 */
Raymond Mao98983392023-07-25 07:53:35 -0700423 if (add_overflow(sizeof(*te), data_size, &sz) ||
Raymond Mao60c3c972023-10-04 09:19:16 -0700424 add_with_round_up_overflow(ev, sz, TRANSFER_LIST_GRANULE, &ev) ||
425 ev > max_tl_ev) {
Raymond Mao98983392023-07-25 07:53:35 -0700426 return NULL;
427 }
428
429 te = (struct transfer_list_entry *)tl_ev;
430 te->tag_id = tag_id;
431 te->reserved0 = 0;
432 te->hdr_size = sizeof(*te);
433 te->data_size = data_size;
434 tl->size += ev - tl_ev;
435
436 if (data) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700437 /* get TE data pointer */
Raymond Mao98983392023-07-25 07:53:35 -0700438 te_data = transfer_list_entry_data(te);
439 if (!te_data) {
440 return NULL;
441 }
442 memmove(te_data, data, data_size);
443 }
444
445 transfer_list_update_checksum(tl);
446
447 return te;
448}
449
450/*******************************************************************************
451 * Add a new transfer entry into a transfer list with specified new data
452 * alignment requirement
453 * Compliant to 2.4.4 of Firmware handoff specification (v0.9)
454 * Return pointer to the added transfer entry or NULL on error
455 ******************************************************************************/
Raymond Mao60c3c972023-10-04 09:19:16 -0700456struct transfer_list_entry *
457transfer_list_add_with_align(struct transfer_list_header *tl, uint16_t tag_id,
458 uint32_t data_size, const void *data,
459 uint8_t alignment)
Raymond Mao98983392023-07-25 07:53:35 -0700460{
461 struct transfer_list_entry *te = NULL;
462 uintptr_t tl_ev, ev, new_tl_ev;
463 size_t dummy_te_data_sz = 0;
464
465 if (!tl) {
466 return NULL;
467 }
468
469 tl_ev = (uintptr_t)tl + tl->size;
470 ev = tl_ev + sizeof(struct transfer_list_entry);
471
472 if (!is_aligned(ev, 1 << alignment)) {
Raymond Mao60c3c972023-10-04 09:19:16 -0700473 /*
474 * TE data address is not aligned to the new alignment
475 * fill the gap with an empty TE as a placeholder before
476 * adding the desire TE
477 */
Raymond Mao98983392023-07-25 07:53:35 -0700478 new_tl_ev = round_up(ev, 1 << alignment) -
Raymond Mao60c3c972023-10-04 09:19:16 -0700479 sizeof(struct transfer_list_entry);
480 dummy_te_data_sz =
481 new_tl_ev - tl_ev - sizeof(struct transfer_list_entry);
Raymond Mao98983392023-07-25 07:53:35 -0700482 if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz,
Raymond Mao60c3c972023-10-04 09:19:16 -0700483 NULL)) {
Raymond Mao98983392023-07-25 07:53:35 -0700484 return NULL;
485 }
486 }
487
488 te = transfer_list_add(tl, tag_id, data_size, data);
489
490 if (alignment > tl->alignment) {
491 tl->alignment = alignment;
492 transfer_list_update_checksum(tl);
493 }
494
495 return te;
496}
497
498/*******************************************************************************
499 * Search for an existing transfer entry with the specified tag id from a
500 * transfer list
501 * Return pointer to the found transfer entry or NULL on error
502 ******************************************************************************/
503struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl,
504 uint16_t tag_id)
505{
506 struct transfer_list_entry *te = NULL;
507
508 do {
509 te = transfer_list_next(tl, te);
510 } while (te && (te->tag_id != tag_id || te->reserved0 != 0));
511
512 return te;
513}
514
515/*******************************************************************************
516 * Retrieve the data pointer of a specified transfer entry
517 * Return pointer to the transfer entry data or NULL on error
518 ******************************************************************************/
519void *transfer_list_entry_data(struct transfer_list_entry *entry)
520{
521 if (!entry) {
522 return NULL;
523 }
524 return (uint8_t *)entry + entry->hdr_size;
525}