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