前言
针对日常工作中板卡与电脑之间需要进行频繁文件互传,而板卡却不带网口、wifi或者没装网络文件传输协议的情况下,还可以通过USB实现模拟U盘的方式进行文件传输。
先上实际效果图,板卡上的16M vfat镜像被模拟为U盘设备,此时可以很方便地在windows上编辑这个U盘里的文件。
进入F盘,往里面丢点东西
改完后回到
linux,重新mount到/mnt/vfat_disk,可以看到里面的内容被正常保存下来了。
实现步骤
1.配置内核
通过menuconfig,找到Device Drivers > USB Support > USB Gadget Support下的Mass storage,找到并勾选,以及USB Gadget functions configurable through configfs。
这个过程中如有缺失的前置依赖项,请自行找到并打开。
2.制作disk.vfat镜像
由于U盘需要在linux和windows之前移动且方便挂载读写,所以使用vfat文件系统,当然也可以使用其他文件系统,前提是win和linux都能识别。
dd if=/dev/zero of=disk.vfat bs=1M count=16
mkdosfs disk.vfat
制作完后可以用file命令查看文件系统格式
file disk.vfat
输出以下内容
disk.vfat: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", sectors/cluster 4, reserved sectors 4, root entries 512, sectors 32768 (volumes <=32 MB), Media descriptor 0xf8, sectors/FAT 32, sectors/track 32, serial number 0xa1541dc2, unlabeled, FAT (16 bit)
说明格式化成功,当然也可以mount命令查看文件系统里的内容,不过现在当然是空的。
3.通过configfs配置usb mass storage gadget
这里直接给出脚本,具体过程请看注释:
mount_usb_mass_storage_to_pc.sh,执行后自动将disk.vfat挂载到pc此时可以通过电脑读写文件。
#!/bin/sh
# ================================================
# USB Mass Storage Gadget Setup Script
# This script prepares a USB gadget using configfs
# and exposes a local disk image as a USB storage device.
#
# Key functions:
# - Ensure a loop device is attached to the disk image
# - Ensure configfs & gadget directories exist
# - Create USB gadget with mass_storage function
# - Bind to the UDC controller
#
# Notes:
# - The loop device is managed manually to avoid
# "auto-created multiple loop devices" caused by mount
# - A mounted filesystem cannot be written while exported
# as USB storage (host PC takes ownership)
# Author: Pan
# ================================================
# ---------------- User Configurable Parameters ----------------
DEBUG=0
USB_GADGET_NAME=tp_storage
DISK_IMAGE=/root/app/disk.vfat
MOUNT_POINT=/mnt/vfat_disk
UDC_SPEC=/sys/class/udc/musb-hdrc.1.auto
USB_GADGET_SERIAL_NUM="1234567890"
USB_GADGET_SERIAL_MANUFACTURE="JIADI Technology"
USB_GADGET_SERIAL_PRODUCT="TP"
# ---------------- Utility Logging Helpers ----------------
debug_log()
{
if [ "$DEBUG" -eq 1 ]; then
echo "[INFO] $1"
fi
}
error_log()
{
echo "[ERROR] $1"
}
warning_log()
{
echo "[WARNING] $1"
}
# ---------------- Derived Paths ----------------
CONFIGFS_PATH=/sys/kernel/config
CONFIGFS_GADGET_PATH=$CONFIGFS_PATH/usb_gadget
GADGET_PATH=$CONFIGFS_GADGET_PATH/$USB_GADGET_NAME
MS_FUNC="$GADGET_PATH/functions/mass_storage.0"
# ---------------- Debug Variable Dump ----------------
print_var_test()
{
debug_log "$CONFIGFS_PATH"
debug_log "$CONFIGFS_GADGET_PATH"
debug_log "$GADGET_PATH"
debug_log "$UDC_SPEC"
debug_log "$MS_FUNC"
debug_log "$DISK_IMAGE"
debug_log "$MOUNT_POINT"
debug_log "$UDC"
}
# ============================================================
# Ensure disk image is attached to a loop device (reusable)
#
# This avoids mount automatically creating new /dev/loopX
# and guarantees that the same loop device will be used.
# ============================================================
check_img_losetup()
{
# Search whether the disk image is already attached
LOOPDEV_PATH=$(losetup -a | grep "$DISK_IMAGE" | cut -d: -f1)
debug_log "Loop dev:$LOOPDEV_PATH"
if [ -n "$LOOPDEV_PATH" ]; then
debug_log "$DISK_IMAGE is already attached to $LOOPDEV_PATH"
else
# Find a free loop device
LOOPDEV_PATH=$(losetup -f)
if [ -z "$LOOPDEV_PATH" ]; then
error_log "No free loop device available"
exit 1
fi
# Attach the image
losetup "$LOOPDEV_PATH" "$DISK_IMAGE" || {
error_log "Failed to attach $DISK_IMAGE to $LOOPDEV_PATH"
exit 1
}
debug_log "Attached $DISK_IMAGE to $LOOPDEV_PATH"
fi
}
# ============================================================
# Check if disk image is mounted locally; unmount if needed.
#
# IMPORTANT:
# - The image MUST NOT be mounted locally while being exported
# via USB Mass Storage, otherwise it will become read-only
# or corrupted.
# ============================================================
check_disk_mounted()
{
if mount | grep -q "$MOUNT_POINT"; then
warning_log "$MOUNT_POINT is currently mounted, unmounting..."
umount "$MOUNT_POINT" || {
error_log "Failed to unmount $MOUNT_POINT"
exit 1
}
debug_log "Unmounted $MOUNT_POINT successfully"
fi
}
# ============================================================
# Ensure configfs is mounted (required for USB gadget)
# ============================================================
check_configfs_mounted()
{
if ! grep -q configfs /proc/mounts ; then
debug_log "Mounting configfs"
mount -t configfs none /sys/kernel/config || {
error_log "Failed to mount configfs"
exit 1
}
fi
}
# ============================================================
# Validate usb_gadget directory exists in configfs
# ============================================================
check_configfs_gadget_path_existed()
{
if [ ! -d "$CONFIGFS_GADGET_PATH" ]; then
error_log "Usb gadget configfs not exist, please check your kernel configure"
exit 1
fi
}
# ============================================================
# Create gadget directory (remove old gadget if exists)
# ============================================================
check_gadget_path_existed()
{
if [ -d "$GADGET_PATH" ]; then
warning_log "Gadget already exists: $GADGET_PATH"
cleanup_gadget
fi
debug_log "Creating new gadget device"
mkdir -p "$GADGET_PATH"
}
# ============================================================
# Pick/validate the UDC controller
# ============================================================
check_udc_existed_and_pick()
{
if [ ! -n "$UDC_SPEC" ]; then
if [ -e "$UDC_SPEC" ]; then
UDC_NAME=$(basename "$UDC_SPEC")
else
if [ -d "/sys/class/udc/$UDC_SPEC" ]; then
UDC_NAME="$UDC_SPEC"
else
error_log "Specified UDC '$UDC_SPEC' not found"
exit 1
fi
fi
else
# Pick first UDC automatically
UDC_NAME=$(ls /sys/class/udc 2> /dev/null | head -n 1 || true)
if [ -z "$UDC_NAME" ]; then
error_log "No UDC (usb device controller) found under /sys/class/udc"
exit 1
fi
fi
debug_log "Using UDC: $UDC_NAME"
}
# ============================================================
# Cleanup existing gadget (unbind + delete directories)
# ============================================================
cleanup_gadget()
{
debug_log "Cleaning USB gadget..."
#step 1: unbind UDC
if [ -f "$GADGET_PATH/UDC" ]; then
echo "" > "$GADGET_PATH/UDC"
debug_log "Unbound UDC"
fi
#step 2: remove mass_storage.0 from config c.1
if [ -L "$GADGET_PATH/configs/c.1/mass_storage.0" ]; then
rm "$GADGET_PATH/configs/c.1/mass_storage.0"
debug_log "Removed function link mass_storage.0"
fi
#step 3: remove config strings
if [ -d "$GADGET_PATH/configs/c.1/strings/0x409" ]; then
rmdir "$GADGET_PATH/configs/c.1/strings/0x409"
debug_log "Removed config strings 0x409"
fi
#step 4: remove config c.1
if [ -d "$GADGET_PATH/configs/c.1" ]; then
rmdir "$GADGET_PATH/configs/c.1"
debug_log "Removed config c.1"
fi
#step 5: remove mass_storage function
if [ -d "$MS_FUNC" ]; then
rmdir "$MS_FUNC"
debug_log "Removed mass_storage.0"
fi
#step 6: remove gadget strings
if [ -d "$GADGET_PATH/strings/0x409" ]; then
rmdir "$GADGET_PATH/strings/0x409"
debug_log "Removed gadget strings 0x409"
fi
#step 7: remove gadget root
if [ -d "$GADGET_PATH" ]; then
rmdir "$GADGET_PATH"
debug_log "Removed gadget root directory"
fi
debug_log "Old gadget removed done"
}
# ============================================================
# Basic USB descriptor configuration
# ============================================================
gadget_config_basic()
{
echo "0x1d6b" > "$GADGET_PATH/idVendor" # Linux Foundation VID
echo "0x0104" > "$GADGET_PATH/idProduct"
echo "0x0100" > "$GADGET_PATH/bcdDevice"
echo "0x0200" > "$GADGET_PATH/bcdUSB"
mkdir -p "$GADGET_PATH/strings/0x409"
echo $USB_GADGET_SERIAL_NUM > "$GADGET_PATH/strings/0x409/serialnumber"
echo $USB_GADGET_SERIAL_MANUFACTURE > "$GADGET_PATH/strings/0x409/manufacturer"
echo $USB_GADGET_SERIAL_PRODUCT > "$GADGET_PATH/strings/0x409/product"
}
# ============================================================
# Create mass_storage function & attach loop device
# ============================================================
create_mass_storage_function()
{
debug_log "allocing new mass_storage device"
mkdir -p "$MS_FUNC" || {
error_log "Failed to alloc new mass_storage device"
exit 1
}
debug_log "allocing lun to mass_storage device"
mkdir -p "$MS_FUNC/lun.0" || {
error_log "Failed to alloc lun to mass_storage device"
exit 1
}
if [ ! -e "$LOOPDEV_PATH" ]; then
error_log "Backing file/device $LOOPDEV_PATH not found"
error_log "Please create or set LOOPDEV_PATH to a valid file/device before binding."
exit 1
fi
#setting lun propeties
echo "$LOOPDEV_PATH" > "$MS_FUNC/lun.0/file"
echo 1 > "$MS_FUNC/lun.0/removable"
# echo 0 > "$MS_FUNC/lun.0/ro"
echo 1 > $MS_FUNC/stall
}
# ============================================================
# Create config and link mass_storage function
# ============================================================
create_config_and_link()
{
mkdir -p "$GADGET_PATH/configs/c.1"
mkdir -p "$GADGET_PATH/configs/c.1/strings/0x409"
echo "Config 1: Mass Storage" > "$GADGET_PATH/configs/c.1/strings/0x409/configuration"
debug_log "Create soft link at $GADGET_PATH/configs/c.1/"
ln -s "$GADGET_PATH/functions/mass_storage.0" "$GADGET_PATH/configs/c.1/" || true
}
# ============================================================
# Bind gadget to UDC controller
# ============================================================
bind_udc()
{
if [ -f "$GADGET_PATH/UDC" ]; then
CURRENT=$(cat "$GADGET_PATH/UDC" || true)
if [ -n "$CURRENT" ]; then
echo "unbinding current UDC: $CURRENT"
echo "" > "$GADGET_PATH/UDC"
fi
else
error_log "$GADGET_PATH/UDC unexist"
exit 1
fi
echo "$UDC_NAME" > "$GADGET_PATH/UDC"
debug_log "Gadget bound to UDC: $UDC_NAME"
debug_log "USB Mass Storage Gadget started"
}
#===========main()===================
check_img_losetup
check_disk_mounted
check_configfs_mounted
check_configfs_gadget_path_existed
check_gadget_path_existed
check_udc_existed_and_pick
gadget_config_basic
create_mass_storage_function
create_config_and_link
bind_udc
mount_vfat_disk_to_local.sh,执行后自动将disk.vfat挂载到板卡上的/mnt/vfat_disk目录,此时板卡设备可以读写文件。
#!/bin/sh
# ============================================================
# Mount the disk image locally as VFAT (read/write)
#
# NOTES:
# - The loop device must already be attached (check_img_losetup)
# - Do not mount while exported via USB to host,
# or host may see it as read-only.
# Author: Pan
# ============================================================
# ---------------- User Configurable Parameters ----------------
DEBUG=0
DISK_IMAGE=/root/app/disk.vfat
MOUNT_POINT=/mnt/vfat_disk
GADGET_PATH=/sys/kernel/config/usb_gadget/tp_storage
MS_FUNC="$GADGET_PATH/functions/mass_storage.0"
# ---------------- Utility Logging Helpers ----------------
debug_log()
{
if [ "$DEBUG" -eq 1 ]; then
echo "[INFO] $1"
fi
}
error_log()
{
echo "[ERROR] $1"
}
warning_log()
{
echo "[WARNING] $1"
}
# ---------------- Debug Variable Dump ----------------
print_var_test()
{
debug_log "$DISK_IMAGE"
debug_log "$MOUNT_POINT"
debug_log "$GADGET_PATH"
debug_log "$MS_FUNC"
}
# ============================================================
# Cleanup existing gadget (unbind + delete directories)
# ============================================================
cleanup_gadget()
{
debug_log "Cleaning USB gadget..."
#step 1: unbind UDC
if [ -f "$GADGET_PATH/UDC" ]; then
echo "" > "$GADGET_PATH/UDC"
debug_log "Unbound UDC"
fi
#step 2: remove mass_storage.0 from config c.1
if [ -L "$GADGET_PATH/configs/c.1/mass_storage.0" ]; then
rm "$GADGET_PATH/configs/c.1/mass_storage.0"
debug_log "Removed function link mass_storage.0"
fi
#step 3: remove config strings
if [ -d "$GADGET_PATH/configs/c.1/strings/0x409" ]; then
rmdir "$GADGET_PATH/configs/c.1/strings/0x409"
debug_log "Removed config strings 0x409"
fi
#step 4: remove config c.1
if [ -d "$GADGET_PATH/configs/c.1" ]; then
rmdir "$GADGET_PATH/configs/c.1"
debug_log "Removed config c.1"
fi
#step 5: remove mass_storage function
if [ -d "$MS_FUNC" ]; then
rmdir "$MS_FUNC"
debug_log "Removed mass_storage.0"
fi
#step 6: remove gadget strings
if [ -d "$GADGET_PATH/strings/0x409" ]; then
rmdir "$GADGET_PATH/strings/0x409"
debug_log "Removed gadget strings 0x409"
fi
#step 7: remove gadget root
if [ -d "$GADGET_PATH" ]; then
rmdir "$GADGET_PATH"
debug_log "Removed gadget root directory"
fi
debug_log "Old gadget removed done"
}
# ============================================================
# remove old gadget if exists
# ============================================================
cleanup_gadget_if_exists()
{
if [ -d "$GADGET_PATH" ]; then
warning_log "Gadget exists: $GADGET_PATH"
cleanup_gadget
fi
}
# ============================================================
# Ensure disk image is attached to a loop device (reusable)
#
# This avoids mount automatically creating new /dev/loopX
# and guarantees that the same loop device will be used.
# ============================================================
check_img_losetup()
{
LOOPDEV_PATH=$(losetup -a | grep "$DISK_IMAGE" | cut -d: -f1)
debug_log "Loop dev:$LOOPDEV_PATH"
if [ -n "$LOOPDEV_PATH" ]; then
debug_log "$DISK_IMAGE is already attached to $LOOPDEV_PATH"
else
LOOPDEV_PATH=$(losetup -f)
if [ -z "$LOOPDEV_PATH" ]; then
error_log "No free loop device available"
exit 1
fi
losetup "$LOOPDEV_PATH" "$DISK_IMAGE" || {
error_log "Failed to attach $DISK_IMAGE to $LOOPDEV_PATH"
exit 1
}
debug_log "Attached $DISK_IMAGE to $LOOPDEV_PATH"
fi
}
# ============================================================
# mount disk image to mount point
# ============================================================
mount_vfat()
{
if [ -z "$LOOPDEV_PATH" ]; then
error_log "LOOPDEV_PATH is empty, check_img_losetup() not executed?"
exit 1
fi
if mount | grep -q "$LOOPDEV_PATH"; then
warning_log "$LOOPDEV_PATH is already mounted, unmounting..."
umount "$LOOPDEV_PATH" || {
error_log "Failed to unmount $LOOPDEV_PATH"
exit 1
}
debug_log "Unmounted $LOOPDEV_PATH"
fi
mkdir -p "$MOUNT_POINT"
mount -t vfat "$LOOPDEV_PATH" "$MOUNT_POINT" || {
error_log "Failed to mount $LOOPDEV_PATH to $MOUNT_POINT"
exit 1
}
echo "[INFO] Mounted $LOOPDEV_PATH to $MOUNT_POINT"
}
# ========== Main ==========
cleanup_gadget_if_exists
check_img_losetup
mount_vfat
需要注意的是,同一个镜像最好不要在挂载到PC的情况下又同时挂载到板卡下的某一目录,因为vfat文件系统不支持多主机,文件操作必须互斥实现,否则可能损坏vfat镜像的文件系统格式。因此正确的做法应当是把disk.vfat当作一个虚拟的U盘来使用,需要和电脑之间交互时调用mount_usb_mass_storage_to_pc.sh,需要和板卡交互时调用mount_vfat_disk_to_local.sh。
4.采用disk.vfat镜像分区替代disk.vfat镜像文件
实际项目中常用的做法是在存储介质中划定指定大小的分区,在该分区中放有指定格式的文件系统镜像,后续挂载时只需挂载分区块设备名即可,此处给出设备树的分区示例:
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "u-boot";
reg = <0x000000 0x100000>; //1M
read-only;
};
partition@1 {
label = "dtb";
reg = <0x100000 0x100000>; //1M
read-only;
};
partition@2 {
label = "kernel";
reg = <0x200000 0x500000>; //5M
read-only;
};
partition@3 {
label = "rootfs";
reg = <0x700000 0x6900000>; //105M
};
partition@4 { //16M
label = "disk.vfat";
reg = <0x7000000 0x1000000>;
};
};
基于此分区去构建完整镜像并烧录即可。