On 02.10.20 14:19, Grant Likely wrote:
On 01/10/2020 21:29, Simon Glass wrote:
Hi Grant,
[who is 'nd'?]
As Ard says, magic bot that suppresses the disclaimer on outgoing Arm email. Others have figured out how to send email without it, but I've not got mine sorted yet.
On Wed, 30 Sep 2020 at 09:26, Grant Likely grant.likely@arm.com wrote:
On 28/09/2020 17:51, Simon Glass wrote:
Case 1: Firmware loads the kernel to a particular address, selects DT and boots it. The kernel may require EFI boot services, or may not, but in the general case the firmware provides them.
Case 2: Firmware loads EFI app and provides EFI boot services to it. How the system actually boots is under control of the app.
As far as bootefi is concerned, case 1 & case 2 are identical. The image to be run is loaded into memory and bootefi executes the payload. In case 1 the payload in Linux's built-in UEFI stub. In case 2 the payload is grub.efi, shell.efi, shim.efi, etc.
bootefi differs from bootm in that instead of a bare vmlinuz image or a FIT image, the loaded image is a PECOFF executable. U=Boot parses the PECOFF headers, loads the sections into memory and jumps to the entry point. It also leaves a pointer to an in-memory information table with callbacks into firmware functions for accessing basic services (console, block device, network, graphics, filesystem, etc).
If the image is Linux, then the stub will do a small amount of setup before calling ExitBootServices(), which tells firmware to stop managing hardware because the kernel is taking over. (In UEFI terms, this is call an "OS loader" image)
If the image is a transient binary like Grub or the UEFI shell, it can use firmware facilities to load additional files (e.g., initrd, kernel, etc) before jumping into another PE/COFF binary. For example, Grub will typically load the kernel into memory, which itself is a PE/COFF image, and then jump to its entry point.
Yes. I think this is the source of the pain, since there is really no limit on what these can do and there is no standard flow. Much of the loading happens outside the control of the bootloader, which means that verified boot and testing are harder.
If an image doesn't call ExitBootServices(), and instead exits, control returns to firmware as one would expect.
I feel that a lot of the confusion about verified boot, DT selections, boot menus, etc. is coming from the introduction of an EFI app which has no specification (it can be grub, shim or something else, as I understand it). Certainly this is very flexible and future-proof, but it is also arbitrarily complex, unpredictable and hard to secure.
Of the items you've listed above, DT selection does indeed need work, but the rest is quite well specified. The UEFI spec has very clear specifications on how the boot image is selected via the BOOTxxxx variables and the format of the binaries. Secure Boot is also standardized so that when Secure Boot is turned on, firmware will only execute images signed by a recognized key.
It is no more arbitrarily complex than booting an OS kernel. When compared against bootm, both boot flows load an image into memory, and both jump to the image starting point. In both cases the OS can do whatever it wants after the firmware jumps into it, and in both cases if security is enabled then the binaries must be signed.
I think you are missing my point. With bootm etc. it jumps straight from U-Boot to linux. Of course linux may have an EFI shim, but there is no grub or any other loader 'in the way'.
I fully understand your point, I just disagree with it. :-)
In both case U-Boot sets up an environment for "something-other" to run. In the case of a raw linux kernel image*, it sets up the environments as specified by Linux in
- Documentation/arm/booting.rst
- Documentation/arm64/booting.rst
- etc.
In the case of UEFI PE/COFF, if sets up the environment as described in the UEFI spec, section 2; particularly 2.3 which describes the calling convention.
These are not fundamentally different, other than the Linux environment is specific to Linux, but the UEFI one is standard across several OSes and tools.
To look at the simplest scenario, UEFI also defines a standard way to choose what to boot next. It uses the BOOTxxxx and BOOTORDER variables to store what to boot, and which one to try next. If BOOTxxxx points at the kernel, then U-Boot goes straight to Linux.
[...]
I am wondering if we can come up with a way to deterministically specify how a system will boot and how to make it boot a different way (i.e. with a different kernel, initrd, DT).
Heinrich mentioned EFI variables as a way of selecting kernel/initrd/DT. Then the problem becomes just a case of being able to change those variables from Linux userspace. Is that right?
We are talking about having a 'secure' part of EBBR, which allows for secure boot. Should we have a 'defined boot' part of EBBR, that defines how the kernel/DT/initrd are selected, based on EFI variables?
Unfortunately I just don't know enough about all the different boot flows used by the different distros. It seems like crazy town. Does anyone have some pointers so I can do some study?
A great way to get familiar with it is to play around with the UEFI code already in U-Boot with the UEFI Self Certification test suite. Building the SCT is straight forward, and there are some instructions for doing so here:
https://github.com/glikely/edk2-test-manifest
If you enable UEFI in U-Boot and copy the SCT to a USB drive, u-boot should be able to find and run the UEFI shell from the USB drive.
There is also good documentation on the U-Boot implementation:
https://github.com/u-boot/u-boot/blob/master/doc/uefi/uefi.rst
I have actually reviewed a lot of the code (and remember 5 years ago I worked on the original lib/efi in U-Boot - see doc/uefi/u-boot_on_efi.rst). But it is just hard to get my head around all the pieces. It is the complexity of the bit between U-Boot and Linux that I find challenging.
It doesn't help that u-boot-on-uefi is completely different from UEFI-in-U-Boot! The first is running U-Boot as a UEFI application on top of some other UEFI implementation (e.g. the stock BIOS on a PC)... which ironically is conceptually similar to running Grub!
The second is U-Boot exposing the UEFI ABI so that it can run UEFI applications itself....
The natural extension of this is of course to start nesting multiple copies of U-Boot, each both running on, and providing the ABI of, UEFI! :-p
Thank you both for the pointers.
I think in general we are talking at cross purposes. I am looking for how we can specify a simple verified boot, with or without EFI, so that we actually create something that covers everything. Distributions and particular cases may go to endless depths of complexity, but if we want to incorporate the embedded case, then in my view we need a simple option, with no more pieces, code, boot time and complexity than is needed. It seems like the EFI protocol allows this, so I imagine:
Simple EFI boot:
- U-Boot starts*, selects and verifies config using UEFI variables,
loads image (linux, inittd, FPGA, DTs, etc.) verifying as it goes, boots linux, passing in the images and providing EFI services as specified by EBBR. OS updates use efibootmgr to select new kernels, images, etc., as (to be) specified by EBBR
- (degenerate case, where EFI is not used) U-Boot starts, selects and
verifies config using a proprietary mechanism, loads image (linux, inittd, FPGA, DTs, etc.) verifying as it goes, boots linux, passing in the images. Updates use a proprietary mechanism not specified by EBBR
Note that for platforms with separate firmware storage (e.g. SPI flash), if the disk is wiped, or fails to to verify before or after linux boots, then a recovery mode is needed to obtain a new disk image. The firmware needs to be able to load, verify and write an image to the disk so that the machine can continue to operate. Storing firmware on the disk is not really viable. For other platforms, presumably the disk is removable so the user can reflash it.
Complex EFI boot:
- U-Boot (or anything else) starts, loads an EFI app (maybe grub or
something else) and provides EFI services as specified by EBBR. What happens from there is distro-specific and not specified by EBBR. Updates are handled by any means necessary and not specified by EBBR.
In the cases above, I think only cases 1 & 3 are relevant for EBBR. Case 2 is out of scope because EBBR is specifically about standardizing on the UEFI ABI, so it isn't going to cover any non-UEFI boot path. Alternate boot paths are certainly not forbidden (e.g. bootm and bootefi happily coexist), but without a strong cross-platform incentive, there isn't any reason to standarize this path.
Case 3 is already covered by the spec, but Case 1 requires a bit more discussion.
In case 1, UEFI already has all the functionality needed to directly load the OS loader (kernel) and provide a DT. The kernel can also itself load from disk both the initrd and dtb using command line options to the kernel. Loading FPGA bitstreams or peripheral firmware is not covered by the core UEFI ABI.
Loading of the initrd and dtb using kernel command line flags is not well liked among the kernel UEFI devs. It doesn't provide a way to validate the image, and it is preferred to load both prior to calling the kernel.
Assuming the FIT image model of shipping an OS image where the kernel, initrd & dtb are all packaged together, as well as ancillary firmware blobs and FPGA bitstreams, it would be valuable to define how that works in the UEFI boot flow. Need some discussion on what that might look like, and what the split of responsibility needs to be between the FIT contents and U-Boot proper.
Please read
https://u-boot.readthedocs.io/en/latest/uefi/uefi.html#launching-a-uefi-bina...
Up to now the signed fit image can provide the UEFI binary and the FDT.
We could easily and probably should extend U-Boot to provide the RAM disk loaded as part of a fit image via the EFI_LOAD_FILE2_PROTOCOL for consumption by Linux.
@Ard: Is there any documentation for the usage of the EFI_LOAD_FILE2_PROTOCOL by Linux? I could not find anything in https://www.kernel.org/doc/html/latest/search.html. We should document how the RAM disk is passed to the EFI stub and how it is communicated to the main Linux.
To my understanding this is how it works:
If the the kernel command line contains "noinitrd", no RAM disk is loaded by the EFI stub.
If the parameter is not passed the initial RAM disk is searched in the following sequence:
* EFI_LOAD_FILE2_PROTOCOL * file path indicated by initrd= command line argument.
After successfully loading a RAM disk the Linux EFI stub sets the device tree properties "linux,initrd-start" and "linux,initrd-end" in the "/chosen" node to indicated the location of the RAM disk.
If the EFI stub is called without a device-tree, an empty device tree is created for adding these properties. So on boards with ACPI the initrd address is passed via the device-tree too.
GRUB uses the same DT properties to pass a RAM disk (function linux_prepare_fdt()). So the RAM disk position passed by GRUB is only a fallback replaced by the kernel EFI stub when loading an image via the EFI_LOAD_FILE2_PROTOCOL or the initrd= parameter.
Best regards
Heinrich
- Of course there is ATF, SPL and other things which complicate this
piece. I think EBBR should specify these too.
Absolutely the layers of trusted firmware and TEEs are in scope.
Cheers, g.