A very expensive SD card reader/writer using i.MX6

Published on April 20, 2013

Archived Notice

This article has been archived and may contain broken links, photos and out-of-date information. If you have any questions, please Contact Us.

If you're using our Android sources, we have a script named mksdcard.sh to partition an SD card and copy the output files. This works pretty well when using a removable SD card, but doesn't address how you might do the same if you're booting to a non-removable device like eMMC or SATA. It also requires you to have an SD card reader.

Well, the i.MX6 has a USB OTG port, and the Linux kernel supports something called g_mass_storage, or USB Mass Storage Gadget. By putting the two of those together, we can make our Nitrogen6X or BD-SL-i.MX6 board look like removable storage and use normal filesystem utilities on a Linux development machine to manipulate the storage devices.

For the impatient

If you're cabled up to your development machine, you should see a new storage device auto-mount with the filesystem you created and booted. If you have multiple storage devices attached to your i.MX6 board, you should see a new device for each. Each device will correspond to one of the block devices:

  • SD card,
  • eMMC (for those of you with custom boards),
  • SATA, or
  • USB flash drive
The last one is kind of pointless, but is supported by default.

Note that when finished, you should shut down gracefully by:

  • Using unmount on the host machine, and
  • Issuing sync && reboot on the i.MX6 device.

Detailed usage:

1. Grab the image

Since all of this is constructed using open-source code, this part is simple:

~/$ wget https://commondatastorage.googleapis.com/boundarydevices.com/usbwrite-nitrogen6x-20130420.zip --2013-04-20 12:53:45-- https://commondatastorage.googleapis.com/boundarydevices.com/usbwrite-nitrogen6x-20130420.zip Resolving commondatastorage.googleapis.com (commondatastorage.googleapis.com)..., 2607:f8b0:400e:c04::84 Connecting to commondatastorage.googleapis.com (commondatastorage.googleapis.com)||:80... connected. HTTP request sent, awaiting response... 200 OK Length: 3507828 (3.3M) [application/zip] Saving to: `usbwrite-nitrogen6x-20130420.zip' 100%[======================================>] 3,507,828 1.56M/s in 2.1s 2013-04-20 12:53:48 (1.56 MB/s) - `usbwrite-nitrogen6x-20130420.zip' saved [3507828/3507828] Note that the image is really tiny (3.5MB) because we pruned the kernel a bit and used Buildroot to construct a userspace using the uClibc library and no tools beyond busybox.

2. Partition an SD card

Okay, so this step is a bit ironic, since it requires an SD card reader other than the i.MX6. It's also not technically necessary. You could certainly boot the kernel and ram disk over TFTP, though we won't cover that here.

If your SD reader shows up as /dev/sdc, you can create a single FAT partition like so:

~/$ sudo umount /media/* # just in case ~/$ echo ',,b,*' | sudo sfdisk /dev/sdc Checking that no-one is using this disk right now ... OK Disk /dev/sdc: 61168 cylinders, 4 heads, 32 sectors/track Old situation: Units = cylinders of 65536 bytes, blocks of 1024 bytes, counting from 0 Device Boot Start End #cyls #blocks Id System /dev/sdc1 * 0+ 61167 61168- 3914751+ 83 Linux /dev/sdc2 0 - 0 0 0 Empty /dev/sdc3 0 - 0 0 0 Empty /dev/sdc4 0 - 0 0 0 Empty New situation: Units = cylinders of 65536 bytes, blocks of 1024 bytes, counting from 0 Device Boot Start End #cyls #blocks Id System /dev/sdc1 * 0+ 61167 61168- 3914751+ b W95 FAT32 /dev/sdc2 0 - 0 0 0 Empty /dev/sdc3 0 - 0 0 0 Empty /dev/sdc4 0 - 0 0 0 Empty Successfully wrote the new partition table Re-reading the partition table ... If you created or changed a DOS partition, /dev/foo7, say, then use dd(1) to zero the first 512 bytes: dd if=/dev/zero of=/dev/foo7 bs=512 count=1 (See fdisk(8).) ~/$ sudo mkfs.vfat -n usbwrite /dev/sdc1 mkfs.vfat 3.0.12 (29 Oct 2011) ~/$ udisks --mount /dev/sdc1 Mounted /org/freedesktop/UDisks/devices/sdc1 at /media/usbwrite

3. Extract

Because the example above used the FAT filesystem, there's no reason to use sudo to extract the files.

~/$ unzip usbwrite-nitrogen6x-20130420.zip -d /media/usbwrite/ Archive: usbwrite-nitrogen6x-20130420.zip inflating: /media/usbwrite/6x_bootscript inflating: /media/usbwrite/uImage inflating: /media/usbwrite/uramdisk.img inflating: /media/usbwrite/buildroot.config inflating: /media/usbwrite/kernel.config ~/$ sync && sudo umount /media/usbwrite

4. Boot, Login, and Run expose-usbdisks

The RAM disk image is configured to offer a login prompt (a getty) on both the serial console (/dev/ttymxc1) and a monitor/keyboard (/dev/tty0) if present.

The only user defined is root and the password is Boundary.

Boundary Devices boundary login: root Password: # expose-usbdisks 1 drives: /dev/mmcblk0 exporting 1 drives: mmcblk0: SD: : 3823 MBytes If you have another SD card, eMMC, or SATA drive connected, you'll see additional devices listed when you run expose-usbdisks.

5. Access from dev machine

If you've connected your USB OTG port to a development machine, you'll see a new device appear and auto-mount:

~/$ mount ... /dev/sdc1 on /media/usbwrite type vfat (rw,nosuid,nodev,uid=1000,gid=1000,shortname=mixed,dmask=0077,utf8=1,showexec,flush,uhelper=udisks) Note that while the storage device on the i.MX6 machine is connected through g_mass_storage, you should not try to access it from the i.MX6 side of the link. There's no locking in place between the two sides, and filesystem corruption will occur.

6. Shut down gracefully

When done manipulating files from your dev machine, you should dismount all partitions: ~/$ sudo umount /media/* And you should reboot gracefully on the i.MX6 side: root@boundary: ~/$ sync && reboot

The details

uramdisk.img was built using Buildroot

Specifically, the RAM disk was built using buildroot-2013.02.tar.bz2 and the configuration is in the zip-file as buildroot.config.

You can repeat this like so: ~/$ wget https://buildroot.uclibc.org/downloads/buildroot-2013.02.tar.bz2 ~/$ tar jxvf buildroot-2013.02.tar.bz2 ~/$ unzip usbwrite-nitrogen6x-20130420.zip -d buildroot-2013.02 buildroot.config ~/$ cd buildroot-2013.02/ ~/buildroot-2013.02$ mv buildroot.config .config ~/buildroot-2013.02$ make

Buildroot is configured to create a gzipped cpio archive in output/images/rootfs.cpio.gz and it's ready to be wrapped for U-Boot using mkimage.

~/buildroot-2013.02$ mkimage -A arm -O linux -T ramdisk -n "Initial Ram Disk" -d output/images/rootfs.cpio.gz uramdisk.img Okay, so this is almost true. We did a little more to this image because we added the expose-usbdisks script. We did this by using the utility scripts described in our Hacking RAM disks post.

The expose-usbdisks script itself.

The expose-usbdisks script is a fairly simple shell script that determines what block devices are connected to the system by looking in /sys/class/block for mmcblk? and sd?:

#!/bin/sh cd /sys/class/block drives=`for d in * ; do echo $d ; done | grep -e 'mmcblk[01]$|sd[a-z]$'` dcount=0; dlist=''; for d in $drives ; do dlist=$dlist,/dev/$d ; dcount=`expr $dcount + 1 `; done ; dlist=${dlist:1} ; echo $dcount drives: $dlist; modprobe g_mass_storage file=$dlist; echo "exporting $dcount drives:" | tee /dev/tty0 for d in $drives ; do name=''; type=''; if [ -e /sys/class/block/$d/device/model ]; then type=`cat /sys/class/block/$d/device/vendor`; name=`cat /sys/class/block/$d/device/model`; else if [ -e /sys/class/block/$d/device/type ]; then type=`cat /sys/class/block/$d/device/type`; else type='unknown'; name=other; fi ; fi size=`cat /sys/class/block/$d/size` size=`expr $size * 512` size=`expr $size / 1048576` echo -e "t$d: $type: $name: $size MBytes" done | tee /dev/tty0 The primary purpose is to build a parameter string for the g_mass_storage kernel module as described the document describing the backing store.

The boot script.

If you've been using and modifying our boot scripts to load your kernel, you might notice that this differs somewhat from some other distributions. In our Timesys, LTIB, and Ubuntu builds, we generally use an ext3 partition as the root filesystem and don't boot to a RAM disk.

The boot script updates for this are pretty simple: we simply load the RAM disk into memory at address 0x12800000 and pass it as a second parameter to the bootm command:

${fs}load ${dtype} ${disk}:1 10800000 /uImage && ${fs}load ${dtype} ${disk}:1 12800000 /uramdisk.img && bootm 10800000 12800000 ; The source for the boot script is in /root/6x_bootscript within the RAM disk itself.


This post may seem like a lot of detail for something simple, but we hope that either the image or the description of the pieces will likely be useful to those of you dealing with how to access storage on your boards.

Let us know if you find this useful (or confusing).