123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 |
- /*
- * Copyright (c) 2023, Linaro Limited and Contributors. All rights reserved.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
- #include <arch.h>
- #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;
- }
- INFO("Dump transfer list:\n");
- INFO("signature 0x%x\n", tl->signature);
- INFO("checksum 0x%x\n", tl->checksum);
- INFO("version 0x%x\n", tl->version);
- INFO("hdr_size 0x%x\n", tl->hdr_size);
- INFO("alignment 0x%x\n", tl->alignment);
- INFO("size 0x%x\n", tl->size);
- INFO("max_size 0x%x\n", tl->max_size);
- INFO("flags 0x%x\n", tl->flags);
- while (true) {
- te = transfer_list_next(tl, te);
- if (!te) {
- break;
- }
- INFO("Entry %d:\n", i++);
- INFO("tag_id 0x%x\n", te->tag_id);
- INFO("hdr_size 0x%x\n", te->hdr_size);
- INFO("data_size 0x%x\n", te->data_size);
- INFO("data_addr 0x%lx\n",
- (unsigned long)transfer_list_entry_data(te));
- }
- }
- /*******************************************************************************
- * Set the handoff arguments according to the transfer list payload
- * Return pointer to the entry point info if arguments are set properly
- * or NULL if not
- ******************************************************************************/
- entry_point_info_t *
- transfer_list_set_handoff_args(struct transfer_list_header *tl,
- entry_point_info_t *ep_info)
- {
- struct transfer_list_entry *te = NULL;
- void *dt = NULL;
- if (!ep_info || !tl || transfer_list_check_header(tl) == TL_OPS_NON) {
- return NULL;
- }
- te = transfer_list_find(tl, TL_TAG_FDT);
- dt = transfer_list_entry_data(te);
- #ifdef __aarch64__
- if (GET_RW(ep_info->spsr) == MODE_RW_64) {
- ep_info->args.arg0 = (uintptr_t)dt;
- ep_info->args.arg1 = TRANSFER_LIST_HANDOFF_X1_VALUE(REGISTER_CONVENTION_VERSION);
- ep_info->args.arg2 = 0;
- } else
- #endif
- {
- ep_info->args.arg0 = 0;
- ep_info->args.arg1 = TRANSFER_LIST_HANDOFF_R1_VALUE(REGISTER_CONVENTION_VERSION);
- ep_info->args.arg2 = (uintptr_t)dt;
- }
- ep_info->args.arg3 = (uintptr_t)tl;
- return ep_info;
- }
- /*******************************************************************************
- * 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;
- tl->flags = TL_FLAGS_HAS_CHECKSUM;
- 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 pointer to the relocated transfer list or NULL 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;
- 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 || !(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
- 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)
- {
- if (!tl) {
- return false;
- }
- if (!(tl->flags & TL_FLAGS_HAS_CHECKSUM)) {
- return true;
- }
- 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->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;
- 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,
- uint32_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->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, uint32_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,
- uint32_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
- * 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;
- }
|