[Linux学习笔记]F1C100s将nand分区挂载为U盘实现文件互传

89 阅读6分钟

前言

针对日常工作中板卡与电脑之间需要进行频繁文件互传,而板卡却不带网口、wifi或者没装网络文件传输协议的情况下,还可以通过USB实现模拟U盘的方式进行文件传输。

先上实际效果图,板卡上的16M vfat镜像被模拟为U盘设备,此时可以很方便地在windows上编辑这个U盘里的文件。 image.png 进入F盘,往里面丢点东西 image.png 改完后回到linux,重新mount/mnt/vfat_disk,可以看到里面的内容被正常保存下来了。

image.png

实现步骤

1.配置内核

通过menuconfig,找到Device Drivers > USB Support > USB Gadget Support下的Mass storage,找到并勾选,以及USB Gadget functions configurable through configfs

image.png 这个过程中如有缺失的前置依赖项,请自行找到并打开。

2.制作disk.vfat镜像

由于U盘需要在linuxwindows之前移动且方便挂载读写,所以使用vfat文件系统,当然也可以使用其他文件系统,前提是winlinux都能识别。

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>;
    };
};

基于此分区去构建完整镜像并烧录即可。