Taking an embedded system from power-on-reset to a running operating system seems like a simple task. In practice, it can be surprisingly complex, and If you are new to embedded systems, the learning curve can be rather steep. In this post, I’ll try to demystify some of what of happens.
The OpenGlow uses the i.MX6 family, with the prototype carrying an i.MX 6Dual processor. There are a few subtleties between processors that are beyond the focus of this post, so we’ll focus only on the 6Dual/6Quad and only as it relates to the Boundary Devices (BD) SOM v2.
i.MX6 Boot ROM
When the processor’s reset logic detects a Power-On-Reset, it starts the execution of the on-chip boot ROM code. This code handles boot device selection, loading of the bootloader code into RAM, and hands off execution to the CPU.
The boot ROM reads the state of two external pins (BOOT_MODE[1:0]) to determine how it should proceed. BD has tied the pin for BOOT_MODE low, and given us a switch (SW1) to determine the state of BOOT_MODE. This leaves us with two boot options: eFUSEs and Serial Downloader (USB OTG port):
The eFUSEs are one-time-programmable registers that control the primary boot device. This device can be set to NOR Flash, NAND Flash, OneNAND Flash, SD/MMC, SATA, or Serial NOR Flash/EEPROM (SPI or I2C). BD has taken the liberty to pre-program these to boot from the on-board SPI Serial NOR Flash.
The Serial Downloader is a protocol that allows the bootloader, flattened device tree, and kernel images to be transferred over a special USB serial connection. This is useful for bringing up a green processor, and a handy way to de-brick the device should a bootloader upgrade go sideways.
BD also pre-programmed the NOR Flash with their own bootloader that expects the SOM to be mounted on one of their carrier boards, so it won’t boot properly on the OpenGlow board without some extra work. More on that later…
Loading the Bootloader
Simply: The boot ROM loads the bootloader image from the Serial NOR Flash into RAM, and tells the processor to execute the code. In our case, that bootloader image is U-Boot. It’s only slightly more complex than that, and those complexities don’t really impact us, so I’ll leave them out.
Like so many things in the embedded world, U-Boot started out as a simple bootloader with one job: Start the operating system. It has since grown into a complex beast that can perform an insane amount of functions. For our purposes, we’re limiting it to some very basic functions.
When U-Boot first executes, it does some very basic hardware setup. These include setting some important RAM timings, configuring additional storage devices, and setting the states of some key I/O ports. I’ll cover all of these in a future post that details the customization of U-Boot for OpenGlow.
One of those configured devices is the serial console. Once that is initialized, we get to see what’s happening behind the scenes:
U-Boot 2018.01-dirty (Feb 21 2018 - 12:01:26 -0500) CPU: Freescale i.MX6D rev1.2 at 792 MHz Reset cause: POR Board: OpenGlow STD Prototype 1 DRAM: 1 GiB MMC: FSL_SDHC: 0, FSL_SDHC: 1 SF: Detected sst25vf016b with page size 256 Bytes, erase size 4 KiB, total 2 MiB In: serial Out: serial Err: serial Hit any key to stop autoboot: 0
Load Kernel and FDT
Next, U-Boot looks for the boot device we have configured: currently SDHC device 0, partition 0, which is the removable SD card slot on the OpenGlow board. From that device, it will load the kernel and the flattened device tree into RAM:
reading openglow_std.dtb 44641 bytes read in 19 ms (2.2 MiB/s) reading zImage 5960032 bytes read in 294 ms (19.3 MiB/s)
and finally, it will execute the kernel:
Kernel image @ 0x12000000 [ 0x000000 - 0x5af160 ] ## Flattened Device Tree blob at 13000000 Booting using the fdt blob at 0x13000000 Loading Device Tree to 4f584000, end 4f591e60 ... OK Starting kernel ...
There you have it, in a nutshell…
In my next post, we’ll take a more detailed look at U-Boot, how to create a custom board package, and how to install it on the OpenGlow board.