基本思路
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 = <®_usb1_vbus>; // USB Host VBUS 电源,连接到电源管理芯片或固定电压供电器件
};
&ohci0 {
drvvbus-supply = <®_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