feat(handoff): introduce firmware handoff library
Add transfer list APIs and firmware handoff build option.
Change-Id: I68a0ace22c7e50fcdacd101eb76b271d7b76d8ff
Signed-off-by: Raymond Mao <raymond.mao@linaro.org>
diff --git a/Makefile b/Makefile
index 1cce234..104d84d 100644
--- a/Makefile
+++ b/Makefile
@@ -1027,6 +1027,10 @@
$(info DRTM_SUPPORT is an experimental feature)
endif
+ifeq (${TRANSFER_LIST},1)
+ $(info TRANSFER_LIST is an experimental feature)
+endif
+
ifeq (${ENABLE_RME},1)
ifneq (${SEPARATE_CODE_AND_RODATA},1)
$(error `ENABLE_RME=1` requires `SEPARATE_CODE_AND_RODATA=1`)
@@ -1191,6 +1195,7 @@
SPMC_AT_EL3 \
SPMD_SPM_AT_SEL2 \
ENABLE_SPMD_LP \
+ TRANSFER_LIST \
TRUSTED_BOARD_BOOT \
USE_COHERENT_MEM \
USE_DEBUGFS \
@@ -1351,6 +1356,7 @@
SPM_MM \
SPMC_AT_EL3 \
SPMD_SPM_AT_SEL2 \
+ TRANSFER_LIST \
TRUSTED_BOARD_BOOT \
CRYPTO_SUPPORT \
TRNG_SUPPORT \
diff --git a/changelog.yaml b/changelog.yaml
index cdbedbb..0d69fb3 100644
--- a/changelog.yaml
+++ b/changelog.yaml
@@ -784,6 +784,9 @@
- title: Semihosting
scope: semihosting
+ - title: Firmware Handoff
+ scope: handoff
+
- title: Drivers
subsections:
diff --git a/docs/getting_started/build-options.rst b/docs/getting_started/build-options.rst
index 2c018c3..bc939e1 100644
--- a/docs/getting_started/build-options.rst
+++ b/docs/getting_started/build-options.rst
@@ -944,6 +944,11 @@
hardware will limit the effective VL to the maximum physically supported
VL.
+- ``TRANSFER_LIST``: Setting this to ``1`` enables support for Firmware
+ Handoff using Transfer List defined in `Firmware Handoff specification`_.
+ This defaults to ``0``. Please note that this is an experimental feature
+ based on Firmware Handoff specification v0.9.
+
- ``TRNG_SUPPORT``: Setting this to ``1`` enables support for True
Random Number Generator Interface to BL31 image. This defaults to ``0``.
@@ -1298,3 +1303,4 @@
.. _PSA DRTM specification: https://developer.arm.com/documentation/den0113/a
.. _GCC: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
.. _Clang: https://clang.llvm.org/docs/DiagnosticsReference.html
+.. _Firmware Handoff specification: https://github.com/FirmwareHandoff/firmware_handoff/releases/tag/v0.9
diff --git a/include/lib/transfer_list.h b/include/lib/transfer_list.h
new file mode 100644
index 0000000..54c8643
--- /dev/null
+++ b/include/lib/transfer_list.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2023, Linaro Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef __TRANSFER_LIST_H
+#define __TRANSFER_LIST_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <lib/utils_def.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 REGISTER_CONVENTION_VERSION_MASK (1 << 24)
+
+#ifndef __ASSEMBLER__
+
+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,
+};
+
+enum transfer_list_ops {
+ TL_OPS_NON, // invalid for any operation
+ TL_OPS_ALL, // valid for all operations
+ TL_OPS_RO, // valid for read only
+ TL_OPS_CUS, // either abort or switch to special code to interpret
+};
+
+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
+ 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 to reach
+ * a 8-byte boundary.
+ *
+ * uint8_t data[ROUNDUP(data_size, 8)];
+ */
+};
+
+void transfer_list_dump(struct transfer_list_header *tl);
+struct transfer_list_header *transfer_list_init(void *addr, size_t max_size);
+
+struct transfer_list_header *transfer_list_relocate(struct transfer_list_header *tl,
+ void *addr, size_t max_size);
+enum transfer_list_ops 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 *entry,
+ uint32_t new_data_size);
+
+void *transfer_list_entry_data(struct transfer_list_entry *entry);
+bool transfer_list_rem(struct transfer_list_header *tl, struct transfer_list_entry *entry);
+
+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/include/lib/utils_def.h b/include/lib/utils_def.h
index ba52bc6..a170a09 100644
--- a/include/lib/utils_def.h
+++ b/include/lib/utils_def.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016-2022, Arm Limited and Contributors. All rights reserved.
+ * Copyright (c) 2016-2023, Arm Limited and Contributors. All rights reserved.
* Copyright (c) 2020, NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
@@ -104,6 +104,41 @@
#define round_down(value, boundary) \
((value) & ~round_boundary(value, boundary))
+/* add operation together with checking whether the operation overflowed
+ * The result is '*res',
+ * return 0 on success and 1 on overflow
+ */
+#define add_overflow(a, b, res) __builtin_add_overflow((a), (b), (res))
+
+/*
+ * Round up a value to align with a given size and
+ * check whether overflow happens.
+ * The rounduped value is '*res',
+ * return 0 on success and 1 on overflow
+ */
+#define round_up_overflow(v, size, res) (__extension__({ \
+ typeof(res) __res = res; \
+ typeof(*(__res)) __roundup_tmp = 0; \
+ typeof(v) __roundup_mask = (typeof(v))(size) - 1; \
+ \
+ add_overflow((v), __roundup_mask, &__roundup_tmp) ? 1 : \
+ (void)(*(__res) = __roundup_tmp & ~__roundup_mask), 0; \
+}))
+
+/*
+ * Add a with b, then round up the result to align with a given size and
+ * check whether overflow happens.
+ * The rounduped value is '*res',
+ * return 0 on success and 1 on overflow
+ */
+#define add_with_round_up_overflow(a, b, size, res) (__extension__({ \
+ typeof(a) __a = (a); \
+ typeof(__a) __add_res = 0; \
+ \
+ add_overflow((__a), (b), &__add_res) ? 1 : \
+ round_up_overflow(__add_res, (size), (res)) ? 1 : 0; \
+}))
+
/**
* Helper macro to ensure a value lies on a given boundary.
*/
diff --git a/lib/transfer_list/transfer_list.c b/lib/transfer_list/transfer_list.c
new file mode 100644
index 0000000..e38bf74
--- /dev/null
+++ b/lib/transfer_list/transfer_list.c
@@ -0,0 +1,474 @@
+/*
+ * Copyright (c) 2023, Linaro Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include <common/debug.h>
+#include <lib/transfer_list.h>
+#include <lib/utils_def.h>
+
+void transfer_list_dump(struct transfer_list_header *tl)
+{
+ struct transfer_list_entry *te = NULL;
+ int i = 0;
+
+ if (!tl) {
+ return;
+ }
+ NOTICE("Dump transfer list:\n");
+ NOTICE("signature 0x%x\n", tl->signature);
+ NOTICE("checksum 0x%x\n", tl->checksum);
+ NOTICE("version 0x%x\n", tl->version);
+ NOTICE("hdr_size 0x%x\n", tl->hdr_size);
+ NOTICE("alignment 0x%x\n", tl->alignment);
+ NOTICE("size 0x%x\n", tl->size);
+ NOTICE("max_size 0x%x\n", tl->max_size);
+ while (true) {
+ te = transfer_list_next(tl, te);
+ if (!te) {
+ break;
+ }
+ NOTICE("Entry %d:\n", i++);
+ NOTICE("tag_id 0x%x\n", te->tag_id);
+ NOTICE("hdr_size 0x%x\n", te->hdr_size);
+ NOTICE("data_size 0x%x\n", te->data_size);
+ NOTICE("data_addr 0x%lx\n",
+ (unsigned long)transfer_list_entry_data(te));
+ }
+}
+
+/*******************************************************************************
+ * Creating a transfer list in a reserved memory region specified
+ * Compliant to 2.4.5 of Firmware handoff specification (v0.9)
+ * Return pointer to the created transfer list or NULL on error
+ ******************************************************************************/
+struct transfer_list_header *transfer_list_init(void *addr, size_t max_size)
+{
+ struct transfer_list_header *tl = addr;
+
+ if (!addr || max_size == 0) {
+ return NULL;
+ }
+
+ if (!is_aligned((uintptr_t)addr, 1 << TRANSFER_LIST_INIT_MAX_ALIGN) ||
+ !is_aligned(max_size, 1 << TRANSFER_LIST_INIT_MAX_ALIGN) ||
+ max_size < sizeof(*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 reserved memory region specified
+ * Compliant to 2.4.6 of Firmware handoff specification (v0.9)
+ * Return true on success or false on error
+ ******************************************************************************/
+struct transfer_list_header *transfer_list_relocate(
+ struct transfer_list_header *tl,
+ void *addr, size_t max_size)
+{
+ uintptr_t new_addr, align_mask, align_off;
+ struct transfer_list_header *new_tl;
+ uint32_t new_max_size;
+
+ if (!tl || !addr || max_size == 0) {
+ return NULL;
+ }
+
+ align_mask = (1 << tl->alignment) - 1;
+ align_off = (uintptr_t)tl & align_mask;
+ new_addr = ((uintptr_t)addr & ~align_mask) + align_off;
+
+ if (new_addr < (uintptr_t)addr) {
+ new_addr += (1 << tl->alignment);
+ }
+
+ new_max_size = max_size - (new_addr - (uintptr_t)addr);
+
+ // the new space is not sufficient for the tl
+ if (tl->size > new_max_size) {
+ return NULL;
+ }
+
+ new_tl = (struct transfer_list_header *)new_addr;
+ memmove(new_tl, tl, tl->size);
+ new_tl->max_size = new_max_size;
+
+ transfer_list_update_checksum(new_tl);
+
+ return new_tl;
+}
+
+/*******************************************************************************
+ * Verifying the header of a transfer list
+ * Compliant to 2.4.1 of Firmware handoff specification (v0.9)
+ * Return transfer list operation status code
+ ******************************************************************************/
+enum transfer_list_ops transfer_list_check_header(
+ const struct transfer_list_header *tl)
+{
+ if (!tl) {
+ return TL_OPS_NON;
+ }
+
+ if (tl->signature != TRANSFER_LIST_SIGNATURE) {
+ ERROR("Bad transfer list signature %#"PRIx32"\n",
+ tl->signature);
+ return TL_OPS_NON;
+ }
+
+ if (!tl->max_size) {
+ ERROR("Bad transfer list max size %#"PRIx32"\n",
+ tl->max_size);
+ return TL_OPS_NON;
+ }
+
+ if (tl->size > tl->max_size) {
+ ERROR("Bad transfer list size %#"PRIx32"\n", tl->size);
+ return TL_OPS_NON;
+ }
+
+ if (tl->hdr_size != sizeof(struct transfer_list_header)) {
+ ERROR("Bad transfer list header size %#"PRIx32"\n", tl->hdr_size);
+ return TL_OPS_NON;
+ }
+
+ if (!transfer_list_verify_checksum(tl)) {
+ ERROR("Bad transfer list checksum %#"PRIx32"\n", tl->checksum);
+ return TL_OPS_NON;
+ }
+
+ if (tl->version == 0) {
+ ERROR("Transfer list version is invalid\n");
+ return TL_OPS_NON;
+ } else if (tl->version == TRANSFER_LIST_VERSION) {
+ INFO("Transfer list version is valid for all operations\n");
+ return TL_OPS_ALL;
+ } else if (tl->version > TRANSFER_LIST_VERSION) {
+ INFO("Transfer list version is valid for read-only\n");
+ return TL_OPS_RO;
+ }
+
+ INFO("Old transfer list version is detected\n");
+ return TL_OPS_CUS;
+}
+
+/*******************************************************************************
+ * Enumerate the next transfer entry
+ * Return pointer to the next transfer entry or NULL on error
+ ******************************************************************************/
+struct transfer_list_entry *transfer_list_next(struct transfer_list_header *tl,
+ struct transfer_list_entry *last)
+{
+ struct transfer_list_entry *te = NULL;
+ uintptr_t tl_ev = 0;
+ uintptr_t va = 0;
+ uintptr_t ev = 0;
+ size_t sz = 0;
+
+ if (!tl) {
+ return NULL;
+ }
+
+ tl_ev = (uintptr_t)tl + tl->size;
+
+ if (last) {
+ va = (uintptr_t)last;
+ // check if the total size overflow
+ if (add_overflow(last->hdr_size,
+ last->data_size, &sz)) {
+ return NULL;
+ }
+ // roundup to the next entry
+ if (add_with_round_up_overflow(va, sz,
+ TRANSFER_LIST_GRANULE, &va)) {
+ return NULL;
+ }
+ } else {
+ va = (uintptr_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 of a 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
+ * Return updated checksum of the transfer list
+ ******************************************************************************/
+void transfer_list_update_checksum(struct transfer_list_header *tl)
+{
+ uint8_t cs;
+
+ 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
+ * 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
+ * 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)
+{
+ uintptr_t tl_old_ev, new_ev = 0, old_ev = 0, ru_new_ev;
+ 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 = (uintptr_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_with_round_up_overflow((uintptr_t)te, sz,
+ TRANSFER_LIST_GRANULE, &old_ev)) {
+ return false;
+ }
+ if (add_overflow(te->hdr_size, new_data_size, &sz) ||
+ add_with_round_up_overflow((uintptr_t)te, sz,
+ 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 (round_up_overflow(mov_dis, 1 << 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
+ * 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 || (uintptr_t)te > (uintptr_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 to 2.4.3 of Firmware handoff specification (v0.9)
+ * 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)
+{
+ uintptr_t max_tl_ev, tl_ev, ev;
+ struct transfer_list_entry *te = NULL;
+ uint8_t *te_data = NULL;
+ size_t sz = 0;
+
+ if (!tl) {
+ return NULL;
+ }
+
+ max_tl_ev = (uintptr_t)tl + tl->max_size;
+ tl_ev = (uintptr_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_with_round_up_overflow(ev, sz,
+ TRANSFER_LIST_GRANULE, &ev) || ev > max_tl_ev) {
+ return NULL;
+ }
+
+ te = (struct transfer_list_entry *)tl_ev;
+ te->tag_id = tag_id;
+ te->reserved0 = 0;
+ te->hdr_size = sizeof(*te);
+ 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 to 2.4.4 of Firmware handoff specification (v0.9)
+ * 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;
+ uintptr_t tl_ev, ev, new_tl_ev;
+ size_t dummy_te_data_sz = 0;
+
+ if (!tl) {
+ return NULL;
+ }
+
+ tl_ev = (uintptr_t)tl + tl->size;
+ ev = tl_ev + sizeof(struct transfer_list_entry);
+
+ if (!is_aligned(ev, 1 << 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 = round_up(ev, 1 << alignment) -
+ 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
+ * Return pointer to the found transfer entry or NULL on error
+ ******************************************************************************/
+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 || te->reserved0 != 0));
+
+ return te;
+}
+
+/*******************************************************************************
+ * Retrieve the data pointer of a specified transfer entry
+ * Return pointer to the transfer entry data or NULL on error
+ ******************************************************************************/
+void *transfer_list_entry_data(struct transfer_list_entry *entry)
+{
+ if (!entry) {
+ return NULL;
+ }
+ return (uint8_t *)entry + entry->hdr_size;
+}
diff --git a/lib/transfer_list/transfer_list.mk b/lib/transfer_list/transfer_list.mk
new file mode 100644
index 0000000..42574e8
--- /dev/null
+++ b/lib/transfer_list/transfer_list.mk
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+ifeq (${TRANSFER_LIST},1)
+
+ifeq (${ARCH},aarch32)
+$(eval $(call add_define,TRANSFER_LIST_AARCH32))
+endif
+
+TRANSFER_LIST_SOURCES += $(addprefix lib/transfer_list/, \
+ transfer_list.c)
+
+BL31_SOURCES += $(TRANSFER_LIST_SOURCES)
+BL2_SOURCES += $(TRANSFER_LIST_SOURCES)
+
+endif # TRANSFER_LIST
+
diff --git a/make_helpers/defaults.mk b/make_helpers/defaults.mk
index aaabb27..b7e6f99 100644
--- a/make_helpers/defaults.mk
+++ b/make_helpers/defaults.mk
@@ -147,6 +147,9 @@
# by lower ELs.
HANDLE_EA_EL3_FIRST_NS := 0
+# Enable Handoff protocol using transfer lists
+TRANSFER_LIST := 0
+
# Secure hash algorithm flag, accepts 3 values: sha256, sha384 and sha512.
# The default value is sha256.
HASH_ALG := sha256