Skip to content

Raspberry Pi USB OTG Mass-Storage Gadget

A Raspberry Pi USB mass-storage gadget appears to a host computer as a flash drive backed by an image file or block device on the Pi. The critical rule is that the host and Raspberry Pi must not mount the same writable filesystem at the same time. Two independent writers can corrupt it.

If your goal is SSH or file transfer over a network, use the supported Trixie USB Gadget Mode for SSH instead. Mass-storage emulation is for appliances, test fixtures, virtual media, and controlled file-exchange workflows.

Choose the safer design

Requirement Recommended design
SSH and normal file transfer USB Ethernet Gadget + SSH/SFTP
Host must see a removable drive File-backed mass-storage gadget
Host only needs fixed content Read-only mass-storage gadget
Both Pi and host need live writable access Do not use one shared filesystem; use networking or an application protocol
Production appliance Use configfs/systemd with explicit attach/detach state and recovery testing

Start with a disposable image file, not a physical partition containing important data.

Hardware and port requirements

Use a Raspberry Pi and port capable of USB device/peripheral mode. Pi Zero, Zero W, and Zero 2 W are common test targets; use the micro-USB port labelled USB, not PWR IN.

Newer boards can have dual-role USB-C ports, but power, controller, boot configuration, and peripheral trade-offs differ. Validate the exact model and port before deployment.

Requirements:

  • USB data cable, not charge-only cable
  • Stable power arrangement
  • Raspberry Pi OS with the dwc2 peripheral controller available
  • Root privileges to load the gadget driver
  • Disposable backing image for initial testing
  • Local recovery access

Architecture

1
2
3
4
5
6
7
8
9
Host computer
    │ USB Mass Storage protocol
Linux USB gadget driver on Raspberry Pi
/srv/usb-gadget/disk.img
    └── FAT filesystem mounted by either host OR Pi, never both writable

The image file is not a synchronisation layer. The kernel exports its blocks to the host, which expects exclusive control of filesystem metadata and caches.

Step 1: enable peripheral mode

Back up the firmware configuration:

sudo cp -a /boot/firmware/config.txt \
  /boot/firmware/config.txt.backup-$(date +%F)

Add the device-controller overlay:

dtoverlay=dwc2,dr_mode=peripheral

Reboot and confirm the controller/driver state:

sudo reboot

After reconnecting:

lsmod | grep dwc2
journalctl -b -k --no-pager | grep -Ei 'dwc2|usb|gadget'

Do not add multiple conflicting gadget frameworks at boot. The modern Trixie rpi-usb-gadget Ethernet workflow and a manual mass-storage function both need control of the USB device controller.

Step 2: create a disposable backing image

Install FAT tools and create a sparse 1 GiB image:

1
2
3
4
5
sudo apt update
sudo apt install -y dosfstools
sudo install -d -m 0750 /srv/usb-gadget
sudo truncate -s 1G /srv/usb-gadget/disk.img
sudo mkfs.vfat -F 32 -n PI_GADGET /srv/usb-gadget/disk.img

This example puts one FAT filesystem directly in the image without a partition table. That is easy to test across common host operating systems.

Confirm its type:

file /srv/usb-gadget/disk.img

Step 3: add initial files on the Pi

The image is not yet exported, so mount it locally:

1
2
3
4
5
6
sudo install -d /mnt/usb-gadget
sudo mount -o loop /srv/usb-gadget/disk.img /mnt/usb-gadget
printf '%s\n' 'Raspberry Pi USB gadget test' | \
  sudo tee /mnt/usb-gadget/README.txt
sync
sudo umount /mnt/usb-gadget

Verify it is unmounted before exporting:

findmnt /srv/usb-gadget/disk.img
losetup -j /srv/usb-gadget/disk.img

findmnt should show no active filesystem mount.

Step 4: export read-only first

Load the mass-storage gadget with the backing image read-only:

1
2
3
4
5
sudo modprobe g_mass_storage \
  file=/srv/usb-gadget/disk.img \
  removable=1 \
  ro=1 \
  stall=0

Connect the data cable to the correct device-mode port. The host should detect a drive named PI_GADGET.

Inspect the Pi log:

journalctl -b -k --no-pager | tail -100
lsmod | grep g_mass_storage

Read-only mode proves enumeration and file visibility without allowing the host to change the filesystem.

Step 5: detach safely

Eject the drive in the host operating system first. Then remove the gadget function on the Pi:

sudo modprobe -r g_mass_storage

Confirm the module is gone before mounting the image locally:

lsmod | grep g_mass_storage || true
sudo mount -o loop /srv/usb-gadget/disk.img /mnt/usb-gadget

After local changes:

sync
sudo umount /mnt/usb-gadget

Then export again.

Writable host access

Only enable writable export after the read-only workflow is reliable:

1
2
3
4
5
sudo modprobe g_mass_storage \
  file=/srv/usb-gadget/disk.img \
  removable=1 \
  ro=0 \
  stall=0

While this module is active, do not mount the image on the Pi. Even a read-only local mount can produce confusing cache behaviour; use a strict attach/detach state machine.

Before detaching:

  1. Stop applications writing to the host drive.
  2. Eject/unmount it on the host.
  3. Wait for the host operation to finish.
  4. Remove g_mass_storage on the Pi.
  5. Run a filesystem check before local reuse when the host disconnected unexpectedly.
sudo fsck.vfat -n /srv/usb-gadget/disk.img

Use -n for a non-modifying first inspection. Back up the image before attempting repair.

Read-only appliance pattern

For installers, documentation, or fixed configuration payloads, keep the exported image read-only and rebuild it offline when content changes. This avoids ambiguous ownership and greatly reduces corruption risk.

Workflow:

Detach gadget → mount image on Pi → update → unmount → verify → export read-only

Record a checksum after building:

sha256sum /srv/usb-gadget/disk.img | \
  sudo tee /srv/usb-gadget/disk.img.sha256

Do not export the active root filesystem

Never point g_mass_storage at a mounted root partition or another filesystem the Pi is actively using. A host OS may write partition metadata, indexing files, recycle-bin data, or repair information without an obvious prompt.

Use an isolated image or dedicated block device with explicit ownership transitions.

Automatic startup considerations

Do not automate the gadget until manual attach, host eject, detach, filesystem check, and recovery all work. A production service should:

  • Refuse to attach if the image is locally mounted.
  • Refuse local mounting while the gadget is attached.
  • Log every state transition.
  • Default to read-only after an unclean shutdown.
  • Stop cleanly before power-off.
  • Expose health/status separately from the mass-storage filesystem.

Configfs supports composite and more controlled gadgets, but increases implementation responsibility. Use it when you need multiple functions or stable descriptors, not merely because an old tutorial uses it.

Host operating-system behaviour

Windows, macOS, and Linux can all add metadata to writable removable media. Examples include indexing databases, recycle-bin directories, or desktop metadata. Design the application to ignore unrelated files and never assume that only your payload changes.

Host write caching also means “copy complete” is not the same as “safe to unplug.” Require a proper eject.

Troubleshooting

The host sees no USB device

Check:

1
2
3
lsmod | grep -E 'dwc2|g_mass_storage'
journalctl -b -k --no-pager | grep -Ei 'dwc2|gadget|usb'
vcgencmd get_throttled

Confirm the data cable, correct device-mode port, board support, peripheral-mode overlay, and adequate power.

modprobe: FATAL: Module g_mass_storage is in use

The host may still have the gadget open or another function owns the controller. Eject on the host, disconnect if necessary, inspect mounts and modules, then detach deliberately.

The host asks to format the drive

Do not accept immediately. Detach safely and inspect the image:

file /srv/usb-gadget/disk.img
sudo fsck.vfat -n /srv/usb-gadget/disk.img

Confirm that you exported the correct file and that local/host concurrent access never occurred.

Files disappear or the filesystem becomes corrupt

The likely causes are concurrent writers, host caching, cable/power interruption, or exporting the wrong backing object. Restore the known-good image and redesign the ownership transition before trying again.

USB Ethernet stopped working

Only one gadget configuration may own the controller. Detach mass storage before enabling rpi-usb-gadget, and avoid configuring both automatically at boot.

FAQ

Can the host and Pi write the image at the same time?

No. Standard FAT/ext filesystems do not coordinate two independent kernels writing the same block device. Use USB networking and a file-transfer protocol for simultaneous access.

Which filesystem should I use?

FAT32 is a common compatibility baseline for a small cross-platform image. It lacks Unix permissions and has file-size limitations. Choose based on host support and payload requirements.

Can I export a physical USB drive instead of a file?

Technically yes, but the exclusive-ownership rule remains and the consequence of a mistake is larger. Prove the workflow with a disposable file image first.

Is g_mass_storage the same as rpi-usb-gadget?

No. g_mass_storage exposes storage blocks. The current Raspberry Pi OS package focuses on USB Ethernet networking for SSH and optional host internet sharing.