Skip to content

Instantly share code, notes, and snippets.

@ajudges
Last active August 27, 2021 15:47
Show Gist options
  • Save ajudges/b0b13b92aa4c1fb0cc80a202bec399ea to your computer and use it in GitHub Desktop.
Save ajudges/b0b13b92aa4c1fb0cc80a202bec399ea to your computer and use it in GitHub Desktop.
A simplified overview on how embedded ARM devices boot

BITE-SIZED BOOTING FOR ARM EMBEDDED SYSTEMS

Hurdled around to watch one of the games in Toyko 2020 Olympics, we put on the TV, a smart TV, and then we waited an agonizing 10 seconds watching the device boot. Being impatient to watch the game, we were understandably irritated with the time it took the TV to load. Most of us have been there - where we just want to start doing stuff as soon as we switch on our device.

So what was happening while we waited for our device to boot?

It starts from the code we executed when we press the power button. Yes! You execute a code when you press a power button. This code is a machine specific code (called Bootloader) that initializes the device to start.

Bootloaders are usually agile. Because they do a sprint, to ensure that our device is available for us to use, on time. And to achieve that, our agile friend would need two items: the operating system image (henceforth referred to as kernel) and the device tree binary, dtb (this is simply a database, containing hardware components that are in our device). Some of the main features of the bootloader are:

  • Small: It should not occupy significant space, usually less than 128 KB.
  • Fast: The end user is not interested in using the boot-loader. They probably don't even care if it exists, they just want to use their device as soon as possible (I just want to watch Olympic games 😀).
  • Configurable: Necessary to turn on different hardware features e.g. boot over ethernet
  • Debuggable: To know what is causing our OS not to boot properly.

The bootloader takes both the kernel image and dtb from a storage location (NAND, SD/eMMC, TFTP server etc.), and loads them in the random access memory (RAM). Because the kernel would need to be aware of the hardware components of the board it is working with, the bootloader takes of the address of the dtb in the RAM and passes it to the kernel. The bootloader which has been operating from a specific partition in the boot medium (e.g. NAND, SD/eMMC) then jumps to the kernel in the RAM.

Next, is the starting of the low-level kernel initialization, such as enabling the memory management unit (MMU) to abstract the physical memory addresses to virtual address spaces for the device's processes (think of an App, such as netflix, as a process) to use, and the running of architecture specific initialization code. This is done in the kernel’s head file i.e. head.s. Here also, the kernel is decompressed.

Note: ARM kernel’s are usually compressed because of MONEY! That is, space is saved on the memory holding the kernel, and speed is also gained. Both speed and memory is money.

When all architecture specific initialization has been done, the kernel entry point then begins with start_kernel() i.e. the non-architecture specific kernel startup, that:

  • Initializes memory, scheduling, interrupts etc.
  • Initializes statically compiled drivers e.g. WiFi module driver
  • Mounts the root filesystem i.e. the / directory
  • Executes the first user process, init – i.e. the services that run when your device completely boots e.g. a date/time service that ensures the time on your device is set correctly.

Hurray, your device is now ready to use!!

References:

  1. Alberto Liberal - Linux Driver Development for Embedded Processors
  2. Dongliang Mu - Linux Insides
  3. Lars Zimmerman - Memory Management Units
  4. IBM - Device Management
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment