With the introduction of the Raspberry Pi HATs (Hardware Attached on Top) there has been a push to support FDT (Flattened Device Tree) on the Pi for describing the actual hardware configuration. FDT isn’t a new thing as such, having been around as part of the Open Firmware specification for quite some time. The main benefit of using FDT is that it allows for easy addition or changing of hardware, without having to try to load every possible driver under the sun. This is especially handy when using HATs, since they can have an FDT fragment stored in their eeprom which is then overlaid onto the main FDT, effectively providing automatic driver configuration. So, FDT is a really good thing.
Another really useful thing is the U-Boot boot loader. It’s quite commonly used in embedded systems, both for initial hardware setup and for choosing which kernel/partition to boot. On a Pi the GPU already takes care of the necessary low-level hardware initialisation, but it does not provide a way to implement conditional boot selection. The latter is of tremendous importance for unattended systems to support safe remote upgrades. In the typical case the system has two OS partitions, A & B, each of which can boot & run the system but only one is “active”. During an upgrade the new version gets written to the inactive partition, and a flag is set for the boot loader to indicate that this partition should be attempted at the next boot. The boot loader, U-Boot in this case, is then responsible for picking that partition (and kernel) next time, and also note whether it was successful, based on some chosen metric. If the system is rebooted again, the U-Boot boot script then makes the decision whether to keep booting the new version, or whether to fall back to the previous version. It’s a pretty simple concept, though the implementation details can get a bit fiddly.
U-Boot also has built-in support for loading FDTs, or rather DTBs (Device Tree Blobs) which are the binary versions of the FDTs. However, on the Raspberry we don’t really want to do this, since the GPU has already loaded not just the main DTB, but also fragments from HATs and/or other fragments specified in the config.txt file. Even if U-Boot could do the same (which, at the time of writing, it can’t), that would be doubling up on the effort. The GPU also handles the “bootargs” (kernel command line arguments), which is an amalgamation of the contents in cmdline.txt and board-specific settings. Again, U-Boot can’t easily do the same, at least not without some non-trivial amount of patching. What we really want is to set things up so that the FDT and bootargs are handled by the GPU, but the boot partition selection is handled by U-Boot.
As it turns out, this is possible, but it does require a little bit of patching of the mainline U-Boot source. By default on a Raspberry the U-Boot loads its boot script into memory at address 0x0. This is a somewhat unfortunate choice, since the default location of the DTB provided by the GPU is 0x100, so for any non-trivial boot script it ends up overwriting the DTB. To fix this a small patch[1] to the include/configs/rpi-common.h is needed, to both move the script load address and to tell U-Boot about the default FDT address. If patching U-Boot isn’t possible for whatever reason, another option is to instruct the GPU to put the FDT at a different address using the “device_tree=0x2000000” entry. If you lose your config.txt, you’re in trouble though, so I much prefer to get U-Boot to Do The Right Thing(tm) on its own.
At this point we run into another challenge, this time thanks to the GPU being too clever. When it loads a kernel it first inspects it and makes a decision as to whether provide a DTB or the legacy ATAGs, all in the name of backwards compatibility. Unfortunately for us, our newly built U-Boot doesn’t look like an FDT-aware kernel, and the GPU doesn’t provide the DTB to us. Again, this is something that can be overridden in the config.txt file, using a “device_tree=…” entry, but that locks everything down to using that particular DTB file. The better option is to use the “mkknlimg” tool from the kernel build toolchain[2]. Simply run it like “mkknlimg –dtok u-boot.bin kernel.img” (or kernel7.img if you built for Pi 2), and put the resulting kernel.img file in your /boot. The GPU will now happily consider your U-Boot to be FDT aware, and prepare a DTB for it at address 0x100.
With all this done, you can turn your attention to your U-Boot boot script. Since the specifics of picking a boot partition is dependent on your system, I’ll only cover how to modify the bootargs – the rest is left as the usual dreaded exercise for the reader, including the error handling.
Extracting the bootargs as provided by the GPU is quite straight-forward once you know where they are, which is inside the DTB, as /chosen/bootargs. To load this into the environment variable named “bootargs”, simply use the commands:
fdt addr ${fdt_addr_r} && fdt get value bootargs /chosen bootargs
Once you’ve worked out what your root partition is, you can append that to your bootargs variable like so:
setenv bootargs "${bootargs} ro root=${my_root_part} rootfstype=ext2 rootwait";
The only remaining step is to load your chosen kernel, and boot it. Assuming your kernel is on the FAT partition and in uImage format, something like this would do the trick:
fatload mmc ${devnum} ${kernel_addr_r} "${my_kernel}" && bootm ${kernel_addr_r} - ${fdt_addr_r};
The key here is to ensure you pass the ${fdt_addr_r} as the third argument to the bootm (or bootz, if you’re using a zImage) command. This is what causes U-Boot to not only write your bootargs variable back into /chosen/bootargs in the DTB, but also to take the necessary steps to hand that DTB over to the kernel. Without that, it would use the legacy ATAGs format, and your hard work would be for nothing.
If you’ve followed this through to the end, you’ll now have a setup for your Raspberry where you get all the benefits of using FDT as well as those of using U-Boot. Enjoy!
[1] U-Boot patch, as of 1733259d25015c28c47990ec11af99b3f62f811c: http://pastebin.com/fYD1HuTr
[2] The “mkknlimg” tool: https://github.com/raspberrypi/tools/mkimage/mkknlimg