Backing Storage for the Mass Storage Gadget

Last Modified: 20 March 2013

The Mass Storage Gadget (MSG) provides support for the USB Mass Storage class. It can appear to a host as a set of up to 8 SCSI disk drives (called Logical Units often referred to as LUNs, even though it technically stands for Logical Unit Number), although most of the time a single LUN is all you will need. The information stored for each LUN must be maintained by the gadget somewhere, either in a normal file or in a block device such as a disk partition or even a ramdisk. This file or block device is called the backing storage for the gadget, and you tell MSG where the backing storage is when you load the gadget driver:
bash# modprobe g_mass_storage file=/root/data/backing_file
This command tells MSG to provide a single LUN with backing storage maintained in /root/data/backing_file. If you wanted to have two LUNs, where the second LUN used /dev/hda7 as its backing storage, you would do:
bash# modprobe g_mass_storage file=/root/data/backing_file,/dev/hda7

Under Linux 2.6, if you add "removable=y" to the modprobe line then MSG will act like a device with removable media and allow you to specify the backing storage using sysfs attributes. In fact, if you do this then you can omit the "file=..." parameter entirely. The gadget will resemble a ZIP drive with no cartridge inserted until you use sysfs to specify some backing storage.

AN IMPORTANT WARNING! While MSG 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.

Creating a backing storage file

Backing storage requires some preparation before MSG can use it. To start with, if the backing storage is a regular file then the file must be created beforehand, with its full desired size. (MSG won't create a backing storage file and won't change the size of an existing file.) In the example above, if you wanted /root/data/backing_file to represent a 64MB drive then you would have to create it using a command something like this:
bash# dd bs=1M count=64 if=/dev/zero of=/root/data/backing_file
64+0 records in
64+0 records out
This has to be done before you can load g_mass_storage, but it only has to be done once. If the backing storage is a block device or disk partition such as /dev/hda7 then you don't have to create it beforehand, because it will already exist.

Partitioning the backing storage

However, creating the backing storage isn't enough. It's like having a raw disk drive; you still need to partition the disk and install a filesystem before you can use it. (Strictly speaking you don't need to partition it. You can treat the entire drive as a single large device, like a floppy disk. This will be confusing, though, and some versions of Windows won't work with an unpartitioned USB drive.) Okay, so how do you partition the backing storage?

Answer: You create a partition table by using the fdisk program. Here's an example showing how to do it. The example assumes you will want to use the gadget with a Windows host. It's a little tricky because fdisk needs help when working with something other than an actual device. Begin by starting up fdisk and telling it the name of your backing storage. You'll get a message something like this:

bash# fdisk /root/data/backing_file
Device contains neither a valid DOS partition table, nor Sun or SGI disklabel
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that, of course, the previous
content won't be recoverable.

You must set heads sectors and cylinders.
You can do this from the extra functions menu.

Command (m for help): 

Heads, Sectors, and Cylinders

As you see, fdisk says that it needs you to set the heads, sectors, and cylinders values. (Some versions only say they need you to set the number of cylinders, but they're wrong. You can tell by the way they'll miscalculate the size of the backing file; they're using default values and ignoring the actual file size.) The numbers you use are somewhat arbitrary; the scheme shown here works okay. Give the "x" (eXpert or eXtra) command:
Command (m for help): x
Then set the number of sectors/track. g_mass_storage uses a sector size of 512 bytes, so 8 sectors/track will give us 4096 bytes per track. This is good because it matches the size of a memory page (on a 32-bit processor).
Expert command (m for help): s
Number of sectors (1-63): 8
Warning: setting sector offset for DOS compatiblity
Next set the number of heads (or tracks/cylinder). With 4 KB per track, 16 heads will give us a total of 64 KB per cylinder, which is convenient since the size of the backing file is 64 MB.
Expert command (m for help): h
Number of heads (1-256): 16
Finally set the number of cylinders. It's important that the total size you specify matches the actual size of the backing file. Since we've got 64 KB per cylinder and 64 MB total, we need to use 1024 cylinders.
Expert command (m for help): c
Number of cylinders (1-131071): 1024
Now return to the normal menu (the "r" command):
Expert command (m for help): r

Creating a primary partition

Create a new primary partition ("n" for new). Let's make it number 1. The defaults for the starting and ending cylinder are perfect because they will make the partition occupy the entire backing file, so just press Enter when asked for the First and Last cylinder:
Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
p
Partition number (1-4): 1
First cylinder (1-1024, default 1):
Using default value 1
Last cylinder or +size or +sizeM or +sizeK (1-1024, default 1024):
Using default value 1024
The new partition is created by default as a Linux partition. Since you want to use the gadget with a Windows host, you should change the partition type (the "t" command) to FAT32 (code "b"):
Command (m for help): t
Partition number (1-4): 1
Hex code (type L to list codes): b
Changed system type of partition 1 to b (Win95 FAT32)
Print out ("p") the new partition table to be sure everything's correct:
Command (m for help): p

Disk /root/data/backing_file: 16 heads, 8 sectors, 1024 cylinders
Units = cylinders of 128 * 512 bytes

                  Device Boot    Start       End    Blocks   Id  System
/root/data/backing_file1             1      1024     65532    b  Win95 FAT32
Finally write out ("w") the partition table to the backing storage:
Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.
Re-read table failed with error 25: Inappropriate ioctl for device.
Reboot your system to ensure the partition table is updated.

WARNING: If you have created or modified any DOS 6.x
partitions, please see the fdisk manual page for additional
information.
Syncing disks.

Adding a filesystem

At this point a new partition has been created but it doesn't yet contain a filesystem. The easiest way to add a filesystem is to load g_mass_storage, connect the gadget to a USB host, and use the host to do the work. With a Linux host you can run mkdosfs; with a Windows host you can double-click on the drive's icon in the "My Computer" window.

Accessing the backing storage from the gadget

It is possible to manipulate the data in the backing storage from the gadget (even to add the filesystem). Don't do this while the gadget is connected to a USB host! The key is to use the loop device driver with the "-o" (offset) option for the losetup program.

For this to work, you have to know what the partition's offset is. If you followed the partitioning scheme given above then the offset will always be 4096. If not, you can use fdisk to find the correct offset value:
# fdisk -lu /root/data/backing_file
You must set cylinders.
You can do this from the extra functions menu.

Disk data: 0 MB, 0 bytes
16 heads, 8 sectors/track, 0 cylinders, total 0 sectors
Units = sectors of 1 * 512 = 512 bytes

                   Device Boot      Start         End      Blocks   Id  System
 /root/data/backing_file1               8        8191        4092    b  Win95 FAT32
Ignore the bogus data at the top and concentrate on the table at the bottom. The number you want is the value in the "Start" column. It gives the offset in sectors; to convert to bytes you must multiply by 512. So we see that the offset is 8 x 512 = 4096 bytes.

You use the losetup program to set up the loop device driver with the proper offset:
# losetup -o 4096 /dev/loop0 /root/data/backing_file
Now /dev/loop0 is mapped to the partition within the backing storage. You can create a filesystem on it:
# mkdosfs /dev/loop0
and then you can mount it:
# mount -t vfat /dev/loop0 /mnt/loop
Now you can transfer files back and forth to your heart's content! When you're done, be sure to unmount and detach the loop device:
# umount /dev/loop0
# losetup -d /dev/loop0