• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #ifdef __aarch64__
67 	if (GET_RW(ep_info->spsr) == MODE_RW_64) {
68 		ep_info->args.arg0 = (uintptr_t)dt;
69 		ep_info->args.arg1 = TRANSFER_LIST_HANDOFF_X1_VALUE(REGISTER_CONVENTION_VERSION);
70 		ep_info->args.arg2 = 0;
71 	} 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;
77 	}
78 
79 	ep_info->args.arg3 = (uintptr_t)tl;
80 
81 	return ep_info;
82 }
83 
84 /*******************************************************************************
85  * 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  ******************************************************************************/
transfer_list_init(void * addr,size_t max_size)89 struct 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);
107 	tl->alignment = TRANSFER_LIST_INIT_MAX_ALIGN; /* initial max align */
108 	tl->size = sizeof(*tl); /* initial size is the size of header */
109 	tl->max_size = max_size;
110 	tl->flags = TL_FLAGS_HAS_CHECKSUM;
111 
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)
120  * Return pointer to the relocated transfer list or NULL on error
121  ******************************************************************************/
122 struct transfer_list_header *
transfer_list_relocate(struct transfer_list_header * tl,void * addr,size_t max_size)123 transfer_list_relocate(struct transfer_list_header *tl, void *addr,
124 		       size_t max_size)
125 {
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 
144 	/* the new space is not sufficient for the tl */
145 	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  ******************************************************************************/
163 enum transfer_list_ops
transfer_list_check_header(const struct transfer_list_header * tl)164 transfer_list_check_header(const struct transfer_list_header *tl)
165 {
166 	if (!tl) {
167 		return TL_OPS_NON;
168 	}
169 
170 	if (tl->signature != TRANSFER_LIST_SIGNATURE) {
171 		ERROR("Bad transfer list signature %#" PRIx32 "\n",
172 		      tl->signature);
173 		return TL_OPS_NON;
174 	}
175 
176 	if (!tl->max_size) {
177 		ERROR("Bad transfer list max size %#" PRIx32 "\n",
178 		      tl->max_size);
179 		return TL_OPS_NON;
180 	}
181 
182 	if (tl->size > tl->max_size) {
183 		ERROR("Bad transfer list size %#" PRIx32 "\n", tl->size);
184 		return TL_OPS_NON;
185 	}
186 
187 	if (tl->hdr_size != sizeof(struct transfer_list_header)) {
188 		ERROR("Bad transfer list header size %#" PRIx32 "\n",
189 		      tl->hdr_size);
190 		return TL_OPS_NON;
191 	}
192 
193 	if (!transfer_list_verify_checksum(tl)) {
194 		ERROR("Bad transfer list checksum %#" PRIx32 "\n",
195 		      tl->checksum);
196 		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  ******************************************************************************/
transfer_list_next(struct transfer_list_header * tl,struct transfer_list_entry * last)218 struct 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;
235 		/* check if the total size overflow */
236 		if (add_overflow(last->hdr_size, last->data_size, &sz)) {
237 			return NULL;
238 		}
239 		/* roundup to the next entry */
240 		if (add_with_round_up_overflow(va, sz, TRANSFER_LIST_GRANULE,
241 					       &va)) {
242 			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) ||
251 	    add_overflow(te->hdr_size, te->data_size, &sz) ||
252 	    add_overflow(va, sz, &ev) || ev > tl_ev) {
253 		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  ******************************************************************************/
calc_byte_sum(const struct transfer_list_header * tl)263 static 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 
269 	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  ******************************************************************************/
transfer_list_update_checksum(struct transfer_list_header * tl)280 void transfer_list_update_checksum(struct transfer_list_header *tl)
281 {
282 	uint8_t cs;
283 
284 	if (!tl || !(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
285 		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  ******************************************************************************/
transfer_list_verify_checksum(const struct transfer_list_header * tl)299 bool transfer_list_verify_checksum(const struct transfer_list_header *tl)
300 {
301 	if (!tl) {
302 		return false;
303 	}
304 
305 	if (!(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
306 		return true;
307 	}
308 
309 	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  ******************************************************************************/
transfer_list_set_data_size(struct transfer_list_header * tl,struct transfer_list_entry * te,uint32_t new_data_size)316 bool 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 
331 	/*
332 	 * calculate the old and new end of TE
333 	 * both must be roundup to align with TRANSFER_LIST_GRANULE
334 	 */
335 	if (add_overflow(te->hdr_size, te->data_size, &sz) ||
336 	    add_with_round_up_overflow((uintptr_t)te, sz, TRANSFER_LIST_GRANULE,
337 				       &old_ev)) {
338 		return false;
339 	}
340 	if (add_overflow(te->hdr_size, new_data_size, &sz) ||
341 	    add_with_round_up_overflow((uintptr_t)te, sz, TRANSFER_LIST_GRANULE,
342 				       &new_ev)) {
343 		return false;
344 	}
345 
346 	if (new_ev > old_ev) {
347 		/*
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 		 */
353 		mov_dis = new_ev - old_ev;
354 		if (round_up_overflow(mov_dis, 1 << tl->alignment, &mov_dis) ||
355 		    tl->size + mov_dis > tl->max_size) {
356 			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)) {
367 		/* create a dummy TE to fill up the gap */
368 		dummy_te = (struct transfer_list_entry *)new_ev;
369 		dummy_te->tag_id = TL_TAG_EMPTY;
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  ******************************************************************************/
transfer_list_rem(struct transfer_list_header * tl,struct transfer_list_entry * te)384 bool transfer_list_rem(struct transfer_list_header *tl,
385 		       struct transfer_list_entry *te)
386 {
387 	if (!tl || !te || (uintptr_t)te > (uintptr_t)tl + tl->size) {
388 		return false;
389 	}
390 	te->tag_id = TL_TAG_EMPTY;
391 	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  ******************************************************************************/
transfer_list_add(struct transfer_list_header * tl,uint32_t tag_id,uint32_t data_size,const void * data)400 struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl,
401 					      uint32_t tag_id,
402 					      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 
418 	/*
419 	 * skip the step 1 (optional step)
420 	 * new TE will be added into the tail
421 	 */
422 	if (add_overflow(sizeof(*te), data_size, &sz) ||
423 	    add_with_round_up_overflow(ev, sz, TRANSFER_LIST_GRANULE, &ev) ||
424 	    ev > max_tl_ev) {
425 		return NULL;
426 	}
427 
428 	te = (struct transfer_list_entry *)tl_ev;
429 	te->tag_id = tag_id;
430 	te->hdr_size = sizeof(*te);
431 	te->data_size = data_size;
432 	tl->size += ev - tl_ev;
433 
434 	if (data) {
435 		/* get TE data pointer */
436 		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  ******************************************************************************/
454 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)455 transfer_list_add_with_align(struct transfer_list_header *tl, uint32_t tag_id,
456 			     uint32_t data_size, const void *data,
457 			     uint8_t alignment)
458 {
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)) {
471 		/*
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 		 */
476 		new_tl_ev = round_up(ev, 1 << alignment) -
477 			    sizeof(struct transfer_list_entry);
478 		dummy_te_data_sz =
479 			new_tl_ev - tl_ev - sizeof(struct transfer_list_entry);
480 		if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz,
481 				       NULL)) {
482 			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  ******************************************************************************/
transfer_list_find(struct transfer_list_header * tl,uint32_t tag_id)501 struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl,
502 					       uint32_t tag_id)
503 {
504 	struct transfer_list_entry *te = NULL;
505 
506 	do {
507 		te = transfer_list_next(tl, te);
508 	} while (te && (te->tag_id != tag_id));
509 
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  ******************************************************************************/
transfer_list_entry_data(struct transfer_list_entry * entry)517 void *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 }
524