Using i.MX RT1060 as the USB Mass Storage Gadget Print

 

This application note explains how to make NXP i.MX RT1060 EVK visible as a USB storage device to a USB host such as, for instance, Windows, Linux PC or notebook.

The most typical example of when this functionality is required is a "data harvesting" application. In such an application, a remote i.MX RT1060 device collects data readings from various sensors and then stores collected readings into non-volatile storage such as SD Card. Once in a while, a technician visits the remote site to offload harvested data and take it to the main site for further processing. Using the functionality described in this note, the data offloading is as simple as connecting the i.MX RT1060 as a USB device to a technician's notebook, which immediately recognizes it as USB storage. Collected data can then be simply copied from the USB storage to other disks on the notebook.

A variation of the same application is a movable device that is periodically taken to the main office for data offloading. In this scenario, the embedded device is simply connected to a PC as a USB device and data is copied from the USB storage to the host computer.


Hardware Platform

The hardware platform for this application note is NXP i.MX RT1060 EVK.


High-Level Architecture

We will use the Linux Mass Storage Gadget (MSG) to implement what we need. The main idea is that the MSG presents a partition on the SD Card as a storage device to the USB host. The backing partition is given to the Mass Storage Gadget as a parameter.

To make data sharing workable using a Windows host, the USB storage must be formatted as a FAT file system. This way, data gathered by the target into the USB storage can be copied or manipulated otherwise, using standard Windows tools on the USB host end.

The implication of the above is that the backing storage must be mounted as a FAT32 file system on the target side. This is not really a problem since Linux supports the FAT32 file system (along with many other file systems). Clearly, a file system can be mounted on a block device.

One more thing we should discuss here is the fact that the SD Card is detected late during the initialization process. Thus, when the Linux Mass Storage Gadget driver attempts to use a SD Card partition as a backstorage during init, the appropriate /dev/mmc* device file does not exist yet. As a workaround, you can:

  • either specify the backing storage file after SD Card detection (this approach is used in this application note),
  • or build a Mass Storage Gadget driver as a separate kernel module, and load it after SD Card detection.

To summarize all the above, the architecture we want to implement will be as follows:

  • The Linux Mass Storage Gadget will be used to implement a USB storage device accessible by the host software.
  • To ensure that the USB storage content survives power-cycles and resets, the backing storage will be on SD Card.
  • To allow upper-level software accessing the backing storage transparently, the backing storage will be a normal partition on SD Card.
  • The backing storage will be mounted as a FAT32 file system on both the Linux and host PC sides.

Software Platform

Perform the following in the Emcraft i.MX RT1060 BSP:

  • Activate the cross development environment:
  • [yur@ubuntu linux-cortexm-2.5.2]$ . ./ACTIVATE.sh
    [yur@ubuntu linux-cortexm-2.5.2]$ cd projects/rootfs
    [yur@ubuntu rootfs]$

  • Reconfigure busybox:
    • Run the configuration procedure:
    • [yur@ubuntu rootfs]$ make bmenuconfig

    • In the configuration menu:
      Go to the Linux System Utilities menu and enable Write support for fdisk.
  • Reconfigure the kernel:
    • Run the configuration procedure:
    • [yur@ubuntu rootfs]$ make kmenuconfig

    • In the configuration menu:
      Go to the Device Drivers -> USB support -> USB Gadget Support -> USB Gadget Drivers menu, and select Mass Storage Gadget variant.
  • Provide the USB Gadget driver with a back-storage file only after SD Card detection. The logic implemented in the /etc/rc script is already waiting for SD Card detection complete (to be able to read U-Boot environment variables), so just add the following string to the end of the projects/rootfs/etc/rc file:
  • echo "/dev/mmcblk0p2" > /sys/devices/platform/soc/soc\:aips-bus@40000000/402e0000.usb/ci_hdrc.0/gadget/lun0/file

  • Build the bootable Linux image:
  • [yur@ubuntu rootfs]$ make

Install the resultant rootfs.uImage to the target as described in Installing Linux images to the SD Card, and update the U-Boot environment as follows:

=> setenv bootargs_gadget g_mass_storage.iSerialNumber=123456 g_mass_storage.stall=0 g_mass_storage.removable=1
=> setenv bootargs console=ttyLP0,115200 ${bootargs_gadget}
=> save
Saving Environment to FAT
... writing uboot.env done
=>


Preparing USB Mass Storage

This section explains how to prepare the USB Mass Storage for deployment. This command sequence is a once-off procedure that needs to be performed on a device at software manufacturing time.

With the bootable Linux image (rootfs.uImage) installed, U-Boot loads the Linux image from the SD Card to the SDRAM and passes control to the kernel entry point.

The default installation procedure described in SD Card partitioning assumes there is a single partition on the SD Card. This partition holds rootfs.uImage, uboot.env, and some other system files. It is mounted from /etc/rc during init:

mmc0: host does not support reading read-only switch, assuming write-enable mmc0: new high speed SD card at address b368 mmcblk0: mmc0:b368 00000 1.86 GiB mmcblk0: p1

Let's create a new partition on SD Card (32 MB in this example). Before proceeding, unmount the first partition (to be able to write to the MBR then):

/ # umount /dev/mmcblk0p1 / # fdisk /dev/mmcblk0 The number of cylinders for this disk is set to 122176. There is nothing wrong with that, but this is larger than 1024, and could in certain setups cause problems with: 1) software that runs at boot time (e.g., old versions of LILO) 2) booting and partitioning software from other OSs (e.g., DOS FDISK, OS/2 FDISK) Command (m for help): u Changing display/entry units to sectors Command (m for help): p Disk /dev/mmcblk0: 2001 MB, 2001731584 bytes 4 heads, 8 sectors/track, 122176 cylinders, total 3909632 sectors Units = sectors of 1 * 512 = 512 bytes Device Boot Start End Blocks Id System /dev/mmcblk0p1 2048 22527 10240 b Win95 FAT32 Command (m for help): n Command action e extended p primary partition (1-4) p Partition number (1-4): 2 First sector (8-3909631, default 8): 22528 Last sector or +size or +sizeM or +sizeK (22528-3909631, default 3909631): +32M Command (m for help): t Partition number (1-4): 2 Hex code (type L to list codes): b Changed system type of partition 2 to b (Win95 FAT32) Command (m for help): p Disk /dev/mmcblk0: 2001 MB, 2001731584 bytes 4 heads, 8 sectors/track, 122176 cylinders, total 3909632 sectors Units = sectors of 1 * 512 = 512 bytes Device Boot Start End Blocks Id System /dev/mmcblk0p1 2048 22527 10240 b Win95 FAT32 /dev/mmcblk0p2 22528 85028 31250+ b Win95 FAT32 Command (m for help): w The partition table has been altered. Calling ioctl() to re-read partition table mmcblk0: p1 p2 / #

Now, restart the i.MX RT1060 EVK board, and let the Mass Storage Gadget run with the backing partition:

...
Machine model: NXP IMXRT1060 board
Built 1 zonelists in Zone order, mobility grouping off. Total pages: 8128
Kernel command line: console=ttyLP0,115200 g_mass_storage.iSerialNumber=123456
g_mass_storage.stall=0 g_mass_storage.removable=1
ip=192.168.1.101:192.168.1.65::255.255.255.0::eth0:off
...
/ # cat /sys/module/g_mass_storage/parameters/iSerialNumber
123456
/ # cat /sys/module/g_mass_storage/parameters/stall
N
/ # cat /sys/module/g_mass_storage/parameters/removable
Y

/ # cat /sys/devices/platform/soc/soc\:aips-bus@40000000/402e0000.usb/
ci_hdrc.0/gadget/lun0/file /dev/mmcblk0p2

Connect the NXP i.MX RT1060 EVK J9 connector to the host using an appropriate USB cable.

At this point the i.MX RT1060 should become visible as a USB storage device to the host, however the storage still needs to be formatted as a FAT32 file system to allow using it with standard host software. The formatting is done on the host using standard software. For instance, on a Windows machine, go to My Computer and then locate an icon for the new removable disk. Ask to format it and agree with anything that Windows suggests, until the storage is formatted as a FAT32 file system.


Harvesting Data on the Target

Ok, so now everything is ready to start deploying the i.MX RT1060 as a "data harvesting" device. In all probability, the commands shown below would be put into the /etc/rc start-up script by any reasonably application, but in order to make the set-up command sequence easier for understanding, let's run the required commands manually.

For the sake of running a clean test, let's power-cycle the device. As expected, Linux comes up to the shell:

...
init started: BusyBox v1.24.2 (2017-11-20 11:22:24 +0400)
/ #

Mount the back-storage partition:

~ # mount /dev/mmcblk0p2 /mnt/

Let's "harvest" some data and store what is collected into a file in the FAT32 file system. In this demo, we emulate a data stream by taking a snapshot of the system time each second:

/ # while true; do date >> /mnt/data.log; sleep 1; done

Having let the "data harvesting" run for a while, let's interrupt it (by pressing ^-C) and take a look at what data we have collected:

^C

/ # cat /mnt/data.log
Thu Jan 1 00:06:17 UTC 1970
Thu Jan 1 00:06:18 UTC 1970
...
Thu Jan 1 00:06:30 UTC 1970
Thu Jan 1 00:06:31 UTC 1970

All looks as expected, so we are ready to try accessing the FAT32 file system from a USB host. Umount the back-storage on the target side:

/ # umount /mnt/


Processing Data on the Host

Given the above setup, the host will be able to see the i.MX RT1060 as a removable disk as soon as a USB connection is made between a free port on the host and the USB OTG interface on the target. A message similar to the following will appear on the target console when connection to the host has been made:

[ 775.224899] usb 1-1.4: new high-speed USB device number 5 using ehci-pci [ 775.335394] usb 1-1.4: New USB device found, idVendor=0525, idProduct=a4a5 [ 775.335409] usb 1-1.4: New USB device strings: Mfr=3, Product=4, SerialNumber=5 [ 775.335417] usb 1-1.4: Product: Mass Storage Gadget [ 775.335423] usb 1-1.4: Manufacturer: Linux 4.5.0-00428-g34d61b4a0ecd-dirty with 402e0000.usb [ 775.335429] usb 1-1.4: SerialNumber: 123456 [ 775.417665] usb-storage 1-1.4:1.0: USB Mass Storage device detected [ 775.417872] usb-storage 1-1.4:1.0: Quirks match for vid 0525 pid a4a5: 10000 [ 775.418125] scsi2 : usb-storage 1-1.4:1.0 [ 775.418399] usbcore: registered new interface driver usb-storage [ 776.417898] scsi 2:0:0:0: Direct-Access Linux File-Stor Gadget 0405 PQ: 0 ANSI: 2 [ 776.418610] sd 2:0:0:0: Attached scsi generic sg2 type 0 [ 776.423497] sd 2:0:0:0: [sdc] 62501 512-byte logical blocks: (32.0 MB/30.5 MiB) [ 776.534112] sd 2:0:0:0: [sdc] Write Protect is off [ 776.534126] sd 2:0:0:0: [sdc] Mode Sense: 0f 00 00 00 [ 776.644186] sd 2:0:0:0: [sdc] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA [ 776.867482] sdc: [ 777.084288] sd 2:0:0:0: [sdc] Attached SCSI removable disk

Host software should recognize the newly plugged USB device as a storage device and mount it as a FAT32 file system. Data collected on the target can be copied to other host disks for further processing.

Note that the format of Windows and Unix text files differs slightly. In Windows, lines end with both the line feed and carriage return ASCII characters, but Unix uses only a line feed. As a consequence, some Windows applications will not show the line breaks in Unix-format files. Assuming that data is stored in a text file (vs a binary file) and Windows is a data processing host, Linux data harvesting applications should take care of the difference by adding a carriage return character to data logs.


Data Synchronization Issues

It is important to note that it is not safe to update the USB storage both on the target and host sides at the same time. Here is what the Mass Storage Gadget documentation has to say about this:

AN IMPORTANT WARNING! While Mass Storage Gadget is running and the gadget is connected to a USB host, that USB host will use the backing storage as a private disk drive. It will not expect to see any changes in the backing storage other than the ones it makes. Extraneous changes are liable to corrupt the filesystem and may even crash the host. Only one system (normally, the USB host) may write to the backing storage, and if one system is writing that data, no other should be reading it. The only safe way to share the backing storage between the host and the gadget's operating system at the same time is to make it read-only on both sides.

Given that the primary use of this technology is to let the host copy harvested data for further processing, this should be an acceptable limitation for most applications. It is important to understand however that the host will see the USB storage in a state it was in when the USB cable was connected between the target and the host. Further updates made to the storage by the target will not be visible on the host until a next plug-in.

As mentioned above, updates made on the host side may be unsafe in case the target continues harvesting data when the host is connected. Probably, the best strategy is to assume a read-only mount of the USB storage on the host side and let application code on the target take care of purging data at appropriate times. Note the Mass Storage Gadget provides a special flag (g_mass_storage.ro=1) to allows mounting the USB storage for read-only access by the host.