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