1<!-- 2 Copyright 2020, Data61, CSIRO (ABN 41 687 119 230) 3 4 SPDX-License-Identifier: GPL-2.0-only 5--> 6# Elfloader 7 8The elfloader is responsible for preparing the hardware for seL4 on ARM 9and RISC-V. It loads the kernel and user image from an embedded CPIO archive, 10initialises secondary cores (if SMP is enabled), and sets up an initial set of page 11tables for the kernel. 12 13## ARM 14 15On ARM platforms, the elfloader supports being booted in four ways: as a binary image, 16as a u-boot uImage, as an ELF file, and as an EFI executable. Each of these methods differs slightly. 17It can also provide seL4 with a DTB - either from the bootloader or included in the embedded CPIO archive. 18 191. (EFI only) Elfloader is entered at `_gnuefi_start` entry point. 202. (EFI only) Elfloader relocates itself 213. Elfloader `_start` is called. This is in `arch-arm/<arch_bitness>/crt0.S`. 224. Elfloader initialises the [driver framework](#driver-framework), which enables UART/printf. 235. Elfloader loads the kernel, user image, and DTB, determining where the kernel needs to be mapped in memory. 246. If the kernel window overlaps the elfloader's code: 25 * (AArch32 EFI only) the elfloader relocates itself. 26 See `relocate_below_kernel` for a detailed explanation of the relocation logic. 27 * (Other platforms) the elfloader aborts. 287. The elfloader resumes booting. If it relocated itself, it will re-initialise the driver model. 298. If the elfloader is in HYP mode but seL4 is not configured to support HYP, it will leave HYP mode. 309. The elfloader sets up the initial page tables for the kernel (see `init_hyp_boot_vspace` or `init_boot_vspace`). 3110. If SMP is enabled, the elfloader boots all secondary cores. 3211. The elfloader enables the MMU. 3312. The elfloader launches seL4, passing information about the user image and the DTB. 34 35### Binary 36 37The elfloader expects to be executed with a base address as generated by the `shoehorn` utility. 38You can determine the correct address for a given image by running 39``` 40aarch64-linux-gnu-objdump -t elfloader/elfloader | grep _text 41``` 42from the kernel build directory. The first field in the output contains the base address. 43 44On aarch64, the elfloader will try and move itself to the right address, however, this will fail 45if the load address and the correct address are too close, as the relocation code will be overwritten. 46 47It is also possible to override `shoehorn` and hardcode a load address by setting IMAGE_START_ADDR in CMake. 48 49### U-Boot 50 51The elfloader can be booted according to the Linux kernel's booting convention for ARM/ARM64. 52The DTB, if provided, will be passed to seL4 (which will then pass it to the root task). 53 54### ELF 55 56The elfloader supports being executed as an ELF image (via `bootelf` in U-Boot or similar). 57 58### EFI 59 60The elfloader integrates EFI support based on the `gnu-efi` project. It will relocate itself as appropriate, 61and supports loading a DTB from the EFI implementation. 62 63## RISC-V 64 65On RISC-V the elfloader is launched by the `bbl`, which is integrated in the seL4 build system. 66The `bbl` brings up secondary cores, and the elfloader uses SBI to provide a serial output on RISC-V. 67 68## Driver framework 69 70The elfloader provides a driver framework to reduce code duplication between platforms. 71Currently the driver framework is only used for UART output, however it is designed with extensibility in mind. 72In practice, this is currently only used on ARM, as RISC-V uses SBI for UART, and SBI has no device tree entries. 73However, in the future it may become useful on RISC-V. 74 75The driver framework uses a header file containing a list of devices generated by the `hardware_gen.py` utility 76included in seL4. Currently, this header only includes the UART specified by the `stdout-path` property in the DTB. 77Each device in the list has a compatible string (`compat`), and a list of addresses (`region_bases[]`) which correspond to the regions specified 78by the `reg` property in the DTB. 79 80Each driver in the elfloader has a list of compatible strings, matching those found in the device tree. 81For instance, the 8250 UART driver, used on Tegra and TI platforms has the following: 82 83```c 84static const struct dtb_match_table uart_8250_matches[] = { 85 { .compatible = "nvidia,tegra20-uart" }, 86 { .compatible = "ti,omap3-uart" }, 87 { .compatible = "snps,dw-apb-uart" }, 88 { .compatible = NULL /* sentinel */ }, 89}; 90``` 91 92Each driver also has a 'type'. Currently the only type supported is `DRIVER_UART`. The `type` 93indicates the type of struct that is found in the `ops` pointer of each driver object, 94and provides type-specific functionality. 95(For instance, UART drivers have a `elfloader_uart_ops` struct which contains a `putc` function). 96Finally, drivers also provide an `init` function, which is called when the driver is matched with a device, 97and can be used to perform device-specific setup (e.g. setting the device as the UART output). 98 99Finally, each driver has a `struct elfloader_driver` and a corresponding `ELFLOADER_DRIVER` statement. 100Taking the 8250 UART driver as an example again: 101 102```c 103static const struct elfloader_driver uart_8250 = { 104 .match_table = uart_8250_matches, 105 .type = DRIVER_UART, 106 .init = &uart_8250_init, 107 .ops = &uart_8250_ops, 108}; 109 110ELFLOADER_DRIVER(uart_8250); 111``` 112 113#### UART 114 115The driver framework provides a "default" (`__attribute__((weak))`) implementation of `plat_console_putchar`, which calls 116the `putc` function for the elfloader device provided to `uart_set_out` - discarding all characters 117that are given to it before `uart_set_out` is called. This can be overridden if you do not wish to use 118the driver framework (e.g. for very early debugging). 119 120 121 122## Porting the elfloader 123 124### To ARM 125 126Once a kernel port has been started (and a DTB provided), porting the elfloader to a platform is reasonably 127straightforward. 128 129Most platform-specific information is extracted from a DTB, including available physical memory ranges. If the 130platform uses a UART compatible with another platform, even the UART will work out of the box. In other cases, 131it might be necessary to add a new `dtb_match_table` entry to an existing driver, or add a new driver 132(which is fairly trivial - only the `match_table` and `putchar` functions from an existing driver would 133need to be changed). 134 135An appropriate image type needs to be selected. By default `ElfloaderImage` is set to `elf`, however, 136various platform-specific overrides exist and can be found in `ApplyData61ElfLoaderSettings` in this repo, at 137`cmake-tool/helpers/application_settings.cmake`. 138 139### To RISC-V 140 141TODO - it seems there's not actually that much that needs to be done on the elfloader side. 142