diff --git a/core/include/kernel/transfer_list.h b/core/include/kernel/transfer_list.h new file mode 100644 index 00000000000..2577ddf92a0 --- /dev/null +++ b/core/include/kernel/transfer_list.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) 2023, Linaro Limited + */ + +#ifndef __KERNEL_TRANSFER_LIST_H +#define __KERNEL_TRANSFER_LIST_H + +#define TRANSFER_LIST_SIGNATURE U(0x006ed0ff) +#define TRANSFER_LIST_VERSION U(0x0001) + +/* + * Init value of maximum alignment required by any TE data in the TL + * specified as a power of two + */ +#define TRANSFER_LIST_INIT_MAX_ALIGN U(3) + +/* Alignment required by TE header start address, in bytes */ +#define TRANSFER_LIST_GRANULE U(8) + +/* + * Version of the register convention used. + * Set to 1 for both AArch64 and AArch32 according to fw handoff spec v0.9 + */ +#define REG_CONVENTION_VER_MASK BIT(24) + +/* Transfer list operation codes */ +#define TL_OPS_NONE U(0) /* invalid for any operation */ +#define TL_OPS_ALL U(1) /* valid for all operations */ +#define TL_OPS_RO U(2) /* valid for read only */ +#define TL_OPS_CUS U(3) /* either abort or special code to interpret */ + +#ifndef __ASSEMBLER__ + +#include + +/* Get alignment from a value specified as power of two */ +#define TL_ALIGNMENT_FROM_ORDER(a) BIT(a) + +enum transfer_list_tag_id { + TL_TAG_EMPTY = 0, + TL_TAG_FDT = 1, + TL_TAG_HOB_BLOCK = 2, + TL_TAG_HOB_LIST = 3, + TL_TAG_ACPI_TABLE_AGGREGATE = 4, + TL_TAG_OPTEE_PAGABLE_PART = 0x100, +}; + +struct transfer_list_header { + uint32_t signature; + uint8_t checksum; + uint8_t version; + uint8_t hdr_size; + uint8_t alignment; /* max alignment of TE data */ + uint32_t size; /* TL header + all TEs */ + uint32_t max_size; + /* + * Commented out element used to visualize dynamic part of the + * data structure. + * + * Note that struct transfer_list_entry also is dynamic in size + * so the elements can't be indexed directly but instead must be + * traversed in order + * + * struct transfer_list_entry entries[]; + */ +}; + +struct transfer_list_entry { + uint16_t tag_id; + uint8_t reserved0; /* place holder for tag ID 3rd byte (MSB) */ + uint8_t hdr_size; + uint32_t data_size; + /* + * Commented out element used to visualize dynamic part of the + * data structure. + * + * Note that padding is added at the end of @data to make it reach + * a 8-byte boundary. + * + * uint8_t data[ROUNDUP(data_size, 8)]; + */ +}; + +struct transfer_list_header *transfer_list_map(paddr_t pa); +void transfer_list_unmap_sync(struct transfer_list_header *tl); +void transfer_list_unmap_nosync(struct transfer_list_header *tl); + +void transfer_list_dump(struct transfer_list_header *tl); +struct transfer_list_header *transfer_list_init(paddr_t pa, size_t max_size); + +struct transfer_list_header * +transfer_list_relocate(struct transfer_list_header *tl, paddr_t pa, + size_t max_size); +int transfer_list_check_header(const struct transfer_list_header *tl); + +void transfer_list_update_checksum(struct transfer_list_header *tl); +bool transfer_list_verify_checksum(const struct transfer_list_header *tl); + +bool transfer_list_set_data_size(struct transfer_list_header *tl, + struct transfer_list_entry *te, + uint32_t new_data_size); + +void *transfer_list_entry_data(struct transfer_list_entry *te); +bool transfer_list_rem(struct transfer_list_header *tl, + struct transfer_list_entry *te); + +struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl, + uint16_t tag_id, + uint32_t data_size, + const void *data); + +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); + +struct transfer_list_entry * +transfer_list_next(struct transfer_list_header *tl, + struct transfer_list_entry *last); + +struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl, + uint16_t tag_id); + +#endif /*__ASSEMBLER__*/ +#endif /*__TRANSFER_LIST_H*/ diff --git a/core/kernel/sub.mk b/core/kernel/sub.mk index 75d33686514..2d36985ef9b 100644 --- a/core/kernel/sub.mk +++ b/core/kernel/sub.mk @@ -51,6 +51,8 @@ endif srcs-$(CFG_EMBEDDED_TS) += embedded_ts.c srcs-y += pseudo_ta.c +srcs-$(CFG_TRANSFER_LIST) += transfer_list.c + ifeq ($(CFG_SYSCALL_FTRACE),y) # We would not like to profile spin_lock_debug.c file as it provides # common APIs that are needed for ftrace framework to trace syscalls. diff --git a/core/kernel/transfer_list.c b/core/kernel/transfer_list.c new file mode 100644 index 00000000000..0aba9f83935 --- /dev/null +++ b/core/kernel/transfer_list.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) 2023, Linaro Limited + */ + +/******************************************************************************* + * Transfer list library compliant with the Firmware Handoff specification at: + * https://github.com/FirmwareHandoff/firmware_handoff + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +/******************************************************************************* + * Adapt a physical address to match the maximum transfer entry data alignment + * required by an existing transfer list. + * Compliant with 2.4.6 of Firmware Handoff specification (v0.9). + * @pa: Physical address for adapting. + * @tl: Pointer to the existing transfer list. + * Return the adapted physical address. + ******************************************************************************/ +static paddr_t get_align_base_addr(paddr_t pa, + struct transfer_list_header *tl) +{ + paddr_t align_mask = TL_ALIGNMENT_FROM_ORDER(tl->alignment) - 1; + paddr_t align_off = (paddr_t)tl & align_mask; + paddr_t new_addr = (pa & ~align_mask) + align_off; + + if (new_addr < pa) + new_addr += TL_ALIGNMENT_FROM_ORDER(tl->alignment); + + return new_addr; +} + +static void unmap_list(struct transfer_list_header *tl, size_t sz) +{ + if (core_mmu_remove_mapping(MEM_AREA_TRANSFER_LIST, tl, sz)) + panic("Failed to remove transfer list mapping"); +} + +struct transfer_list_header *transfer_list_map(paddr_t pa) +{ + struct transfer_list_header *tl = NULL; + size_t sz = SMALL_PAGE_SIZE; + size_t old_sz = 0; + + while (true) { + tl = core_mmu_add_mapping(MEM_AREA_TRANSFER_LIST, pa, sz); + if (!tl) { + EMSG("Failed to map TL with PA %#"PRIxPA", size %#zx", + pa, sz); + return NULL; + } + old_sz = sz; + + if (transfer_list_check_header(tl) == TL_OPS_NONE) { + unmap_list(tl, sz); + return NULL; + } + + if (tl->max_size <= sz) + return tl; + + sz = ROUNDUP(tl->max_size, SMALL_PAGE_SIZE); + unmap_list(tl, old_sz); + } +} + +void transfer_list_unmap_sync(struct transfer_list_header *tl) +{ + size_t sz = tl->max_size; + + transfer_list_update_checksum(tl); + dcache_cleaninv_range(tl, sz); + unmap_list(tl, sz); +} + +void transfer_list_unmap_nosync(struct transfer_list_header *tl) +{ + unmap_list(tl, tl->max_size); +} + +void transfer_list_dump(struct transfer_list_header *tl) +{ + struct transfer_list_entry *te = NULL; + int i = 0; + + if (!tl) + return; + + DMSG("Dump transfer list:"); + DMSG("signature %#"PRIx32, tl->signature); + DMSG("checksum %#"PRIx8, tl->checksum); + DMSG("version %#"PRIx8, tl->version); + DMSG("hdr_size %#"PRIx8, tl->hdr_size); + DMSG("alignment %#"PRIx8, tl->alignment); + DMSG("size %#"PRIx32, tl->size); + DMSG("max_size %#"PRIx32, tl->max_size); + while (true) { + te = transfer_list_next(tl, te); + if (!te) + break; + + DMSG("Entry %d:", i++); + DMSG("tag_id %#"PRIx16, te->tag_id); + DMSG("hdr_size %#"PRIx8, te->hdr_size); + DMSG("data_size %#"PRIx32, te->data_size); + DMSG("data_addr %#"PRIxVA, + (vaddr_t)transfer_list_entry_data(te)); + } +} + +/******************************************************************************* + * Creating a transfer list in a specified reserved memory region. + * Compliant with 2.4.5 of Firmware Handoff specification (v0.9). + * @pa: Physical address for residing the new transfer list. + * @max_size: Maximum size of the new transfer list. + * Return pointer to the created transfer list or NULL on error. + ******************************************************************************/ +struct transfer_list_header *transfer_list_init(paddr_t pa, size_t max_size) +{ + struct transfer_list_header *tl = NULL; + int align = TL_ALIGNMENT_FROM_ORDER(TRANSFER_LIST_INIT_MAX_ALIGN); + + if (!pa || !max_size) + return NULL; + + if (!IS_ALIGNED(pa, align) || !IS_ALIGNED(max_size, align) || + max_size < sizeof(*tl)) + return NULL; + + tl = core_mmu_add_mapping(MEM_AREA_TRANSFER_LIST, pa, max_size); + if (!tl) + return NULL; + + memset(tl, 0, max_size); + tl->signature = TRANSFER_LIST_SIGNATURE; + tl->version = TRANSFER_LIST_VERSION; + tl->hdr_size = sizeof(*tl); + tl->alignment = TRANSFER_LIST_INIT_MAX_ALIGN; /* initial max align */ + tl->size = sizeof(*tl); /* initial size is the size of header */ + tl->max_size = max_size; + + transfer_list_update_checksum(tl); + + return tl; +} + +/******************************************************************************* + * Relocating a transfer list to a specified reserved memory region. + * Compliant with 2.4.6 of Firmware Handoff specification (v0.9). + * @tl: Pointer to the transfer list for relocating. + * @pa: Physical address for relocating the transfer list. + * @max_size: Maximum size of the transfer list after relocating + * Return pointer to the relocated transfer list or NULL on error. + ******************************************************************************/ +struct transfer_list_header * +transfer_list_relocate(struct transfer_list_header *tl, paddr_t pa, + size_t max_size) +{ + paddr_t new_addr = 0; + struct transfer_list_header *new_tl = NULL; + size_t new_max_size = 0; + + if (!tl || !pa || !max_size) + return NULL; + + new_addr = get_align_base_addr(pa, tl); + new_max_size = max_size - (new_addr - pa); + + /* The new space is not sufficient for the TL */ + if (tl->size > new_max_size) + return NULL; + + new_tl = core_mmu_add_mapping(MEM_AREA_TRANSFER_LIST, new_addr, + new_max_size); + if (!new_tl) + return NULL; + + memmove(new_tl, tl, tl->size); + new_tl->max_size = new_max_size; + + transfer_list_update_checksum(new_tl); + transfer_list_unmap_nosync(tl); + + return new_tl; +} + +/******************************************************************************* + * Verifying the header of a transfer list. + * Compliant with 2.4.1 of Firmware Handoff specification (v0.9). + * @tl: Pointer to the transfer list. + * Return transfer list operation status code. + ******************************************************************************/ +int transfer_list_check_header(const struct transfer_list_header *tl) +{ + if (!tl) + return TL_OPS_NONE; + + if (tl->signature != TRANSFER_LIST_SIGNATURE) { + EMSG("Bad transfer list signature %#"PRIx32, tl->signature); + return TL_OPS_NONE; + } + + if (!tl->max_size) { + EMSG("Bad transfer list max size %#"PRIx32, tl->max_size); + return TL_OPS_NONE; + } + + if (tl->size > tl->max_size) { + EMSG("Bad transfer list size %#"PRIx32, tl->size); + return TL_OPS_NONE; + } + + if (tl->hdr_size != sizeof(struct transfer_list_header)) { + EMSG("Bad transfer list header size %#"PRIx8, tl->hdr_size); + return TL_OPS_NONE; + } + + if (!transfer_list_verify_checksum(tl)) { + EMSG("Bad transfer list checksum %#"PRIx8, tl->checksum); + return TL_OPS_NONE; + } + + if (tl->version == 0) { + EMSG("Transfer list version is invalid"); + return TL_OPS_NONE; + } else if (tl->version == TRANSFER_LIST_VERSION) { + DMSG("Transfer list version is valid for all operations"); + return TL_OPS_ALL; + } else if (tl->version > TRANSFER_LIST_VERSION) { + DMSG("Transfer list version is valid for read-only"); + return TL_OPS_RO; + } + + DMSG("Old transfer list version is detected"); + return TL_OPS_CUS; +} + +/******************************************************************************* + * Enumerate the next transfer entry. + * @tl: Pointer to the transfer list. + * @cur: Pointer to the current transfer entry where we want to search for the + * next one. + * Return pointer to the next transfer entry or NULL on error or if @cur is the + * last entry. + ******************************************************************************/ +struct transfer_list_entry *transfer_list_next(struct transfer_list_header *tl, + struct transfer_list_entry *cur) +{ + struct transfer_list_entry *te = NULL; + vaddr_t tl_ev = 0; + vaddr_t va = 0; + vaddr_t ev = 0; + size_t sz = 0; + + if (!tl) + return NULL; + + tl_ev = (vaddr_t)tl + tl->size; + + if (cur) { + va = (vaddr_t)cur; + /* check if the total size overflow */ + if (ADD_OVERFLOW(cur->hdr_size, cur->data_size, &sz)) + return NULL; + /* roundup to the next entry */ + if (ADD_OVERFLOW(va, sz, &va) || + ROUNDUP_OVERFLOW(va, TRANSFER_LIST_GRANULE, &va)) + return NULL; + } else { + va = (vaddr_t)tl + tl->hdr_size; + } + + te = (struct transfer_list_entry *)va; + + if (va + sizeof(*te) > tl_ev || te->hdr_size < sizeof(*te) || + ADD_OVERFLOW(te->hdr_size, te->data_size, &sz) || + ADD_OVERFLOW(va, sz, &ev) || ev > tl_ev) + return NULL; + + return te; +} + +/******************************************************************************* + * Calculate the byte sum (modulo 256) of a transfer list. + * @tl: Pointer to the transfer list. + * Return byte sum of the transfer list. + ******************************************************************************/ +static uint8_t calc_byte_sum(const struct transfer_list_header *tl) +{ + uint8_t *b = (uint8_t *)tl; + uint8_t cs = 0; + size_t n = 0; + + if (!tl) + return 0; + + for (n = 0; n < tl->size; n++) + cs += b[n]; + + return cs; +} + +/******************************************************************************* + * Update the checksum of a transfer list. + * @tl: Pointer to the transfer list. + * Return updated checksum of the transfer list. + ******************************************************************************/ +void transfer_list_update_checksum(struct transfer_list_header *tl) +{ + uint8_t cs = 0; + + if (!tl) + return; + + cs = calc_byte_sum(tl); + cs -= tl->checksum; + cs = 256 - cs; + tl->checksum = cs; + assert(transfer_list_verify_checksum(tl)); +} + +/******************************************************************************* + * Verify the checksum of a transfer list. + * @tl: Pointer to the transfer list. + * Return true if verified or false if not. + ******************************************************************************/ +bool transfer_list_verify_checksum(const struct transfer_list_header *tl) +{ + return !calc_byte_sum(tl); +} + +/******************************************************************************* + * Update the data size of a transfer entry. + * @tl: Pointer to the transfer list. + * @te: Pointer to the transfer entry. + * @new_data_size: New data size of the transfer entry. + * Return true on success or false on error. + ******************************************************************************/ +bool transfer_list_set_data_size(struct transfer_list_header *tl, + struct transfer_list_entry *te, + uint32_t new_data_size) +{ + vaddr_t tl_old_ev = 0; + vaddr_t new_ev = 0; + vaddr_t old_ev = 0; + vaddr_t ru_new_ev = 0; + struct transfer_list_entry *dummy_te = NULL; + size_t gap = 0; + size_t mov_dis = 0; + size_t sz = 0; + + if (!tl || !te) + return false; + + tl_old_ev = (vaddr_t)tl + tl->size; + + /* + * calculate the old and new end of TE + * both must be roundup to align with TRANSFER_LIST_GRANULE + */ + if (ADD_OVERFLOW(te->hdr_size, te->data_size, &sz) || + ADD_OVERFLOW((vaddr_t)te, sz, &old_ev) || + ROUNDUP_OVERFLOW(old_ev, TRANSFER_LIST_GRANULE, &old_ev)) + return false; + + if (ADD_OVERFLOW(te->hdr_size, new_data_size, &sz) || + ADD_OVERFLOW((vaddr_t)te, sz, &new_ev) || + ROUNDUP_OVERFLOW(new_ev, TRANSFER_LIST_GRANULE, &new_ev)) + return false; + + if (new_ev > old_ev) { + /* + * move distance should be roundup + * to meet the requirement of TE data max alignment + * ensure that the increased size doesn't exceed + * the max size of TL + */ + mov_dis = new_ev - old_ev; + if (ROUNDUP_OVERFLOW(mov_dis, + TL_ALIGNMENT_FROM_ORDER(tl->alignment), + &mov_dis) || + tl->size + mov_dis > tl->max_size) { + return false; + } + ru_new_ev = old_ev + mov_dis; + memmove((void *)ru_new_ev, (void *)old_ev, tl_old_ev - old_ev); + tl->size += mov_dis; + gap = ru_new_ev - new_ev; + } else { + gap = old_ev - new_ev; + } + + if (gap >= sizeof(*dummy_te)) { + /* create a dummy TE to fill up the gap */ + dummy_te = (struct transfer_list_entry *)new_ev; + dummy_te->tag_id = TL_TAG_EMPTY; + dummy_te->reserved0 = 0; + dummy_te->hdr_size = sizeof(*dummy_te); + dummy_te->data_size = gap - sizeof(*dummy_te); + } + + te->data_size = new_data_size; + + transfer_list_update_checksum(tl); + return true; +} + +/******************************************************************************* + * Remove a specified transfer entry from a transfer list. + * @tl: Pointer to the transfer list. + * @te: Pointer to the transfer entry. + * Return true on success or false on error. + ******************************************************************************/ +bool transfer_list_rem(struct transfer_list_header *tl, + struct transfer_list_entry *te) +{ + if (!tl || !te || (vaddr_t)te > (vaddr_t)tl + tl->size) + return false; + + te->tag_id = TL_TAG_EMPTY; + te->reserved0 = 0; + transfer_list_update_checksum(tl); + return true; +} + +/******************************************************************************* + * Add a new transfer entry into a transfer list. + * Compliant with 2.4.3 of Firmware Handoff specification (v0.9). + * @tl: Pointer to the transfer list. + * @tag_id: Tag ID for the new transfer entry. + * @data_size: Data size of the new transfer entry. + * @data: Pointer to the data for the new transfer entry. + * Return pointer to the added transfer entry or NULL on error. + ******************************************************************************/ +struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl, + uint16_t tag_id, + uint32_t data_size, + const void *data) +{ + vaddr_t max_tl_ev = 0; + vaddr_t tl_ev = 0; + vaddr_t ev = 0; + struct transfer_list_entry *te = NULL; + uint8_t *te_data = NULL; + size_t sz = 0; + + if (!tl) + return NULL; + + max_tl_ev = (vaddr_t)tl + tl->max_size; + tl_ev = (vaddr_t)tl + tl->size; + ev = tl_ev; + + /* + * skip the step 1 (optional step) + * new TE will be added into the tail + */ + if (ADD_OVERFLOW(sizeof(*te), data_size, &sz) || + ADD_OVERFLOW(ev, sz, &ev) || + ROUNDUP_OVERFLOW(ev, TRANSFER_LIST_GRANULE, &ev) || + ev > max_tl_ev) { + return NULL; + } + + te = (struct transfer_list_entry *)tl_ev; + *te = (struct transfer_list_entry){ + .tag_id = tag_id, + .hdr_size = sizeof(*te), + .data_size = data_size, + }; + + tl->size += ev - tl_ev; + + if (data) { + /* get TE data pointer */ + te_data = transfer_list_entry_data(te); + if (!te_data) + return NULL; + + memmove(te_data, data, data_size); + } + + transfer_list_update_checksum(tl); + + return te; +} + +/******************************************************************************* + * Add a new transfer entry into a transfer list with specified new data + * alignment requirement. + * Compliant with 2.4.4 of Firmware Handoff specification (v0.9). + * @tl: Pointer to the transfer list. + * @tag_id: Tag ID for the new transfer entry. + * @data_size: Data size of the new transfer entry. + * @data: Pointer to the data for the new transfer entry. + * @alignment: New data alignment specified as a power of two. + * Return pointer to the added transfer entry or NULL on error. + ******************************************************************************/ +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) +{ + struct transfer_list_entry *te = NULL; + vaddr_t tl_ev = 0; + vaddr_t ev = 0; + vaddr_t new_tl_ev = 0; + size_t dummy_te_data_sz = 0; + + if (!tl) + return NULL; + + tl_ev = (vaddr_t)tl + tl->size; + ev = tl_ev + sizeof(struct transfer_list_entry); + + if (!IS_ALIGNED(ev, TL_ALIGNMENT_FROM_ORDER(alignment))) { + /* + * TE data address is not aligned to the new alignment + * fill the gap with an empty TE as a placeholder before + * adding the desire TE + */ + new_tl_ev = ROUNDUP(ev, TL_ALIGNMENT_FROM_ORDER(alignment)) - + sizeof(struct transfer_list_entry); + assert(new_tl_ev - tl_ev > sizeof(struct transfer_list_entry)); + dummy_te_data_sz = new_tl_ev - tl_ev - + sizeof(struct transfer_list_entry); + if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz, + NULL)) { + return NULL; + } + } + + te = transfer_list_add(tl, tag_id, data_size, data); + + if (alignment > tl->alignment) { + tl->alignment = alignment; + transfer_list_update_checksum(tl); + } + + return te; +} + +/******************************************************************************* + * Search for an existing transfer entry with the specified tag id from a + * transfer list. + * @tl: Pointer to the transfer list. + * @tag_id: Tag ID to match a transfer entry. + * Return pointer to the found transfer entry or NULL if not found. + ******************************************************************************/ +struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl, + uint16_t tag_id) +{ + struct transfer_list_entry *te = NULL; + + do { + te = transfer_list_next(tl, te); + } while (te && te->tag_id != tag_id); + + return te; +} + +/******************************************************************************* + * Retrieve the data pointer of a specified transfer entry. + * @te: Pointer to the transfer entry. + * Return pointer to the transfer entry data or NULL on error. + ******************************************************************************/ +void *transfer_list_entry_data(struct transfer_list_entry *te) +{ + if (!te) + return NULL; + + return (uint8_t *)te + te->hdr_size; +} diff --git a/mk/config.mk b/mk/config.mk index a9a68e3a48d..9cff8c9e5f1 100644 --- a/mk/config.mk +++ b/mk/config.mk @@ -495,6 +495,16 @@ ifeq ($(CFG_MAP_EXT_DT_SECURE),y) $(call force,CFG_DT,y) endif +# This option enables OP-TEE to support boot arguments handover via Transfer +# List defined in Firmware Handoff specification. +# This feature requires the support of Device Tree. +CFG_TRANSFER_LIST ?= n +ifeq ($(CFG_TRANSFER_LIST),y) +$(call force,CFG_DT,y) +$(call force,CFG_EXTERNAL_DT,y) +$(call force,CFG_MAP_EXT_DT_SECURE,y) +endif + # Maximum size of the Device Tree Blob, has to be large enough to allow # editing of the supplied DTB. CFG_DTB_MAX_SIZE ?= 0x10000