Friday, April 19, 2013

UEFI and ARM

Introduction

UEFI [Universal Extensible Firmware Interface] is a standard for implementing the bootloaders and the interface between the bootloader and the OS.

UEFI defines a standard for executable images (second-stage loaders which load the OS - like rEFIt for OS X, bootx64.efi for Windows, grub-efi and elilo for linux). Which is actually a PE/COFF (windows "MZ" exe files).

Advantages

  • Fixed API and ABI
  • Extended type annotations [like, IN/OUT/INOUT for function arguments]. This can theoretically help spot some coding mistakes at compilation time.
  • Providing IO range and IRQ descriptions (like ACPI) for ARM systems

Disadvantages

  • It was designed by Microsoft and Intel (therefore, unnecessary code bloat)
  • All the code runs in the same address space, MMIO access is not protected by capabilities or any other security mechanism. While the situation is the same with other bootloaders and using the one-to-one memory mapping shared between all components [processes or libraries] allows to use the bootloader in the systems without MMU, UEFI was devised to provide a secure chain  of trust for the bootup process and 

UEFI Services

Since UEFI services are PE/COFF binaries, they export the symbols via the import/export tables. This allows to lookup the needed functions in the binary modules. UEFI defines a number of services. For example, the initial bootloader can rely on the UEFI bootloader for reading data from the disk. The part which confuses me is that most of these services are destroyed with the ExitBootServices() call, and the only usable service available at runtime is the RTC/Timer service. Since the OS cannot piggy-back on the bootloader for all its driver routines, why introduce a complex bootloader at all? It does deliver potential vulnerabilities but does not have advantages over BIOS in terms of hardware initialization.

TianoCore EDK2 and UEFI on ARM

TianoCore EDK2 is a reference UEFI implementation from Intel. It comes with various interesting packages and can be used to build both UEFI bootloaders and standalone applications for systems already running UEFI (like, most consumer-grade motherboards and laptops available on the market)

  • ArmPkg - contains the Linux Loader and the drivers for CPUS (Cortex A8, A9, A15) - cache, interrupts (GIC), Timer
  • ArmPlatformPkg - contains the Uart, GPIO and Nor drivers for the ARM reference platforms, TrustZone setup routines, Exception handling and stack switching code
  • CryptoPkg - contains the wrappers for OpenSSL to allow using cryptography (for example, for UEFI Secure Boot)
  • DuetPkg - the package to test UEFI on an X86 computer
  • EdkShellPkg - the UEFI shell which allows browsing mass storage driver, booting custom images and interacting with drivers via the configurable variables
  • MdePkg - contains the runtime services and HAL (Hardware Abstraction Layer) for PCI and other busses.
  • NetworkPkg - contains the support for IPv6, DHCP, TFTP and SCSI (obviously, for network booting)
  • Nt32Pkg - Bootloader services for Windows 7 Embedded
  • OvmfPkg - ACPI emulation and Virtio
  • PcAtChipsetPkg - obviously the x86 support - Timer, PIC, HPET and PCI bridge drivers.
  • StdLib - EFI library and sockets


I think this particular implementation sucks. I could bear with the unreadable code, the fact that you have to put all definitions in like three files (platform, board files and .ini files). But. The reference implementation only works for ARM BeagleBoard. For PandaBoard, the source code clearly states that "the display driver is broken and uncommenting it leads to mysterious freeze". I've spent some time hacking on it but still didn't manage to get the display working. Neither the MMC. On PandaBoard, it just failed to work. On Galaxy Nexus, it started working after I switched it from the block mode to the "unsupported" streaming mode in the generic mmc host code. Weird.

Windows RT Bootup Process

Windows RT is a port of Windows 8 to the ARM architecture. Here, the UEFI is used to emulate an X86 setup on ARM: the ACPI tables and power states. Besides, UEFI is used to query the IO ranges for peripheral controllers (it is known that most ARM SoCs use memory-mapped IO for accessing peripherals and the only things an OS has to care about are the IO range and the IRQ pin. This is what UEFI provides). So, the advantage of the UEFI is that it allows to compile the OS kernel and drivers once and use that on various boards.

For Windows RT and Windows Phone 8, the boot process starts with the UEFI bootloader loading the bootarm.efi file. It then tries to read the BCD (Boot Configuration Data) table and mount the root partition. It does rely on UEFI for reading the drive at this stage. Next, control is transferred to the Windows NT kernel which calls the ExitBootServices() routine and loads the native block driver. So far, I've managed to partition the usb thumb drive properly and boot the bootarm.efi and make it recognize the partition on qemu emulating the BeagleBoard, but that's about all.

Alternatives

Linux kernel has recently also gained the support for building mutiple SoCs in one kernel. You can have a single kernel that boots on OMAP, Tegra and what not. The advantage is that you can have a kernel-side board driver that will initialize the drivers with needed data eliminating the need for ACPI emulation and complex binary table parsers.

Suppose you're an evil hardware manufacturer wanting to hide the details about your board and not contribute code to linux. What do you do then? Right, use the DTS or Device Tree. Originated on the PowerPC MACs, the flattened device tree was subsequently ported to ARM and is now supported by the u-boot bootloader and both Linux and FreeBSD kernels. It allows to describe all peripherals and driver parameters in a hierarchial text format which will then either be passed to kernel as is or compiled to a human-unreadable binary format (which is security by obscurity of course but that's what proprietary developers think is cool).

Let's take a look at the FTD file for a tegra board taken from u-boot. I find it neat that you can specify everything - IO ranges, gpio and irq pins. If the BSP code is written properly, you can get away with writing no board code, only a declarative IO map description.

/dts-v1/;

#include "tegra114.dtsi"

/ {
 model = "NVIDIA Dalmore";
 compatible = "nvidia,dalmore", "nvidia,tegra114";

 aliases {
  i2c0 = "/i2c@7000d000";
  i2c1 = "/i2c@7000c000";
  i2c2 = "/i2c@7000c400";
  i2c3 = "/i2c@7000c500";
  i2c4 = "/i2c@7000c700";
  sdhci0 = "/sdhci@78000600";
  sdhci1 = "/sdhci@78000400";
 };

 memory {
  device_type = "memory";
  reg = <0x80000000 0x80000000>;
 };

 i2c@7000c000 {
  status = "okay";
  clock-frequency = <100000>;
 };

 i2c@7000c400 {
  status = "okay";
  clock-frequency = <100000>;
 };

 i2c@7000c500 {
  status = "okay";
  clock-frequency = <100000>;
 };

 i2c@7000c700 {
  status = "okay";
  clock-frequency = <100000>;
 };

 i2c@7000d000 {
  status = "okay";
  clock-frequency = <400000>;
 };

 spi@7000da00 {
  status = "okay";
  spi-max-frequency = <25000000>;
 };

 sdhci@78000400 {
  cd-gpios = <&gpio 170 1>; /* gpio PV2 */
  bus-width = <4>;
  status = "okay";
 };

 sdhci@78000600 {
  bus-width = <8>;
  status = "okay";
 };
};

Conclusion

UEFI is evil. It does not solve any particular problems on X86 and brings bug-ridden ACPI to ARM. Besides, on ARM Microsoft devices, Secure Boot cannot be turned off to boot custom images. This effectively locks the user out of their device. In other words, it is a typical DRM (digital rights management) system with a chain of trust (or, rather, a chain of distrust) which is aimed at preventing the "bad" user from running custom software. This allows the vendor to remove all the freeware content from the application market, and users will be forced to buy it because they have no alternative.

On X86, most vendors have not implemented the support for choosing the UEFI image to load. While this does not prevent you from renaming your binary to windozish bootx64.efi, it does show the attitude and the direction we're heading into.