i.MX6 U-Boot

Table of Contents

Description

U-Boot is the bootloader for all i.MX6 devices. The bootloader first loads the devicetree, kernel and ramdisk, and then executes the kernel providing it with additional information such as the name of the device holding the root filesystem. U-Boot supports the ext2/3/4 filesystems as well as fat. Since our 2018.01 release, USB, SATA, eMMC and microSD are supported for both reading and writing.

General Operation

The first thing that the i.MX6 does on power-on is load executable code from a predefined location. In terms of U-Boot this first piece of code is the SPL. Our devices can load it from either microSD, eMMC, SATA or SPI. This selection can be made by either fusing the SoM or adjusting the boot select jumpers if populated.

What happens next is decided by the SPL. It brings up the most important subsystems of the SoC, and then proceeds with loading the next stage of u-boot at predefined offsets from predefined storage. This is decided at compile-time.

Fallback

The i.MX6 can also load code from USB using the Serial Download Protocol. When

  • the SoM isn’t fused and

  • no bootable code was found

the SoM falls back to the SDP protocol on the USB OTG port automatically.

Furthermore when the SPL has been loaded and does not find a usable u-boot binary, it too will fall back to SDP on the OTG port allowing for recovery. This is also true for fused SoMs unless explicitly disabled at compile-time.

Success Indicator

The Cubox-i has an LED that is off by default and turned on by u-boot. So if the LED turns red, U-Boot was loaded successfully.

Download Binaries

We are automatically building binaries whenever code is pushed to our U-Boot repository on github, currently tracking the v2018.01-solidrun-imx6 branch. Please find the results at SolidRun Images .

Installing to removable storage

microSD

This section assumes that you have a device running linux, and the target sdcard attached to it. This can be any device! Optionally create an MBR partition table, and any partitions you may want.

The Boot-ROM searches for the SPL after the first 1024 bytes. The SPL then looks for the full u-boot binary at both 69k and 42k. The dd command can be used for writing SPL and u-boot to these locations on your microSD card. Substitute sdX by the device node of your sdcard.

dd if=SPL of=/dev/sdX bs=1k seek=1 conv=sync dd if=u-boot.img of=/dev/sdX bs=1k seek=69 conv=sync

Note – Take your time while identifying where your designated SD-Card is mapped on your linux system. Failure to do so can result in overwriting an arbitrary disk on your system!

SATA

This section assumes that you have a device running linux, and the target sata drive attached to it. This can be any device! Optionally create an MBR partition table, and any partitions you may want.

The Boot-ROM searches for the SPL after the first 1024 bytes. The SPL then looks for the full u-boot binary at 69k. The dd command can be used for writing SPL and u-boot to these locations on your sata drive. Substitute sdX by the device node of your sata drive.

dd if=SPL of=/dev/sdX bs=1k seek=1 conv=sync dd if=u-boot.img of=/dev/sdX bs=1k seek=69 conv=sync

Note – Take your time while identifying where your designated SD-Card is mapped on your linux system. Failure to do so can result in overwriting an arbitrary disk on your system!

Booting from USB (OTG)

An unfused i.MX6 SoM will fall back to booting from USB when it has not found any bootable code on the attached storage. Therefore it can be used to deploy software to a new system. Since the HummingBoard 2 there are also boot select jumpers that allow explicitly selecting USB as the boot source. The particulars can be found HummingBoard Edge/Gate Boot Jumpers .

Identify the OTG port

To quote our developer Jon: “it is the top port next to the Ethernet jack” More formally it is the top port on the U5 header. This holds true of all our i.MX6 based boards.

Build an OTG cable

Essentially a straight Male to Male USB-A cable would accomplish the task. However it is highly recommended to build a custom cable with a large resistor on the VCC line to protect both your PC and your board from destruction. For this task some skills in electrical engineering are required! Hint: Both the voltage, and the maximum allowed current are defined by the USB standard.

imx_usb_loader

This application implements the Serial Download Protocol that the i.MX6 Boot-ROM uses to communicate. It is available on the github account of boundarydevices. Download and compile:

The final binary is called imx_usb and can be executed in place.

load SPL

sudo ./imx_usb ../u-boot_imx6/SPL

On success, SPL should announce itself on the serial console:

U-Boot SPL 2018.01-00024-g457cdd60c3 (Aug 07 2018 - 21:16:13) Trying to boot from USB SDP SDP: initialize... SDP: handle requests...

load U-Boot

On success, U-Boot should announce itself on the serial console:

At this point U-Boot has been loaded to RAM and is running. The next step is installing it to some internal storage such as eMMC or SPI Flash.

Installing to integrated storage

The most important thing to realize is that for copying data to integrated storage, the board has to already be running at least U-Boot, or even Linux. A fresh board where nothing has yet been installed can be brought up by the following methods:

  • booting from a removable microSD

  • booting from USB (OTG)

Please see the previous sections for more details.

The remainder of this section assumes that a recent version of U-Boot, at least 2018.01 is running.

There are actually three methods for installing the U-Boot binaries:

  1. usb mass storage mode: The board presents itself as a usb drive to the host PC

  2. DFU: The board listens for the DFU protocol on USB

  3. the hard way: load and write files by hand using u-boot commands

We highly recommend using methods 1 or 2 as they are much easier to understand than the third!

USB Mass Storage:

This is probably the easiest option for most people. U-Boot has to be built from source with the ums command enabled:

The second step is to launch usb mass storage mode from the U-Boot console and specify what storage device to use.

For presenting the eMMC (mmc1) as a usb storage device, execute

ums 0 mmc 1

Now on a connected PC a new usb drive should have shown up. From this point onwards anything is possible! Partitioning, mounting, writing binaries in arbitrary locations, … . Note that this will also work for a microSD by using mmc0 instead.

Here is how U-Boot and SPL would be installed on this storage device:

Note – Take your time while identifying where the board is mapped on your linux system. Failure to do so can result in overwriting an arbitrary disk on your system!

Using DFU:

DFU is not yet enabled by default, so it has to be built from source with two additional options:

In addition this patch is required to avoid a memory allocation problem!

DFU-Mode in U-Boot is configured and started on the serial console by first setting the dfu_alt_info environment variable, and then launching the dfu command. Below are samples targeting each eMMC, microSD and SPI Flash:

  • eMMC

  • microSD:

  • SPI

The syntax for dfu_alt_info can be looked up in the corresponding u-boot sources, namely drivers/dfu/dfu_mmc.c and drivers/dfu/dfu_sf.c. For (e)MMC the syntax is: <alt name> <access method> <starting block> <size in blocks>

The dfu command takes 3 arguments: <usb controller number> <storage type> <device number> The controller number should always be 0 which means the OTG port is used!

The first command sets up two dfu names:

  • spl.raw: space for SPL at offset 1kB

  • u-boot.img.raw: space for u-boot.img at offset 69kB

The second command enables dfu mode on usb controller 0 (OTG port) for mmc 1 (eMMC).

Now dfu-util, which is available here on sourceforge, can be used on a PC to send both SPL and u-boot.img to the previously selected mmc 1:

If everything went well the u-boot console should show

and the dfu commands on the pc should end with:

Using U-Boot console and USB Flash Drive:

If Linux is already running on the device, the procedure is identical to microSD. In this case the previous section can be used, where sdX is replaced with mmcblk1.

However it is more likely that neither Linux nor even U-Boot are available at this point. Refer to section i.MX6 U-Boot  how to load u-boot to RAM.

Now that at least U-Boot is running on the target system, SPL and u-boot.img can be loaded from removable media or network, and then written to the eMMC. Below are the steps for loading binaries from a USB drive and writing them to eMMC:

There are many magic numbers in this short script that require attention!:

  • mmc dev x y:

    • x = 0 is microSD, x = 1 is eMMC

    • y = 0 is eMMC main (data) partition

    • y = 1 is eMMC hardware boot0 partition, y = 2 is eMMC hardware boot1 partition

  • mmc write ${kernel_addr_r} 0x2 0x66:

    • 0x2 = destination block number in hexadecimal. Here one block is 512 bytes –> SPL goes to block 2 (1024 bytes)

    • 0x66: The size of SPL in blocks in hexadecimal. This is the ceiling of filesize divided by 512, in this example 52224 / 512 = 102 = 0x66

  • mmc write ${kernel_addr_r} 0x8A 0x284

    • 0x8A: SPL is expected at 69kB –> 138 = 0x8A

    • 0x284: ceil(329312 / 512) = ceil(643,1875) = 644 = 0x284

  • mmc partconf x a y b:

    • x = 0 is microSD, x = 1 is eMMC

    • a = 0 is disable acknowledge to boot-rom

    • y = 7 is eMMC main (data) partition

    • y = 1 is eMMC hardware boot0 partition, y = 2 is eMMC hardware boot1 partition

    • y = 0 is disable eMMC boot

    • b is partition access enable, values same as y: 0, 1, 2, 7

Boot Process after U-Boot has loaded

The way U-Boot searches for an operating system to load has recently undergone substantial changes towards a standard behaviour across all devices. This new standard is called distro support, and fully supported by our 2018.01 release. It automatically searches all attached storage for bootable files such as boot scripts, extlinux configuration or EFI applications. For documentation please refer to the official U-Boot documentation.

Loading Linux By Hand:

The manual way for loading Linux from U-Boot is very straight forward:

Here is a sample for loading a kernel from microSD:

Transitioning from 2013.04 and earlier

We have developed a transitional boot-script that simulates the old behaviour to allow booting old system images that have not adopted support for distro boot: i.MX6 Legacy Boot-Script


Download:

This boot-script should be placed at a well-defined location that u-boot can read from, under a name that does *not* equal boot.scr. We suggest calling it legacyboot.scr. Then the bootcmd environment variable has to be configured to force always running this script unconditionally. Here is a sample for when legacyboot.scr lives on the first partition on microSD:

 

Compiling from source

This section assumes that you have git, make and a cross-compiler targeting 32-bit arm available on your system!

These are the instructions to fetch the code, and build a binary:

This will generate SPL and u-boot.img to be used when booting from eMMC. To target other boot media, set one of the following options in configs/mx6cuboxi_defconfig or through menuconfig:

  • eMMC data partition (default)

  • eMMC boot0 partition

  • eMMC boot1 partition

  • SD-Card

  • mSATA SSD

  • SPI

Note: The resulting binaries are SPL and u-boot.img.

eMMC Tips

Size matters: The production line burns the image to emmc. the process runs at 5.6 MB/sec, which means that burning 8GB image of which 80% is free space is very wasteful. The best way to optimize for production is to create a single partition (as small as possible to contain all the data), and a “firstboot” script that will complete the image creation.

This is what I did to create a smaller image for burning: – mount a SD with the original image (2 mount points) – tar the contents of the 3rd partition and store the archive in /boot – create a first boot, oneshot systemd service that will:

  • resize the rootfs partition

  • resize the rootfs filesystem

  • create and format the 2nd and 3rd partitions

  • extract the archives from /boot into the relevant partitions

  • delete the archives from /boot

  • disable itself from systemd

  • calculate the smallest image size that will contain your data

    • run du -s -B M /path/to/your/rootfs to get the size of your data in megabytes

    • add 10%

    • partition size is at least 1MB smaller than the drive.

    • create an image file with a single ext4 partition: and with the bootloader

    • dd if=/dev/zero of=/path/to/vivi.img bs=1M count=$SIZE_MB

    • losetup /dev/loop0 /path/to/vivi.img

    • dd if=path/to/u-boot-imx6/SPL of=/dev/loop0 bs=1k seek=1 oflag=sync

    • dd if=path/to/u-boot-imx6/u-boot.img of=/dev/loop0 bs=1k seek=42 oflag=sync

    • sync

    • losetup -d /dev/loop0

  • populate the single partition with the rootfs, the archives of the other partitions, and the firstboot script

    • losetup -o 1048576 /dev/loop0 /path/to/vivi.img # load the partition and not the entire disk image

    • mkfs -t ext4 /dev/loop0

    • mount /dev/loop0 /mnt

    • do populate

    • add the firstboot script: systemd firstboot script - Google Search

    • umount /dev/loop0

    • losetup -d /dev/loop0

Further reading

  • Booting from network/PXE

SolidRun Ltd.