T113-S3模拟成摄像头

USB Gadget

Posted by LXG on May 15, 2025

Linux USB开发指南

基本思路

T113-S3 主板端(Device)充当 USB 设备

  • 启用 USB Device Controller(UDC);
  • 加载 g_webcam 模拟 UVC 摄像头;
  • 将数据源(比如来自本地摄像头或文件)通过 v4l2loopback 注入;
  • 最终通过 USB 提供视频数据给主机端(Host PC/Android 识别为摄像头设备)。

安卓主机端(Host)识别为摄像头

  • 插上 USB 后自动识别为标准摄像头

简化后的流程图


T113-S3:
[Image Source] → /dev/video10 → v4l2loopback → /dev/video0 → g_webcam → USB Gadget

连接线:
T113 USB0 (OTG Device) <==> RK3568 USB-Host

RK3568:
通过 USB 识别为 UVC 摄像头 → /dev/videoX

确保 USB 接口支持 Device 模式


sh-4.4# ls /sys/class/udc/
4100000.udc-controller

配置内核(Buildroot + 外部内核)


CONFIG_USB_G_WEBCAM=y        # Gadget 摄像头支持
CONFIG_USB_LIBCOMPOSITE=y    # 必须支持 composite gadget 框架
CONFIG_USB_MUSB_HDRC=y       # 全志 USB 控制器
CONFIG_USB_MUSB_GADGET=y

以上配置后无法进入串口终端,修改成CONFIGFS方式


# CONFIG_USB_G_WEBCAM=y  不再需要
CONFIG_USB_CONFIGFS_F_UVC=y

CONFIG_USB_G_WEBCAM=y 与 configfs 脚本方式对比

对比项 内核直接加载(g_webcam=y) configfs 脚本加载(推荐)
启动安全性 ❌ 容易卡死串口/系统 ✅ 启动后再加载,安全
串口可用性 ❌ 可能导致 ttyS0 卡死或无输出 ✅ 串口不会受影响
参数可调性(分辨率、描述符) ❌ 固定参数 ✅ 可自定义分辨率、帧率、VID/PID 等
视频源灵活性 ❌ 默认由内核控制 ✅ 可绑定任意 /dev/videoX
热插拔支持 ❌ 不支持热插拔 ✅ 支持卸载/重新绑定 gadget
复合设备支持(如串口+摄像头) ❌ 需要重新编译 gadget 驱动 ✅ 支持 gadget 多功能组合
调试友好程度 ❌ 不利于调试 ✅ 可随时启用/禁用,更灵活
适合阶段 开发初期测试用 ✅ 推荐用于量产或高级定制场景

configfs 配置 USB 摄像头 Gadget


#!/bin/bash
set -e

GADGET_DIR=/sys/kernel/config/usb_gadget/g1

mount -t configfs none /sys/kernel/config > /dev/null 2>&1

# 1. 创建 gadget 目录
mkdir -p $GADGET_DIR

# 2. 设置 USB设备ID和版本
echo 0x1d6b > $GADGET_DIR/idVendor    # Linux Foundation
echo 0x0104 > $GADGET_DIR/idProduct   # Multifunction Composite Gadget
echo 0x0100 > $GADGET_DIR/bcdDevice   # v1.0.0
echo 0x0200 > $GADGET_DIR/bcdUSB      # USB2

# 3. 创建字符串目录
mkdir -p $GADGET_DIR/strings/0x409
echo "0123456789" > $GADGET_DIR/strings/0x409/serialnumber
echo "MyManufacturer" > $GADGET_DIR/strings/0x409/manufacturer
echo "MyUSBWebcam" > $GADGET_DIR/strings/0x409/product

# 4. 创建配置
mkdir -p $GADGET_DIR/configs/c.1
echo 120 > $GADGET_DIR/configs/c.1/MaxPower  # 最大功率单位mA

# 配置描述字符串
mkdir -p $GADGET_DIR/configs/c.1/strings/0x409
echo "UVC Config" > $GADGET_DIR/configs/c.1/strings/0x409/configuration

# 5. 添加 UVC 功能
mkdir -p $GADGET_DIR/functions/uvc.usb0

# 可选:设置视频格式,分辨率,帧率
echo 307200 > $GADGET_DIR/functions/uvc.usb0/streaming_maxpacket  # 设置最大包大小

# 6. 绑定 UVC功能到配置
ln -s $GADGET_DIR/functions/uvc.usb0 $GADGET_DIR/configs/c.1/

# 7. 绑定到 UDC
# 等待至少一个 UDC 出现
while [ ! "$(ls /sys/class/udc 2>/dev/null)" ]; do
    echo "等待 UDC 出现..."
    sleep 1
done

# 获取第一个可用的 UDC 名称
UDC=$(ls /sys/class/udc | head -n 1)
echo "使用 UDC: $UDC"


echo $UDC > $GADGET_DIR/UDC

echo "USB Gadget 启动完成,设备上线"

T113-S3 自带的/etc/adb_conf.sh与虚拟相机会冲突

/etc/init.d/rcS


/etc/adb_conf.sh start &

adb_conf.sh


#!/bin/sh

disable_udc="/etc/.disable_udc"
udc_config=/sys/kernel/config/usb_gadget/g1/UDC

function enable_udc(){
    while [ 1 ];do
        udc=`ls /sys/class/udc 2>/dev/null`
        isudc=`cat $udc_config 2>/dev/null`
        if [ "x$isudc" = "x" ] && [ -f $udc_config ]; then
            echo $udc > $udc_config
        fi
        sleep 1
        if [ -f $disable_udc ];then
            rm $disable_udc
            break
        fi
    done
}

function start_adb(){
    serialnumber=$1
    if [ "x$serialnumber" = "x" ];then
        serialnumber="0402101560"
    fi
    printf "Starting adb "
    # for adbd compatibilities
    mkdir -p /system/
    mkdir -p /system/bin
    if [ ! -f /system/bin/sh ];then
        ln -s /bin/sh /system/bin/sh
    fi

    # config ptmx
    mkdir -p /dev/pts
    mount -t devpts none /dev/pts

    # config adb function
    mount -t configfs none /sys/kernel/config > /dev/null 2>&1
    mkdir -p /sys/kernel/config/usb_gadget/g1
    echo "0x18d1" > /sys/kernel/config/usb_gadget/g1/idVendor
    echo "0x0002" > /sys/kernel/config/usb_gadget/g1/idProduct
    mkdir -p /sys/kernel/config/usb_gadget/g1/strings/0x409
    echo "$serialnumber" > /sys/kernel/config/usb_gadget/g1/strings/0x409/serialnumber
    echo "Google.Inc" > /sys/kernel/config/usb_gadget/g1/strings/0x409/manufacturer
    echo "Configfs ffs gadget" > /sys/kernel/config/usb_gadget/g1/strings/0x409/product
    mkdir -p /sys/kernel/config/usb_gadget/g1/functions/ffs.adb
    mkdir -p /sys/kernel/config/usb_gadget/g1/configs/c.1
    mkdir -p /sys/kernel/config/usb_gadget/g1/configs/c.1/strings/0x409
    echo 0xc0 > /sys/kernel/config/usb_gadget/g1/configs/c.1/bmAttributes
    echo 500 > /sys/kernel/config/usb_gadget/g1/configs/c.1/MaxPower
    ln -s /sys/kernel/config/usb_gadget/g1/functions/ffs.adb/ /sys/kernel/config/usb_gadget/g1/configs/c.1/ffs.adb > /dev/null 2>&1
    mkdir -p /dev/usb-ffs
    mkdir -p /dev/usb-ffs/adb
    if [ "x`ls -A /dev/usb-ffs/adb`" = "x" ];then
        mount -o uid=2000,gid=2000 -t functionfs adb /dev/usb-ffs/adb/
    fi

    # start adbd daemon
    adbd &
    sleep 1

    # enable udc
    #UDC_DEVICE=`find -name "usb_device"`
    #cat $UDC_DEVICE
    cat /sys/bus/platform/drivers/otg\ manager/soc\@3000000\:usbc0\@0/usb_device

    enable_udc &
}

case "$1" in
    start|"")
        otg_role_file="/sys/bus/platform/drivers/otg manager/usbc0/otg_role"
        [ -f "$otg_role_file" ] && otg_role=`cat "$otg_role_file"`
        if ifconfig > /dev/null 2>&1 && [ "x$otg_role" != "xusb_host" ];then
			autotest=/etc/.autotest
            [ -f $autotest ] && serialnumber=`cat $autotest`
	    if [ "x$serialnumber" = "x" ];then
		serialnumber=`cat /proc/cmdline | tr ' ' '\n' | grep 'snum' | awk -F "=" '{print $2}'`
	    fi
            start_adb $serialnumber
        fi
        ;;
    stop)
        printf "Stopping adbd "
        touch $disable_udc
		sleep 2
        killall adbd &
        [ $? -eq 0 ] && echo "OK" || "FAIL"
        ;;
    restart|reload)
        "$0" stop
        "$0" start
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
        ;;
esac

DTS USB 配置


/*
 * usb_port_type: USB 控制器工作模式
 *     0 - Device 模式(从机)
 *     1 - Host 模式(主机)
 *     2 - OTG 模式(可切换)
 *
 * usb_detect_type: USB 插拔检测类型
 *     0 - 无检测(固定模式)
 *     1 - 通过 ID/VBUS GPIO 检测
 *     2 - 使用 ID + DPDM 检测(需要 DP/DM 引脚)
 *
 * usb_detect_mode: 检测方式
 *     0 - 轮询(线程轮询方式)
 *     1 - 中断(通过 ID GPIO 触发中断)
 *
 * usb_id_gpio: OTG 中 ID 引脚对应的 GPIO,用于判断插入的是主机还是设备
 *
 * usb_det_vbus_gpio: 检测 VBUS 电压的 GPIO(插入主机时会有 VBUS 供电)
 *
 * usb_wakeup_suspend:
 *     0 - 使用 SUPER_STANDBY 模式唤醒
 *     1 - 使用 USB_STANDBY 模式唤醒(更省电)
 */

&usbc0 {
	device_type = "usbc0";  // 控制器名称,可选,描述性信息

	usb_port_type = <0x2>;  // 设置为 OTG 模式(既能做主机,也能做设备)

	usb_detect_type = <0x1>;  // 使用 ID/VBUS 检测来决定是主机还是从机模式

	usb_detect_mode = <0>;  // 采用线程轮询方式检测 ID 状态,适配范围更广

	usb_id_gpio = <&pio PE 12 GPIO_ACTIVE_HIGH>;  // ID 检测使用 PE12 引脚,GPIO 高电平表示“设备模式”

	enable-active-high;  // 指定上述 GPIO 为高电平有效(Active High)

	usb_det_vbus_gpio = <&pio PF 6 GPIO_ACTIVE_HIGH>;  // 用 PF6 引脚检测 VBUS(主机是否插入)

	usb_wakeup_suspend = <0>;  // 设置为 SUPER_STANDBY 唤醒模式,功耗略高但兼容性好

	usb_serial_unique = <0>;  // 是否使用 CPU ID 作为唯一 USB 序列号。0=否(使用固定字符串)

	usb_serial_number = "20080411";  // 设置 USB 设备序列号(固定值)

	rndis_wceis = <1>;  // 启用 Windows RNDIS 描述符扩展,便于与 Windows PC 建立网络共享连接

	status = "okay";  // 启用此节点
};

&ehci0 {
	drvvbus-supply = <&reg_usb1_vbus>;  // USB Host VBUS 电源,连接到电源管理芯片或固定电压供电器件
};

&ohci0 {
	drvvbus-supply = <&reg_usb1_vbus>;  // 与 EHCI 相同,为 OHCI 控制器配置供电
};

运行usb_cam.sh的报错信息

打开调试开关


CONFIG_USB_GADGET_DEBUG=y
CONFIG_USB_GADGET_DEBUG_FILES=y
CONFIG_USB_GADGET_DEBUG_FS=y

报错日志


[    5.601218] udc 4100000.udc-controller: registering UDC driver [g1]
[    5.608443] configfs-gadget gadget: adding 'uvc'/(ptrval) to config 'c'/(ptrval)
[    5.617201] configfs-gadget gadget: uvc: uvc_function_bind() enter
[    5.624401] configfs-gadget gadget: uvc: opts after clamp: interval=1, maxpacket=1024, maxburst=0
[    5.634506] configfs-gadget gadget: uvc: max_packet_mult=1, max_packet_size=1024
[    5.642972] configfs-gadget gadget: uvc: Allocated control EP: ep4-int
[    5.650271] configfs-gadget gadget: uvc: Allocated streaming EP: ep3-iso
[    5.657946] configfs-gadget gadget: uvc: usb strings attached
[    5.664513] configfs-gadget gadget: uvc: Allocated control interface ID: 0
[    5.672312] configfs-gadget gadget: uvc: Allocated streaming interface ID: 1
[    5.680173] configfs-gadget gadget: uvc: uvc_copy_descriptors FS failed: -19
[    5.688218] configfs-gadget gadget: uvc: error handling cleanup
[    5.694947] configfs-gadget gadget: adding 'uvc'/(ptrval) --> -19
[    5.701777] configfs-gadget 4100000.udc-controller: driver->bind() failed: -19
[    5.710091] configfs-gadget 4100000.udc-controller: failed to start g1: -19

报错代码位置

kernel/linux-5.4/drivers/usb/gadget/udc/core.c


static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *driver)
{
	int ret;

	dev_dbg(&udc->dev, "registering UDC driver [%s]\n",
			driver->function);

	udc->driver = driver;
	udc->dev.driver = &driver->driver;
	udc->gadget->dev.driver = &driver->driver;

	usb_gadget_udc_set_speed(udc, driver->max_speed);

	ret = driver->bind(udc->gadget, driver);
	if (ret)
		goto err1;
	ret = usb_gadget_udc_start(udc);
	if (ret) {
		driver->unbind(udc->gadget);
		goto err1;
	}
	usb_udc_connect_control(udc);

	kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
	return 0;
err1:
	if (ret != -EISNAM)
		dev_err(&udc->dev, "failed to start %s: %d\n",
			udc->driver->function, ret);
	udc->driver = NULL;
	udc->dev.driver = NULL;
	udc->gadget->dev.driver = NULL;
	return ret;
}

如何进一步分析


echo <UDC> > UDC
        ↓
usb_gadget_bind_driver()                             ← udc/core.c
        ↓
udc_bind_to_driver()                                 ← udc/core.c
        ↓
driver->bind(udc->gadget, driver)                    ← composite_driver.bind 指向 composite_bind
        ↓
composite_bind()                                     ← composite.c
        ↓
usb_add_config()                                     ← composite.c
        ↓
config->bind()                                       ← 对应 configfs 中的 gadget 配置函数
        ↓
f->bind() = uvc_function_bind()                      ← f_uvc.c
        ↓
注册 V4L2、分配 descriptors、ep、interface id 

代码调用链


用户空间(Shell 脚本):
┌──────────────────────────────────────────────┐
│  mkdir /sys/kernel/config/usb_gadget/g1      │
│  echo idVendor/idProduct/bcdUSB              │
│  mkdir functions/uvc.usb0                    │
│  ln -s functions/uvc.usb0 → configs/c.1/     │
│  echo <UDC> > UDC                            │
└──────────────────────────────────────────────┘
                     │
                     ▼
内核 ConfigFS 处理:
┌──────────────────────────────────────────────┐
│  drivers/usb/gadget/function/f_uvc.c         │
│  ├─ uvc_alloc_inst()                         │
│  └─ uvc_alloc()                              │
│     (创建 function 结构体)                 │
└──────────────────────────────────────────────┘
                     │
                     ▼
符号链接触发 Gadget Function 插入:
┌──────────────────────────────────────────────┐
│ drivers/usb/gadget/composite.c              │
│ └─ composite_bind()                         │
│     └─ f->bind() = uvc_function_bind()      │
│         ↳ 初始化 UVC 功能,注册 V4L2        │
└──────────────────────────────────────────────┘
                     │
                     ▼
echo <UDC> > UDC 时:
┌──────────────────────────────────────────────┐
│ kernel/linux-5.4/drivers/usb/gadget/udc/core.c│
│ └─ usb_gadget_bind_driver()                  │
│     └─ udc_bind_to_driver()                  │
│         ↳ 调用 composite_driver->bind()      │
│            (即上面的 composite_bind)       │
│         ↳ usb_gadget_udc_start()            │
│         ↳ usb_udc_connect_control()         │
└──────────────────────────────────────────────┘
                     │
                     ▼
🔔 成功后:function(uvc)注册完毕,系统出现 `/dev/video*`


小机做UVC


mount -t configfs none /sys/kernel/config
mkdir /sys/kernel/config/usb_gadget/g1
echo "0x1f3a" > /sys/kernel/config/usb_gadget/g1/idVendor
echo "0x100d" > /sys/kernel/config/usb_gadget/g1/idProduct
mkdir /sys/kernel/config/usb_gadget/g1/strings/0x409
mkdir /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0

mkdir -p /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/mjpeg/m/720p
echo 1280 > /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/mjpeg/m/720p/wWidth
echo 720 > /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/mjpeg/m/720p/wHeight
echo 333333 > /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/mjpeg/m/720p/dwFrameInterval
echo 333333 > /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/mjpeg/m/720p/dwDefaultFrameInterval
echo 442368000 > /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/mjpeg/m/720p/dwMinBitRate
echo 442368000 > /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/mjpeg/m/720p/dwMaxBitRate
echo 1843200 > /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/mjpeg/m/720p/dwMaxVideoFrameBufferSize

mkdir /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/header/h
ln -s /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/mjpeg/m/ /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/header/h/
ln -s /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/header/h/ /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/class/fs
ln -s /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/header/h/ /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/class/hs
mkdir /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/control/header/h
ln -s /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/control/header/h/ /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/control/class/fs/
ln -s /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/control/header/h/ /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/control/class/ss/

mkdir /sys/kernel/config/usb_gadget/g1/configs/c.1
echo 0xc0 > /sys/kernel/config/usb_gadget/g1/configs/c.1/bmAttributes
echo 500 > /sys/kernel/config/usb_gadget/g1/configs/c.1/MaxPower
mkdir /sys/kernel/config/usb_gadget/g1/configs/c.1/strings/0x409

ln -s /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/ /sys/kernel/config/usb_gadget/g1/configs/c.1/
ls /sys/class/udc/ | xargs echo > /sys/kernel/config/usb_gadget/g1/UDC