Transfer list

The TL is composed of a TL header which is followed by sequence of Transfer Entries (TE). The whole TL is contiguous in physical address space. The TL header and all the TEs are 8-byte aligned (we use align8() to denote this). The TL header specifies the number of bytes occupied by the TL. The TEs are defined in sec_tl_entry_hdr and sec_std_entries. Each TE carries a header which contains an identifier, tag_id, that is used to determine the content of the associated TE. The TL header is located at tl_base_pa. The tl_base_pa is passed in the register allocated for that handoff boundary (as specified in handoff_arch_bindings). A depiction of the TL is present in Fig. 2 , there the first TE in the list (TE[0]) is shown to start at the end of the TL header (tl_base_pa + 8). The second TE in the list (TE[1]) starts at the next multiple of 8, after the end of the TE[0].

Transfer list example

Fig. 2 Transfer list example

Transfer list requirements

R1: The tl_base_pa address must be at a 8-byte boundary.

R2: All fields defined in this specification must be stored in memory with little-endian byte order.

R3: The base address of a TE must be the 8-byte aligned address immediately after the end of the previous entry (or TL header, if the TE is the first entry on the TL).

R4: When relocating the TL, the offset from tl_base_pa to the nearest alignment boundary specified by the alignment field in the TL header must be preserved.

Transfer list header

A TL must begin with a TL header. The layout of the TL header is shown in Table 1. The presence of a TL header can be verified by inspecting the signature field which must contain the 0x6e_d0ff value. The version field determines the contents of the handoff start header. The version will only be changed by an update to this specification when new TL header or TE header fields are defined (i.e. not when allocating new tag IDs), and all changes will be backwards-compatible to older readers.

Table 1 TL header

Field

Size (bytes)

Offset (bytes)

Description

signature

0x4

0x0

The value of signature must be 0x6e_d0ff.

checksum

0x1

0x4

The checksum is used to ensure the data contained within the list is intact. The checksum is set to a value such that the sum over every byte in the {tl_base_pa, …, tl_base_pa + size - 1} address range, modulo 256, is equal to 0. For the purposes of this calculation, the value of this checksum field in the TL header must be assumed as 0. Note that the checksum includes the TL header, all TEs and the inter-TE padding, but not the range reserved for future TE additions up to max_size. The values of inter-TE padding bytes are not defined by this specification and may be uninitialized memory. (This means that multiple TLs with exactly the same size and contents may still have different checksum values.)

version

0x1

0x5

The version of the TL header. This field is set to 0x1 for the TL header layout described in this version of the table. Code that encounters a TL with a version higher than it knows to support may still read the TL and all its TEs, and assume that it is backwards-compatible to previous versions (ignoring any extra bytes in a potentially larger TL or TE header). However, code may not append new entries to a TL unless it knows how to append entries for the specified version.

hdr_size

0x1

0x6

The size of this TL header in bytes. This field is set to 16 for the TL header layout described in this version of the table.

alignment

0x1

0x7

The maximum alignment required by any TE in the TL, specified as a power of two. For a newly created TL, the alignment requirement is 8 so this value should be set to 3. It should be updated whenever a new TE is added with a larger requirement than the current value.

size

0x4

0x8

The number of bytes occupied by the TL. This field accounts for the size of the TL header plus the size of all the entries contained in the TL. It must be a multiple of 8 (i.e. it includes the inter-TE padding after the end of the last TE). This field must be updated when any entry is added to the TL.

max_size

0x4

0xc

The maximum number of bytes that the TL can occupy. Any entry producer must check if there is sufficient space before adding an entry to the list. Firmware can resize and/or relocate the TL and update this field accordingly, provided that the TL requirements are respected. This field must be a multiple of 8.

TL entry header

All TEs start with an entry header followed by a data section.

Note: the size of an entry (hdr_size + data_size) is not mandatorily an 8-byte multiple. When traversing the TL firmware must compute the next TE address following R3.

For example, assume the current TE is te and its address is te_base_addr. Using C language notation, a derivation of the base address of the next TE (next_base_addr) is the following:

next_base_addr = align8(te_base_addr + te.hdr_size + te.data_size)

The TE header is defined in Table 2.

Table 2 TE header

Field

Size (bytes)

Offset (bytes)

Description

tag_id

0x3

0x0

The entry type identifier.

hdr_size

0x1

0x3

The size of this entry header in bytes. This field is set to 8 for the TE header layout described in this version of the table.

data_size

0x4

0x4

The exact size of the data content in bytes, not including inter-TE padding. May be 0.

Standard operations

This section describes the valid operations that can be performed on a TL in more detail, in order to clarify how to use the various fields and to serve as a guideline for implementation.

Validating a TL header

Inputs:

  • tl_base_addr: Base address of the existing TL.

  1. Compare tl.signature (tl_base_addr + 0x0) to 0x6e_d0ff. On a mismatch, abort (this is not a valid TL).

  2. Compare tl.version (tl_base_addr + 0x5) to the expected version (currently 0x1). If there is an exact match, the TL is valid for all operations outlined in this section. If tl.version is larger, the TL is valid for reading but must not be modified or relocated. If tl.version is smaller, either abort or switch to code designed to interpret the respective previous version of this specification (note that the version number 0x0 is illegal and processing should always abort if it is found).

  3. (optional) Check that tl.size (tl_base_addr + 0x8) is smaller or equal to tl.max_size (tl_base_addr + 0xc), and that tl.max_size is smaller or equal to the size of the total area reserved for the TL (if known). If not, abort (TL is corrupted).

  4. (optional) Check that the sum of tl.size bytes starting at tl_base_addr minus tl.checksum is equal to tl.checksum. If not, abort (TL is corrupted).

Reading a TL

Inputs:

  • tl_base_addr: Base address of the existing TL.

  1. Calculate te_base_addr as align8(tl_base_addr + tl.hdr_size). (Do not hardcode the value for tl.hdr_size!)

  2. While te_base_addr - tl_base_addr is smaller or equal to tl.size:

    1. (optional) Check that te_base_addr + te.hdr_size + te.data_size - tl_base_addr is smaller or equal to tl.size, otherwise abort (the TL is corrupted).

    2. If te.tag_id (te_base_addr + 0x0) is a known tag, interpret the data at te_base_addr + te.hdr_size accordingly. (Do not hardcode the value for te.hdr_size, even for known tags!) Otherwise, ignore the tag and proceed with the next step.

    3. Add te.hdr_size + te.data_size to te_base_addr.

Adding a new TE

Inputs:

  • tl_base_addr: Base address of the TL to add a TE to.

  • new_tag_id: ID number of the tag for the new TE.

  • new_data_size: Size in bytes of the data to be encapsulated in the TE.

  • [data]: Data to be copied into the TE or generated on the fly.

  1. (optional) Follow the steps in Reading a TL to look for a TE where te.tag_id is 0x0 (XFERLIST_VOID) and te.data_size is greater or equal to new_data_size. If found:

    1. Remember te.data_size as old_void_data_size.

    2. Use the te_base_addr of this tag for the rest of the operation.

    3. Subtract the sum of align8(new_data_size + 0x8) bytes starting at te_base_addr from tl.checksum.

    4. Skip the next step (step 2) with all its substeps.

  2. Calculate te_base_addr as tl_base_addr + tl.size.

    1. If tl.max_size - tl.size is smaller than align8(new_data_size + 0x8), abort (not enough room to add TE).

    2. Subtract the sum of the 4 bytes from tl_base_addr + 0x8 to tl_base_addr + 0xc from tl.checksum.

    3. Add align8(new_data_size + 0x8) to tl.size.

    4. Add the sum of the 4 bytes from tl_base_addr + 0x8 to tl_base_addr + 0xc to tl.checksum.

  3. Set te.tag_id (te_base_addr + 0x0) to new_tag_id.

  4. Set te.hdr_size (te_base_addr + 0x3) to 8.

  5. Set te.data_size (te_base_addr + 0x4) to new_data_size.

  6. Copy or generate the TE data into te_base_addr + 0x8.

  7. Add the sum of align8(new_data_size + 0x8) bytes starting at te_base_addr to tl.checksum.

  8. If an existing XFERLIST_VOID TE was chosen to be overwritten in step 1, and old_void_data_size - new_data_size is greater or equal to 0x8:

    1. Use te_base_addr + align8(new_data_size + 0x8) as the new te_base_addr for a new XFERLIST_VOID tag.

    2. Subtract the sum of the 8 bytes from te_base_addr to te_base_addr + 0x8 from tl.checksum.

    3. Set te.tag_id (te_base_addr + 0x0) to 0x0 (XFERLIST_VOID).

    4. Set te.hdr_size (te_base_addr + 0x3) to 0x8.

    5. Set te.data_size (te_base_addr + 0x4) to old_void_data_size - align8(new_data_size) - 0x8.

    6. Add the sum of the 8 bytes from te_base_addr to te_base_addr + 0x8 to tl.checksum.

Adding a new TE with special data alignment requirement

Inputs:

  • tl_base_addr: Base address of the TL to add a TE to.

  • new_tag_id: ID number of the tag for the new TE.

  • new_alignment: The alignment boundary as a power of 2 that the data must be aligned to.

  • new_data_size: Size in bytes of the data to be encapsulated in the TE.

  • [data]: Data to be copied into the TE or generated on the fly.

  1. Calculate alignment_mask as (1 << new_alignment) - 1.

  2. If (tl_base_addr + tl.size) & alignment_mask is not 0x0, follow the steps in Adding a new TE with the following inputs (bypass the option to overwrite an existing XFERLIST_VOID TE):

    1. tl_base_addr remains the same

    2. new_tag_id is 0x0 (XFERLIST_VOID)

    3. new_data_size is (1 << new_alignment) - ((tl_base_addr + tl.size) & alignment_mask) - 0x8.

    4. No data (i.e. just don’t touch the bytes that form the data portion for this TE).

  3. Follow the steps in Adding a new TE with the original inputs (again bypass the option to overwrite an existing XFERLIST_VOID TE).

  4. If new_alignment is larger than tl.alignment:

    1. Subtract tl.alignment from tl.checksum.

    2. Set tl.alignment to new_alignment.

    3. Add tl.alignment to tl.checksum.

Creating a TL

Inputs:

  • tl_base_addr: Base address where to place the new TL.

  • available_size: Available size in bytes to reserve for the TL after tl_base_addr.

  1. Check that available_size is larger than 0x10, otherwise abort.

  2. Set tl.signature (tl_base_addr + 0x0) to 0x6e_d0ff.

  3. Set tl.checksum (tl_base_addr + 0x4) to 0x0 (for now).

  4. Set tl.version (tl_base_addr + 0x5) to 0x1.

  5. Set tl.hdr_size (tl_base_addr + 0x6) to 0x10.

  6. Set tl.alignment (tl_base_addr + 0x7) to 0x3.

  7. Set tl.size (tl_base_addr + 0x8) to 0x10.

  8. Set tl.max_size (tl_base_addr + 0xc) to available_size.

  9. Calculate the checksum as the sum of all bytes from tl_base_addr to tl_base_addr + 0x10, and write the result to tl.checksum.

Relocating a TL

Inputs:

  • tl_base_addr: Base address of the existing TL.

  • target_base: Base address of the target region to relocate into.

  • target_size: Total size in bytes of the target region to relocate into.

  1. Calculate alignment_mask as (1 << tl.alignment) - 1.

  2. Calculate new_tl_base as (target_base + alignment_mask) & ~alignment_mask.

  3. If new_tl_base is below target_base, add alignment_mask + 1 to new_tl_base.

  4. If new_tl_base - target_base + tl.size is larger than target_size, abort (not enough space to relocate).

  5. Copy tl.size bytes from tl_base_addr to new_tl_base.

  6. Subtract the sum of the 4 bytes from new_tl_base + 0xc to new_tl_base + 0x10 from tl.checksum (new_tl_base + 0x4).

  7. Set tl.max_size (new_tl_base + 0xc) to target_size - new_tl_base.

  8. Add the sum of the 4 bytes from new_tl_base + 0xc to new_tl_base + 0x10 to tl.checksum (new_tl_base + 0x4).

Entry type allocation

Tag IDs must be allocated in this specification before use. A new tag ID can be allocated by submitting a pull request to this repository that adds a description of the respective TE data layout to this specification. Tag IDs do not have to be allocated in order. Submitters are encouraged to try to group tag IDs together in logical clusters at 16 or 256-aligned boundaries (e.g. all tags related to a particular chipset or to a particular firmware project could use adjacent tag numbers), but there are no predefined ranges and no reservations of tag ranges for specific use.

Tags are expected to have a simple layout (representable by a C structure) and each tag should only represent data for a single logical concept. Data for multiple distinct concepts should be split across different tags, even if they’re always expected to appear together on the first platform adding the tag (to encourage reusability in different situations). Alternatively, complex data may be represented in a different kind of well-established handoff data structure (e.g. FDT [DT], HOB [PI]) that is inserted into the TL as a single TE. The same tag ID may occur multiple times in the TL to represent multiple instances of the same kind of object. Tag layouts (including the meaning of all fields) are considered stable after being added to this specification and may never be changed in a backwards-incompatible way. If a backwards-incompatible change is desired, a new tag ID should be allocated for the new version of the layout instead.

Tag layouts may be changed in a backwards-compatible manner by allowing new valid values in existing fields (including reserved fields), as long as the original layout definition clearly defined how unknown values in those fields should be handled, and the rest of the TE would still be considered valid and correct for older readers that consider the new values unknown. TE layouts may also be expanded by adding new fields at the end, with the same restrictions. TEs should not contain explicit version numbers and instead just use the data_size value to infer how many fields exist. TE layouts which have been changed like this must clearly document which fields or valid values were added at a later time, and in what order.

The {0xff_f000, …, 0xff_ffff} range is reserved for non-standardized use. Anyone is free to use tags from that range for any custom TE layout without adding their definitions to this specification first. The use of this range is strongly discouraged for anything other than local experiments or code that will only ever be used in closed-source components owned by the entity controlling the entire final firmware image. In particular, a TE just containing platform-specific data or internal structures specific to a single firmware implementation is no reason not to allocate a standardized tag for it in this specification. Since standards often emerge organically, the goal is to create unique tag IDs for everything just in case it turns out to be useful in more applications than initially anticipated. Basically, whenever you’re submitting code for a new TE layout to any public open-source project, that’s probably a good indication that you should allocate a tag ID for it in this specification.

Table 3 Tag ID ranges

tag ID range

Description

0x0 – 0x7f_ffff

Standardized range. Any tag ID in this range must first be allocated in this specification before being used. The allocation of the tag ID requires the entry layout to be defined as well.

0x80_0000 – 0xff_efff

Reserved. (Can later be used to extend standardized range if necessary.)

0xff_f000 – 0xff_ffff

Non-standardized range. Tag IDs in this range can be used without allocation in this specification. Using this range for anything other than local experimentation or closed-source components that are entirely under the control of a single platform firmware integrator is strongly discouraged. Tags in this range are not tracked in this repository and PRs to add tag defintions for this range will not be accepted.

Standard transfer entries

The following entry types are currently defined:

  • empty entry: tag_id = 0 (void_entry).

  • fdt entry: tag_id = 1 (fdt_entry).

  • single HOB block entry: tag_id = 2 (hob_block_entry).

  • HOB list entry: tag_id = 3 (hob_list_entry).

  • ACPI table aggregate entry: tag_id = 4 (acpi_aggr_entry).

Empty entry layout (XFERLIST_VOID)

The empty or void entry should not contain any information to be consumed by any firmware stage. The intent of the void entry type is to remove information from the list without needing to relocate subsequent entries, or to create padding for entries that require a specific alignment. Void entries may be freely overwritten with new TEs, provided the resulting TL remains valid (i.e. a void entry can only be overwritten by a TE of equal or smaller size; if the size is more than 8 bytes smaller, a new void entry must be created behind the new TE to cover the remaining space up to the next TE).

FDT entry layout (XFERLIST_FDT)

The fdt is defined in [DT]. The FDT TE contains the fdt in the data section. The intent of the FDT entry is to carry the hardware description devicetree in the flattened devicetree (FDT) [DT] representation.

Table 4 FDT type layout

Field

Size (bytes)

Offset (bytes)

Description

tag_id

0x3

0x0

The tag_id field must be set to 1.

hdr_size

0x1

0x3

The size of this entry header in bytes must be set to 8.

data_size

0x4

0x4

The size of the FDT in bytes.

fdt

data_size

hdr_size

The fdt field contains the hardware description fdt.

HOB block entry layout (XFERLIST_HOB_B)

The HOB is defined in [PI]. This entry type encapsulates a single HOB block. The intent of the HOB block entry is to hold a single HOB block. A complete HOB list can then be constructed, by a receiver, by obtaining all the HOB blocks in the TL and following the HOB list requirements defined in [PI].

Table 5 HOB block type layout

Field

Size (bytes)

Offset (bytes)

Description

tag_id

0x3

0x0

The tag_id field must be set to 2.

hdr_size

0x1

0x3

The size of this entry header in bytes must be set to 8.

data_size

0x4

0x4

The size of the HOB block in bytes.

hob_block

data_size

hdr_size

Holds a single HOB block.

HOB list entry layout (XFERLIST_HOB_L)

The HOB list is defined in [PI]. The HOB list starts with a PHIT block and can contain an arbitrary number of HOB blocks. This entry type encapsulates a complete HOB list. An enclosed HOB list must respect the HOB list constraints specified in [PI].

Table 6 HOB list type layout

Field

Size (bytes)

Offset (bytes)

Description

tag_id

0x3

0x0

The tag_id field must be set to 3.

hdr_size

0x1

0x3

The size of this entry header in bytes must be set to 8.

data_size

0x4

0x4

The size of the HOB list in bytes.

hob_list

data_size

hdr_size

Holds a complete HOB list.

ACPI table aggregate entry layout (XFERLIST_ACPI_AGGR)

This entry type holds one or more ACPI tables. The first table must start at offset hdr_size from the start of the entry. Since ACPI tables usually have an alignment requirement larger than 8, writers may first need to create an XFERLIST_VOID padding entry so that the subsequent te_base_addr + te.hdr_size will be correctly aligned. Any subsequent ACPI tables must be located at the next 16-byte alligned address following the preceding ACPI table. Note that each ACPI table has a Length field in the ACPI table header [ACPI], which must be used to determine the end of the ACPI table. The data_size value must be set such that the last ACPI table in this entry ends at offset hdr_size + data_size from the start of the entry.

Table 7 ACPI table aggregate type layout

Field

Size (bytes)

Offset (bytes)

Description

tag_id

0x3

0x0

The tag_id field must be set to 4.

hdr_size

0x1

0x3

The size of this entry header in bytes must be set to 8.

data_size

0x4

0x4

The size of all included ACPI tables + padding in bytes.

acpi_tables

data_size

hdr_size

One or more ACPI tables.