/** @file
Library that implements the helper functions to parse and pack a Transfer
List as specified by the A-profile Firmware Handoff Specification.
Copyright (c) 2022 - 2025, Arm Limited. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
@par Reference(s):
- https://github.com/FirmwareHandoff/firmware_handoff
**/
#include
#include
#include
#include
#include
/**
Get the TransferList from HOB list.
@param[out] TransferList TransferList
@retval EFI_SUCCESS TransferList is found.
@retval EFI_NOT_FOUND TransferList is not found.
**/
EFI_STATUS
EFIAPI
TransferListGetFromHobList (
OUT TRANSFER_LIST_HEADER **TransferList
)
{
VOID *HobList;
EFI_HOB_GUID_TYPE *GuidHob;
UINTN *GuidHobData;
*TransferList = NULL;
HobList = GetHobList ();
if (HobList == NULL) {
return EFI_NOT_FOUND;
}
GuidHob = GetNextGuidHob (&gArmTransferListHobGuid, HobList);
if (GuidHob == NULL) {
return EFI_NOT_FOUND;
}
GuidHobData = GET_GUID_HOB_DATA (GuidHob);
*TransferList = (TRANSFER_LIST_HEADER *)(*GuidHobData);
return EFI_SUCCESS;
}
/**
This function verifies the checksum of the Transfer List.
@param [in] TransferListHeader Pointer to the Transfer List Header
@retval FALSE Invalid Checksum
@retval TRUE Valid Checksum
**/
BOOLEAN
EFIAPI
TransferListVerifyChecksum (
IN TRANSFER_LIST_HEADER *TransferListHeader
)
{
if (TransferListHeader == NULL) {
return FALSE;
}
if ((TransferListHeader->Flags & TRANSFER_LIST_FL_HAS_CHECKSUM) == 0) {
return TRUE;
}
return (CalculateSum8 ((UINT8 *)TransferListHeader, TransferListHeader->UsedSize) == 0);
}
/**
This function checks the header of the Transfer List.
@param [in] TransferListHeader Pointer to the Transfer List Header
@return TRANSFER_LIST_OPS code indicating the validity of the Transfer List
**/
TRANSFER_LIST_OPS
EFIAPI
TransferListCheckHeader (
IN TRANSFER_LIST_HEADER *TransferListHeader
)
{
if (TransferListHeader == NULL) {
return TRANSFER_LIST_OPS_INVALID;
}
if (TransferListHeader->Signature != TRANSFER_LIST_SIGNATURE_64) {
DEBUG ((DEBUG_ERROR, "Bad transfer list signature 0x%x\n", TransferListHeader->Signature));
return TRANSFER_LIST_OPS_INVALID;
}
if (TransferListHeader->TotalSize == 0) {
DEBUG ((DEBUG_ERROR, "Bad transfer list total size 0x%x\n", TransferListHeader->TotalSize));
return TRANSFER_LIST_OPS_INVALID;
}
if (TransferListHeader->UsedSize > TransferListHeader->TotalSize) {
DEBUG ((DEBUG_ERROR, "Bad transfer list used size 0x%x\n", TransferListHeader->UsedSize));
return TRANSFER_LIST_OPS_INVALID;
}
if (TransferListHeader->HeaderSize != sizeof (TRANSFER_LIST_HEADER)) {
DEBUG ((DEBUG_ERROR, "Bad transfer list header size 0x%x\n", TransferListHeader->HeaderSize));
return TRANSFER_LIST_OPS_INVALID;
}
if (TransferListVerifyChecksum (TransferListHeader) == FALSE) {
DEBUG ((DEBUG_ERROR, "Bad transfer list checksum 0x%x\n", TransferListHeader->Checksum));
return TRANSFER_LIST_OPS_INVALID;
}
if (TransferListHeader->Version == 0) {
DEBUG ((DEBUG_ERROR, "Transfer list version is invalid\n"));
return TRANSFER_LIST_OPS_INVALID;
} else if (TransferListHeader->Version == ARM_FW_HANDOFF_PROTOCOL_VERSION) {
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "Transfer list version is valid for all operations\n"));
return TRANSFER_LIST_OPS_ALL;
} else if (TransferListHeader->Version > ARM_FW_HANDOFF_PROTOCOL_VERSION) {
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "Transfer list version is valid for read-only\n"));
return TRANSFER_LIST_OPS_RO;
}
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "Old or custom transfer list version is detected\n"));
return TRANSFER_LIST_OPS_CUSTOM;
}
/**
Return the first Transfer Entry Node in the Transfer List.
@param [in] TransferListHeader TransferListHeader
@return Pointer to the Transfer Entry Node if successful otherwise NULL
**/
TRANSFER_ENTRY_HEADER *
EFIAPI
TransferListGetFirstEntry (
IN TRANSFER_LIST_HEADER *TransferListHeader
)
{
return TransferListGetNextEntry (TransferListHeader, NULL);
}
/**
Return the next Transfer Entry Node in the Transfer List from
last Transfer Entry Node.
@param [in] TransferListHeader Pointer to the Transfer List Header.
@param [in] CurrentEntry Pointer to the Current Transfer Entry.
If this is NULL, the first Transfer Entry
is returned.
@return Pointer to the Transfer Entry Node if successful otherwise NULL
**/
TRANSFER_ENTRY_HEADER *
EFIAPI
TransferListGetNextEntry (
IN TRANSFER_LIST_HEADER *TransferListHeader,
IN TRANSFER_ENTRY_HEADER *CurrentEntry
)
{
TRANSFER_ENTRY_HEADER *Entry;
UINTN CurrentAddr;
UINTN EndAddr;
if (TransferListHeader == NULL) {
return NULL;
}
EndAddr = (UINTN)TransferListHeader + TransferListHeader->UsedSize;
if (CurrentEntry != NULL) {
CurrentAddr = (UINTN)CurrentEntry + CurrentEntry->HeaderSize + CurrentEntry->DataSize;
} else {
CurrentAddr = (UINTN)TransferListHeader + TransferListHeader->HeaderSize;
}
CurrentAddr = ALIGN_VALUE (CurrentAddr, (1 << TransferListHeader->Alignment));
Entry = (TRANSFER_ENTRY_HEADER *)CurrentAddr;
if (((CurrentAddr + sizeof (TRANSFER_LIST_HEADER)) < CurrentAddr) ||
((CurrentAddr + sizeof (TRANSFER_ENTRY_HEADER)) > EndAddr) ||
((CurrentAddr + Entry->HeaderSize + Entry->DataSize) < CurrentAddr) ||
((CurrentAddr + Entry->HeaderSize + Entry->DataSize) > EndAddr))
{
return NULL;
}
return Entry;
}
/**
Return the first Transfer Entry Node in the Transfer List
matched with given tag-id.
@param [in] TransferListHeader Pointer to the Transfer List Header.
@param [in] TagId Tag id
@return Pointer to the Transfer Entry Node if successful otherwise NULL
**/
TRANSFER_ENTRY_HEADER *
EFIAPI
TransferListFindFirstEntry (
IN TRANSFER_LIST_HEADER *TransferListHeader,
IN UINT16 TagId
)
{
TRANSFER_ENTRY_HEADER *Entry;
Entry = TransferListGetFirstEntry (TransferListHeader);
while ((Entry != NULL) && ((Entry->TagId != TagId) || Entry->Reserved0 != 0)) {
Entry = TransferListGetNextEntry (TransferListHeader, Entry);
}
return Entry;
}
/**
Return the Next Transfer Entry Node in the Transfer List
matched with given tag-id from last Transfer Entry Node.
@param [in] TransferListHeader Pointer to the Transfer List Header.
@param [in] CurrentEntry Pointer to the Current Transfer Entry.
If this is NULL, the first Transfer Entry
is returned.
@param [in] TagId Tag id
@return Pointer to the Transfer Entry Node if successful otherwise NULL
**/
TRANSFER_ENTRY_HEADER *
EFIAPI
TransferListFindNextEntry (
IN TRANSFER_LIST_HEADER *TransferListHeader,
IN TRANSFER_ENTRY_HEADER *CurrentEntry,
IN UINT16 TagId
)
{
TRANSFER_ENTRY_HEADER *Entry;
if (CurrentEntry == NULL) {
return TransferListFindFirstEntry (TransferListHeader, TagId);
} else {
Entry = TransferListGetNextEntry (TransferListHeader, CurrentEntry);
}
while ((Entry != NULL) && ((Entry->TagId != TagId) || Entry->Reserved0 != 0)) {
Entry = TransferListGetNextEntry (TransferListHeader, Entry);
}
return Entry;
}
/**
Return the data in Transfer Entry.
@param [in] TransferEntry Pointer to a Transfer Entry Header
@return Pointer to the Data of Transfer Entry Node if successful otherwise NULL
**/
VOID *
EFIAPI
TransferListGetEntryData (
IN TRANSFER_ENTRY_HEADER *TransferEntry
)
{
if ((TransferEntry == NULL) || (TransferEntry->DataSize == 0)) {
return NULL;
}
return (VOID *)((UINTN)TransferEntry + TransferEntry->HeaderSize);
}
/**
Find a Transfer Entry Node in the Transfer List matched with the given tag-id.
@param [in] TransferListHeader Pointer to the Transfer List Header
@param [in] TagId Tag id
@return Pointer to the Transfer Entry Node if successful otherwise NULL
**/
TRANSFER_ENTRY_HEADER *
EFIAPI
TransferListFindEntry (
IN TRANSFER_LIST_HEADER *TransferListHeader,
IN UINT16 TagId
)
{
TRANSFER_ENTRY_HEADER *Entry = NULL;
do {
Entry = TransferListGetNextEntry (TransferListHeader, Entry);
} while ((Entry != NULL) && (Entry->TagId != TagId));
return Entry;
}
/**
Get TPM event log from TransferList
@param [in] TransferListHeader Pointer to the Transfer List Header
@param [out] EventLog Pointer to Eventlog in TransferList
@param [out] EventLogSize Size of Event log
@param [out] EventLogFlags Flags for Event log
@return EFI_SUCCESS
@return EFI_NOT_FOUND No Event log in TransferListHeader
@return EFI_INVALID_PARAMETER Invalid parameters
**/
EFI_STATUS
EFIAPI
TransferListGetEventLog (
IN TRANSFER_LIST_HEADER *TransferListHeader,
OUT VOID **EventLog,
OUT UINTN *EventLogSize,
OUT UINT32 *EventLogFlags OPTIONAL
)
{
TRANSFER_ENTRY_HEADER *Entry;
TRANSFER_LIST_EVENTLOG *EntryData;
if ((TransferListHeader == NULL) || (EventLog == NULL) || (EventLogSize == NULL)) {
return EFI_INVALID_PARAMETER;
}
*EventLog = NULL;
*EventLogSize = 0;
Entry = TransferListFindFirstEntry (TransferListHeader, TRANSFER_ENTRY_TAG_ID_TPM_EVENT_LOG);
if ((Entry == NULL) || (Entry->DataSize == 0) ||
((Entry->DataSize - OFFSET_OF (TRANSFER_LIST_EVENTLOG, EventLog)) == 0))
{
return EFI_NOT_FOUND;
}
EntryData = TransferListGetEntryData (Entry);
if (EventLogFlags != NULL) {
*EventLogFlags = EntryData->Flags;
}
*EventLogSize = Entry->DataSize - OFFSET_OF (TRANSFER_LIST_EVENTLOG, EventLog);
*EventLog = (VOID *)&EntryData->EventLog;
return EFI_SUCCESS;
}
/**
Dump the transfer list to the debug output.
@param [in] TransferListHeader Pointer to the Transfer List Header
**/
VOID
EFIAPI
TransferListDump (
IN TRANSFER_LIST_HEADER *TransferListHeader
)
{
TRANSFER_ENTRY_HEADER *Entry;
UINTN Idx;
Entry = NULL;
Idx = 0;
if (TransferListHeader == NULL) {
return;
}
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "Dump transfer list:\n"));
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "signature 0x%x\n", TransferListHeader->Signature));
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "checksum 0x%x\n", TransferListHeader->Checksum));
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "version 0x%x\n", TransferListHeader->Version));
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "hdr_size 0x%x\n", TransferListHeader->HeaderSize));
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "alignment 0x%x\n", TransferListHeader->Alignment));
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "used_size 0x%x\n", TransferListHeader->UsedSize));
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "total_size 0x%x\n", TransferListHeader->TotalSize));
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "flags 0x%x\n", TransferListHeader->Flags));
while (TRUE) {
Entry = TransferListGetNextEntry (TransferListHeader, Entry);
if (Entry == NULL) {
break;
}
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "Entry %d:\n", Idx++));
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "tag_id 0x%x\n", Entry->TagId));
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "hdr_size 0x%x\n", Entry->HeaderSize));
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "data_size 0x%x\n", Entry->DataSize));
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "data_addr 0x%lx\n", (UINTN)TransferListGetEntryData (Entry)));
}
}