September 20, 2014

U-Boot USB Mass Storage gadget

Thanks to the efforts of many in the U-Boot community, the upcoming v2014.07 release of U-Boot for our boards will include a new feature: the USB Mass Storage Gadget, or ums. Since this feature adds a major piece of functionality, we’ll describe its operation and our usage of it in this post.

To begin with, the purpose of this feature is to expose the storage of a block device on a board over USB gadget. In other words, this feature allows you to turn a board into a virtual USB stick in a very similar manner to what we described in a previous post.

The earlier post involved a lot more software though. In order to use it, you were required to have a specially-compiled kernel and RAM disk, and a mechanism for loading and launching said kernel over the network or some form of storage.

That’s a lot of stuff, and requires quite a bit of effort both to configure and to use. In previous U-Boot releases, we’ve included some tools such as the usbrecover command to make it easier, but integrating this into U-Boot makes things even easier.

Usage


Usage is straightforward:

U-Boot > ums 0 mmc 0

The ums command invokes the USB Mass Storage function. The second parameter specifies the USB controller, and the remaining arguments choose the block device.

After ums is invoked, you should see a new drive appear if you’re connected to the USB OTG port on one of our boards. You can use this to download and burn a new image, or if the drive has previously been formatted, you can copy files back and forth.

Since this feature is immediately available, we have used this for both. Because of the
connection-oriented nature (no network setup needed), it can much faster to use ums than to copy files using adb under Android or SSH under another Linux distribution.

We’ve tested against Alex Page’s USB Image Tool under Windows, and dd under Linux, and it has proven very reliable.

Differences


There are differences between this and the Kernel/RAM disk approach.

In particular:

  1. ums only supports a single block device, so we’ll need to determine which to use when invoking it, and
  2. It’s slower. U-Boot is a single-tasker and doesn’t support asynchronous writes to block devices, so ums will toggle between USB transfers and disk transfers. Our testing shows that this results in transfer times that are 3x longer.

To put the speed into perspective, we’ve seen transfer times of ~30 minutes for a 3600 MiB Ubuntu Trusty image written to SD card.

Writing ~150 MiB of an Android image through the mksdcard.sh script takes around 6 minutes. That script uses normal mkfs commands to create the filesystem and cp to copy files, so many fewer writes are needed.

This is not great performance, but still very usable.

Configuration


Okay, so the ums feature is present, and easy to use. Now how and when do we invoke it?

The use case that drives this feature is internal storage. Our Nitrogen6_Max board has on-board eMMC, which can’t be connected to a PC for initialization, and invoking ums is a natural way to expose it.

We also have customers using SATA drives on our Nitrogen6x and BD-SL-i.MX6 boards, and copying images to them can be inconvenient.

Based on this, we’ve decided to invoke ums as a part of our standard bootcmd variable as a fall-back if the normal boot fails.

As you may be aware, we currently iterate through available boot devices with a couple of nested for loops, more or less as follows:

for dtype in sata mmc usb ; do 
    for disk in 0 1 ; do
        try to load and source 6x_bootscript

If no 6x_bootscript is found, or if execution of the script fails, we put error messages and hints about how to fix the issue on the display.

setenv stdout serial,vga
echo ; echo 6x_bootscript not found
echo ; echo serial console at 115200, 8N1
echo details at http://boundarydevices.com/6q_bootscript
setenv stdin serial,usbkbd

These commands are only executed if no bootable storage was found, so it’s reasonable to invoke ums to allow it to be fixed.

This is what we’ve done, but the details of how may not be obvious. The for loops above define the boot priority and are implemented via the bootdevs variable.

The boot priority really isn’t what we want when determining what block device to expose though. Instead, we’ve chosen to invert the order of iteration of the drives. This has the effect of choosing the non-removable mmc interface (eMMC) first.

for dtype in sata mmc ; do
    for disk in 1 0 ; do
        ums 0 $dtype $disk

On boards with an on-board eMMC, that interface is chosen before SATA, since it is the least accessible.

Since the ums command will fail if a drive is not populated, this will choose any form of storage if only a single block device is connected. In practice, none of our boards currently contains more than one non-removable block device, unless you consider SATA non-removable.

Please note that this is only the default behavior. The bootcmd variable can and often should be over-ridden to perform precisely once you’ve decided how you’d like your board to behave. Our aim is to provide the best introductory use of U-Boot for the general case.

Availability


The sources are currently available in the staging branch of our Github tree, and we’ll be publishing a new set of production binaries in coming days.

As always, let us know when you have a chance to try this out. We’re very happy that the feature is now present, and think that this fills a much needed gap in functionality.