USB Gadget Framework 与 USB 大容量存储(UMS)

USB Gadget Framework 是 Linux 在嵌入式领域非常强大的能力,可以让设备通过 USB 模拟成:

  • U 盘(Mass Storage)
  • USB 网络适配器(RNDIS / ECM / NCM)
  • HID 输入设备
  • 串口 ACM
  • 复合设备(组合多种功能)

本教程以 Radxa 瑞莎系列设备(如 ROCK 系列、E 系列、O 系列)为示例,展示如何在 Linux 上启用 USB Mass Storage Gadget(UMS),让设备通过 USB‑C 被主机识别为一块虚拟 U 盘。

UMS 的典型应用包括:

  • 固件量产、镜像写盘
  • 工控设备与 PC 之间的数据交换
  • 模拟 U 盘用于系统调试
  • 将 NAS 或网络镜像映射为 USB 存储设备

本文提供完整脚本、排错步骤和工程化实践,适合希望在嵌入式设备上启用 USB Gadget 的工程师参考。


一、前置要求(适用于 Radxa 官方系统)

在开始之前,请确认以下条件。

1. 内核支持 USB Gadget 与 ConfigFS

检查内核配置:

zcat /proc/config.gz | grep CONFIG_USB_CONFIGFS
zcat /proc/config.gz | grep CONFIG_USB_GADGET

关键配置必须包含:

CONFIG_USB_GADGET=y
CONFIG_USB_CONFIGFS=m 或 y

2. configfs 已挂载

检查 configfs 挂载状态:

mount | grep configfs

若未挂载:

sudo mount -t configfs none /sys/kernel/config

3. Radxa 设备的 UDC 名称

在瑞莎设备上,一般通过以下命令查看 UDC(USB Device Controller)名称:

ls /sys/class/udc

示例输出:

23000000.usb

该名称即为你的 gadget_name,后续脚本中会频繁使用。


二、加载 Gadget 所需内核模块

如果使用 Radxa 官方系统,且内核配置为:

CONFIG_USB_CONFIGFS=m

则需要手动加载模块:

sudo modprobe libcomposite

加载完成后,确认 gadget 子系统是否出现:

ls /sys/kernel/config

应包含如下目录:

usb_gadget/

三、初始化 Gadget(只需执行一次)

创建初始化脚本:

sudo nano /usr/local/sbin/gadget-init.sh

脚本内容如下:

#!/bin/bash
set -euo pipefail

GADGET_NAME=${1:-}

if [[ -z "${GADGET_NAME}" ]]; then
  echo "用法: sudo $0 <gadget_name>"
  exit 1
fi

if [[ $EUID -ne 0 ]]; then
  echo "请以 root 权限运行"
  exit 1
fi

# 确保 configfs 已挂载
if ! mountpoint -q /sys/kernel/config; then
  mount -t configfs none /sys/kernel/config
fi

# 尝试加载 configfs gadget 核心
# 在 CONFIG_USB_CONFIGFS=m 时需要这一句
modprobe libcomposite 2>/dev/null || true

# 检查 usb_gadget 子系统是否存在
if [[ ! -d /sys/kernel/config/usb_gadget ]]; then
  echo "当前内核已启用 CONFIG_USB_CONFIGFS=m,但未发现 /sys/kernel/config/usb_gadget。"
  echo "请确认 libcomposite 模块已正确加载,或检查内核模块安装是否完整。"
  exit 1
fi

G="/sys/kernel/config/usb_gadget/${GADGET_NAME}"

if [[ -d "${G}" ]]; then
  echo "Gadget 已存在:${GADGET_NAME}"
  exit 0
fi

mkdir -p "${G}"
cd "${G}"

echo 0x1d6b > idVendor
echo 0x0104 > idProduct

mkdir -p strings/0x409
echo "1234567890"        > strings/0x409/serialnumber
echo "Radxa"             > strings/0x409/manufacturer
echo "Radxa USB Gadget"  > strings/0x409/product

mkdir -p configs/r.1
mkdir -p configs/r.1/strings/0x409
echo "UMS Config" > configs/r.1/strings/0x409/configuration

echo "Gadget ${GADGET_NAME} 初始化完成。"

赋权并执行(以 23000000.usb 为例):

sudo chmod +x /usr/local/sbin/gadget-init.sh
sudo /usr/local/sbin/gadget-init.sh 23000000.usb

执行成功后,应能看到:

ls /sys/kernel/config/usb_gadget
23000000.usb

四、准备 UMS 镜像文件(可放在 NAS 上)

例如,在 Radxa 设备上挂载 NAS 共享目录:

sudo mount -t cifs -o username=user,password=pass \
  //192.168.1.100/share /mnt/nas

创建一个 8GB 镜像文件:

sudo dd if=/dev/zero of=/mnt/nas/pcdisk.img bs=1M count=8192

不要在 Radxa(Linux)一侧挂载这个镜像,镜像中的文件系统将由主机电脑独占控制。


五、核心脚本:启用 / 禁用 UMS

创建控制脚本:

sudo nano /usr/local/sbin/ums.sh

脚本内容(生产可用版本):

#!/bin/bash
set -euo pipefail

ACTION=${1:-}
GADGET_NAME=${2:-}
IMG=${3:-}

if [[ $EUID -ne 0 ]]; then
  echo "请以 root 身份运行"
  exit 1
fi

G="/sys/kernel/config/usb_gadget/${GADGET_NAME}"

detect_udc() {
  if [[ -f "${G}/UDC" ]]; then
    local cur
    cur=$(cat "${G}/UDC" 2>/dev/null || echo "")
    if [[ -n "${cur}" ]]; then
      echo "${cur}"
      return
    fi
  fi

  if [[ -e "/sys/class/udc/${GADGET_NAME}" ]]; then
    echo "${GADGET_NAME}"
    return
  fi

  local lst
  mapfile -t lst < <(ls /sys/class/udc || true)
  if [[ ${#lst[@]} -eq 1 ]]; then
    echo "${lst[0]}"
    return
  fi

  echo ""
}

disable_gadget() {
  if [[ ! -d "${G}" ]]; then
    return 0
  fi

  local cur
  cur=$(cat "${G}/UDC" 2>/dev/null || echo "")
  if [[ -n "${cur}" ]]; then
    echo "" > "${G}/UDC" 2>/dev/null || true
  fi

  rm -f "${G}/configs/r.1/mass_storage.usb1" 2>/dev/null || true
  rmdir "${G}/functions/mass_storage.usb1" 2>/dev/null || true
}

enable_gadget() {
  local UDC
  UDC=$(detect_udc)
  if [[ -z "${UDC}" ]]; then
    echo "无法确定 UDC"
    exit 1
  fi

  disable_gadget

  local loopdev
  loopdev=$(losetup -j "${IMG}" | cut -d: -f1 || true)
  if [[ -n "${loopdev}" ]]; then
    losetup -d "${loopdev}" || true
  fi

  mount | grep -q " ${IMG} " && umount "${IMG}" || true

  mkdir -p "${G}/functions/mass_storage.usb1"

  echo 0 > "${G}/functions/mass_storage.usb1/stall"
  echo 1 > "${G}/functions/mass_storage.usb1/lun.0/removable"
  echo 0 > "${G}/functions/mass_storage.usb1/lun.0/ro"
  echo 1 > "${G}/functions/mass_storage.usb1/lun.0/nofua"
  echo "${IMG}" > "${G}/functions/mass_storage.usb1/lun.0/file"

  ln -sf "${G}/functions/mass_storage.usb1" "${G}/configs/r.1/mass_storage.usb1"

  echo "${UDC}" > "${G}/UDC"
  echo "UMS 已启用"
}

case "${ACTION}" in
  enable)
    enable_gadget
    ;;
  disable)
    disable_gadget
    echo "UMS 已关闭"
    ;;
  *)
    echo "用法:"
    echo "  sudo ums.sh enable <gadget_name> <image>"
    echo "  sudo ums.sh disable <gadget_name>"
    ;;
esac

赋予执行权限:

sudo chmod +x /usr/local/sbin/ums.sh

六、启用 UMS(Radxa 设备 → 上位机)

示例命令:

sudo ums.sh enable 23000000.usb /mnt/nas/pcdisk.img

此时 Radxa 设备会模拟成一个 U 盘,主机电脑应立即识别并弹出磁盘设备。


七、关闭 UMS

  1. 在主机上先弹出 USB 设备(安全移除)。
  2. 在 Radxa 设备上执行:
sudo ums.sh disable 23000000.usb

八、常见问题排查(建议收藏)

1. 主机完全无反应

查看 USB 控制器是否处于 Device 模式:

dmesg | grep -i dwc3

期望输出中包含:

mode: peripheral

若不是 peripheral 模式,UMS 将无法工作。

2. UDC 未绑定

查看当前 gadget 绑定状态:

cat /sys/kernel/config/usb_gadget/*/UDC

输出应为你的 gadget_name,否则说明尚未正确绑定。

3. 使用了只支持充电的 USB‑C 线

请务必使用支持 USB 数据传输的线缆,而非仅支持充电的线缆。

4. 镜像被 Linux 侧占用

检查是否有 loop 设备或挂载:

losetup -j <img>
mount | grep <img>

必须确保没有任何进程或挂载点正在占用该镜像文件,否则可能导致 UMS 行为异常或数据损坏。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐