Fastboot on i.MX
Published on July 31, 2012
One of the hidden gems produced by Google as a part of the Android project is the inappropriately named Fastboot tool. In this article, we'll describe Fastboot as implemented for our Nitrogen-E and Nitrogen53 boards. We'll also make some arguments about why we suggest the use of this tool for use with Android and also other Operating Systems.
I said it was inappropriately named, because I view Fastboot as primarily a tool for flashing Operating System images over a high-speed USB device channel. It doesn't speed up your boot and in fact it doesn't really have much to do with booting. I presume that it was named by someone in the production process, who saw it as a way to speed the process of first boot, but that's just a supposition.
There are a lot of tools around for flashing. We programmers are inherently tool builders and always looking for ways to automate drudgery. Some of the alternatives used on i.MX processors include:
- The Freescale Manufacturing Tool. This Windows (.NET) application and driver package allows you to program the persistent storage on an i.MX device. Since it is specific to i.MX, it even supports initial board bringup through support of the USB boot protocol we used to create our USB boot tool.
- The Device Firmware Upgrade utility. This set of tools was defined by the USB Implementers Forum as a mechanism for re-loading the firmware for various USB devices. Because it uses libusb, versions are available for Windows, Linux, and MacOS and the protocol is well defined for those use cases where a single firmware defines the operation of a device.
By comparison, the Fastboot protocol and tools:
- Supports Windows, Linux, and MacOS,
- Supports flashing of multiple components,
- Supports a number of extra commands for configuring a device, and
- Because of the success of Android, we expect the Host-side tools to be around and well supported in the future.
Based on these criteria, we've decided to embrace Fastboot on all of our devices, and a with a recent commit, we've started with our Nitrogen-E and Nitrogen53 boards.
Existing code base
If you have a background on i.MX boards from Freescale, you may know that they've supported Fastboot for a couple of years now. That's true. They've supported it for Android builds of U-Boot, but a number of things have prevented us from adopting and supporting it.The first is a common usage pattern for Fastboot. If you look at this page describing fastboot commands and usage, you'll see how Fastboot is normally used for Android devices.
Take a look at this example command.
$ fastboot erase recovery
Who defines the name recovery ? In the Android world, a recovery partition is typically contains a root filesystem used to recover a broken primary filesystem, but even if you're running Android, who decides where it's placed? Who decides how big it is?We build lots of custom boards, and one of the primary choices made by our customers is where and how the devices boot. For some customers, it's SATA. For others, it's eMMC or SD card.
Freescale directly supports a smaller set of boot options through the use of custom U-Boot headers like this one for the MX53 EVK. Re-compiling U-Boot to make a partitioning change doesn't scale, and doesn't take into account the wide variety of options.
Based on this, we've decided to implement Fastboot support differently.
Implementation
Our goals for the implementation are to be able to (re)program any persistent storage on the Nitrogen series of boards using Fastboot:- SPI-NOR, which typically contains both U-Boot and U-Boot environment variables, and
- SD cards and eMMC (U-Boot doesn't know or care about the difference), and
- NAND flash, and
- SATA
The primary mechanism for this will be the aptly-named flash command.
The Android Fastboot host-side tools don't know or care about the payload in the flash command. They are perfectly happy to hand off named BLOBs from any file on a host to a device like so:
$ fastboot flash mystoragename /path/to/mystorage.img
It's kind of ironic that the Android tools don't enforce policies about the Android namespace but all of the U-Boot implementations we've seen do.Based on this, the challenge is to define a namespace that can handle all of our targets and making sure that we can produce proper BLOBs for each.
Since we're not very creative, it seems that the simplest way is to define the strings (mystoragename above) in a way that defines the device and the offset. From the highest level, all persistent storage can be dealt with in this manner. The underlying media may have restrictions on the size increments used to perform writes, but in general, we want to tell the device:
Store this thereWhere this is a BLOB downloaded over the USB link and there is a reference to the storage location.
Storage locations can be specified in device:offset form, where device is one of the following:
- spi - SPI NOR
- mmc0|mmc1 - SD card or eMMC device corresponding to U-Boot's numbering conventions
- sata - SATA drive (only one is supported as of this writing)
Putting this all together, we can flash a new U-Boot binary at offset 0x400 using a command like so:
~/$ fastboot flash spi:0x400 u-boot.binSimilarly, iff you know how to create a U-Boot environment blob and know that it's stored at offset 0x5f000 on a Nitrogen board, you can (re)program it like this:
~/$ fastboot flash spi:0x5f000 u-boot.env
Creating the images
Most of the BLOBs you'll want to store should be readily available:- U-Boot executable - Always created in binary form by the U-Boot build process. This is typically stored at offset 0x400 on i.MX. Please see this post for details on our Nitrogen-E and Nitrogen-53 boards.
- filesystem partitions - Can generally be created by your userspace-creation tools through tools like mkfs, and
- U-Boot environment - I'm not aware of a tool to create one of these from scratch, but tools that access them from Linux are readily available. We've even written our own.
- Partition table - This may be the trickiest piece and requires either a local device or loopback device as a proxy. We'll discuss this in another post where we can go into more detail.
Invocation
In all Fastboot implementations that we've seen, the Fastboot protocol is supported by a U-Boot command named fastboot. This command configures the USB device port as a Fastboot device and acts upon commands from a Host PC until either the user aborts from the U-Boot console with <Ctrl-C> or a Fastboot command from the host tells it to stop.The naming makes sense, but you may be asking how and when this command is invoked. In a typical Android device, this can be done by some combination of magic keys being pressed during boot.
We added keypress support to main-line U-Boot for Sabre Lite a while ago, and added it to our Nitrogen53A variant more recently. This feature can be used as described in this post to the U-Boot mailing list.
Freescale also implemented Fastboot on Sabre Lite, but in a different way. In Freescale's implementation, the recovery commands are implemented at compile time based on definitions in the android configuration file.
Of course, you can also invoke Fastboot by issuing the command from the U-Boot console or through a boot script.
A simple example
Let's take a simple example first. Starting with a Nitrogen53 running U-Boot (never mind how it got there), that has an SD card inserted in mmc 0, and connected over USB to a host PC, let's do the following:- Format the disk with a single 400MB ext3 filesystem to run Timesys,
- Write the ext3 filesystem,
- Upgrade U-Boot, and
- Reboot the device
~$ sudo `which fastboot` getvar version-bootloader
version-bootloader: U-Boot 2009.08-00723-gcb36183 (Jul 27 2012 - 17:50:26)
finished. total time: 0.004s
~$ sudo `which fastboot` getvar version
version: 0.5
finished. total time: 0.003s
~/$ sudo `which fastboot` flash mmc0:0 timesys-small-footprint.ext3.mbr
sending 'mmc0:0' (0 KB)...
OKAY [ 0.008s]
writing 'mmc0:0'...
OKAY [ 0.230s]
finished. total time: 0.238s
~/$ sudo `which fastboot` flash mmc0:62 timesys-small-footprint.ext3.img
sending 'mmc0:0x7C00' (102400 KB)...
OKAY [137.337s]
writing 'mmc0:62'...
OKAY [ 15.274s]
finished. total time: 152.611s
~/u-boot-nitrogen$ sudo `which fastboot` flash spi:0x400 mx53_ubl_ecspi.bin
sending 'spi:0x400' (254 KB)...
OKAY [ 0.354s]
writing 'spi:0x400'...
OKAY [ 48.750s]
finished. total time: 49.104s
~/u-boot-nitrogen$ sudo `which fastboot` reboot
rebooting...
Take particular note of the following things in the example above:
- It uses sudo `which fastboot`. I don't have proper udev rules set up on my workstation yet, and don't have fastboot in the PATH for user root.
- The getvar version command yields the fastboot version.
- The getvar version-bootloader command shows the version of U-Boot.
- We don't use u-boot.bin on Nitrogen53. Instead, we wrap it with a first level loader as described in our "Going serial"post.
- It has a single mbr segment because it uses less than 4 partitions.
- The filesystem image is programmed at offset 62. This is done because fdisk used an implied sectors-per-cylinder of 62. I retrieved this offset by running the command fdisk -u -l /dev/mmcblk0.
- The download of the filesystem image took considerably longer than the write. This is because I was hooked up through a low-speed USB hub. When hooked up over a high-speed link, the 100MB download takes less than 20 seconds.
~/$ sudo dd bs=512 count=1 if=/dev/mmcblk0 of=timesys-small-footprint.ext3.mbr
~/$ sudo dd if=/dev/mmcblk0p1 of=timesys-small-footprint.ext3.img
In more complex cases with extended partitions, we'll need some additional tools to manage the image creation. We'll cover that topic later.There's also a hidden weakness in the design and/or implementation of Fastboot above. If you look closely at the output, you'll see that each flash command has two sets of output: sending and writing. The protocol implies, if not insists that an entire image be downloaded first and programmed afterwards. This restricts any flash operation to a size which will fit in RAM. On the Nitrogen53, slightly more than 140MB (1024x1024 bytes) is available.
A more complicated example
If you had a 4GB SD card and wanted to place a 6-partition Android image onto it, you might end up with a sequence like this:sudo `which fastboot` flash mmc0:0x0 mbr0 sudo `which fastboot` flash mmc0:0x186812 mbr1599506 sudo `which fastboot` flash mmc0:0x205823 mbr2119715 sudo `which fastboot` flash mmc0:0x284834 mbr2639924 sudo `which fastboot` flash mmc0:16 part1.0 sudo `which fastboot` flash mmc0:300016 part1.1 sudo `which fastboot` flash mmc0:520209 part2.0 sudo `which fastboot` flash mmc0:820209 part2.1 sudo `which fastboot` flash mmc0:1120209 part2.2 sudo `which fastboot` flash mmc0:1420209 part2.3 sudo `which fastboot` flash mmc0:7106579 part4.0 sudo `which fastboot` flash mmc0:7406579 part4.1 sudo `which fastboot` flash mmc0:7706579 part4.2 sudo `which fastboot` flash mmc0:1599522 part5.0 sudo `which fastboot` flash mmc0:1899522 part5.1 sudo `which fastboot` flash mmc0:2119731 part6.0 sudo `which fastboot` flash mmc0:2419731 part6.1 sudo `which fastboot` flash mmc0:2639940 part7.0 sudo `which fastboot` flash mmc0:2939940 part7.1IOW, 19 commands to program just the SD card, and lots of them with magic numbers. Furthermore, there are some pretty big questions like:
- Where did the mbr files come from?
- Where did the part#.# files come from?
- Who calculated the offsets?
If run with a single argument (the device to be read), the savembrs program will save all of the MBR segments to files and produce a set of fastboot commands to restore them on stdout. It will also display the sectors available for each filesystem partition and the partition type:
~/mbrsave$ sudo savembrs /dev/mmcblk0
fastboot flash mmc0:0x0 mbr0
# p1 16 520208 520193 Win95 FAT32
# p2 520209 1599505 1079297 Linux
# p4 7106579 7716863 610285 Win95 FAT32
fastboot flash mmc0:0x186812 mbr1599506
# p5 1599522 2119714 520193 Linux
fastboot flash mmc0:0x205823 mbr2119715
# p6 2119731 2639923 520193 Linux
fastboot flash mmc0:0x284834 mbr2639924
# p7 2639940 3160132 520193 Win95 FAT32
~/mbrsave$ ls -l
total 16
-rw-r--r-- 1 root root 512 2012-07-31 13:44 mbr0
-rw-r--r-- 1 root root 512 2012-07-31 13:44 mbr1599506
-rw-r--r-- 1 root root 512 2012-07-31 13:44 mbr2119715
-rw-r--r-- 1 root root 512 2012-07-31 13:44 mbr2639924
In this case, there are four MBR segments, the primary at offset 0, and extended boot records at offsets 1599506, 2119715 and 2639924.We've also added the ability to save the content of each filesystem. This will be done if the '-d' flag is passed at the end of the command-line:
~/mbrsave$ sudo savembrs /dev/mmcblk0 -d
fastboot flash mmc0:0x0 mbr0
# p1 16 520208 520193 Win95 FAT32
# p2 520209 1599505 1079297 Linux
# p4 7106579 7716863 610285 Win95 FAT32
fastboot flash mmc0:0x186812 mbr1599506
# p5 1599522 2119714 520193 Linux
fastboot flash mmc0:0x205823 mbr2119715
# p6 2119731 2639923 520193 Linux
fastboot flash mmc0:0x284834 mbr2639924
# p7 2639940 3160132 520193 Win95 FAT32
# ../fastboot/savembrs2: restore 6 parts here
# p1: 16.520193
fastboot flash mmc0:16 part1.0
fastboot flash mmc0:300016 part1.1
...
As mentioned above, the filesystem content will normally be constructed by the Android build tools in the form of system.img, userdata.img and recovery.img. These will need to be split into a number of parts if they exceed 140MB.Note that we don't consider the savembrs program to be a reasonable tool, but it was quick to develop and allows testing of the Fastboot code on the Nitrogen and Nitrogen53 boards. It essentially provides some early scaffolding and is easier than hand-crafting a script using dd and hexdump.
Quick aside
For those inclined to use dd and hexdump, here's a useful fragment:~$ blockdump() { sudo dd if=/dev/mmcblk0 bs=512 count=1 skip=$1 2>/dev/null | hexdump -C ; }
~$ blockdump 0
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000001b0 00 00 00 00 00 00 00 00 13 08 e1 c4 00 00 00 01 |................|
000001c0 01 00 0b 03 d0 ff 10 00 00 00 01 f0 07 00 00 03 |................|
000001d0 d0 ff 83 03 d0 ff 11 f0 07 00 01 78 10 00 00 03 |...........x....|
000001e0 d0 ff 05 03 d0 ff 12 68 18 00 01 08 54 00 00 03 |.......h....T...|
000001f0 d0 ff 0b 03 d0 ff 13 70 6c 00 ed 4f 09 00 55 aa |.......pl..O..U.|
00000200
Wrap up
This was a lengthy post, and I'm beginning to regret that whole comment about a hidden gem, but we also covered a lot of ground. The basics are also pretty simple:- You can store up to 148MB to a Nitrogen or Nitrogen53 using the fastboot flash command, and
- You can use this to program SPI-NOR, SD card or SATA devices, but
- Partition tables are tricky, and
- Filesystems will likely require splitting.
Getting Fastboot Host Tools
Todo: Find some web references for how to get and install Fastboot.Android-DLS has some notes.
Future endeavors
This article describes our overall approach to Fastboot and how we've embraced and extended the Freescale implementation for use on i.MX5x.As mentioned above, we'll be putting together some tools to make Fastboot easier to use:
- Create MBR/EMBR images and scripts to restore them
- Split filesystem images into 100MB chunks for use with Fastboot
Should we add support for getenv and setenv for U-Boot variables?