xref: /optee_os/core/kernel/transfer_list.c (revision 91f02c8c2c9f1f26fc4e16a28c1c54dce4550483)
1a1222502SRaymond Mao // SPDX-License-Identifier: BSD-2-Clause
2a1222502SRaymond Mao /*
3a1222502SRaymond Mao  * Copyright (c) 2023, Linaro Limited
4a1222502SRaymond Mao  */
5a1222502SRaymond Mao 
6a1222502SRaymond Mao /*******************************************************************************
7a1222502SRaymond Mao  * Transfer list library compliant with the Firmware Handoff specification at:
8a1222502SRaymond Mao  * https://github.com/FirmwareHandoff/firmware_handoff
9a1222502SRaymond Mao  ******************************************************************************/
10a1222502SRaymond Mao 
11a1222502SRaymond Mao #include <kernel/cache_helpers.h>
12a1222502SRaymond Mao #include <kernel/panic.h>
13a1222502SRaymond Mao #include <kernel/transfer_list.h>
14a1222502SRaymond Mao #include <mm/core_memprot.h>
15a1222502SRaymond Mao #include <mm/core_mmu.h>
16a1222502SRaymond Mao #include <string.h>
17a1222502SRaymond Mao #include <util.h>
18a1222502SRaymond Mao 
19a1222502SRaymond Mao /*******************************************************************************
20a1222502SRaymond Mao  * Adapt a physical address to match the maximum transfer entry data alignment
21a1222502SRaymond Mao  * required by an existing transfer list.
22a1222502SRaymond Mao  * Compliant with 2.4.6 of Firmware Handoff specification (v0.9).
23a1222502SRaymond Mao  * @pa: Physical address for adapting.
24a1222502SRaymond Mao  * @tl: Pointer to the existing transfer list.
25a1222502SRaymond Mao  * Return the adapted physical address.
26a1222502SRaymond Mao  ******************************************************************************/
get_align_base_addr(paddr_t pa,struct transfer_list_header * tl)27a1222502SRaymond Mao static paddr_t get_align_base_addr(paddr_t pa,
28a1222502SRaymond Mao 				   struct transfer_list_header *tl)
29a1222502SRaymond Mao {
30a1222502SRaymond Mao 	paddr_t align_mask = TL_ALIGNMENT_FROM_ORDER(tl->alignment) - 1;
31a1222502SRaymond Mao 	paddr_t align_off = (paddr_t)tl & align_mask;
32a1222502SRaymond Mao 	paddr_t new_addr = (pa & ~align_mask) + align_off;
33a1222502SRaymond Mao 
34a1222502SRaymond Mao 	if (new_addr < pa)
35a1222502SRaymond Mao 		new_addr += TL_ALIGNMENT_FROM_ORDER(tl->alignment);
36a1222502SRaymond Mao 
37a1222502SRaymond Mao 	return new_addr;
38a1222502SRaymond Mao }
39a1222502SRaymond Mao 
unmap_list(struct transfer_list_header * tl,size_t sz)40a1222502SRaymond Mao static void unmap_list(struct transfer_list_header *tl, size_t sz)
41a1222502SRaymond Mao {
42a1222502SRaymond Mao 	if (core_mmu_remove_mapping(MEM_AREA_TRANSFER_LIST, tl, sz))
43a1222502SRaymond Mao 		panic("Failed to remove transfer list mapping");
44a1222502SRaymond Mao }
45a1222502SRaymond Mao 
transfer_list_map(paddr_t pa)46a1222502SRaymond Mao struct transfer_list_header *transfer_list_map(paddr_t pa)
47a1222502SRaymond Mao {
48a1222502SRaymond Mao 	struct transfer_list_header *tl = NULL;
49a1222502SRaymond Mao 	size_t sz = SMALL_PAGE_SIZE;
50a1222502SRaymond Mao 	size_t old_sz = 0;
51a1222502SRaymond Mao 
52a1222502SRaymond Mao 	while (true) {
53a1222502SRaymond Mao 		tl = core_mmu_add_mapping(MEM_AREA_TRANSFER_LIST, pa, sz);
54a1222502SRaymond Mao 		if (!tl) {
55a1222502SRaymond Mao 			EMSG("Failed to map TL with PA %#"PRIxPA", size %#zx",
56a1222502SRaymond Mao 			     pa, sz);
57a1222502SRaymond Mao 			return NULL;
58a1222502SRaymond Mao 		}
59a1222502SRaymond Mao 		old_sz = sz;
60a1222502SRaymond Mao 
61a1222502SRaymond Mao 		if (transfer_list_check_header(tl) == TL_OPS_NONE) {
62a1222502SRaymond Mao 			unmap_list(tl, sz);
63a1222502SRaymond Mao 			return NULL;
64a1222502SRaymond Mao 		}
65a1222502SRaymond Mao 
66a1222502SRaymond Mao 		if (tl->max_size <= sz)
67a1222502SRaymond Mao 			return tl;
68a1222502SRaymond Mao 
69a1222502SRaymond Mao 		sz = ROUNDUP(tl->max_size, SMALL_PAGE_SIZE);
70a1222502SRaymond Mao 		unmap_list(tl, old_sz);
71a1222502SRaymond Mao 	}
72a1222502SRaymond Mao }
73a1222502SRaymond Mao 
transfer_list_unmap_sync(struct transfer_list_header * tl)74a1222502SRaymond Mao void transfer_list_unmap_sync(struct transfer_list_header *tl)
75a1222502SRaymond Mao {
76a1222502SRaymond Mao 	size_t sz = tl->max_size;
77a1222502SRaymond Mao 
78a1222502SRaymond Mao 	transfer_list_update_checksum(tl);
79a1222502SRaymond Mao 	dcache_cleaninv_range(tl, sz);
80a1222502SRaymond Mao 	unmap_list(tl, sz);
81a1222502SRaymond Mao }
82a1222502SRaymond Mao 
transfer_list_unmap_nosync(struct transfer_list_header * tl)83a1222502SRaymond Mao void transfer_list_unmap_nosync(struct transfer_list_header *tl)
84a1222502SRaymond Mao {
85a1222502SRaymond Mao 	unmap_list(tl, tl->max_size);
86a1222502SRaymond Mao }
87a1222502SRaymond Mao 
transfer_list_dump(struct transfer_list_header * tl)88a1222502SRaymond Mao void transfer_list_dump(struct transfer_list_header *tl)
89a1222502SRaymond Mao {
90a1222502SRaymond Mao 	struct transfer_list_entry *tl_e = NULL;
91a1222502SRaymond Mao 	int i __maybe_unused = 0;
92a1222502SRaymond Mao 
93a1222502SRaymond Mao 	if (!tl)
94a1222502SRaymond Mao 		return;
95a1222502SRaymond Mao 
96a1222502SRaymond Mao 	DMSG("Dump transfer list:");
97a1222502SRaymond Mao 	DMSG("signature  %#"PRIx32, tl->signature);
98a1222502SRaymond Mao 	DMSG("checksum   %#"PRIx8, tl->checksum);
99a1222502SRaymond Mao 	DMSG("version    %#"PRIx8, tl->version);
100a1222502SRaymond Mao 	DMSG("hdr_size   %#"PRIx8, tl->hdr_size);
101a1222502SRaymond Mao 	DMSG("alignment  %#"PRIx8, tl->alignment);
102a1222502SRaymond Mao 	DMSG("size       %#"PRIx32, tl->size);
103a1222502SRaymond Mao 	DMSG("max_size   %#"PRIx32, tl->max_size);
104508e2476SRaymond Mao 	DMSG("flags      %#"PRIx32, tl->flags);
105a1222502SRaymond Mao 	while (true) {
106a1222502SRaymond Mao 		tl_e = transfer_list_next(tl, tl_e);
107a1222502SRaymond Mao 		if (!tl_e)
108a1222502SRaymond Mao 			break;
109a1222502SRaymond Mao 
110a1222502SRaymond Mao 		DMSG("Entry %d:", i++);
111a1222502SRaymond Mao 		DMSG("tag_id     %#"PRIx16, tl_e->tag_id);
112a1222502SRaymond Mao 		DMSG("hdr_size   %#"PRIx8, tl_e->hdr_size);
113a1222502SRaymond Mao 		DMSG("data_size  %#"PRIx32, tl_e->data_size);
114a1222502SRaymond Mao 		DMSG("data_addr  %#"PRIxVA,
115a1222502SRaymond Mao 		     (vaddr_t)transfer_list_entry_data(tl_e));
116a1222502SRaymond Mao 	}
117a1222502SRaymond Mao }
118a1222502SRaymond Mao 
119a1222502SRaymond Mao /*******************************************************************************
120a1222502SRaymond Mao  * Creating a transfer list in a specified reserved memory region.
121a1222502SRaymond Mao  * Compliant with 2.4.5 of Firmware Handoff specification (v0.9).
122a1222502SRaymond Mao  * @pa: Physical address for residing the new transfer list.
123a1222502SRaymond Mao  * @max_size: Maximum size of the new transfer list.
124a1222502SRaymond Mao  * Return pointer to the created transfer list or NULL on error.
125a1222502SRaymond Mao  ******************************************************************************/
transfer_list_init(paddr_t pa,size_t max_size)126a1222502SRaymond Mao struct transfer_list_header *transfer_list_init(paddr_t pa, size_t max_size)
127a1222502SRaymond Mao {
128a1222502SRaymond Mao 	struct transfer_list_header *tl = NULL;
129a1222502SRaymond Mao 	int align = TL_ALIGNMENT_FROM_ORDER(TRANSFER_LIST_INIT_MAX_ALIGN);
130a1222502SRaymond Mao 
131a1222502SRaymond Mao 	if (!pa || !max_size)
132a1222502SRaymond Mao 		return NULL;
133a1222502SRaymond Mao 
134a1222502SRaymond Mao 	if (!IS_ALIGNED(pa, align) || !IS_ALIGNED(max_size, align) ||
135a1222502SRaymond Mao 	    max_size < sizeof(*tl))
136a1222502SRaymond Mao 		return NULL;
137a1222502SRaymond Mao 
138a1222502SRaymond Mao 	tl = core_mmu_add_mapping(MEM_AREA_TRANSFER_LIST, pa, max_size);
139a1222502SRaymond Mao 	if (!tl)
140a1222502SRaymond Mao 		return NULL;
141a1222502SRaymond Mao 
142a1222502SRaymond Mao 	memset(tl, 0, max_size);
143a1222502SRaymond Mao 	tl->signature = TRANSFER_LIST_SIGNATURE;
144a1222502SRaymond Mao 	tl->version = TRANSFER_LIST_VERSION;
145a1222502SRaymond Mao 	tl->hdr_size = sizeof(*tl);
146a1222502SRaymond Mao 	tl->alignment = TRANSFER_LIST_INIT_MAX_ALIGN; /* initial max align */
147a1222502SRaymond Mao 	tl->size = sizeof(*tl); /* initial size is the size of header */
148a1222502SRaymond Mao 	tl->max_size = max_size;
149508e2476SRaymond Mao 	tl->flags = TL_FLAGS_HAS_CHECKSUM;
150a1222502SRaymond Mao 
151a1222502SRaymond Mao 	transfer_list_update_checksum(tl);
152a1222502SRaymond Mao 
153a1222502SRaymond Mao 	return tl;
154a1222502SRaymond Mao }
155a1222502SRaymond Mao 
156a1222502SRaymond Mao /*******************************************************************************
157a1222502SRaymond Mao  * Relocating a transfer list to a specified reserved memory region.
158a1222502SRaymond Mao  * Compliant with 2.4.6 of Firmware Handoff specification (v0.9).
159a1222502SRaymond Mao  * @tl: Pointer to the transfer list for relocating.
160a1222502SRaymond Mao  * @pa: Physical address for relocating the transfer list.
161a1222502SRaymond Mao  * @max_size: Maximum size of the transfer list after relocating
162a1222502SRaymond Mao  * Return pointer to the relocated transfer list or NULL on error.
163a1222502SRaymond Mao  ******************************************************************************/
164a1222502SRaymond Mao struct transfer_list_header *
transfer_list_relocate(struct transfer_list_header * tl,paddr_t pa,size_t max_size)165a1222502SRaymond Mao transfer_list_relocate(struct transfer_list_header *tl, paddr_t pa,
166a1222502SRaymond Mao 		       size_t max_size)
167a1222502SRaymond Mao {
168a1222502SRaymond Mao 	paddr_t new_addr = 0;
169a1222502SRaymond Mao 	struct transfer_list_header *new_tl = NULL;
170a1222502SRaymond Mao 	size_t new_max_size = 0;
171a1222502SRaymond Mao 
172a1222502SRaymond Mao 	if (!tl || !pa || !max_size)
173a1222502SRaymond Mao 		return NULL;
174a1222502SRaymond Mao 
175a1222502SRaymond Mao 	new_addr = get_align_base_addr(pa, tl);
176a1222502SRaymond Mao 	new_max_size = max_size - (new_addr - pa);
177a1222502SRaymond Mao 
178a1222502SRaymond Mao 	/* The new space is not sufficient for the TL */
179a1222502SRaymond Mao 	if (tl->size > new_max_size)
180a1222502SRaymond Mao 		return NULL;
181a1222502SRaymond Mao 
182a1222502SRaymond Mao 	new_tl = core_mmu_add_mapping(MEM_AREA_TRANSFER_LIST, new_addr,
183a1222502SRaymond Mao 				      new_max_size);
184a1222502SRaymond Mao 	if (!new_tl)
185a1222502SRaymond Mao 		return NULL;
186a1222502SRaymond Mao 
187a1222502SRaymond Mao 	memmove(new_tl, tl, tl->size);
188a1222502SRaymond Mao 	new_tl->max_size = new_max_size;
189a1222502SRaymond Mao 
190a1222502SRaymond Mao 	transfer_list_update_checksum(new_tl);
191a1222502SRaymond Mao 	transfer_list_unmap_nosync(tl);
192a1222502SRaymond Mao 
193a1222502SRaymond Mao 	return new_tl;
194a1222502SRaymond Mao }
195a1222502SRaymond Mao 
196a1222502SRaymond Mao /*******************************************************************************
197a1222502SRaymond Mao  * Verifying the header of a transfer list.
198a1222502SRaymond Mao  * Compliant with 2.4.1 of Firmware Handoff specification (v0.9).
199a1222502SRaymond Mao  * @tl: Pointer to the transfer list.
200a1222502SRaymond Mao  * Return transfer list operation status code.
201a1222502SRaymond Mao  ******************************************************************************/
transfer_list_check_header(const struct transfer_list_header * tl)202a1222502SRaymond Mao int transfer_list_check_header(const struct transfer_list_header *tl)
203a1222502SRaymond Mao {
204a1222502SRaymond Mao 	if (!tl)
205a1222502SRaymond Mao 		return TL_OPS_NONE;
206a1222502SRaymond Mao 
207a1222502SRaymond Mao 	if (tl->signature != TRANSFER_LIST_SIGNATURE) {
208a1222502SRaymond Mao 		EMSG("Bad transfer list signature %#"PRIx32, tl->signature);
209a1222502SRaymond Mao 		return TL_OPS_NONE;
210a1222502SRaymond Mao 	}
211a1222502SRaymond Mao 
212a1222502SRaymond Mao 	if (!tl->max_size) {
213a1222502SRaymond Mao 		EMSG("Bad transfer list max size %#"PRIx32, tl->max_size);
214a1222502SRaymond Mao 		return TL_OPS_NONE;
215a1222502SRaymond Mao 	}
216a1222502SRaymond Mao 
217a1222502SRaymond Mao 	if (tl->size > tl->max_size) {
218a1222502SRaymond Mao 		EMSG("Bad transfer list size %#"PRIx32, tl->size);
219a1222502SRaymond Mao 		return TL_OPS_NONE;
220a1222502SRaymond Mao 	}
221a1222502SRaymond Mao 
222a1222502SRaymond Mao 	if (tl->hdr_size != sizeof(struct transfer_list_header)) {
223a1222502SRaymond Mao 		EMSG("Bad transfer list header size %#"PRIx8, tl->hdr_size);
224a1222502SRaymond Mao 		return TL_OPS_NONE;
225a1222502SRaymond Mao 	}
226a1222502SRaymond Mao 
227a1222502SRaymond Mao 	if (!transfer_list_verify_checksum(tl)) {
228a1222502SRaymond Mao 		EMSG("Bad transfer list checksum %#"PRIx8, tl->checksum);
229a1222502SRaymond Mao 		return TL_OPS_NONE;
230a1222502SRaymond Mao 	}
231a1222502SRaymond Mao 
232a1222502SRaymond Mao 	if (tl->version == 0) {
233a1222502SRaymond Mao 		EMSG("Transfer list version is invalid");
234a1222502SRaymond Mao 		return TL_OPS_NONE;
235a1222502SRaymond Mao 	} else if (tl->version == TRANSFER_LIST_VERSION) {
236a1222502SRaymond Mao 		DMSG("Transfer list version is valid for all operations");
237a1222502SRaymond Mao 		return TL_OPS_ALL;
238a1222502SRaymond Mao 	} else if (tl->version > TRANSFER_LIST_VERSION) {
239a1222502SRaymond Mao 		DMSG("Transfer list version is valid for read-only");
240a1222502SRaymond Mao 		return TL_OPS_RO;
241a1222502SRaymond Mao 	}
242a1222502SRaymond Mao 
243a1222502SRaymond Mao 	DMSG("Old transfer list version is detected");
244a1222502SRaymond Mao 	return TL_OPS_CUS;
245a1222502SRaymond Mao }
246a1222502SRaymond Mao 
247a1222502SRaymond Mao /*******************************************************************************
248a1222502SRaymond Mao  * Enumerate the next transfer entry.
249a1222502SRaymond Mao  * @tl: Pointer to the transfer list.
250a1222502SRaymond Mao  * @cur: Pointer to the current transfer entry where we want to search for the
251a1222502SRaymond Mao  *       next one.
252a1222502SRaymond Mao  * Return pointer to the next transfer entry or NULL on error or if @cur is the
253a1222502SRaymond Mao  * last entry.
254a1222502SRaymond Mao  ******************************************************************************/
transfer_list_next(struct transfer_list_header * tl,struct transfer_list_entry * cur)255a1222502SRaymond Mao struct transfer_list_entry *transfer_list_next(struct transfer_list_header *tl,
256a1222502SRaymond Mao 					       struct transfer_list_entry *cur)
257a1222502SRaymond Mao {
258a1222502SRaymond Mao 	struct transfer_list_entry *tl_e = NULL;
259a1222502SRaymond Mao 	vaddr_t tl_ev = 0;
260a1222502SRaymond Mao 	vaddr_t va = 0;
261a1222502SRaymond Mao 	vaddr_t ev = 0;
262a1222502SRaymond Mao 	size_t sz = 0;
263a1222502SRaymond Mao 
264a1222502SRaymond Mao 	if (!tl)
265a1222502SRaymond Mao 		return NULL;
266a1222502SRaymond Mao 
267a1222502SRaymond Mao 	tl_ev = (vaddr_t)tl + tl->size;
268a1222502SRaymond Mao 
269a1222502SRaymond Mao 	if (cur) {
270a1222502SRaymond Mao 		va = (vaddr_t)cur;
271a1222502SRaymond Mao 		/* check if the total size overflow */
272a1222502SRaymond Mao 		if (ADD_OVERFLOW(cur->hdr_size, cur->data_size, &sz))
273a1222502SRaymond Mao 			return NULL;
274a1222502SRaymond Mao 		/* roundup to the next entry */
275a1222502SRaymond Mao 		if (ADD_OVERFLOW(va, sz, &va) ||
276a1222502SRaymond Mao 		    ROUNDUP_OVERFLOW(va, TRANSFER_LIST_GRANULE, &va))
277a1222502SRaymond Mao 			return NULL;
278a1222502SRaymond Mao 	} else {
279a1222502SRaymond Mao 		va = (vaddr_t)tl + tl->hdr_size;
280a1222502SRaymond Mao 	}
281a1222502SRaymond Mao 
282a1222502SRaymond Mao 	tl_e = (struct transfer_list_entry *)va;
283a1222502SRaymond Mao 
284a1222502SRaymond Mao 	if (va + sizeof(*tl_e) > tl_ev || tl_e->hdr_size < sizeof(*tl_e) ||
285a1222502SRaymond Mao 	    ADD_OVERFLOW(tl_e->hdr_size, tl_e->data_size, &sz) ||
286a1222502SRaymond Mao 	    ADD_OVERFLOW(va, sz, &ev) || ev > tl_ev)
287a1222502SRaymond Mao 		return NULL;
288a1222502SRaymond Mao 
289a1222502SRaymond Mao 	return tl_e;
290a1222502SRaymond Mao }
291a1222502SRaymond Mao 
292a1222502SRaymond Mao /*******************************************************************************
293a1222502SRaymond Mao  * Calculate the byte sum (modulo 256) of a transfer list.
294a1222502SRaymond Mao  * @tl: Pointer to the transfer list.
295a1222502SRaymond Mao  * Return byte sum of the transfer list.
296a1222502SRaymond Mao  ******************************************************************************/
calc_byte_sum(const struct transfer_list_header * tl)297a1222502SRaymond Mao static uint8_t calc_byte_sum(const struct transfer_list_header *tl)
298a1222502SRaymond Mao {
299a1222502SRaymond Mao 	uint8_t *b = (uint8_t *)tl;
300a1222502SRaymond Mao 	uint8_t cs = 0;
301a1222502SRaymond Mao 	size_t n = 0;
302a1222502SRaymond Mao 
303a1222502SRaymond Mao 	for (n = 0; n < tl->size; n++)
304a1222502SRaymond Mao 		cs += b[n];
305a1222502SRaymond Mao 
306a1222502SRaymond Mao 	return cs;
307a1222502SRaymond Mao }
308a1222502SRaymond Mao 
309a1222502SRaymond Mao /*******************************************************************************
310a1222502SRaymond Mao  * Update the checksum of a transfer list.
311a1222502SRaymond Mao  * @tl: Pointer to the transfer list.
312a1222502SRaymond Mao  * Return updated checksum of the transfer list.
313a1222502SRaymond Mao  ******************************************************************************/
transfer_list_update_checksum(struct transfer_list_header * tl)314a1222502SRaymond Mao void transfer_list_update_checksum(struct transfer_list_header *tl)
315a1222502SRaymond Mao {
316a1222502SRaymond Mao 	uint8_t cs = 0;
317a1222502SRaymond Mao 
318508e2476SRaymond Mao 	if (!tl || !(tl->flags & TL_FLAGS_HAS_CHECKSUM))
319a1222502SRaymond Mao 		return;
320a1222502SRaymond Mao 
321a1222502SRaymond Mao 	cs = calc_byte_sum(tl);
322a1222502SRaymond Mao 	cs -= tl->checksum;
323a1222502SRaymond Mao 	cs = 256 - cs;
324a1222502SRaymond Mao 	tl->checksum = cs;
325a1222502SRaymond Mao 	assert(transfer_list_verify_checksum(tl));
326a1222502SRaymond Mao }
327a1222502SRaymond Mao 
328a1222502SRaymond Mao /*******************************************************************************
329a1222502SRaymond Mao  * Verify the checksum of a transfer list.
330a1222502SRaymond Mao  * @tl: Pointer to the transfer list.
331a1222502SRaymond Mao  * Return true if verified or false if not.
332a1222502SRaymond Mao  ******************************************************************************/
transfer_list_verify_checksum(const struct transfer_list_header * tl)333a1222502SRaymond Mao bool transfer_list_verify_checksum(const struct transfer_list_header *tl)
334a1222502SRaymond Mao {
335508e2476SRaymond Mao 	if (!tl)
336508e2476SRaymond Mao 		return false;
337508e2476SRaymond Mao 
338508e2476SRaymond Mao 	if (!(tl->flags & TL_FLAGS_HAS_CHECKSUM))
339508e2476SRaymond Mao 		return true;
340508e2476SRaymond Mao 
341a1222502SRaymond Mao 	return !calc_byte_sum(tl);
342a1222502SRaymond Mao }
343a1222502SRaymond Mao 
344a1222502SRaymond Mao /*******************************************************************************
345a1222502SRaymond Mao  * Update the data size of a transfer entry.
346a1222502SRaymond Mao  * @tl: Pointer to the transfer list.
347a1222502SRaymond Mao  * @tl_e: Pointer to the transfer entry.
348a1222502SRaymond Mao  * @new_data_size: New data size of the transfer entry.
349a1222502SRaymond Mao  * Return true on success or false on error.
350a1222502SRaymond Mao  ******************************************************************************/
transfer_list_set_data_size(struct transfer_list_header * tl,struct transfer_list_entry * tl_e,uint32_t new_data_size)351a1222502SRaymond Mao bool transfer_list_set_data_size(struct transfer_list_header *tl,
352a1222502SRaymond Mao 				 struct transfer_list_entry *tl_e,
353a1222502SRaymond Mao 				 uint32_t new_data_size)
354a1222502SRaymond Mao {
355a1222502SRaymond Mao 	vaddr_t tl_old_ev = 0;
356a1222502SRaymond Mao 	vaddr_t new_ev = 0;
357a1222502SRaymond Mao 	vaddr_t old_ev = 0;
35886dbb179SRaymond Mao 	vaddr_t r_new_ev = 0;
359a1222502SRaymond Mao 	struct transfer_list_entry *dummy_te = NULL;
360a1222502SRaymond Mao 	size_t gap = 0;
361a1222502SRaymond Mao 	size_t mov_dis = 0;
362a1222502SRaymond Mao 	size_t sz = 0;
363a1222502SRaymond Mao 
364a1222502SRaymond Mao 	if (!tl || !tl_e)
365a1222502SRaymond Mao 		return false;
366a1222502SRaymond Mao 
367a1222502SRaymond Mao 	tl_old_ev = (vaddr_t)tl + tl->size;
368a1222502SRaymond Mao 
369a1222502SRaymond Mao 	/*
370a1222502SRaymond Mao 	 * Calculate the old and new end of transfer entry
371a1222502SRaymond Mao 	 * both must be roundup to align with TRANSFER_LIST_GRANULE
372a1222502SRaymond Mao 	 */
373a1222502SRaymond Mao 	if (ADD_OVERFLOW(tl_e->hdr_size, tl_e->data_size, &sz) ||
374a1222502SRaymond Mao 	    ADD_OVERFLOW((vaddr_t)tl_e, sz, &old_ev) ||
375a1222502SRaymond Mao 	    ROUNDUP_OVERFLOW(old_ev, TRANSFER_LIST_GRANULE, &old_ev))
376a1222502SRaymond Mao 		return false;
377a1222502SRaymond Mao 
378a1222502SRaymond Mao 	if (ADD_OVERFLOW(tl_e->hdr_size, new_data_size, &sz) ||
379a1222502SRaymond Mao 	    ADD_OVERFLOW((vaddr_t)tl_e, sz, &new_ev) ||
380a1222502SRaymond Mao 	    ROUNDUP_OVERFLOW(new_ev, TRANSFER_LIST_GRANULE, &new_ev))
381a1222502SRaymond Mao 		return false;
382a1222502SRaymond Mao 
383a1222502SRaymond Mao 	if (new_ev > old_ev) {
384a1222502SRaymond Mao 		/*
38586dbb179SRaymond Mao 		 * Move distance should be rounded up to match the entry data
38686dbb179SRaymond Mao 		 * alignment.
387a1222502SRaymond Mao 		 * Ensure that the increased size doesn't exceed the max size
388a1222502SRaymond Mao 		 * of TL
389a1222502SRaymond Mao 		 */
390a1222502SRaymond Mao 		mov_dis = new_ev - old_ev;
39176d6685eSEtienne Carriere 		if (ROUNDUP2_OVERFLOW(mov_dis,
392a1222502SRaymond Mao 				      TL_ALIGNMENT_FROM_ORDER(tl->alignment),
393a1222502SRaymond Mao 				      &mov_dis) ||
394a1222502SRaymond Mao 		    tl->size + mov_dis > tl->max_size) {
395a1222502SRaymond Mao 			return false;
396a1222502SRaymond Mao 		}
39786dbb179SRaymond Mao 		r_new_ev = old_ev + mov_dis;
398a1222502SRaymond Mao 		tl->size += mov_dis;
399a1222502SRaymond Mao 	} else {
40086dbb179SRaymond Mao 		/*
40186dbb179SRaymond Mao 		 * Move distance should be rounded down to match the entry data
40286dbb179SRaymond Mao 		 * alignment.
40386dbb179SRaymond Mao 		 */
40476d6685eSEtienne Carriere 		mov_dis = ROUNDDOWN2(old_ev - new_ev,
40586dbb179SRaymond Mao 				     TL_ALIGNMENT_FROM_ORDER(tl->alignment));
40686dbb179SRaymond Mao 		r_new_ev = old_ev - mov_dis;
40786dbb179SRaymond Mao 		tl->size -= mov_dis;
408a1222502SRaymond Mao 	}
40986dbb179SRaymond Mao 	/* Move all following entries to fit in the expanded or shrunk space */
4100cd8ec0fSRaymond Mao 	if (tl_old_ev > old_ev) {
41186dbb179SRaymond Mao 		memmove((void *)r_new_ev, (void *)old_ev, tl_old_ev - old_ev);
41286dbb179SRaymond Mao 		/*
4130cd8ec0fSRaymond Mao 		 * Fill the gap due to round up/down with a void entry if the
4140cd8ec0fSRaymond Mao 		 * size of the gap is more than an entry header.
41586dbb179SRaymond Mao 		 */
41686dbb179SRaymond Mao 		gap = r_new_ev - new_ev;
417a1222502SRaymond Mao 		if (gap >= sizeof(*dummy_te)) {
418a1222502SRaymond Mao 			/* Create a dummy transfer entry to fill up the gap */
419a1222502SRaymond Mao 			dummy_te = (struct transfer_list_entry *)new_ev;
420a1222502SRaymond Mao 			dummy_te->tag_id = TL_TAG_EMPTY;
421a1222502SRaymond Mao 			dummy_te->reserved0 = 0;
422a1222502SRaymond Mao 			dummy_te->hdr_size = sizeof(*dummy_te);
423a1222502SRaymond Mao 			dummy_te->data_size = gap - sizeof(*dummy_te);
424a1222502SRaymond Mao 		}
4250cd8ec0fSRaymond Mao 	}
426a1222502SRaymond Mao 
427a1222502SRaymond Mao 	tl_e->data_size = new_data_size;
428a1222502SRaymond Mao 
429a1222502SRaymond Mao 	transfer_list_update_checksum(tl);
430a1222502SRaymond Mao 	return true;
431a1222502SRaymond Mao }
432a1222502SRaymond Mao 
433a1222502SRaymond Mao /*******************************************************************************
434a1222502SRaymond Mao  * Remove a specified transfer entry from a transfer list.
435a1222502SRaymond Mao  * @tl: Pointer to the transfer list.
436a1222502SRaymond Mao  * @tl_e: Pointer to the transfer entry.
437a1222502SRaymond Mao  * Return true on success or false on error.
438a1222502SRaymond Mao  ******************************************************************************/
transfer_list_rem(struct transfer_list_header * tl,struct transfer_list_entry * tl_e)439a1222502SRaymond Mao bool transfer_list_rem(struct transfer_list_header *tl,
440a1222502SRaymond Mao 		       struct transfer_list_entry *tl_e)
441a1222502SRaymond Mao {
442a1222502SRaymond Mao 	if (!tl || !tl_e || (vaddr_t)tl_e > (vaddr_t)tl + tl->size)
443a1222502SRaymond Mao 		return false;
444a1222502SRaymond Mao 
445a1222502SRaymond Mao 	tl_e->tag_id = TL_TAG_EMPTY;
446a1222502SRaymond Mao 	tl_e->reserved0 = 0;
447a1222502SRaymond Mao 	transfer_list_update_checksum(tl);
448a1222502SRaymond Mao 	return true;
449a1222502SRaymond Mao }
450a1222502SRaymond Mao 
451a1222502SRaymond Mao /*******************************************************************************
452a1222502SRaymond Mao  * Add a new transfer entry into a transfer list.
453a1222502SRaymond Mao  * Compliant with 2.4.3 of Firmware Handoff specification (v0.9).
454a1222502SRaymond Mao  * @tl: Pointer to the transfer list.
455a1222502SRaymond Mao  * @tag_id: Tag ID for the new transfer entry.
456a1222502SRaymond Mao  * @data_size: Data size of the new transfer entry.
457a1222502SRaymond Mao  * @data: Pointer to the data for the new transfer entry.
458a1222502SRaymond Mao  *        NULL to skip data copying.
459a1222502SRaymond Mao  * Return pointer to the added transfer entry or NULL on error.
460a1222502SRaymond Mao  ******************************************************************************/
transfer_list_add(struct transfer_list_header * tl,uint16_t tag_id,uint32_t data_size,const void * data)461a1222502SRaymond Mao struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl,
462a1222502SRaymond Mao 					      uint16_t tag_id,
463a1222502SRaymond Mao 					      uint32_t data_size,
464a1222502SRaymond Mao 					      const void *data)
465a1222502SRaymond Mao {
466a1222502SRaymond Mao 	vaddr_t max_tl_ev = 0;
467a1222502SRaymond Mao 	vaddr_t tl_ev = 0;
468a1222502SRaymond Mao 	vaddr_t ev = 0;
469a1222502SRaymond Mao 	struct transfer_list_entry *tl_e = NULL;
470a1222502SRaymond Mao 	size_t sz = 0;
471a1222502SRaymond Mao 
472a1222502SRaymond Mao 	if (!tl)
473a1222502SRaymond Mao 		return NULL;
474a1222502SRaymond Mao 
475a1222502SRaymond Mao 	max_tl_ev = (vaddr_t)tl + tl->max_size;
476a1222502SRaymond Mao 	tl_ev = (vaddr_t)tl + tl->size;
477*91f02c8cSRaymond Mao 	if (ROUNDUP_OVERFLOW(tl_ev, TRANSFER_LIST_GRANULE, &ev))
478*91f02c8cSRaymond Mao 		return NULL;
479*91f02c8cSRaymond Mao 
480*91f02c8cSRaymond Mao 	tl_e = (struct transfer_list_entry *)ev;
481a1222502SRaymond Mao 
482a1222502SRaymond Mao 	/*
483a1222502SRaymond Mao 	 * Skip the step 1 (optional step).
484a1222502SRaymond Mao 	 * New transfer entry will be added into the tail
485a1222502SRaymond Mao 	 */
486a1222502SRaymond Mao 	if (ADD_OVERFLOW(sizeof(*tl_e), data_size, &sz) ||
487*91f02c8cSRaymond Mao 	    ADD_OVERFLOW(ev, sz, &ev) || ev > max_tl_ev)
488a1222502SRaymond Mao 		return NULL;
489a1222502SRaymond Mao 
490a1222502SRaymond Mao 	*tl_e = (struct transfer_list_entry){
491a1222502SRaymond Mao 		.tag_id = tag_id,
492a1222502SRaymond Mao 		.hdr_size = sizeof(*tl_e),
493a1222502SRaymond Mao 		.data_size = data_size,
494a1222502SRaymond Mao 	};
495a1222502SRaymond Mao 
496a1222502SRaymond Mao 	tl->size += ev - tl_ev;
497a1222502SRaymond Mao 
498a1222502SRaymond Mao 	if (data)
49995b0e915SRaymond Mao 		memmove(transfer_list_entry_data(tl_e), data, data_size);
500a1222502SRaymond Mao 
501a1222502SRaymond Mao 	transfer_list_update_checksum(tl);
502a1222502SRaymond Mao 
503a1222502SRaymond Mao 	return tl_e;
504a1222502SRaymond Mao }
505a1222502SRaymond Mao 
506a1222502SRaymond Mao /*******************************************************************************
507a1222502SRaymond Mao  * Add a new transfer entry into a transfer list with specified new data
508a1222502SRaymond Mao  * alignment requirement.
509a1222502SRaymond Mao  * Compliant with 2.4.4 of Firmware Handoff specification (v0.9).
510a1222502SRaymond Mao  * @tl: Pointer to the transfer list.
511a1222502SRaymond Mao  * @tag_id: Tag ID for the new transfer entry.
512a1222502SRaymond Mao  * @data_size: Data size of the new transfer entry.
513a1222502SRaymond Mao  * @data: Pointer to the data for the new transfer entry.
514a1222502SRaymond Mao  * @alignment: New data alignment specified as a power of two.
515a1222502SRaymond Mao  * Return pointer to the added transfer entry or NULL on error.
516a1222502SRaymond Mao  ******************************************************************************/
517a1222502SRaymond Mao struct transfer_list_entry *
transfer_list_add_with_align(struct transfer_list_header * tl,uint16_t tag_id,uint32_t data_size,const void * data,uint8_t alignment)518a1222502SRaymond Mao transfer_list_add_with_align(struct transfer_list_header *tl, uint16_t tag_id,
519a1222502SRaymond Mao 			     uint32_t data_size, const void *data,
520a1222502SRaymond Mao 			     uint8_t alignment)
521a1222502SRaymond Mao {
522a1222502SRaymond Mao 	struct transfer_list_entry *tl_e = NULL;
523a1222502SRaymond Mao 	vaddr_t tl_ev = 0;
524a1222502SRaymond Mao 	vaddr_t ev = 0;
525a1222502SRaymond Mao 	vaddr_t new_tl_ev = 0;
526a1222502SRaymond Mao 	size_t dummy_te_data_sz = 0;
527a1222502SRaymond Mao 
528a1222502SRaymond Mao 	if (!tl)
529a1222502SRaymond Mao 		return NULL;
530a1222502SRaymond Mao 
531a1222502SRaymond Mao 	tl_ev = (vaddr_t)tl + tl->size;
532*91f02c8cSRaymond Mao 	if (ROUNDUP_OVERFLOW(tl_ev, TRANSFER_LIST_GRANULE, &tl_ev))
533*91f02c8cSRaymond Mao 		return NULL;
534*91f02c8cSRaymond Mao 
535a1222502SRaymond Mao 	ev = tl_ev + sizeof(struct transfer_list_entry);
536a1222502SRaymond Mao 
537a1222502SRaymond Mao 	if (!IS_ALIGNED(ev, TL_ALIGNMENT_FROM_ORDER(alignment))) {
538a1222502SRaymond Mao 		/*
539a1222502SRaymond Mao 		 * Transfer entry data address is not aligned to the new
540a1222502SRaymond Mao 		 * alignment. Fill the gap with an empty transfer entry as a
541a1222502SRaymond Mao 		 * placeholder before adding the desired transfer entry
542a1222502SRaymond Mao 		 */
54376d6685eSEtienne Carriere 		new_tl_ev = ROUNDUP2(ev, TL_ALIGNMENT_FROM_ORDER(alignment)) -
544a1222502SRaymond Mao 			    sizeof(struct transfer_list_entry);
545a1222502SRaymond Mao 		assert(new_tl_ev - tl_ev > sizeof(struct transfer_list_entry));
546a1222502SRaymond Mao 		dummy_te_data_sz = new_tl_ev - tl_ev -
547a1222502SRaymond Mao 				   sizeof(struct transfer_list_entry);
548a1222502SRaymond Mao 		if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz,
549a1222502SRaymond Mao 				       NULL)) {
550a1222502SRaymond Mao 			return NULL;
551a1222502SRaymond Mao 		}
552a1222502SRaymond Mao 	}
553a1222502SRaymond Mao 
554a1222502SRaymond Mao 	tl_e = transfer_list_add(tl, tag_id, data_size, data);
555a1222502SRaymond Mao 
556a1222502SRaymond Mao 	if (alignment > tl->alignment) {
557a1222502SRaymond Mao 		tl->alignment = alignment;
558a1222502SRaymond Mao 		transfer_list_update_checksum(tl);
559a1222502SRaymond Mao 	}
560a1222502SRaymond Mao 
561a1222502SRaymond Mao 	return tl_e;
562a1222502SRaymond Mao }
563a1222502SRaymond Mao 
564a1222502SRaymond Mao /*******************************************************************************
565a1222502SRaymond Mao  * Search for an existing transfer entry with the specified tag id from a
566a1222502SRaymond Mao  * transfer list.
567a1222502SRaymond Mao  * @tl: Pointer to the transfer list.
568a1222502SRaymond Mao  * @tag_id: Tag ID to match a transfer entry.
569a1222502SRaymond Mao  * Return pointer to the found transfer entry or NULL if not found.
570a1222502SRaymond Mao  ******************************************************************************/
transfer_list_find(struct transfer_list_header * tl,uint16_t tag_id)571a1222502SRaymond Mao struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl,
572a1222502SRaymond Mao 					       uint16_t tag_id)
573a1222502SRaymond Mao {
574a1222502SRaymond Mao 	struct transfer_list_entry *tl_e = NULL;
575a1222502SRaymond Mao 
576a1222502SRaymond Mao 	do {
577a1222502SRaymond Mao 		tl_e = transfer_list_next(tl, tl_e);
578a1222502SRaymond Mao 	} while (tl_e && tl_e->tag_id != tag_id);
579a1222502SRaymond Mao 
580a1222502SRaymond Mao 	return tl_e;
581a1222502SRaymond Mao }
582a1222502SRaymond Mao 
583a1222502SRaymond Mao /*******************************************************************************
584a1222502SRaymond Mao  * Retrieve the data pointer of a specified transfer entry.
585a1222502SRaymond Mao  * @tl_e: Pointer to the transfer entry.
586a1222502SRaymond Mao  * Return pointer to the transfer entry data or NULL on error.
587a1222502SRaymond Mao  ******************************************************************************/
transfer_list_entry_data(struct transfer_list_entry * tl_e)588a1222502SRaymond Mao void *transfer_list_entry_data(struct transfer_list_entry *tl_e)
589a1222502SRaymond Mao {
590a1222502SRaymond Mao 	if (!tl_e)
591a1222502SRaymond Mao 		return NULL;
592a1222502SRaymond Mao 
593a1222502SRaymond Mao 	return (uint8_t *)tl_e + tl_e->hdr_size;
594a1222502SRaymond Mao }
595