Getting Started With Metal

Beginning bare metal development on Arm devices can be challenging becuase there is a lot that you haven’t seen before. In this post, I will explain Uboot and Device Trees (DTS). I will also point you to some resources to get you started with development.

Device Trees

A device tree is a data structure that describes the hardware components of a system. Operating systems read device trees to know where all of your hardware components are.

In x86 all hardware is discoverable. This means that the operating system doesn’t need device trees to figure out where something is. The x86 CPU can figure out all the devices connected to the system bus. For PCs x86 discoverability is extremely useful because componets are always being swaped in and out all the time. On the other hand, embedded systems don’t really have removable components, so they can’t really benefit from discoverability. Futhermore, most busses on the embedded systems don’t support discoverability because of this we have to use device trees.

Below is an example device tree.

/dts-v1/;

/ {
	
	cpus { ... };
	
	aliases {
		sensor-controller = &i2c1;
	};

	soc {
		i2c1: i2c@40002000 {
			compatible = "vnd,soc-i2c";
			label = "I2C_1";
			reg = <0x40002000 0x1000>;
			status = "okay";
			clock-frequency = < 100000 >;
		};
	};
};

This device tree describes a simple system on chip (SoC) with an I2C. Let’s take a look at what this all means. In this device tree we have two nodes one called “aliases” and one called “soc”. The aliases nodes contains properties that are aliases, for example, “sensor-controller” is referring to i2c1: the I2C controller in the SoC. The “soc” node is the SoC this contains devices that are on the SoC. If we were to draw this simple SoC, this is what it would look like.

soc

An actual SoC will be more complex that this, but now you have an idea of what a device tree is. I am not going to explain everything about device trees here, but if you want to learn more Thomas Petazzoni gives a comprehensive presentation here.

Uboot

Before we jump into Uboot, let’s first talk about first stage and second stage bootloaders. First stage bootloaders boot up the board by activating peripherals like UART ports, I2C, and I/O ports. After setting up these devices the first stage bootloader willl run the second stage bootloader. Second stage bootloaders are responsible for running applications. For example, running a kernel.

You may be wondering why you would need two bootloaders. The reason we have two bootloaders is because there is only so much memory we can work with. Bootloaders are only 512 bytes in size. In order to get more memory to work with, we have to use another bootloader. We use the first bootloader to boot into the second one, which doesn’t have any memory constraints.

Alright, let’s get back to Uboot. Uboot is a multistage bootloader used for embedded systems. Uboot loads your device tree to configure your SoC components then boots your kernel. Uboot has tons of commands that you can use, but in this post I will only go over a few. In the code below, we use uboot to load an image at the address 0x42000000 then jump to that address.


setenv imgname img.bin

setenv loadaddr 0x42000000

# Make sure the caches are off for now
setenv bootimg 'tftp ${loadaddr} ${serverip}:${imgname}; go ${loadaddr}'

Let’s go through this uboot code. The “setenv” command sets up environmental variables. Here we have two environmental varables “imgname” and “loadaddr”. These two variables store the name of our image and our load address. Our last environmental variable “bootimg” stores a command that we will use to load an image from a server and jump to it. “tftp ${loadaddr} ${serverip}:${imgname}” this comand downloads an image from our server to our load address “loadaddr”. TFTP is the Trival File Transfer Protocol, which is typically used to boot from a local area network. TFTP has a small memory footprint, which makes it perfect for SoC. The “go ${loadaddr}” command jumps to our loadaddress.

For a detailed list of uboot commands visit https://www.denx.de/wiki/DULG/Manual

Metal

When starting development it is important to choose a development board. Emulating is okay, but setting up the hardware is much more fun. There are so many embedded boards out there, but I recommend that you just choose one to start with. If you are used to developing with one board, this doesn’t mean that you won’t be able to develope with another board. There are a lot of simularites between boards. Most boards will be running with the same processor. It just depends on what components you want on your SoC. Here are a few boards that I recommend for beginners.

The reason I have selected those two boards is because the community is pretty big. Finding resources, tutorials, and examples won’t be too hard. They should be just a Google search away. If you have any questions or just want to discuss embedded systems, feel free to contact me at frd20@pitt.edu.

Written on May 22, 2021