mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-11-03 21:48:15 +00:00 
			
		
		
		
	The OsIndications is a 64 bit variable, and the current code expects the value of the variable to be 64 bit. Update the documentation to reflect this fact. Signed-off-by: Sughosh Ganu <sughosh.ganu@linaro.org> Reviewed-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
		
			
				
	
	
		
			796 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			796 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
.. SPDX-License-Identifier: GPL-2.0+
 | 
						|
.. Copyright (c) 2018 Heinrich Schuchardt
 | 
						|
 | 
						|
UEFI on U-Boot
 | 
						|
==============
 | 
						|
 | 
						|
The Unified Extensible Firmware Interface Specification (UEFI) [1] has become
 | 
						|
the default for booting on AArch64 and x86 systems. It provides a stable API for
 | 
						|
the interaction of drivers and applications with the firmware. The API comprises
 | 
						|
access to block storage, network, and console to name a few. The Linux kernel
 | 
						|
and boot loaders like GRUB or the FreeBSD loader can be executed.
 | 
						|
 | 
						|
Development target
 | 
						|
------------------
 | 
						|
 | 
						|
The implementation of UEFI in U-Boot strives to reach the requirements described
 | 
						|
in the "Embedded Base Boot Requirements (EBBR) Specification - Release v1.0"
 | 
						|
[2]. The "Server Base Boot Requirements System Software on ARM Platforms" [3]
 | 
						|
describes a superset of the EBBR specification and may be used as further
 | 
						|
reference.
 | 
						|
 | 
						|
A full blown UEFI implementation would contradict the U-Boot design principle
 | 
						|
"keep it small".
 | 
						|
 | 
						|
Building U-Boot for UEFI
 | 
						|
------------------------
 | 
						|
 | 
						|
The UEFI standard supports only little-endian systems. The UEFI support can be
 | 
						|
activated for ARM and x86 by specifying::
 | 
						|
 | 
						|
    CONFIG_CMD_BOOTEFI=y
 | 
						|
    CONFIG_EFI_LOADER=y
 | 
						|
 | 
						|
in the .config file.
 | 
						|
 | 
						|
Support for attaching virtual block devices, e.g. iSCSI drives connected by the
 | 
						|
loaded UEFI application [4], requires::
 | 
						|
 | 
						|
    CONFIG_BLK=y
 | 
						|
    CONFIG_PARTITIONS=y
 | 
						|
 | 
						|
Executing a UEFI binary
 | 
						|
~~~~~~~~~~~~~~~~~~~~~~~
 | 
						|
 | 
						|
The bootefi command is used to start UEFI applications or to install UEFI
 | 
						|
drivers. It takes two parameters::
 | 
						|
 | 
						|
    bootefi <image address> [fdt address]
 | 
						|
 | 
						|
* image address - the memory address of the UEFI binary
 | 
						|
* fdt address - the memory address of the flattened device tree
 | 
						|
 | 
						|
Below you find the output of an example session starting GRUB::
 | 
						|
 | 
						|
    => load mmc 0:2 ${fdt_addr_r} boot/dtb
 | 
						|
    29830 bytes read in 14 ms (2 MiB/s)
 | 
						|
    => load mmc 0:1 ${kernel_addr_r} efi/debian/grubaa64.efi
 | 
						|
    reading efi/debian/grubaa64.efi
 | 
						|
    120832 bytes read in 7 ms (16.5 MiB/s)
 | 
						|
    => bootefi ${kernel_addr_r} ${fdt_addr_r}
 | 
						|
 | 
						|
When booting from a memory location it is unknown from which file it was loaded.
 | 
						|
Therefore the bootefi command uses the device path of the block device partition
 | 
						|
or the network adapter and the file name of the most recently loaded PE-COFF
 | 
						|
file when setting up the loaded image protocol.
 | 
						|
 | 
						|
Launching a UEFI binary from a FIT image
 | 
						|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
						|
 | 
						|
A signed FIT image can be used to securely boot a UEFI image via the
 | 
						|
bootm command. This feature is available if U-Boot is configured with::
 | 
						|
 | 
						|
    CONFIG_BOOTM_EFI=y
 | 
						|
 | 
						|
A sample configuration is provided as file doc/uImage.FIT/uefi.its.
 | 
						|
 | 
						|
Below you find the output of an example session starting GRUB::
 | 
						|
 | 
						|
    => load mmc 0:1 ${kernel_addr_r} image.fit
 | 
						|
    4620426 bytes read in 83 ms (53.1 MiB/s)
 | 
						|
    => bootm ${kernel_addr_r}#config-grub-nofdt
 | 
						|
    ## Loading kernel from FIT Image at 40400000 ...
 | 
						|
       Using 'config-grub-nofdt' configuration
 | 
						|
       Verifying Hash Integrity ... sha256,rsa2048:dev+ OK
 | 
						|
       Trying 'efi-grub' kernel subimage
 | 
						|
         Description:  GRUB EFI Firmware
 | 
						|
         Created:      2019-11-20   8:18:16 UTC
 | 
						|
         Type:         Kernel Image (no loading done)
 | 
						|
         Compression:  uncompressed
 | 
						|
         Data Start:   0x404000d0
 | 
						|
         Data Size:    450560 Bytes = 440 KiB
 | 
						|
         Hash algo:    sha256
 | 
						|
         Hash value:   4dbee00021112df618f58b3f7cf5e1595533d543094064b9ce991e8b054a9eec
 | 
						|
       Verifying Hash Integrity ... sha256+ OK
 | 
						|
       XIP Kernel Image (no loading done)
 | 
						|
    ## Transferring control to EFI (at address 404000d0) ...
 | 
						|
    Welcome to GRUB!
 | 
						|
 | 
						|
See doc/uImage.FIT/howto.txt for an introduction to FIT images.
 | 
						|
 | 
						|
Configuring UEFI secure boot
 | 
						|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
						|
 | 
						|
The UEFI specification[1] defines a secure way of executing UEFI images
 | 
						|
by verifying a signature (or message digest) of image with certificates.
 | 
						|
This feature on U-Boot is enabled with::
 | 
						|
 | 
						|
    CONFIG_EFI_SECURE_BOOT=y
 | 
						|
 | 
						|
To make the boot sequence safe, you need to establish a chain of trust;
 | 
						|
In UEFI secure boot the chain trust is defined by the following UEFI variables
 | 
						|
 | 
						|
* PK - Platform Key
 | 
						|
* KEK - Key Exchange Keys
 | 
						|
* db - white list database
 | 
						|
* dbx - black list database
 | 
						|
 | 
						|
An in depth description of UEFI secure boot is beyond the scope of this
 | 
						|
document. Please, refer to the UEFI specification and available online
 | 
						|
documentation. Here is a simple example that you can follow for your initial
 | 
						|
attempt (Please note that the actual steps will depend on your system and
 | 
						|
environment.):
 | 
						|
 | 
						|
Install the required tools on your host
 | 
						|
 | 
						|
* openssl
 | 
						|
* efitools
 | 
						|
* sbsigntool
 | 
						|
 | 
						|
Create signing keys and the key database on your host:
 | 
						|
 | 
						|
The platform key
 | 
						|
 | 
						|
.. code-block:: bash
 | 
						|
 | 
						|
    openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_PK/ \
 | 
						|
            -keyout PK.key -out PK.crt -nodes -days 365
 | 
						|
    cert-to-efi-sig-list -g 11111111-2222-3333-4444-123456789abc \
 | 
						|
            PK.crt PK.esl;
 | 
						|
    sign-efi-sig-list -c PK.crt -k PK.key PK PK.esl PK.auth
 | 
						|
 | 
						|
The key exchange keys
 | 
						|
 | 
						|
.. code-block:: bash
 | 
						|
 | 
						|
    openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_KEK/ \
 | 
						|
            -keyout KEK.key -out KEK.crt -nodes -days 365
 | 
						|
    cert-to-efi-sig-list -g 11111111-2222-3333-4444-123456789abc \
 | 
						|
            KEK.crt KEK.esl
 | 
						|
    sign-efi-sig-list -c PK.crt -k PK.key KEK KEK.esl KEK.auth
 | 
						|
 | 
						|
The whitelist database
 | 
						|
 | 
						|
.. code-block:: bash
 | 
						|
 | 
						|
    openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_db/ \
 | 
						|
            -keyout db.key -out db.crt -nodes -days 365
 | 
						|
    cert-to-efi-sig-list -g 11111111-2222-3333-4444-123456789abc \
 | 
						|
            db.crt db.esl
 | 
						|
    sign-efi-sig-list -c KEK.crt -k KEK.key db db.esl db.auth
 | 
						|
 | 
						|
Copy the \*.auth files to media, say mmc, that is accessible from U-Boot.
 | 
						|
 | 
						|
Sign an image with one of the keys in "db" on your host
 | 
						|
 | 
						|
.. code-block:: bash
 | 
						|
 | 
						|
    sbsign --key db.key --cert db.crt helloworld.efi
 | 
						|
 | 
						|
Now in U-Boot install the keys on your board::
 | 
						|
 | 
						|
    fatload mmc 0:1 <tmpaddr> PK.auth
 | 
						|
    setenv -e -nv -bs -rt -at -i <tmpaddr>:$filesize PK
 | 
						|
    fatload mmc 0:1 <tmpaddr> KEK.auth
 | 
						|
    setenv -e -nv -bs -rt -at -i <tmpaddr>:$filesize KEK
 | 
						|
    fatload mmc 0:1 <tmpaddr> db.auth
 | 
						|
    setenv -e -nv -bs -rt -at -i <tmpaddr>:$filesize db
 | 
						|
 | 
						|
Set up boot parameters on your board::
 | 
						|
 | 
						|
    efidebug boot add -b 1 HELLO mmc 0:1 /helloworld.efi.signed ""
 | 
						|
 | 
						|
Since kernel 5.7 there's an alternative way of loading an initrd using
 | 
						|
LoadFile2 protocol if CONFIG_EFI_LOAD_FILE2_INITRD is enabled.
 | 
						|
The initrd path can be specified with::
 | 
						|
 | 
						|
    efidebug boot add -b ABE0 'kernel' mmc 0:1 Image -i mmc 0:1 initrd
 | 
						|
 | 
						|
Now your board can run the signed image via the boot manager (see below).
 | 
						|
You can also try this sequence by running Pytest, test_efi_secboot,
 | 
						|
on the sandbox
 | 
						|
 | 
						|
.. code-block:: bash
 | 
						|
 | 
						|
    cd <U-Boot source directory>
 | 
						|
    pytest.py test/py/tests/test_efi_secboot/test_signed.py --bd sandbox
 | 
						|
 | 
						|
UEFI binaries may be signed by Microsoft using the following certificates:
 | 
						|
 | 
						|
* KEK: Microsoft Corporation KEK CA 2011
 | 
						|
  http://go.microsoft.com/fwlink/?LinkId=321185.
 | 
						|
* db: Microsoft Windows Production PCA 2011
 | 
						|
  http://go.microsoft.com/fwlink/p/?linkid=321192.
 | 
						|
* db: Microsoft Corporation UEFI CA 2011
 | 
						|
  http://go.microsoft.com/fwlink/p/?linkid=321194.
 | 
						|
 | 
						|
Using OP-TEE for EFI variables
 | 
						|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
						|
 | 
						|
Instead of implementing UEFI variable services inside U-Boot they can
 | 
						|
also be provided in the secure world by a module for OP-TEE[1]. The
 | 
						|
interface between U-Boot and OP-TEE for variable services is enabled by
 | 
						|
CONFIG_EFI_MM_COMM_TEE=y.
 | 
						|
 | 
						|
Tianocore EDK II's standalone management mode driver for variables can
 | 
						|
be linked to OP-TEE for this purpose. This module uses the Replay
 | 
						|
Protected Memory Block (RPMB) of an eMMC device for persisting
 | 
						|
non-volatile variables. When calling the variable services via the
 | 
						|
OP-TEE API U-Boot's OP-TEE supplicant relays calls to the RPMB driver
 | 
						|
which has to be enabled via CONFIG_SUPPORT_EMMC_RPMB=y.
 | 
						|
 | 
						|
EDK2 Build instructions
 | 
						|
***********************
 | 
						|
 | 
						|
.. code-block:: bash
 | 
						|
 | 
						|
    $ git clone https://github.com/tianocore/edk2.git
 | 
						|
    $ git clone https://github.com/tianocore/edk2-platforms.git
 | 
						|
    $ cd edk2
 | 
						|
    $ git submodule init && git submodule update --init --recursive
 | 
						|
    $ cd ..
 | 
						|
    $ export WORKSPACE=$(pwd)
 | 
						|
    $ export PACKAGES_PATH=$WORKSPACE/edk2:$WORKSPACE/edk2-platforms
 | 
						|
    $ export ACTIVE_PLATFORM="Platform/StandaloneMm/PlatformStandaloneMmPkg/PlatformStandaloneMmRpmb.dsc"
 | 
						|
    $ export GCC5_AARCH64_PREFIX=aarch64-linux-gnu-
 | 
						|
    $ source edk2/edksetup.sh
 | 
						|
    $ make -C edk2/BaseTools
 | 
						|
    $ build -p $ACTIVE_PLATFORM -b RELEASE -a AARCH64 -t GCC5 -n `nproc`
 | 
						|
 | 
						|
OP-TEE Build instructions
 | 
						|
*************************
 | 
						|
 | 
						|
.. code-block:: bash
 | 
						|
 | 
						|
    $ git clone https://github.com/OP-TEE/optee_os.git
 | 
						|
    $ cd optee_os
 | 
						|
    $ ln -s ../Build/MmStandaloneRpmb/RELEASE_GCC5/FV/BL32_AP_MM.fd
 | 
						|
    $ export ARCH=arm
 | 
						|
    $ CROSS_COMPILE32=arm-linux-gnueabihf- make -j32 CFG_ARM64_core=y \
 | 
						|
        PLATFORM=<myboard> CFG_STMM_PATH=BL32_AP_MM.fd CFG_RPMB_FS=y \
 | 
						|
        CFG_RPMB_FS_DEV_ID=0 CFG_CORE_HEAP_SIZE=524288 CFG_RPMB_WRITE_KEY=y \
 | 
						|
        CFG_CORE_DYN_SHM=y CFG_RPMB_TESTKEY=y CFG_REE_FS=n \
 | 
						|
        CFG_CORE_ARM64_PA_BITS=48 CFG_TEE_CORE_LOG_LEVEL=1 \
 | 
						|
        CFG_TEE_TA_LOG_LEVEL=1 CFG_SCTLR_ALIGNMENT_CHECK=n
 | 
						|
 | 
						|
U-Boot Build instructions
 | 
						|
*************************
 | 
						|
 | 
						|
Although the StandAloneMM binary comes from EDK2, using and storing the
 | 
						|
variables is currently available in U-Boot only.
 | 
						|
 | 
						|
.. code-block:: bash
 | 
						|
 | 
						|
    $ git clone https://github.com/u-boot/u-boot.git
 | 
						|
    $ cd u-boot
 | 
						|
    $ export CROSS_COMPILE=aarch64-linux-gnu-
 | 
						|
    $ export ARCH=<arch>
 | 
						|
    $ make <myboard>_defconfig
 | 
						|
    $ make menuconfig
 | 
						|
 | 
						|
Enable ``CONFIG_OPTEE``, ``CONFIG_CMD_OPTEE_RPMB`` and ``CONFIG_EFI_MM_COMM_TEE``
 | 
						|
 | 
						|
.. warning::
 | 
						|
 | 
						|
    - Your OP-TEE platform port must support Dynamic shared memory, since that's
 | 
						|
      the only kind of memory U-Boot supports for now.
 | 
						|
 | 
						|
[1] https://optee.readthedocs.io/en/latest/building/efi_vars/stmm.html
 | 
						|
 | 
						|
Enabling UEFI Capsule Update feature
 | 
						|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
						|
 | 
						|
Support has been added for the UEFI capsule update feature which
 | 
						|
enables updating the U-Boot image using the UEFI firmware management
 | 
						|
protocol (FMP). The capsules are not passed to the firmware through
 | 
						|
the UpdateCapsule runtime service. Instead, capsule-on-disk
 | 
						|
functionality is used for fetching capsules from the EFI System
 | 
						|
Partition (ESP) by placing capsule files under the directory::
 | 
						|
 | 
						|
    \EFI\UpdateCapsule
 | 
						|
 | 
						|
The directory is checked for capsules only within the
 | 
						|
EFI system partition on the device specified in the active boot option,
 | 
						|
which is determined by BootXXXX variable in BootNext, or if not, the highest
 | 
						|
priority one within BootOrder. Any BootXXXX variables referring to devices
 | 
						|
not present are ignored when determining the active boot option.
 | 
						|
 | 
						|
Please note that capsules will be applied in the alphabetic order of
 | 
						|
capsule file names.
 | 
						|
 | 
						|
Creating a capsule file
 | 
						|
***********************
 | 
						|
 | 
						|
A capsule file can be created by using tools/mkeficapsule.
 | 
						|
To build this tool, enable::
 | 
						|
 | 
						|
    CONFIG_TOOLS_MKEFICAPSULE=y
 | 
						|
    CONFIG_TOOLS_LIBCRYPTO=y
 | 
						|
 | 
						|
Run the following command
 | 
						|
 | 
						|
.. code-block:: console
 | 
						|
 | 
						|
    $ mkeficapsule \
 | 
						|
      --index <index> --instance 0 \
 | 
						|
      --guid <image GUID> \
 | 
						|
      <capsule_file_name>
 | 
						|
 | 
						|
Performing the update
 | 
						|
*********************
 | 
						|
 | 
						|
Put capsule files under the directory mentioned above.
 | 
						|
Then, following the UEFI specification, you'll need to set
 | 
						|
the EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED
 | 
						|
bit in OsIndications variable with
 | 
						|
 | 
						|
.. code-block:: console
 | 
						|
 | 
						|
    => setenv -e -nv -bs -rt -v OsIndications =0x0000000000000004
 | 
						|
 | 
						|
Since U-boot doesn't currently support SetVariable at runtime, its value
 | 
						|
won't be taken over across the reboot. If this is the case, you can skip
 | 
						|
this feature check with the Kconfig option (CONFIG_EFI_IGNORE_OSINDICATIONS)
 | 
						|
set.
 | 
						|
 | 
						|
A few values need to be defined in the board file for performing the
 | 
						|
capsule update. These values are defined in the board file by
 | 
						|
initialisation of a structure which provides information needed for
 | 
						|
capsule updates. The following structures have been defined for
 | 
						|
containing the image related information
 | 
						|
 | 
						|
.. code-block:: c
 | 
						|
 | 
						|
	struct efi_fw_image {
 | 
						|
		efi_guid_t image_type_id;
 | 
						|
		u16 *fw_name;
 | 
						|
		u8 image_index;
 | 
						|
	};
 | 
						|
 | 
						|
	struct efi_capsule_update_info {
 | 
						|
		const char *dfu_string;
 | 
						|
		struct efi_fw_image *images;
 | 
						|
	};
 | 
						|
 | 
						|
 | 
						|
A string is defined which is to be used for populating the
 | 
						|
dfu_alt_info variable. This string is used by the function
 | 
						|
set_dfu_alt_info. Instead of taking the variable from the environment,
 | 
						|
the capsule update feature requires that the variable be set through
 | 
						|
the function, since that is more robust. Allowing the user to change
 | 
						|
the location of the firmware updates is not a very secure
 | 
						|
practice. Getting this information from the firmware itself is more
 | 
						|
secure, assuming the firmware has been verified by a previous stage
 | 
						|
boot loader.
 | 
						|
 | 
						|
The firmware images structure defines the GUID values, image index
 | 
						|
values and the name of the images that are to be updated through
 | 
						|
the capsule update feature. These values are to be defined as part of
 | 
						|
an array. These GUID values would be used by the Firmware Management
 | 
						|
Protocol(FMP) to populate the image descriptor array and also
 | 
						|
displayed as part of the ESRT table. The image index values defined in
 | 
						|
the array should be one greater than the dfu alt number that
 | 
						|
corresponds to the firmware image. So, if the dfu alt number for an
 | 
						|
image is 2, the value of image index in the fw_images array for that
 | 
						|
image should be 3. The dfu alt number can be obtained by running the
 | 
						|
following command::
 | 
						|
 | 
						|
    dfu list
 | 
						|
 | 
						|
When using the FMP for FIT images, the image index value needs to be
 | 
						|
set to 1.
 | 
						|
 | 
						|
Finally, the capsule update can be initiated by rebooting the board.
 | 
						|
 | 
						|
An example of setting the values in the struct efi_fw_image and
 | 
						|
struct efi_capsule_update_info is shown below
 | 
						|
 | 
						|
.. code-block:: c
 | 
						|
 | 
						|
	struct efi_fw_image fw_images[] = {
 | 
						|
		{
 | 
						|
			.image_type_id = DEVELOPERBOX_UBOOT_IMAGE_GUID,
 | 
						|
			.fw_name = u"DEVELOPERBOX-UBOOT",
 | 
						|
			.image_index = 1,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			.image_type_id = DEVELOPERBOX_FIP_IMAGE_GUID,
 | 
						|
			.fw_name = u"DEVELOPERBOX-FIP",
 | 
						|
			.image_index = 2,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			.image_type_id = DEVELOPERBOX_OPTEE_IMAGE_GUID,
 | 
						|
			.fw_name = u"DEVELOPERBOX-OPTEE",
 | 
						|
			.image_index = 3,
 | 
						|
		},
 | 
						|
	};
 | 
						|
 | 
						|
	struct efi_capsule_update_info update_info = {
 | 
						|
		.dfu_string = "mtd nor1=u-boot.bin raw 200000 100000;"
 | 
						|
				"fip.bin raw 180000 78000;"
 | 
						|
				"optee.bin raw 500000 100000",
 | 
						|
		.images = fw_images,
 | 
						|
	};
 | 
						|
 | 
						|
Platforms must declare a variable update_info of type struct
 | 
						|
efi_capsule_update_info as shown in the example above. The platform
 | 
						|
will also define a fw_images array which contains information of all
 | 
						|
the firmware images that are to be updated through capsule update
 | 
						|
mechanism. The dfu_string is the string that is to be set as
 | 
						|
dfu_alt_info. In the example above, the image index to be set for
 | 
						|
u-boot.bin binary is 0x1, for fip.bin is 0x2 and for optee.bin is 0x3.
 | 
						|
 | 
						|
As an example, for generating the capsule for the optee.bin image, the
 | 
						|
following command can be issued
 | 
						|
 | 
						|
.. code-block:: bash
 | 
						|
 | 
						|
    $ ./tools/mkeficapsule \
 | 
						|
      --index 0x3 --instance 0 \
 | 
						|
      --guid c1b629f1-ce0e-4894-82bf-f0a38387e630 \
 | 
						|
      optee.bin optee.capsule
 | 
						|
 | 
						|
 | 
						|
Enabling Capsule Authentication
 | 
						|
*******************************
 | 
						|
 | 
						|
The UEFI specification defines a way of authenticating the capsule to
 | 
						|
be updated by verifying the capsule signature. The capsule signature
 | 
						|
is computed and prepended to the capsule payload at the time of
 | 
						|
capsule generation. This signature is then verified by using the
 | 
						|
public key stored as part of the X509 certificate. This certificate is
 | 
						|
in the form of an efi signature list (esl) file, which is embedded in
 | 
						|
a device tree.
 | 
						|
 | 
						|
The capsule authentication feature can be enabled through the
 | 
						|
following config, in addition to the configs listed above for capsule
 | 
						|
update::
 | 
						|
 | 
						|
    CONFIG_EFI_CAPSULE_AUTHENTICATE=y
 | 
						|
 | 
						|
The public and private keys used for the signing process are generated
 | 
						|
and used by the steps highlighted below.
 | 
						|
 | 
						|
1. Install utility commands on your host
 | 
						|
       * openssl
 | 
						|
       * efitools
 | 
						|
 | 
						|
2. Create signing keys and certificate files on your host
 | 
						|
 | 
						|
.. code-block:: console
 | 
						|
 | 
						|
    $ openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=CRT/ \
 | 
						|
        -keyout CRT.key -out CRT.crt -nodes -days 365
 | 
						|
    $ cert-to-efi-sig-list CRT.crt CRT.esl
 | 
						|
 | 
						|
3. Run the following command to create and sign the capsule file
 | 
						|
 | 
						|
.. code-block:: console
 | 
						|
 | 
						|
    $ mkeficapsule --monotonic-count 1 \
 | 
						|
      --private-key CRT.key \
 | 
						|
      --certificate CRT.crt \
 | 
						|
      --index 1 --instance 0 \
 | 
						|
      [--fit | --raw | --guid <guid-string] \
 | 
						|
      <image_blob> <capsule_file_name>
 | 
						|
 | 
						|
4. Insert the signature list into a device tree in the following format::
 | 
						|
 | 
						|
    {
 | 
						|
            signature {
 | 
						|
                    capsule-key = [ <binary of signature list> ];
 | 
						|
            }
 | 
						|
            ...
 | 
						|
    }
 | 
						|
 | 
						|
You can do step-4 manually with
 | 
						|
 | 
						|
.. code-block:: console
 | 
						|
 | 
						|
    $ dtc -@ -I dts -O dtb -o signature.dtbo signature.dts
 | 
						|
    $ fdtoverlay -i orig.dtb -o new.dtb -v signature.dtbo
 | 
						|
 | 
						|
where signature.dts looks like::
 | 
						|
 | 
						|
    &{/} {
 | 
						|
            signature {
 | 
						|
                    capsule-key = /incbin/("CRT.esl");
 | 
						|
            };
 | 
						|
    };
 | 
						|
 | 
						|
Executing the boot manager
 | 
						|
~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
						|
 | 
						|
The UEFI specification foresees to define boot entries and boot sequence via
 | 
						|
UEFI variables. Booting according to these variables is possible via::
 | 
						|
 | 
						|
    bootefi bootmgr [fdt address]
 | 
						|
 | 
						|
As of U-Boot v2020.10 UEFI variables cannot be set at runtime. The U-Boot
 | 
						|
command 'efidebug' can be used to set the variables.
 | 
						|
 | 
						|
Executing the built in hello world application
 | 
						|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
						|
 | 
						|
A hello world UEFI application can be built with::
 | 
						|
 | 
						|
    CONFIG_CMD_BOOTEFI_HELLO_COMPILE=y
 | 
						|
 | 
						|
It can be embedded into the U-Boot binary with::
 | 
						|
 | 
						|
    CONFIG_CMD_BOOTEFI_HELLO=y
 | 
						|
 | 
						|
The bootefi command is used to start the embedded hello world application::
 | 
						|
 | 
						|
    bootefi hello [fdt address]
 | 
						|
 | 
						|
Below you find the output of an example session::
 | 
						|
 | 
						|
    => bootefi hello ${fdtcontroladdr}
 | 
						|
    ## Starting EFI application at 01000000 ...
 | 
						|
    WARNING: using memory device/image path, this may confuse some payloads!
 | 
						|
    Hello, world!
 | 
						|
    Running on UEFI 2.7
 | 
						|
    Have SMBIOS table
 | 
						|
    Have device tree
 | 
						|
    Load options: root=/dev/sdb3 init=/sbin/init rootwait ro
 | 
						|
    ## Application terminated, r = 0
 | 
						|
 | 
						|
The environment variable fdtcontroladdr points to U-Boot's internal device tree
 | 
						|
(if available).
 | 
						|
 | 
						|
Executing the built-in self-test
 | 
						|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
						|
 | 
						|
An UEFI self-test suite can be embedded in U-Boot by building with::
 | 
						|
 | 
						|
    CONFIG_CMD_BOOTEFI_SELFTEST=y
 | 
						|
 | 
						|
For testing the UEFI implementation the bootefi command can be used to start the
 | 
						|
self-test::
 | 
						|
 | 
						|
    bootefi selftest [fdt address]
 | 
						|
 | 
						|
The environment variable 'efi_selftest' can be used to select a single test. If
 | 
						|
it is not provided all tests are executed except those marked as 'on request'.
 | 
						|
If the environment variable is set to 'list' a list of all tests is shown.
 | 
						|
 | 
						|
Below you can find the output of an example session::
 | 
						|
 | 
						|
    => setenv efi_selftest simple network protocol
 | 
						|
    => bootefi selftest
 | 
						|
    Testing EFI API implementation
 | 
						|
    Selected test: 'simple network protocol'
 | 
						|
    Setting up 'simple network protocol'
 | 
						|
    Setting up 'simple network protocol' succeeded
 | 
						|
    Executing 'simple network protocol'
 | 
						|
    DHCP Discover
 | 
						|
    DHCP reply received from 192.168.76.2 (52:55:c0:a8:4c:02)
 | 
						|
      as broadcast message.
 | 
						|
    Executing 'simple network protocol' succeeded
 | 
						|
    Tearing down 'simple network protocol'
 | 
						|
    Tearing down 'simple network protocol' succeeded
 | 
						|
    Boot services terminated
 | 
						|
    Summary: 0 failures
 | 
						|
    Preparing for reset. Press any key.
 | 
						|
 | 
						|
The UEFI life cycle
 | 
						|
-------------------
 | 
						|
 | 
						|
After the U-Boot platform has been initialized the UEFI API provides two kinds
 | 
						|
of services:
 | 
						|
 | 
						|
* boot services
 | 
						|
* runtime services
 | 
						|
 | 
						|
The API can be extended by loading UEFI drivers which come in two variants:
 | 
						|
 | 
						|
* boot drivers
 | 
						|
* runtime drivers
 | 
						|
 | 
						|
UEFI drivers are installed with U-Boot's bootefi command. With the same command
 | 
						|
UEFI applications can be executed.
 | 
						|
 | 
						|
Loaded images of UEFI drivers stay in memory after returning to U-Boot while
 | 
						|
loaded images of applications are removed from memory.
 | 
						|
 | 
						|
An UEFI application (e.g. an operating system) that wants to take full control
 | 
						|
of the system calls ExitBootServices. After a UEFI application calls
 | 
						|
ExitBootServices
 | 
						|
 | 
						|
* boot services are not available anymore
 | 
						|
* timer events are stopped
 | 
						|
* the memory used by U-Boot except for runtime services is released
 | 
						|
* the memory used by boot time drivers is released
 | 
						|
 | 
						|
So this is a point of no return. Afterwards the UEFI application can only return
 | 
						|
to U-Boot by rebooting.
 | 
						|
 | 
						|
The UEFI object model
 | 
						|
---------------------
 | 
						|
 | 
						|
UEFI offers a flexible and expandable object model. The objects in the UEFI API
 | 
						|
are devices, drivers, and loaded images. These objects are referenced by
 | 
						|
handles.
 | 
						|
 | 
						|
The interfaces implemented by the objects are referred to as protocols. These
 | 
						|
are identified by GUIDs. They can be installed and uninstalled by calling the
 | 
						|
appropriate boot services.
 | 
						|
 | 
						|
Handles are created by the InstallProtocolInterface or the
 | 
						|
InstallMultipleProtocolinterfaces service if NULL is passed as handle.
 | 
						|
 | 
						|
Handles are deleted when the last protocol has been removed with the
 | 
						|
UninstallProtocolInterface or the UninstallMultipleProtocolInterfaces service.
 | 
						|
 | 
						|
Devices offer the EFI_DEVICE_PATH_PROTOCOL. A device path is the concatenation
 | 
						|
of device nodes. By their device paths all devices of a system are arranged in a
 | 
						|
tree.
 | 
						|
 | 
						|
Drivers offer the EFI_DRIVER_BINDING_PROTOCOL. This protocol is used to connect
 | 
						|
a driver to devices (which are referenced as controllers in this context).
 | 
						|
 | 
						|
Loaded images offer the EFI_LOADED_IMAGE_PROTOCOL. This protocol provides meta
 | 
						|
information about the image and a pointer to the unload callback function.
 | 
						|
 | 
						|
The UEFI events
 | 
						|
---------------
 | 
						|
 | 
						|
In the UEFI terminology an event is a data object referencing a notification
 | 
						|
function which is queued for calling when the event is signaled. The following
 | 
						|
types of events exist:
 | 
						|
 | 
						|
* periodic and single shot timer events
 | 
						|
* exit boot services events, triggered by calling the ExitBootServices() service
 | 
						|
* virtual address change events
 | 
						|
* memory map change events
 | 
						|
* read to boot events
 | 
						|
* reset system events
 | 
						|
* system table events
 | 
						|
* events that are only triggered programmatically
 | 
						|
 | 
						|
Events can be created with the CreateEvent service and deleted with CloseEvent
 | 
						|
service.
 | 
						|
 | 
						|
Events can be assigned to an event group. If any of the events in a group is
 | 
						|
signaled, all other events in the group are also set to the signaled state.
 | 
						|
 | 
						|
The UEFI driver model
 | 
						|
---------------------
 | 
						|
 | 
						|
A driver is specific for a single protocol installed on a device. To install a
 | 
						|
driver on a device the ConnectController service is called. In this context
 | 
						|
controller refers to the device for which the driver is installed.
 | 
						|
 | 
						|
The relevant drivers are identified using the EFI_DRIVER_BINDING_PROTOCOL. This
 | 
						|
protocol has has three functions:
 | 
						|
 | 
						|
* supported - determines if the driver is compatible with the device
 | 
						|
* start - installs the driver by opening the relevant protocol with
 | 
						|
  attribute EFI_OPEN_PROTOCOL_BY_DRIVER
 | 
						|
* stop - uninstalls the driver
 | 
						|
 | 
						|
The driver may create child controllers (child devices). E.g. a driver for block
 | 
						|
IO devices will create the device handles for the partitions. The child
 | 
						|
controllers  will open the supported protocol with the attribute
 | 
						|
EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER.
 | 
						|
 | 
						|
A driver can be detached from a device using the DisconnectController service.
 | 
						|
 | 
						|
U-Boot devices mapped as UEFI devices
 | 
						|
-------------------------------------
 | 
						|
 | 
						|
Some of the U-Boot devices are mapped as UEFI devices
 | 
						|
 | 
						|
* block IO devices
 | 
						|
* console
 | 
						|
* graphical output
 | 
						|
* network adapter
 | 
						|
 | 
						|
As of U-Boot 2018.03 the logic for doing this is hard coded.
 | 
						|
 | 
						|
The development target is to integrate the setup of these UEFI devices with the
 | 
						|
U-Boot driver model [5]. So when a U-Boot device is discovered a handle should
 | 
						|
be created and the device path protocol and the relevant IO protocol should be
 | 
						|
installed. The UEFI driver then would be attached by calling ConnectController.
 | 
						|
When a U-Boot device is removed DisconnectController should be called.
 | 
						|
 | 
						|
UEFI devices mapped as U-Boot devices
 | 
						|
-------------------------------------
 | 
						|
 | 
						|
UEFI drivers binaries and applications may create new (virtual) devices, install
 | 
						|
a protocol and call the ConnectController service. Now the matching UEFI driver
 | 
						|
is determined by iterating over the implementations of the
 | 
						|
EFI_DRIVER_BINDING_PROTOCOL.
 | 
						|
 | 
						|
It is the task of the UEFI driver to create a corresponding U-Boot device and to
 | 
						|
proxy calls for this U-Boot device to the controller.
 | 
						|
 | 
						|
In U-Boot 2018.03 this has only been implemented for block IO devices.
 | 
						|
 | 
						|
UEFI uclass
 | 
						|
~~~~~~~~~~~
 | 
						|
 | 
						|
An UEFI uclass driver (lib/efi_driver/efi_uclass.c) has been created that
 | 
						|
takes care of initializing the UEFI drivers and providing the
 | 
						|
EFI_DRIVER_BINDING_PROTOCOL implementation for the UEFI drivers.
 | 
						|
 | 
						|
A linker created list is used to keep track of the UEFI drivers. To create an
 | 
						|
entry in the list the UEFI driver uses the U_BOOT_DRIVER macro specifying
 | 
						|
UCLASS_EFI_LOADER as the ID of its uclass, e.g::
 | 
						|
 | 
						|
    /* Identify as UEFI driver */
 | 
						|
    U_BOOT_DRIVER(efi_block) = {
 | 
						|
        .name  = "EFI block driver",
 | 
						|
        .id    = UCLASS_EFI_LOADER,
 | 
						|
        .ops   = &driver_ops,
 | 
						|
    };
 | 
						|
 | 
						|
The available operations are defined via the structure struct efi_driver_ops::
 | 
						|
 | 
						|
    struct efi_driver_ops {
 | 
						|
        const efi_guid_t *protocol;
 | 
						|
        const efi_guid_t *child_protocol;
 | 
						|
        int (*bind)(efi_handle_t handle, void *interface);
 | 
						|
    };
 | 
						|
 | 
						|
When the supported() function of the EFI_DRIVER_BINDING_PROTOCOL is called the
 | 
						|
uclass checks if the protocol GUID matches the protocol GUID of the UEFI driver.
 | 
						|
In the start() function the bind() function of the UEFI driver is called after
 | 
						|
checking the GUID.
 | 
						|
The stop() function of the EFI_DRIVER_BINDING_PROTOCOL disconnects the child
 | 
						|
controllers created by the UEFI driver and the UEFI driver. (In U-Boot v2013.03
 | 
						|
this is not yet completely implemented.)
 | 
						|
 | 
						|
UEFI block IO driver
 | 
						|
~~~~~~~~~~~~~~~~~~~~
 | 
						|
 | 
						|
The UEFI block IO driver supports devices exposing the EFI_BLOCK_IO_PROTOCOL.
 | 
						|
 | 
						|
When connected it creates a new U-Boot block IO device with interface type
 | 
						|
IF_TYPE_EFI_LOADER, adds child controllers mapping the partitions, and installs
 | 
						|
the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL on these. This can be used together with the
 | 
						|
software iPXE to boot from iSCSI network drives [4].
 | 
						|
 | 
						|
This driver is only available if U-Boot is configured with::
 | 
						|
 | 
						|
    CONFIG_BLK=y
 | 
						|
    CONFIG_PARTITIONS=y
 | 
						|
 | 
						|
Miscellaneous
 | 
						|
-------------
 | 
						|
 | 
						|
Load file 2 protocol
 | 
						|
~~~~~~~~~~~~~~~~~~~~
 | 
						|
 | 
						|
The load file 2 protocol can be used by the Linux kernel to load the initial
 | 
						|
RAM disk. U-Boot can be configured to provide an implementation with::
 | 
						|
 | 
						|
    EFI_LOAD_FILE2_INITRD=y
 | 
						|
 | 
						|
When the option is enabled the user can add the initrd path with the efidebug
 | 
						|
command.
 | 
						|
 | 
						|
Load options Boot#### have a FilePathList[] member.  The first element of
 | 
						|
the array (FilePathList[0]) is the EFI binary to execute.  When an initrd
 | 
						|
is specified the Device Path for the initrd is denoted by a VenMedia node
 | 
						|
with the EFI_INITRD_MEDIA_GUID. Each entry of the array is terminated by the
 | 
						|
'end of entire device path' subtype (0xff). If a user wants to define multiple
 | 
						|
initrds, those must by separated by the 'end of this instance' identifier of
 | 
						|
the end node (0x01).
 | 
						|
 | 
						|
So our final format of the FilePathList[] is::
 | 
						|
 | 
						|
    Loaded image - end node (0xff) - VenMedia - initrd_1 - [end node (0x01) - initrd_n ...] - end node (0xff)
 | 
						|
 | 
						|
Links
 | 
						|
-----
 | 
						|
 | 
						|
* [1] http://uefi.org/specifications - UEFI specifications
 | 
						|
* [2] https://github.com/ARM-software/ebbr/releases/download/v1.0/ebbr-v1.0.pdf -
 | 
						|
  Embedded Base Boot Requirements (EBBR) Specification - Release v1.0
 | 
						|
* [3] https://developer.arm.com/docs/den0044/latest/server-base-boot-requirements-system-software-on-arm-platforms-version-11 -
 | 
						|
  Server Base Boot Requirements System Software on ARM Platforms - Version 1.1
 | 
						|
* [4] :doc:`iscsi`
 | 
						|
* [5] :doc:`../driver-model/index`
 |