1 /*
2  * Copyright (c) 2023, Linaro Limited and Contributors. All rights reserved.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6 
7 #include <assert.h>
8 #include <inttypes.h>
9 #include <string.h>
10 
11 #include <common/debug.h>
12 #include <lib/transfer_list.h>
13 #include <lib/utils_def.h>
14 
transfer_list_dump(struct transfer_list_header * tl)15 void transfer_list_dump(struct transfer_list_header *tl)
16 {
17 	struct transfer_list_entry *te = NULL;
18 	int i = 0;
19 
20 	if (!tl) {
21 		return;
22 	}
23 	NOTICE("Dump transfer list:\n");
24 	NOTICE("signature  0x%x\n", tl->signature);
25 	NOTICE("checksum   0x%x\n", tl->checksum);
26 	NOTICE("version    0x%x\n", tl->version);
27 	NOTICE("hdr_size   0x%x\n", tl->hdr_size);
28 	NOTICE("alignment  0x%x\n", tl->alignment);
29 	NOTICE("size       0x%x\n", tl->size);
30 	NOTICE("max_size   0x%x\n", tl->max_size);
31 	while (true) {
32 		te = transfer_list_next(tl, te);
33 		if (!te) {
34 			break;
35 		}
36 		NOTICE("Entry %d:\n", i++);
37 		NOTICE("tag_id     0x%x\n", te->tag_id);
38 		NOTICE("hdr_size   0x%x\n", te->hdr_size);
39 		NOTICE("data_size  0x%x\n", te->data_size);
40 		NOTICE("data_addr  0x%lx\n",
41 		(unsigned long)transfer_list_entry_data(te));
42 	}
43 }
44 
45 /*******************************************************************************
46  * Creating a transfer list in a reserved memory region specified
47  * Compliant to 2.4.5 of Firmware handoff specification (v0.9)
48  * Return pointer to the created transfer list or NULL on error
49  ******************************************************************************/
transfer_list_init(void * addr,size_t max_size)50 struct transfer_list_header *transfer_list_init(void *addr, size_t max_size)
51 {
52 	struct transfer_list_header *tl = addr;
53 
54 	if (!addr || max_size == 0) {
55 		return NULL;
56 	}
57 
58 	if (!is_aligned((uintptr_t)addr, 1 << TRANSFER_LIST_INIT_MAX_ALIGN) ||
59 	    !is_aligned(max_size, 1 << TRANSFER_LIST_INIT_MAX_ALIGN) ||
60 	    max_size < sizeof(*tl)) {
61 		return NULL;
62 	}
63 
64 	memset(tl, 0, max_size);
65 	tl->signature = TRANSFER_LIST_SIGNATURE;
66 	tl->version = TRANSFER_LIST_VERSION;
67 	tl->hdr_size = sizeof(*tl);
68 	tl->alignment = TRANSFER_LIST_INIT_MAX_ALIGN; // initial max align
69 	tl->size = sizeof(*tl); // initial size is the size of header
70 	tl->max_size = max_size;
71 
72 	transfer_list_update_checksum(tl);
73 
74 	return tl;
75 }
76 
77 /*******************************************************************************
78  * Relocating a transfer list to a reserved memory region specified
79  * Compliant to 2.4.6 of Firmware handoff specification (v0.9)
80  * Return true on success or false on error
81  ******************************************************************************/
transfer_list_relocate(struct transfer_list_header * tl,void * addr,size_t max_size)82 struct transfer_list_header *transfer_list_relocate(
83 						struct transfer_list_header *tl,
84 						void *addr, size_t max_size)
85 {
86 	uintptr_t new_addr, align_mask, align_off;
87 	struct transfer_list_header *new_tl;
88 	uint32_t new_max_size;
89 
90 	if (!tl || !addr || max_size == 0) {
91 		return NULL;
92 	}
93 
94 	align_mask = (1 << tl->alignment) - 1;
95 	align_off = (uintptr_t)tl & align_mask;
96 	new_addr = ((uintptr_t)addr & ~align_mask) + align_off;
97 
98 	if (new_addr < (uintptr_t)addr) {
99 		new_addr += (1 << tl->alignment);
100 	}
101 
102 	new_max_size = max_size - (new_addr - (uintptr_t)addr);
103 
104 	// the new space is not sufficient for the tl
105 	if (tl->size > new_max_size) {
106 		return NULL;
107 	}
108 
109 	new_tl = (struct transfer_list_header *)new_addr;
110 	memmove(new_tl, tl, tl->size);
111 	new_tl->max_size = new_max_size;
112 
113 	transfer_list_update_checksum(new_tl);
114 
115 	return new_tl;
116 }
117 
118 /*******************************************************************************
119  * Verifying the header of a transfer list
120  * Compliant to 2.4.1 of Firmware handoff specification (v0.9)
121  * Return transfer list operation status code
122  ******************************************************************************/
transfer_list_check_header(const struct transfer_list_header * tl)123 enum transfer_list_ops transfer_list_check_header(
124 					const struct transfer_list_header *tl)
125 {
126 	if (!tl) {
127 		return TL_OPS_NON;
128 	}
129 
130 	if (tl->signature != TRANSFER_LIST_SIGNATURE) {
131 		ERROR("Bad transfer list signature %#"PRIx32"\n",
132 		      tl->signature);
133 		return TL_OPS_NON;
134 	}
135 
136 	if (!tl->max_size) {
137 		ERROR("Bad transfer list max size %#"PRIx32"\n",
138 		      tl->max_size);
139 		return TL_OPS_NON;
140 	}
141 
142 	if (tl->size > tl->max_size) {
143 		ERROR("Bad transfer list size %#"PRIx32"\n", tl->size);
144 		return TL_OPS_NON;
145 	}
146 
147 	if (tl->hdr_size != sizeof(struct transfer_list_header)) {
148 		ERROR("Bad transfer list header size %#"PRIx32"\n", tl->hdr_size);
149 		return TL_OPS_NON;
150 	}
151 
152 	if (!transfer_list_verify_checksum(tl)) {
153 		ERROR("Bad transfer list checksum %#"PRIx32"\n", tl->checksum);
154 		return TL_OPS_NON;
155 	}
156 
157 	if (tl->version == 0) {
158 		ERROR("Transfer list version is invalid\n");
159 		return TL_OPS_NON;
160 	} else if (tl->version == TRANSFER_LIST_VERSION) {
161 		INFO("Transfer list version is valid for all operations\n");
162 		return TL_OPS_ALL;
163 	} else if (tl->version > TRANSFER_LIST_VERSION) {
164 		INFO("Transfer list version is valid for read-only\n");
165 		return TL_OPS_RO;
166 	}
167 
168 	INFO("Old transfer list version is detected\n");
169 	return TL_OPS_CUS;
170 }
171 
172 /*******************************************************************************
173  * Enumerate the next transfer entry
174  * Return pointer to the next transfer entry or NULL on error
175  ******************************************************************************/
transfer_list_next(struct transfer_list_header * tl,struct transfer_list_entry * last)176 struct transfer_list_entry *transfer_list_next(struct transfer_list_header *tl,
177 					       struct transfer_list_entry *last)
178 {
179 	struct transfer_list_entry *te = NULL;
180 	uintptr_t tl_ev = 0;
181 	uintptr_t va = 0;
182 	uintptr_t ev = 0;
183 	size_t sz = 0;
184 
185 	if (!tl) {
186 		return NULL;
187 	}
188 
189 	tl_ev = (uintptr_t)tl + tl->size;
190 
191 	if (last) {
192 		va = (uintptr_t)last;
193 		// check if the total size overflow
194 		if (add_overflow(last->hdr_size,
195 			last->data_size, &sz)) {
196 			return NULL;
197 		}
198 		// roundup to the next entry
199 		if (add_with_round_up_overflow(va, sz,
200 			TRANSFER_LIST_GRANULE, &va)) {
201 			return NULL;
202 		}
203 	} else {
204 		va = (uintptr_t)tl + tl->hdr_size;
205 	}
206 
207 	te = (struct transfer_list_entry *)va;
208 
209 	if (va + sizeof(*te) > tl_ev || te->hdr_size < sizeof(*te) ||
210 		add_overflow(te->hdr_size, te->data_size, &sz) ||
211 		add_overflow(va, sz, &ev) ||
212 		ev > tl_ev) {
213 		return NULL;
214 	}
215 
216 	return te;
217 }
218 
219 /*******************************************************************************
220  * Calculate the byte sum of a transfer list
221  * Return byte sum of the transfer list
222  ******************************************************************************/
calc_byte_sum(const struct transfer_list_header * tl)223 static uint8_t calc_byte_sum(const struct transfer_list_header *tl)
224 {
225 	uint8_t *b = (uint8_t *)tl;
226 	uint8_t cs = 0;
227 	size_t n = 0;
228 
229 	if (!tl) {
230 		return 0;
231 	}
232 
233 	for (n = 0; n < tl->size; n++) {
234 		cs += b[n];
235 	}
236 
237 	return cs;
238 }
239 
240 /*******************************************************************************
241  * Update the checksum of a transfer list
242  * Return updated checksum of the transfer list
243  ******************************************************************************/
transfer_list_update_checksum(struct transfer_list_header * tl)244 void transfer_list_update_checksum(struct transfer_list_header *tl)
245 {
246 	uint8_t cs;
247 
248 	if (!tl) {
249 		return;
250 	}
251 
252 	cs = calc_byte_sum(tl);
253 	cs -= tl->checksum;
254 	cs = 256 - cs;
255 	tl->checksum = cs;
256 	assert(transfer_list_verify_checksum(tl));
257 }
258 
259 /*******************************************************************************
260  * Verify the checksum of a transfer list
261  * Return true if verified or false if not
262  ******************************************************************************/
transfer_list_verify_checksum(const struct transfer_list_header * tl)263 bool transfer_list_verify_checksum(const struct transfer_list_header *tl)
264 {
265 	return !calc_byte_sum(tl);
266 }
267 
268 /*******************************************************************************
269  * Update the data size of a transfer entry
270  * Return true on success or false on error
271  ******************************************************************************/
transfer_list_set_data_size(struct transfer_list_header * tl,struct transfer_list_entry * te,uint32_t new_data_size)272 bool transfer_list_set_data_size(struct transfer_list_header *tl,
273 				 struct transfer_list_entry *te,
274 				 uint32_t new_data_size)
275 {
276 	uintptr_t tl_old_ev, new_ev = 0, old_ev = 0, ru_new_ev;
277 	struct transfer_list_entry *dummy_te = NULL;
278 	size_t gap = 0;
279 	size_t mov_dis = 0;
280 	size_t sz = 0;
281 
282 	if (!tl || !te) {
283 		return false;
284 	}
285 	tl_old_ev = (uintptr_t)tl + tl->size;
286 
287 	// calculate the old and new end of TE
288 	// both must be roundup to align with TRANSFER_LIST_GRANULE
289 	if (add_overflow(te->hdr_size, te->data_size, &sz) ||
290 		add_with_round_up_overflow((uintptr_t)te, sz,
291 		TRANSFER_LIST_GRANULE, &old_ev)) {
292 		return false;
293 	}
294 	if (add_overflow(te->hdr_size, new_data_size, &sz) ||
295 		add_with_round_up_overflow((uintptr_t)te, sz,
296 		TRANSFER_LIST_GRANULE, &new_ev)) {
297 		return false;
298 	}
299 
300 	if (new_ev > old_ev) {
301 		// move distance should be roundup
302 		// to meet the requirement of TE data max alignment
303 		// ensure that the increased size doesn't exceed
304 		// the max size of TL
305 		mov_dis = new_ev - old_ev;
306 		if (round_up_overflow(mov_dis, 1 << tl->alignment,
307 			&mov_dis) || tl->size + mov_dis > tl->max_size) {
308 			return false;
309 		}
310 		ru_new_ev = old_ev + mov_dis;
311 		memmove((void *)ru_new_ev, (void *)old_ev, tl_old_ev - old_ev);
312 		tl->size += mov_dis;
313 		gap = ru_new_ev - new_ev;
314 	} else {
315 		gap = old_ev - new_ev;
316 	}
317 
318 	if (gap >= sizeof(*dummy_te)) {
319 		// create a dummy TE to fill up the gap
320 		dummy_te = (struct transfer_list_entry *)new_ev;
321 		dummy_te->tag_id = TL_TAG_EMPTY;
322 		dummy_te->reserved0 = 0;
323 		dummy_te->hdr_size = sizeof(*dummy_te);
324 		dummy_te->data_size = gap - sizeof(*dummy_te);
325 	}
326 
327 	te->data_size = new_data_size;
328 
329 	transfer_list_update_checksum(tl);
330 	return true;
331 }
332 
333 /*******************************************************************************
334  * Remove a specified transfer entry from a transfer list
335  * Return true on success or false on error
336  ******************************************************************************/
transfer_list_rem(struct transfer_list_header * tl,struct transfer_list_entry * te)337 bool transfer_list_rem(struct transfer_list_header *tl,
338 			struct transfer_list_entry *te)
339 {
340 	if (!tl || !te || (uintptr_t)te > (uintptr_t)tl + tl->size) {
341 		return false;
342 	}
343 	te->tag_id = TL_TAG_EMPTY;
344 	te->reserved0 = 0;
345 	transfer_list_update_checksum(tl);
346 	return true;
347 }
348 
349 /*******************************************************************************
350  * Add a new transfer entry into a transfer list
351  * Compliant to 2.4.3 of Firmware handoff specification (v0.9)
352  * Return pointer to the added transfer entry or NULL on error
353  ******************************************************************************/
transfer_list_add(struct transfer_list_header * tl,uint16_t tag_id,uint32_t data_size,const void * data)354 struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl,
355 					      uint16_t tag_id,
356 					      uint32_t data_size,
357 					      const void *data)
358 {
359 	uintptr_t max_tl_ev, tl_ev, ev;
360 	struct transfer_list_entry *te = NULL;
361 	uint8_t *te_data = NULL;
362 	size_t sz = 0;
363 
364 	if (!tl) {
365 		return NULL;
366 	}
367 
368 	max_tl_ev = (uintptr_t)tl + tl->max_size;
369 	tl_ev = (uintptr_t)tl + tl->size;
370 	ev = tl_ev;
371 
372 	// skip the step 1 (optional step)
373 	// new TE will be added into the tail
374 	if (add_overflow(sizeof(*te), data_size, &sz) ||
375 		add_with_round_up_overflow(ev, sz,
376 		TRANSFER_LIST_GRANULE, &ev) || ev > max_tl_ev) {
377 		return NULL;
378 	}
379 
380 	te = (struct transfer_list_entry *)tl_ev;
381 	te->tag_id = tag_id;
382 	te->reserved0 = 0;
383 	te->hdr_size = sizeof(*te);
384 	te->data_size = data_size;
385 	tl->size += ev - tl_ev;
386 
387 	if (data) {
388 		// get TE data pointer
389 		te_data = transfer_list_entry_data(te);
390 		if (!te_data) {
391 			return NULL;
392 		}
393 		memmove(te_data, data, data_size);
394 	}
395 
396 	transfer_list_update_checksum(tl);
397 
398 	return te;
399 }
400 
401 /*******************************************************************************
402  * Add a new transfer entry into a transfer list with specified new data
403  * alignment requirement
404  * Compliant to 2.4.4 of Firmware handoff specification (v0.9)
405  * Return pointer to the added transfer entry or NULL on error
406  ******************************************************************************/
transfer_list_add_with_align(struct transfer_list_header * tl,uint16_t tag_id,uint32_t data_size,const void * data,uint8_t alignment)407 struct transfer_list_entry *transfer_list_add_with_align(
408 					struct transfer_list_header *tl,
409 					uint16_t tag_id, uint32_t data_size,
410 					const void *data, uint8_t alignment)
411 {
412 	struct transfer_list_entry *te = NULL;
413 	uintptr_t tl_ev, ev, new_tl_ev;
414 	size_t dummy_te_data_sz = 0;
415 
416 	if (!tl) {
417 		return NULL;
418 	}
419 
420 	tl_ev = (uintptr_t)tl + tl->size;
421 	ev = tl_ev + sizeof(struct transfer_list_entry);
422 
423 	if (!is_aligned(ev, 1 << alignment)) {
424 		// TE data address is not aligned to the new alignment
425 		// fill the gap with an empty TE as a placeholder before
426 		// adding the desire TE
427 		new_tl_ev = round_up(ev, 1 << alignment) -
428 				sizeof(struct transfer_list_entry);
429 		dummy_te_data_sz = new_tl_ev - tl_ev -
430 					sizeof(struct transfer_list_entry);
431 		if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz,
432 					NULL)) {
433 			return NULL;
434 		}
435 	}
436 
437 	te = transfer_list_add(tl, tag_id, data_size, data);
438 
439 	if (alignment > tl->alignment) {
440 		tl->alignment = alignment;
441 		transfer_list_update_checksum(tl);
442 	}
443 
444 	return te;
445 }
446 
447 /*******************************************************************************
448  * Search for an existing transfer entry with the specified tag id from a
449  * transfer list
450  * Return pointer to the found transfer entry or NULL on error
451  ******************************************************************************/
transfer_list_find(struct transfer_list_header * tl,uint16_t tag_id)452 struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl,
453 					       uint16_t tag_id)
454 {
455 	struct transfer_list_entry *te = NULL;
456 
457 	do {
458 		te = transfer_list_next(tl, te);
459 	} while (te && (te->tag_id != tag_id || te->reserved0 != 0));
460 
461 	return te;
462 }
463 
464 /*******************************************************************************
465  * Retrieve the data pointer of a specified transfer entry
466  * Return pointer to the transfer entry data or NULL on error
467  ******************************************************************************/
transfer_list_entry_data(struct transfer_list_entry * entry)468 void *transfer_list_entry_data(struct transfer_list_entry *entry)
469 {
470 	if (!entry) {
471 		return NULL;
472 	}
473 	return (uint8_t *)entry + entry->hdr_size;
474 }
475