1 /*
2 * Copyright (c) 2023, Linaro Limited and Contributors. All rights reserved.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 #include <arch.h>
8 #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
transfer_list_dump(struct transfer_list_header * tl)16 void 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 }
24 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);
33 while (true) {
34 te = transfer_list_next(tl, te);
35 if (!te) {
36 break;
37 }
38 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));
44 }
45 }
46
47 /*******************************************************************************
48 * 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 ******************************************************************************/
52 entry_point_info_t *
transfer_list_set_handoff_args(struct transfer_list_header * tl,entry_point_info_t * ep_info)53 transfer_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 /*******************************************************************************
84 * 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 ******************************************************************************/
transfer_list_init(void * addr,size_t max_size)88 struct 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);
106 tl->alignment = TRANSFER_LIST_INIT_MAX_ALIGN; /* initial max align */
107 tl->size = sizeof(*tl); /* initial size is the size of header */
108 tl->max_size = max_size;
109 tl->flags = TL_FLAGS_HAS_CHECKSUM;
110
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)
119 * Return pointer to the relocated transfer list or NULL on error
120 ******************************************************************************/
121 struct transfer_list_header *
transfer_list_relocate(struct transfer_list_header * tl,void * addr,size_t max_size)122 transfer_list_relocate(struct transfer_list_header *tl, void *addr,
123 size_t max_size)
124 {
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
143 /* the new space is not sufficient for the tl */
144 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 ******************************************************************************/
162 enum transfer_list_ops
transfer_list_check_header(const struct transfer_list_header * tl)163 transfer_list_check_header(const struct transfer_list_header *tl)
164 {
165 if (!tl) {
166 return TL_OPS_NON;
167 }
168
169 if (tl->signature != TRANSFER_LIST_SIGNATURE) {
170 ERROR("Bad transfer list signature %#" PRIx32 "\n",
171 tl->signature);
172 return TL_OPS_NON;
173 }
174
175 if (!tl->max_size) {
176 ERROR("Bad transfer list max size %#" PRIx32 "\n",
177 tl->max_size);
178 return TL_OPS_NON;
179 }
180
181 if (tl->size > tl->max_size) {
182 ERROR("Bad transfer list size %#" PRIx32 "\n", tl->size);
183 return TL_OPS_NON;
184 }
185
186 if (tl->hdr_size != sizeof(struct transfer_list_header)) {
187 ERROR("Bad transfer list header size %#" PRIx32 "\n",
188 tl->hdr_size);
189 return TL_OPS_NON;
190 }
191
192 if (!transfer_list_verify_checksum(tl)) {
193 ERROR("Bad transfer list checksum %#" PRIx32 "\n",
194 tl->checksum);
195 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 ******************************************************************************/
transfer_list_next(struct transfer_list_header * tl,struct transfer_list_entry * last)217 struct 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;
234 /* check if the total size overflow */
235 if (add_overflow(last->hdr_size, last->data_size, &sz)) {
236 return NULL;
237 }
238 /* roundup to the next entry */
239 if (add_with_round_up_overflow(va, sz, TRANSFER_LIST_GRANULE,
240 &va)) {
241 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) ||
250 add_overflow(te->hdr_size, te->data_size, &sz) ||
251 add_overflow(va, sz, &ev) || ev > tl_ev) {
252 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 ******************************************************************************/
calc_byte_sum(const struct transfer_list_header * tl)262 static 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
268 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 ******************************************************************************/
transfer_list_update_checksum(struct transfer_list_header * tl)279 void transfer_list_update_checksum(struct transfer_list_header *tl)
280 {
281 uint8_t cs;
282
283 if (!tl || !(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
284 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 ******************************************************************************/
transfer_list_verify_checksum(const struct transfer_list_header * tl)298 bool transfer_list_verify_checksum(const struct transfer_list_header *tl)
299 {
300 if (!tl) {
301 return false;
302 }
303
304 if (!(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
305 return true;
306 }
307
308 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 ******************************************************************************/
transfer_list_set_data_size(struct transfer_list_header * tl,struct transfer_list_entry * te,uint32_t new_data_size)315 bool 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
330 /*
331 * calculate the old and new end of TE
332 * both must be roundup to align with TRANSFER_LIST_GRANULE
333 */
334 if (add_overflow(te->hdr_size, te->data_size, &sz) ||
335 add_with_round_up_overflow((uintptr_t)te, sz, TRANSFER_LIST_GRANULE,
336 &old_ev)) {
337 return false;
338 }
339 if (add_overflow(te->hdr_size, new_data_size, &sz) ||
340 add_with_round_up_overflow((uintptr_t)te, sz, TRANSFER_LIST_GRANULE,
341 &new_ev)) {
342 return false;
343 }
344
345 if (new_ev > old_ev) {
346 /*
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 */
352 mov_dis = new_ev - old_ev;
353 if (round_up_overflow(mov_dis, 1 << tl->alignment, &mov_dis) ||
354 tl->size + mov_dis > tl->max_size) {
355 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)) {
366 /* create a dummy TE to fill up the gap */
367 dummy_te = (struct transfer_list_entry *)new_ev;
368 dummy_te->tag_id = TL_TAG_EMPTY;
369 dummy_te->hdr_size = sizeof(*dummy_te);
370 dummy_te->data_size = gap - sizeof(*dummy_te);
371 }
372
373 te->data_size = new_data_size;
374
375 transfer_list_update_checksum(tl);
376 return true;
377 }
378
379 /*******************************************************************************
380 * Remove a specified transfer entry from a transfer list
381 * Return true on success or false on error
382 ******************************************************************************/
transfer_list_rem(struct transfer_list_header * tl,struct transfer_list_entry * te)383 bool transfer_list_rem(struct transfer_list_header *tl,
384 struct transfer_list_entry *te)
385 {
386 if (!tl || !te || (uintptr_t)te > (uintptr_t)tl + tl->size) {
387 return false;
388 }
389 te->tag_id = TL_TAG_EMPTY;
390 transfer_list_update_checksum(tl);
391 return true;
392 }
393
394 /*******************************************************************************
395 * Add a new transfer entry into a transfer list
396 * Compliant to 2.4.3 of Firmware handoff specification (v0.9)
397 * Return pointer to the added transfer entry or NULL on error
398 ******************************************************************************/
transfer_list_add(struct transfer_list_header * tl,uint32_t tag_id,uint32_t data_size,const void * data)399 struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl,
400 uint32_t tag_id,
401 uint32_t data_size,
402 const void *data)
403 {
404 uintptr_t max_tl_ev, tl_ev, ev;
405 struct transfer_list_entry *te = NULL;
406 uint8_t *te_data = NULL;
407 size_t sz = 0;
408
409 if (!tl) {
410 return NULL;
411 }
412
413 max_tl_ev = (uintptr_t)tl + tl->max_size;
414 tl_ev = (uintptr_t)tl + tl->size;
415 ev = tl_ev;
416
417 /*
418 * skip the step 1 (optional step)
419 * new TE will be added into the tail
420 */
421 if (add_overflow(sizeof(*te), data_size, &sz) ||
422 add_with_round_up_overflow(ev, sz, TRANSFER_LIST_GRANULE, &ev) ||
423 ev > max_tl_ev) {
424 return NULL;
425 }
426
427 te = (struct transfer_list_entry *)tl_ev;
428 te->tag_id = tag_id;
429 te->hdr_size = sizeof(*te);
430 te->data_size = data_size;
431 tl->size += ev - tl_ev;
432
433 if (data) {
434 /* get TE data pointer */
435 te_data = transfer_list_entry_data(te);
436 if (!te_data) {
437 return NULL;
438 }
439 memmove(te_data, data, data_size);
440 }
441
442 transfer_list_update_checksum(tl);
443
444 return te;
445 }
446
447 /*******************************************************************************
448 * Add a new transfer entry into a transfer list with specified new data
449 * alignment requirement
450 * Compliant to 2.4.4 of Firmware handoff specification (v0.9)
451 * Return pointer to the added transfer entry or NULL on error
452 ******************************************************************************/
453 struct transfer_list_entry *
transfer_list_add_with_align(struct transfer_list_header * tl,uint32_t tag_id,uint32_t data_size,const void * data,uint8_t alignment)454 transfer_list_add_with_align(struct transfer_list_header *tl, uint32_t tag_id,
455 uint32_t data_size, const void *data,
456 uint8_t alignment)
457 {
458 struct transfer_list_entry *te = NULL;
459 uintptr_t tl_ev, ev, new_tl_ev;
460 size_t dummy_te_data_sz = 0;
461
462 if (!tl) {
463 return NULL;
464 }
465
466 tl_ev = (uintptr_t)tl + tl->size;
467 ev = tl_ev + sizeof(struct transfer_list_entry);
468
469 if (!is_aligned(ev, 1 << alignment)) {
470 /*
471 * TE data address is not aligned to the new alignment
472 * fill the gap with an empty TE as a placeholder before
473 * adding the desire TE
474 */
475 new_tl_ev = round_up(ev, 1 << alignment) -
476 sizeof(struct transfer_list_entry);
477 dummy_te_data_sz =
478 new_tl_ev - tl_ev - sizeof(struct transfer_list_entry);
479 if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz,
480 NULL)) {
481 return NULL;
482 }
483 }
484
485 te = transfer_list_add(tl, tag_id, data_size, data);
486
487 if (alignment > tl->alignment) {
488 tl->alignment = alignment;
489 transfer_list_update_checksum(tl);
490 }
491
492 return te;
493 }
494
495 /*******************************************************************************
496 * Search for an existing transfer entry with the specified tag id from a
497 * transfer list
498 * Return pointer to the found transfer entry or NULL on error
499 ******************************************************************************/
transfer_list_find(struct transfer_list_header * tl,uint32_t tag_id)500 struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl,
501 uint32_t tag_id)
502 {
503 struct transfer_list_entry *te = NULL;
504
505 do {
506 te = transfer_list_next(tl, te);
507 } while (te && (te->tag_id != tag_id));
508
509 return te;
510 }
511
512 /*******************************************************************************
513 * Retrieve the data pointer of a specified transfer entry
514 * Return pointer to the transfer entry data or NULL on error
515 ******************************************************************************/
transfer_list_entry_data(struct transfer_list_entry * entry)516 void *transfer_list_entry_data(struct transfer_list_entry *entry)
517 {
518 if (!entry) {
519 return NULL;
520 }
521 return (uint8_t *)entry + entry->hdr_size;
522 }
523