123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574 |
- .. 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_UEFI_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=1 \
- CFG_CORE_HEAP_SIZE=524288 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
- 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 as the ID of its uclass, e.g::
- /* Identify as UEFI driver */
- U_BOOT_DRIVER(efi_block) = {
- .name = "EFI block driver",
- .id = UCLASS_EFI,
- .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, 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`
|