Files
u-boot/doc/develop/std_passage.rst
Simon Glass 2d29554e18 passage: Add documentation
Add documentation about standard passage and update the maintainers.

Series-changes: 2
- Add detailed arch-specific information
- Add comments about passing a bloblist to Linux
- Add comments about how to pass standard passage to EFI
- Fix 'it' typo

Series-changes: 3
- Fix 'that' typo
- Update docs for the various code changes

Signed-off-by: Simon Glass <sjg@chromium.org>
Change-Id: I91d5b9ae45e8d1081b1e165620ad1946bd69af00
2025-05-29 19:06:30 +01:00

385 lines
15 KiB
ReStructuredText

.. SPDX-License-Identifier: GPL-2.0+
Standard Passage
================
Introduction
------------
It is sometimes necessary for SPL to communicate information to U-Boot proper,
such as the RAM size. This can sometimes be handled by adding the value to a
Kconfig which both SPL and U-Boot proper can use. But this does not work for
values which are detected at runtime.
In some cases other firmware binaries are used alongside U-Boot and these may
need to pass information to U-Boot or receive information from it. In this case
there is no shared build system and it is clumsy so have to specify matching
build options across projects.
U-Boot provides a standard way of passing information between different phases
(TPL, SPL, U-Boot). This is called `standard passage` since it creates a
standard passage through which any sort of information can flow.
How it works
------------
The standard passage is very simple. It is really just a way of sending a
bloblist between programs, either at a fixed address, or using registers to
indicate the location.
A :doc:`bloblist` is a simple, contiguous data structure containing a number of
blobs. Each blob has a tag to indicate what it contains. It is designed for
simple information, like a small C struct. For more complex data, a devicetree
is preferred since it has bindings and is extensible.
The bloblist is typically set up initially by one of the early phases of U-Boot,
such as TPL. It starts either at a fixed address or is allocated in memory using
malloc(). After that, TPL passes the location of the bloblist to SPL (using
machine register in an architecture-specific way) and SPL passes it to U-Boot
proper. It is possible to add new blobs to the bloblist at each phase. U-Boot
proper relocates the bloblist so can expand it if desired.
Use by other projects
---------------------
The standard passage is also intended to be used by other firmware projects,
particularly if they interface with U-Boot. It allows that project's firmware
binaries to pass information to U-Boot (if they run before U-Boot) or receive
information from U-Boot (if they run afterwards).
These projects can copy and modify the bloblist code provided they have a
compatible license (GPLv2 or later).
Allocating tags
---------------
Tags are defined in the `bloblist.h` header file. For the moment, the U-Boot
tree is the upstream repository for tags.
Tags may be allocated in the following areas:
BLOBLISTT_AREA_FIRMWARE_TOP
A small area for tags of considerable relevance to multiple projects
BLOBLISTT_AREA_FIRMWARE
A larger area for tags likely to be relevant to multiple projects either now
or in the future
BLOBLISTT_PRIVATE_AREA
Used for private flags. Do not send patches with these. They are for local
or temporary use. Standard firmware binaries which see these tags in the
bloblist typically refuse to boot.
To add a new tag for your project, send a patch to the U-Boot project with:
- your new tag, using the next available number in the area your choose
- a header file in include/stdpass/ containing your struct definition if your
struct is not actually used in U-Boot
- a line of code in `board/sandbox/stdpass_check.c` to use the struct (see
that file for instructions)
The struct definition does not need to match the code style or types names used
in the other project. For example, your project might use a type like
__UNSIGNED_INT32 which in U-Boot would be written as u32. Types should be sized
so that the struct is the same on 32- and 64-bit machines. Avoid using __packed
if possible. Instead try to order members so that it is not necessary.
Conflicts are resolved before applying patches to mainline, so your actual tag
value may change when the patch is applied. Once your patch is accepted your tag
is allocated for all time.
Devicetree
----------
Devicetree has a special place in the standard passage. One of the bloblist tags
is BLOBLISTT_CONTROL_FDT which indicates that the blob contains a devicetree
intended to control U-Boot (or other binaries). This devicetree provides
hardware information and configuration in a generic way using standard bindings,
so that it is available for any project to use. The bindings are compatible with
operating systems (including Linux) so there is no need to remove them before
calling the OS.
In cases where a binary wants to access the devicetree but does not want to
implement the bloblist feature, the offset of the devicetree within the
bloblist is provided. This avoids the need to implement bloblists just to
access the devicetree. This is a convenience feature intended for use in
degenerate cases.
However U-Boot itself does not permit accepting a devicetree through standard
passage unless it is part of a valid bloblist. It is easy to turn on the
bloblist feature in U-Boot, so such a variation would only serve to confuse
things and encourage degeneration in other projects.
Standard passage API
--------------------
The protocol for passing a bloblist through the standard passage from one
binary to another is architecture-specific, meaning it works differently on
32-/64-bit ARM and x86, for example, if only because of the different register
naming.
The bloblist is mandatory. If there is no information to pass, the bloblist must
still be provided. Following firmware stages may add blobs to the bloblist,
making use of the existing space.
The minimum bloblist size is 256 bytes, but 4KB is recommended.
Two registers are used to pass the bloblist and devicetree pointers. Others may
be used depending on the architecture. The protocol chosen for each architecture
should be compatible with the Linux protocol, with a way of determining whether
standard passage is used. This is done not because Linux might start using
standard passage, but to avoid reinventing the wheel.
For 32-bit ARM:
========= ==============================================
Register Contents
========= ==============================================
r0 0
r1 0x010fb10b (indicates standard passage v1)
r2 Address of devicetree
r3 Address of bloblist
lr Return address
========= ==============================================
For 64-bit ARM:
========= ===================================================
Register Contents
========= ===================================================
x0 Address of devicetree
x1 00000001_4a0fb10b (indicates standard passage v1)
x2 0
x3 Address of bloblist
x30 Return address
========= ===================================================
Registers for x86 will be added also, at some point.
The devicetree is provided as an address but is normally included within the
bloblist, meaning that searching for the BLOBLISTT_CONTROL_DTB blob with the
bloblist produces the same result as the devicetree address passed here. Having
everything in a bloblist makes it easier to manage memory, since it is all in
one contiguous block.
Usage guidelines
----------------
As mentioned above, blobs should contain small, simple blocks of information,
typically represented by a C structure. Using it for large or complex structures
is only permitted if these are defined by a standard byte-accurate form.
Examples include devicetree, ACPI tables, SMBIOS tables and the like.
There are also a lot of pre-existing firmware binaries which are quite complex
but qualify because it is not possible to convert them to devicetree now. Apart
from those exceptions, keep it simple!
For complex data structures, devicetree can be used. The libfdt library has an
overhead of around 5-10KB which is small enough that most firmware binaries can
easily incorporate it. For those that must run in very constrained environments,
like U-Boot TPL, a simple blob can be used instead, as explained in the
preceding paragraph.
Devicetree bindings must be defined so that the format of the data is well
understood. This is done through the `dt-schema`_ project, although this process
is still in its infancy.
EFI
---
When using EFI a different calling convention is used. For example, with 64-bit
ARM we have:
========= ===================================================
Register Contents
========= ===================================================
x0 handle
x1 system table
x30 Return address
========= ===================================================
It is not possible to pass the bloblist in a register in this case. Instead, a
config table can be used. The GUID for a bloblist is
4effe9da-7728-11ec-8df6-136969a780ff
Linux and bloblists
-------------------
It is possible to effectively pass a bloblist to Linux, or other Operating
Systems. However it is not expected that Linux would actually implement the
bloblist data structure. How does this work?
The bloblist sits in memory with some things in it, including a devicetree,
perhaps an SMBIOS table and a TPM log. But when U-Boot calls Linux it puts the
address/size of those individual things in the devicetree. They don't move and
are still contiguous in memory, but the bloblist around them is forgotten. Linux
doesn't know that the three separate things it is picking up are actually part
of a bloblist structure, since it doesn't care about that. Even a console log
can work the same way. There is no need to teach Linux about bloblist when it
already has a perfectly good means to accept these items (via devicetree).
ACPI can operate in a similar way. The ACPI tables can point to things that
happen to be in a bloblist, but without any knowledge of that needed in Linux,
grub, etc. In fact the ACPI tables themselves may well be in a bloblist, as they
are in U-Boot.
Private firmware
----------------
Standard passage is intended to operate with open-source firmware. It may be
that closed-source firmware may use standard passage, or happen to work with it.
If so, that is a happy coincidence, but the focus here is on open-source
firmware.
Updates
-------
Updates and patches to this documentation are welcome. Please submit them to
the U-Boot project in the normal way.
Design notes
------------
This section describes some of the reasons behind the design decisions implied
by this feature.
Why devicetree?
~~~~~~~~~~~~~~~
Firmware is getting large and complicated, with a concomitant need for more
complex communication between binaries. We don't want to use C structs to pass
around complex data, nor invent new binary formats for everything that comes up.
The devicetree provides a useful format and is already widely used in firmware.
It supports bindings and provides validation to check for compliance, by virtue
of the Linux project and `dt-schema`_. It is easily extensible, being basically
an efficient, hierarchical, ordered dictionary.
Some examples of how complex and annoying binary formats can become are SMBIOS
tables and Intel's Video BIOS tables. The world does not need another binary
format.
Why not *just* devicetree?
~~~~~~~~~~~~~~~~~~~~~~~~~~
Some early firmware binaries run in very little memory and only need to pass a
few values on to later phases. Devicetree is too heavy-weight for these cases.
For example, it is generally not possible for TPL to access a devicetree, which
is one of the motivations for the of-platdata feature.
Why not protobuf, YAML, JSON?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
They are not as efficient and either use a lot more code or require parsing
before use. Devicetree happens to be a nice compromise.
Why not something else?
~~~~~~~~~~~~~~~~~~~~~~~
Possibly. Please propose it.
Why not UUIDs?
~~~~~~~~~~~~~~
Why use a simple integer tag instead of an 'industry-standard' UUID? Many
reasons:
- Code should be as readable as possible. GUIDs require something like::
EFI_GUID(0x6dadf1d1, 0xd4cc, 0x4910, \
0xbb, 0x6e, 0x82, 0xb1, 0xfd, 0x80, 0xff, 0x3d)
which is much harder to read than::
enum {
BLOBLISTT_SPL_HANDOFF = 123,
};
- UUIDs are more like a hash than a sequence number. Git uses them, although a
short form of the hash is commonly shown. Why use a hash to identify
something when we only have a small number of items?
- We don't need to worry about collisions in open source software. We can have
a shared repo and allocate sequence numbers from there. UUIDs come from the
idea that everyone is working independently so people need to be able to
allocate their own numbers and be sure that they will not conflict. This is
needed in the PC BIOS industry where there is little shared source /
cooperation. It is not helpful with open source.
- UUIDs come across as just obfuscation. Does anyone know what these values
mean? How would we look them up? Who owns which one? Is there a central
registry?::
EFI_GUID(0x721acf02, 0x4d77, 0x4c2a, 0xb3, 0xdc, 0x27, 0x0b, 0x7b, 0xa9, 0xe4, 0xb0)
EFI_GUID(0xa034147d, 0x690c, 0x4154, 0x8d, 0xe6, 0xc0, 0x44, 0x64, 0x1d, 0xe9, 0x42)
EFI_GUID(0xbbcff46c, 0xc8d3, 0x4113, 0x89, 0x85, 0xb9, 0xd4, 0xf3, 0xb3, 0xf6, 0x4e)
EFI_GUID(0x69a79759, 0x1373, 0x4367, 0xa6, 0xc4, 0xc7, 0xf5, 0x9e, 0xfd, 0x98, 0x6e)
EFI_GUID(0xd038747c, 0xd00c, 0x4980, 0xb3, 0x19, 0x49, 0x01, 0x99, 0xa4, 0x7d, 0x55)
EFI_GUID(0x9c7c3aa7, 0x5332, 0x4917, 0x82, 0xb9, 0x56, 0xa5, 0xf3, 0xe6, 0x2a, 0x07)
- It is overkill to use 16 bytes for a unique identifier in a shared project.
It is about 10^38. Modern SoCs cannot keep that in a register and there is no
C int type to represent it on most common hardware today! Having to check that
adds time and code to no benefit. In early boot, space and time are
particularly precious.
Why contiguous?
~~~~~~~~~~~~~~~
It is easier to allocate a single block of data than to allocate lots of little
blocks. It is easy to relocate if needed (a simple copy of a block of memory).
It can be expanded by relocating it. If we absolutely need to create a linked
list then pointers to external data can be added to a blob.
Why bloblist?
~~~~~~~~~~~~~
Bloblist is a simple format with an integer tag. It avoids UUIDs and meets the
requirements above. Some tweaks may be desirable to the format, but that can be
worked out in code review.
Why pass the devicetree address?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In cases where a binary wants to access the devicetree but does not want to
implement the bloblist feature, the address of the devicetree within the
bloblist is provided. This avoids the need to implement bloblists just to
access the devicetree. This is a convenience feature intended for use in
degenerate cases. It also allows access to the devicetree before the bloblist
has been parsed.
Why use registers to pass values between binaries?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It seems like the obvious solution. We could use a shared memory region with
shared configuration between projects, but that is error prone and difficult to
keep in sync.
Firmware Handoff Specification
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For ARM, the `Firmware Handoff Specification
<https://firmwarehandoff.github.io/firmware_handoff/main/index.html>`_
has been created. See the
`repo <https://github.com/FirmwareHandoff/firmware_handoff>`_ also.
.. _`dt-schema`: https://github.com/devicetree-org/dt-schema