USB 驱动开发 --- Gadget 设备连接 Windows 免驱
环境信息
测试使用 DuoS(Arm CA53, Linux 5.10) 搭建方案验证环境,使用 USB sniff + Wirekshark 抓包分析,合照如下:

注:左侧图中设备:1. 蓝色,USB sniff 非侵入工 USB 抓包工具;2. 绿色,DuoS 开发板,
系统初始化
查看 DuoS 上作为 USB设备时,初始化流程
# cat /etc/inittab
...
# now run any rc scripts
::sysinit:/etc/init.d/rcS# cat /etc/init.d/rcS
...
for i in /etc/init.d/S??* ;do...case "$i" in ...*)$i start# cat /etc/init.d/S99user
...
export USERDATAPATH=/mnt/data/
export SYSTEMPATH=/mnt/system/case "$1" instart)...if [ -f $SYSTEMPATH/usb.sh ]; then. $SYSTEMPATH/usb.sh &fi# ll /mnt/system/usb.sh
lrwxrwxrwx 1 1000 1000 10 Dec 19 2024 /mnt/system/usb.sh -> usb-ncm.sh*
由软链可知,当前 DuoS 作为 USB 设备工作,功能为 NCM。具体查看ncm.sh脚本实现
#----> device/generic/rootfs_overlay/duos/mnt/system/usb-ncm.sh
... # GPIO 相关控制,用于切换 USB 通道与 HUB 控制
/etc/uhubon.sh device >> /tmp/ncm.log 2>&1
/etc/run_usb.sh probe ncm >> /tmp/ncm.log 2>&1
/etc/run_usb.sh start ncm >> /tmp/ncm.log 2>&1
可知,除了配置外部 GPIO 修改以 USB 通路和电源配置外,初始化过程大致分为两个阶段:
- USB OTG(ID 管脚)控制;
- Gadget 设备创建与启用;
阶段一、USB OTG 控制
初始化脚本中 OTG 控制实现如下:
#----> device/generic/br_overlay/common/etc/uhubon.sh
...
case "$1" in...device)echo device > /proc/cviusb/otg_role;;
查找 /proc 子系统下otg_role节点功能归属
$ grep -wrn "otg_role" linux_5.10/drivers/
linux_5.10/drivers/usb/dwc2/platform.c:395:#define CVIUSB_ROLE_PROC_NAME "cviusb/otg_role"
源码文件
//----> linux_5.10/drivers/usb/dwc2/platform.c#define CVIUSB_ROLE_PROC_NAME "cviusb/otg_role"static int dwc2_driver_probe(struct platform_device *dev)...hsotg = devm_kzalloc(&dev->dev, sizeof(*hsotg), GFP_KERNEL);...hsotg->dev = &dev->dev;...dwc2_lowlevel_hw_init(hsotg);...cviusb_proc_dir = proc_mkdir("cviusb", NULL); // 创建 /proc/cviusb 目录; 创建其下节点:otg_rolecviusb_role_proc_entry = proc_create_data(CVIUSB_ROLE_PROC_NAME, 0644, NULL, &role_proc_ops, hsotg);#define CVIUSB_ROLE_PROC_NAME "cviusb/otg_role"static const struct proc_ops role_proc_ops = {....proc_write = role_proc_write,static ssize_t role_proc_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)...sel_role_hdler(hsotg, procdata);n = ARRAY_SIZE(sel_role);for (i = 0; i < n; i++) {if (!strcmp(str, sel_role[i])) {t = i;break; hsotg->cviusb.id_override = t;dwc2_set_hw_id(hsotg, t); // is_dev = t;if (is_dev) {iowrite32((ioread32((void *)hsotg->cviusb.usb_pin_regs) & ~0x0000C0) | 0xC0, (void *)hsotg->cviusb.usb_pin_regs);// 截取
// cviusb->usb_pin_regs = ioremap(0x03000048, 0x4);
参考 SG2000 技术手册,查看 usb_phy_ctrl_reg 寄存器如下:

可知:向 otg_role 节点写入 device,在硬件上控制了 USB PHY ID 线的驱动和控制方式,影响 USB OTG 功能中 ID 线的使用。
阶段二、USB Gadget 设备创建
初始化脚本中 Gadget 设备创建与启动实现如下,可分为 probe、start 两个动作:
#----> device/generic/br_overlay/common/etc/run_usb.shCVI_DIR=/tmp/usb
CVI_GADGET=$CVI_DIR/usb_gadget/cvitekcase "$1" instart)start;;...probe)probeprobe() {mkdir $CVI_DIRif [ ! -d $CVI_DIR/usb_gadget ]; then mount none $CVI_DIR -t configfs # 挂载 USB Configfsmkdir $CVI_GADGET # 创建gadget设备:cvitekecho $VID > $CVI_GADGET/idVendor # 设置设备信息:VID、PIDecho $PID > $CVI_GADGET/idProductmkdir $CVI_GADGET/strings/0x409 # 创建dadget设备 语言信息...mkdir $CVI_GADGET/configs/c.1 # 创建gadget设备 配置...echo 0xEF > $CVI_GADGET/bDeviceClass # 设备类型、子类型、协议信息echo 0x02 > $CVI_GADGET/bDeviceSubClassecho 0x01 > $CVI_GADGET/bDeviceProtocol ...if [ "$CLASS" = "ffs.adb" ] ; then ... elsemkdir $CVI_GADGET/functions/$CLASS.usb$FUNC_NUM # 当前为 NCM 功能设备if [ "$CLASS" = "ncm" ] ; thenln -s $CVI_FUNC/ncm.usb$FUNC_NUM $CVI_GADGET/configs/c.1 # 关联 function ... start() {...if [ -d $CVI_GADGET/functions/ffs.adb ]; then ... elseUDC=`ls /sys/class/udc/ | awk '{print $1}'`echo ${UDC} >$CVI_GADGET/UDC # Gadget NCM 功能设备启用,实际对应外设:4340000.usb
可知 Gadget 设备依赖以 configfs 格式挂载的目录 /tmp/usb,使用文件操作(mkdir、ln)形式对 Gadget 设备 cvitek 进行配置与管理,涉及:
- 配置基本信息:PID、VID和语言信息;
- 设置配置文件:语言、功率和接口等信息;
- 创建功能设备:当前仅有一个 NCM 功能;
- 关联功能设备:关联 配置(configuration) 与 功能(function);
- 启动功能设备:向
UDC写入需要启动的 功能设备;
方案背景
参考 USB 中文网相关文档可知:WinUSB是微软提供的一个USB设备的通用驱动程序。使用这个驱动用户不需要编写内核层的驱动程序就能访问USB设备。WCID则是USB驱动一种新的匹配机制,通常USB设备都是通过VID和PID来进行匹配的,而使用了WCID之后,设备不通过VID和PID来匹配驱动,而是通过一个叫做 WCID(Windows Compatible ID) 来匹配,这样就不用为每一个VID和PID不同的设备编写INF文件了。
在完成WCID匹配之后,不需要编写inf文件,系统会根据设备类型来安装驱动,最终实现免驱。
问题现状
收集现有环境信息,已知 DuoS 上电启动后将以 USB 设备 NCM 功能工作,接入 Windows 后无法免驱使用,设备与抓包信息如下:

由设备管理器 其他设备->CDC NCM 可知,当前 DuoS 未能正常免驱使用。
方案开发
参考 《简单几步,让自定义USB设备也能免驱动运行》,分别测试 微软系统描述符 1.0 与 2.0 方式实现免驱。
开发验证一、OS 1.0 免驱方案
总结 《使用微软系统描述符1.0制作免驱动自定义USB设备》方案,实施步骤如下:
- 先读取设备描述符和配置描述符,判断设备描述符中的bcdUSB字段,检查设备支持的USB版本号是否大于等于2.0;
- 如果
bcdUSB大于等于0x0200,主机请求 OS字符串描述符,请求索引index值为0xee; - 设备应答 OS 字符串描述符(总长度为18,内容unicode编码为 ”MSFT100″,vendor code由厂商自己定义);
- 主机对OS字符串描述验证通过后,发出功能描述符请求(两种):
- 设备应答 扩展兼容ID描述符(请求索引
wIndex值为0x04); - 设备应答 扩展属性描述符(请求索引
wIndex值为0x05);
- 设备应答 扩展兼容ID描述符(请求索引
- 完成枚举,免驱使用;
通过兼容ID,系统已经知道了设备需要WinUSB驱动;通过扩展属性,告诉系统我们设备的GUID是什么。但 Windows系统对扩展“属性描述”和“兼容ID”处理逻辑不太一样,如果设备的“扩展属性“已经有了,就不会再去获取。而是否已经存在“扩展属性”可查看注册表:
- 路径:[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_
$<VID>&PID_$<PID>\$<SPEC>\Device Parameters] - 键值对:
DeviceInterfaceGUIDs
测试验证
验证方案,对原设备直接抓包测试,文件《DuoS_原USB从设备.pcapng》;

从USB抓包可知:原设备直接插入 Win10 主机,可以抓取枚举过程中的数据包。在此基础上有选择的过滤出 GET DESCRIPTOR 请求包,不能查找到 Index 为 0xEE 的 ”OS字符串描述符“ 请求包,所以只能说明当前测试方式无法触发 Win10 主机的 “微软系统描述符1.0" 机制。
理论中 ”设备支持的USB版本号是否大于等于2.0,即bcdUSB大于等于0x0200“ 是 必要不充分 条件,原因另外再做探索。在 “微软系统描述符1.0" 机制之外还有个 ”微软系统描述符2.0“ 机制可做测试。
开发验证二、OS 2.0 免驱方案
总结《使用微软系统描述符2.0制作免驱动自定义USB设备》,实施步骤如下:
- 先读取设备描述符和配置描述符,判断设备描述符中的 bcdUSB 字段是否大于等于
0x0210; - 如果 bcdUSB 大于等于
0x0210,主机请求 BOS 描述符(类型 bDescriptorType 值为0x0f); - 设备应答 BOS 描述符(总长度为33(5 + 28),bDevCapabilityType 值为
0x05,UUID、VendorCode 由厂商自己定义); - 主机对 BOS 描述验证通过后,依据 bVendorCode 发出 OS2.0 描述符集请求(索引 wIndex 值为
0xC0),包含:- WCID20 兼容ID 描述符(类型 wDescriptorType 值为
0x03; cCID_8 值为 ”WINUSB“); - WCID20 注册表属性 描述符(类型 wDescriptorType 值为
0x04;内容为接口 GUID 键对,其中 GUID 值由厂商自己定义);
- WCID20 兼容ID 描述符(类型 wDescriptorType 值为
- 完成枚举,免驱使用;
bDeviceCapabilityType = 0x05,在 USB 标准中 5 是保留值,在 Windows 系统中定义为 Platform Capability BOS Descriptor,里面包含了uuid,操作系统版本,vendor code信息。
比较起来,2.0 不再需要OS字符串描述符,而是使用了USB标准的 BOS 描述符来获取设备的vendor code。然后再通过一个叫做描述符集的描述符一次性返回所有接口所有配置的compat ID和属性。
测试验证一、bcdUSB ≥ 0x0210
由抓包查看 DEVICE 应答包的 bcdUSB 值

可知当前设备 bcdUSB 值为 0x0200 不符合 ”大于等于 0x0210“ 的要求,所以需要调试 bcdUSB 值尝试打通方案步骤2 ---- bcdUSB 大于等于 0x0210,主机请求 BOS 描述符。
修改 bcdUSB 值按递进关系分为两步,分别是:configfs 下 Gadget 设备 bcdUSB 节点修改,若失败,则再从驱动源码级别修改。
修改验证一、configfs 配置,设备节点 bcdUSB
configfs,直接修改 gadget 驱动节点 bcdUSB
# 查看 gadget 设备内容
$ ls /tmp/usb/usb_gadget/cvitek/
UDC bMaxPacketSize0 functions/ os_desc/
bDeviceClass bcdDevice idProduct strings/
bDeviceProtocol bcdUSB idVendor
bDeviceSubClass configs/ max_speed# 查看当前 bcdUSB 值
$ cat /tmp/usb/usb_gadget/cvitek/bcdUSB
0x0200# 修改当前值为 0x0210
$ echo 0x0210 > /tmp/usb/usb_gadget/cvitek/bcdUSB
# 读出验证
$ cat /tmp/usb/usb_gadget/cvitek/bcdUSB
0x0210# 重新插拔后# 两次读取
$ cat /tmp/usb/usb_gadget/cvitek/bcdUSB
0x0200
由测试可知:configfs 方式生成的 Gadget 设备属性 bcdUSB 无法直接修改。
修改验证二、驱动源码,bcdUSB 初始化
寻找 bcdUSB 赋值时机,在查找之前需要对 USB Gadget 驱动有个大致认识,以下逻辑框图大致可传达 Win10 主机与 DuoS 连接与功能层级:

由图可猜想:与 configfs 下 Gadget 设备 bcdUSB 节点最直接关联的是 composite framework(以下简称 libcomposite 框架) ,实际查找过程如下:
$ grep -wrn "bcdUSB" linux_5.10/drivers/usb/
...
linux_5.10/drivers/usb/gadget/configfs.c:335:CONFIGFS_ATTR(gadget_dev_desc_, bcdUSB);
源码跟读
//----> linux_5.10/drivers/usb/gadget/configfs.cCONFIGFS_ATTR(gadget_dev_desc_, bcdUSB);static struct configfs_attribute *gadget_root_attrs[] = {...&gadget_dev_desc_attr_bcdUSB,&gadget_dev_desc_attr_UDC,#define GI_DEVICE_DESC_SIMPLE_R_u16(__name) \
static ssize_t gadget_dev_desc_##__name##_show(struct config_item *item, \char *page) \
{ \return sprintf(page, "0x%04x\n", \le16_to_cpup(&to_gadget_info(item)->cdev.desc.__name)); \
}static ssize_t gadget_dev_desc_bcdUSB_store(struct config_item *item, const char *page, size_t len) {...to_gadget_info(item)->cdev.desc.bcdUSB = cpu_to_le16(bcdUSB);GI_DEVICE_DESC_SIMPLE_R_u16(bcdUSB); # 宏展开后如下:
static ssize_t gadget_dev_desc_bcdUSB_show(struct config_item *item, char *page) { return sprintf(page, "0x%04x\n", le16_to_cpup(&to_gadget_info(item)->cdev.desc.bcdUSB)); # 关键 cdev->desc.bcdUSB
梳理 usb_udc 设备 与 usb_composite_dev 设备的关系,而 usb_composiste_dev 设备 又是 libcomposite 框架的核心结构之一,所以继续在 libcomposite框架下查找:
$ grep -wrn "bcdUSB" linux_5.10/drivers/usb/
...
linux_5.10/drivers/usb/gadget/composite.c:1773: cdev->desc.bcdUSB = cpu_to_le16(0x0320);
linux_5.10/drivers/usb/gadget/composite.c:1776: cdev->desc.bcdUSB = cpu_to_le16(0x0210);
linux_5.10/drivers/usb/gadget/composite.c:1780: cdev->desc.bcdUSB = cpu_to_le16(0x0201);
linux_5.10/drivers/usb/gadget/composite.c:1782: cdev->desc.bcdUSB = cpu_to_le16(0x0201);
源码跟读
//----> linux_5.10/drivers/usb/gadget/composite.c/** The setup() callback implements all the ep0 functionality that's* not handled lower down, in hardware or the hardware driver(like* device and endpoint feature flags, and their status). It's all* housekeeping for the gadget function we're implementing. Most of* the work is in config and function specific setup.*/
int composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) {struct usb_composite_dev *cdev = get_gadget_data(gadget);struct usb_request *req = cdev->req;...if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) // 非标准描述符请求,走 unkown 分支处理goto unknown;switch (ctrl->bRequest) { // 检查 请求类型/* we handle all standard USB descriptors */case USB_REQ_GET_DESCRIPTOR: // GET 类型if (ctrl->bRequestType != USB_DIR_IN)goto unknown;switch (w_value >> 8) {case USB_DT_DEVICE: // 设备描述符...if (gadget_is_superspeed(gadget)) { // 超高速设备(USB 3.0 标准)if (gadget->speed >= USB_SPEED_SUPER) {cdev->desc.bcdUSB = cpu_to_le16(0x0320);cdev->desc.bMaxPacketSize0 = 9;} else {cdev->desc.bcdUSB = cpu_to_le16(0x0210);}} else { // 高速(USB 2.0)、全速(USB 1.1)、低速(USB 1.0)if (gadget->lpm_capable) // 支持 链路电源管理,USB 2.0 支持部分cdev->desc.bcdUSB = cpu_to_le16(0x0201);elsecdev->desc.bcdUSB = cpu_to_le16(0x0200);}
结合实际 DuoS SOC 核心设计,其 USB 控制器使用 DWC2 IP核心,支持 USB 2.0 规范所以只能走到最后一个分支 ---- bcdUSB = 0x0200。
可靠方案应考虑 lpm_capable 如何打通以打通倒数第二个分支 ---- bcdUSB = 0x0201。为了快速验证,将最后一个分支修改为 0x020,补丁如下:
diff --git a/linux_5.10/drivers/usb/gadget/composite.c b/linux_5.10/drivers/usb/gadget/composite.c
index 1a556a628..364ef4a0c 100644
--- a/linux_5.10/drivers/usb/gadget/composite.c
+++ b/linux_5.10/drivers/usb/gadget/composite.c
@@ -1685,7 +1685,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)if (gadget->lpm_capable)cdev->desc.bcdUSB = cpu_to_le16(0x0201);else
- cdev->desc.bcdUSB = cpu_to_le16(0x0200);
+ cdev->desc.bcdUSB = cpu_to_le16(0x0210);}value = min(w_length, (u16) sizeof cdev->desc);
稳妥起见,查看相关模块是否纳入构建以保证修改可以最终生效。先查看对应源码的构建 Makefile 文件:
...
obj-$(CONFIG_USB_LIBCOMPOSITE) += libcomposite.o # libcomposite.ko,即是文中提及的 libcomposite 框架
libcomposite-y := usbstring.o config.o epautoconf.o
libcomposite-y += composite.o functions.o configfs.o u_f.o # 当前修改文件对应编译产物:composite.oobj-$(CONFIG_USB_GADGET) += udc/ function/ legacy/
查看配置文件生成的 .config 文件是否开启 CONFIG_USB_LIBCOMPOSITE:
$ grep -wrn -E "CONFIG_USB_LIBCOMPOSITE|CONFIG_USB_GADGET" linux_5.10/build/sg2000_milkv_duos_glibc_arm64_sd/.config
2579:CONFIG_USB_GADGET=y
2605:CONFIG_USB_LIBCOMPOSITE=y
可知:libcomposite 内核模块 内嵌至Image镜像中,修改驱动后需要整编Linux内核。编译命令如下:
$ source build/cvisetup.sh
$ defconfig sg2000_milkv_duos_glibc_arm64_sd# 编译内核;更新fip.bin文件
build_kernel
设备更新内核(包含于 boot.sd),操作命令:
# 分区挂载
# 创建boot分区,挂载目录:/mnt/boot
mkdir -p /mnt/boot/ && mount /dev/mmcblk0p1 /mnt/boot/# 固件下载, SCP
SDK_ROOT=Source/01-SG200x/SDK_SG200x_V2
scp gaoyang3513@192.168.8.100:${SDK_ROOT}/install/soc_sg2000_milkv_duos_glibc_arm64_sd/rawimages/boot.sd /mnt/boot/# 重启生效
reboot
抓包测试:

结论:在bcdUSB调整为0x0201后,Windows 枚举USB设备时会读取 BOS(OS字符串描述符)。
至此打通步骤2,紧跟需要打通步骤3 ---- 设备应答 OS 字符串 描述符。
测试验证二、应答 “BOS 描述符”
在 libcomposite 框架下,同步骤2 ---- 应答 Device 描述符处理的 composite_setup 函数下就有 BOS 描述符请求的应答处理逻辑。
可靠方案应考虑: libcomposite 连接了底层 UDC 驱动和上层 Function 驱动,setup 是一个自下由上贯通的流程处理,以何时、何种方式合理地对 BOS 描述符进行应答 。 当前需要快速验证,参考 《使用微软系统描述符2.0制作免驱动自定义USB设备》给出的 BOS 描述符样式,直接在 libcomposite 框架 setup中应答 BOS 请求,补丁如下:
diff --git a/linux_5.10/drivers/usb/Makefile b/linux_5.10/drivers/usb/Makefile
index 1c1c1d659..0da7c468f 100644
--- a/linux_5.10/drivers/usb/Makefile
+++ b/linux_5.10/drivers/usb/Makefile
@@ -66,3 +66,5 @@ obj-$(CONFIG_USBIP_CORE) += usbip/obj-$(CONFIG_TYPEC) += typec/obj-$(CONFIG_USB_ROLE_SWITCH) += roles/
+
+subdir-ccflags-y += -DDEBUG -DCONFIG_GAOYANGdiff --git a/linux_5.10/drivers/usb/gadget/composite.c b/linux_5.10/drivers/usb/gadget/composite.c
index 1a556a628..0b772c924 100644
--- a/linux_5.10/drivers/usb/gadget/composite.c
+++ b/linux_5.10/drivers/usb/gadget/composite.c
@@ -664,6 +717,35 @@ static int bos_desc(struct usb_composite_dev *cdev)struct usb_ext_cap_descriptor *usb_ext;struct usb_dcd_config_params dcd_config_params;struct usb_bos_descriptor *bos = cdev->req->buf;
+#if defined (CONFIG_GAOYANG)
+ /* WCID20 device capability descriptor */
+ #define USB_CAP_TYPE_WCID 5
+ #define USB_CAP_WCID_SIZE 0x1C
+ #define WINUSB20_WCID_VENDOR_CODE 0x00
+ #define WINUSB20_WCID_DESC_SET_SIZE 162
+
+ struct usb_cap_wcid20_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDevCapabilityType;
+ __u8 bReserved ;
+ __u8 bPlatformCapabilityUUID_16[16];
+ __u32 dwWindowsVersion;
+ __le32 wDescriptorSetTotalLength;
+ __u8 bVendorCode;
+ __u8 bAltEnumCode;
+ } __attribute__((packed));
+
+ char winusb20_wcidbos_uuid[] = {
+ 0xdf, 0x60, 0xdd, 0xd8, 0x89, 0x45, 0xc7, 0x4c,
+ 0x9c, 0xd2, 0x65, 0x9d, 0x9e, 0x64, 0x8a, 0x9f,
+ };
+
+ uint32_t winusb20_wcidbos_version = 0x06030000;
+
+ struct usb_cap_wcid20_descriptor *usb_cap_wcid;
+
+#endif // defined (CONFIG_GAOYANG)unsigned int besl = 0;bos->bLength = USB_DT_BOS_SIZE;
@@ -708,6 +789,19 @@ static int bos_desc(struct usb_composite_dev *cdev)usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |USB_BESL_SUPPORT | besl);+#if defined (CONFIG_GAOYANG)
+ usb_cap_wcid = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
+ le16_add_cpu(&bos->wTotalLength, USB_CAP_WCID_SIZE);
+ bos->bNumDeviceCaps++;
+
+ usb_cap_wcid->bLength = USB_CAP_WCID_SIZE;
+ usb_cap_wcid->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ usb_cap_wcid->bDevCapabilityType = USB_CAP_TYPE_WCID;
+ usb_cap_wcid->dwWindowsVersion = winusb20_wcidbos_version;
+ usb_cap_wcid->wDescriptorSetTotalLength = cpu_to_le32(WINUSB20_WCID_DESC_SET_SIZE);
+ usb_cap_wcid->bVendorCode = WINUSB20_WCID_VENDOR_CODE;
+ memcpy(usb_cap_wcid->bPlatformCapabilityUUID_16, winusb20_wcidbos_uuid, sizeof(winusb20_wcidbos_uuid));
+#endif // defined (CONFIG_GAOYANG)/** The Superspeed USB Capability descriptor shall be implemented by all* SuperSpeed devices.
@@ -1716,11 +1810,16 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)value = min(w_length, (u16) value);break;case USB_DT_BOS:
+#if !defined (CONFIG_GAOYANG)if (gadget_is_superspeed(gadget) ||gadget->lpm_capable) {value = bos_desc(cdev);value = min(w_length, (u16) value);}
+#else
+ value = bos_desc(cdev);
+ value = min(w_length, (u16) value);
+#endif // defined (CONFIG_GAOYANG)break;case USB_DT_OTG:if (gadget_is_otg(gadget)) {
补丁简要说明:
- Makfile,使用
subdir-ccflags-y对当前 Makefile 下的所有构建过程添加宏:- DEBUG,添加调试信息打印;
- CONFIG_GAOYANG,快速方案 控制宏;
- composite.c 源码修改:
- 添加 WCID20 设备能力 描述符结构体定义:usb_cap_wcid20_descriptor;
- 跳过 超高速设备 检查,在 BOS 描述符请求处理分支直接返回 “BOS 描述符”;
- 返回 “BOS 描述符” 时(bos_desc 实现中),追加 ”WCID20 设备能力 描述符“;
抓包测试:

结论:应答 BOS 请求并追加 ”WCID20 设备能力 描述符“后,主机新发出 DATA0 请求。
至此,打通步骤3,紧跟需要打通步骤4 ---- 设备应答 “OS2.0 描述符集”。
测试验证三、应答 “OS2.0 描述符集”
应答的“OS2.0 描述符集”应包含:
- WCID20 兼容ID 描述符(类型 wDescriptorType 值为
0x03; cCID_8 值为 ”WINUSB“); - WCID20 注册表属性 描述符(类型 wDescriptorType 值为
0x04;内容为接口 GUID 键对,其中 GUID 值由厂商自己定义);
在 libcomposite 框架下,同步骤3 ---- 应答“BOS 描述符”处理的 composite_setup 函数下就有“OS2.0 描述符集”请求的应答处理逻辑。
可靠方案同样应考虑: 在 libcomposite 框架下,怎么更合理地对“OS2.0 描述符集”进行应答 。 当前需要快速验证,参考 《使用微软系统描述符2.0制作免驱动自定义USB设备》给出的“OS2.0 描述符集”样式,直接在 libcomposite 框架 setup中应答 BOS 请求,补丁如下:
diff --git a/linux_5.10/drivers/usb/gadget/composite.c b/linux_5.10/drivers/usb/gadget/composite.c
index 1a556a628..0b772c924 100644
--- a/linux_5.10/drivers/usb/gadget/composite.c
+++ b/linux_5.10/drivers/usb/gadget/composite.c
@@ -20,6 +20,50 @@#include "u_os_desc.h"+#if defined (CONFIG_GAOYANG)
+
+#define WINUSB_IF0_WCID_PROPERTIES_SIZE (162)
+
+const uint8_t WINUSB20_WCIDDescriptorSet [162] = {
+ ///
+ /// WCID20 descriptor set descriptor
+ ///
+ 0x0a, 0x00, /* wLength */
+ 0x00, 0x00, /* wDescriptorType */
+ 0x00, 0x00, 0x03, 0x06, /* dwWindowsVersion */
+ 0xa2, 0x00, /* wDescriptorSetTotalLength */
+ ///
+ /// WCID20 compatible ID descriptor
+ ///
+ 0x14, 0x00, /* wLength */
+ 0x03, 0x00, /* wDescriptorType */
+ /* WINUSB */
+ 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, /* cCID_8 */
+ /* */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* cSubCID_8 */
+ ///
+ /// WCID20 registry property descriptor
+ ///
+ 0x84, 0x00, /* wLength */
+ 0x04, 0x00, /* wDescriptorType */
+ 0x07, 0x00, /* wPropertyDataType */
+ 0x2a, 0x00, /* wPropertyNameLength */
+ /* DeviceInterfaceGUIDs */
+ 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, /* wcPropertyName_21 */
+ 'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00, /* wcPropertyName_21 */
+ 't', 0x00, 'e', 0x00, 'r', 0x00, 'f', 0x00, /* wcPropertyName_21 */
+ 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00, /* wcPropertyName_21 */
+ 'U', 0x00, 'I', 0x00, 'D', 0x00, 's', 0x00, /* wcPropertyName_21 */
+ 0x00, 0x00, /* wcPropertyName_21 */
+ 0x50, 0x00, /* wPropertyDataLength */
+ /* {1D4B2365-4749-48EA-B38A-7C6FDDDD7E26} */
+ '{', 0x00, '1', 0x00, 'D', 0x00, '4', 0x00, /* wcPropertyData_40 */
+ 'B', 0x00, '2', 0x00, '3', 0x00, '6', 0x00, /* wcPropertyData_40 */
+ '5', 0x00, '-', 0x00, '4', 0x00, '7', 0x00, /* wcPropertyData_40 */
+ '4', 0x00, '9', 0x00, '-', 0x00, '4', 0x00, /* wcPropertyData_40 */
+ '8', 0x00, 'E', 0x00, 'A', 0x00, '-', 0x00, /* wcPropertyData_40 */
+ 'B', 0x00, '3', 0x00, '8', 0x00, 'A', 0x00, /* wcPropertyData_40 */
+ '-', 0x00, '7', 0x00, 'C', 0x00, '6', 0x00, /* wcPropertyData_40 */
+ 'F', 0x00, 'D', 0x00, 'D', 0x00, 'D', 0x00, /* wcPropertyData_40 */
+ 'D', 0x00, '7', 0x00, 'E', 0x00, '2', 0x00, /* wcPropertyData_40 */
+ '6', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00, /* wcPropertyData_40 */
+};
+#endif // defined (CONFIG_GAOYANG)
+/*** struct usb_os_string - represents OS String to be reported by a gadget* @bLength: total length of the entire descritor, always 0x12
@@ -1891,6 +1990,14 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)/** OS descriptors handling*/
+#if defined (CONFIG_GAOYANG)
+ if (ctrl->bRequestType == 0xC0) {
+ value = min_t(int, w_length, sizeof(WINUSB20_WCIDDescriptorSet));
+ memcpy(req->buf, WINUSB20_WCIDDescriptorSet, value);
+
+ goto check_value;
+ }
+#endif // defined (CONFIG_GAOYANG)if (cdev->use_os_string && cdev->os_desc_config &&(ctrl->bRequestType & USB_TYPE_VENDOR) &&ctrl->bRequest == cdev->b_vendor_code) {
补丁简要说明:
- 定义“OS2.0 描述符集”变量
WINUSB20_WCIDDescriptorSet,其中包含:- “兼容ID 描述符",固定为:”WINUSB“;
- ”注册表属性 描述符“,新增键值对:
DeviceInterfaceGUIDs : 1D4B2365-4749-48EA-B38A-7C6FDDDD7E26;
- 跳过”OS 描述符集“检查,在”unknown:“处理分支直接返回 “OS2.0 描述符集”,内容为
WINUSB20_WCIDDescriptorSet;
抓包测试:

结论:应答的“OS2.0 描述符集”后,主机完成设备枚举,视设备为 WinUSb 设备而免驱。
至此,完成 DuoS 免驱的快速验证。
方案总结
- 当前环境(Win10 USB 2.0 主机,Linux USB 2.0 设备)下,无法触发 Win10 主机的 “微软系统描述符1.0" 机制,OS 1.0 免驱方案不见效;
- 经快速修改{ bcdUSB = 0x0210;应答 ”BOS 描述符“;应答 ”OS2.0 描述符集“}后,可以触发 Win10 主机的 “微软系统描述符2.0" 机制,方案有效;
- 完整方案需要参考 gadget 驱动框架,考虑更合理的 “微软系统描述符2.0" 免驱方案实现;
调试关注
-
及时删除注册表中的
usbflags下的对应目录,否则设备不再发出BOS描述符请求。 -
configfs 方式下, DuoS 调试关注:
-
生成 usb_f_ss_lb.ko 模块,补丁:
diff --git a/build/boards/cv181x/sg2000_milkv_duos_glibc_arm64_sd/linux/cvitek_sg2000_milkv_duos_glibc_arm64_sd_defconfig b/build/boards/cv181x/sg2000_milkv_duos_glibc_arm64_sd/linux/cvitek_sg2000_milkv_duos_glibc_arm64_sd_defconfig index 255e7cbd8..e7cf8ad64 100644 --- a/build/boards/cv181x/sg2000_milkv_duos_glibc_arm64_sd/linux/cvitek_sg2000_milkv_duos_glibc_arm64_sd_defconfig +++ b/build/boards/cv181x/sg2000_milkv_duos_glibc_arm64_sd/linux/cvitek_sg2000_milkv_duos_glibc_arm64_sd_defconfig @@ -230,3 +230,6 @@ CONFIG_DEBUG_FS=y# CONFIG_FTRACE is not set# CONFIG_STRICT_DEVMEM is not set# CONFIG_RUNTIME_TESTING_MENU is not set +CONFIG_USB_ZERO=m +CONFIG_USB_F_SS_LB=m -
SourceSink Function 设备命令:
# 加载 f_ss_lb 驱动 insmod /mnt/system/ko/usb_f_ss_lb.ko# 变量声明 CVI_DIR=/tmp/usb CVI_GADGET=$CVI_DIR/usb_gadget/cvitek CVI_FUNC=$CVI_GADGET/functions MANUFACTURER="Cvitek" PRODUCT="USB Com Port" SERIAL="0123456789" VID=0x3346 PID=0x1003 CLASS=SourceSink FUNC_NUM=0# 环境初始化 mkdir $CVI_DIR# configfs 挂载 mount none $CVI_DIR -t configfs# 创建 Gadget 设备 mkdir $CVI_GADGET# Gadget 设备初始化 echo $VID >$CVI_GADGET/idVendor echo $PID >$CVI_GADGET/idProduct# 信息初始化 mkdir $CVI_GADGET/strings/0x409 echo $MANUFACTURER>$CVI_GADGET/strings/0x409/manufacturer echo $PRODUCT> $CVI_GADGET/strings/0x409/product echo $SERIAL> $CVI_GADGET/strings/0x409/serialnumber# 创建并关联 configuration 与 function mkdir $CVI_GADGET/configs/c.1 mkdir $CVI_GADGET/functions/$CLASS.usb$FUNC_NUM ln -s $CVI_FUNC/$CLASS.usb$FUNC_NUM $CVI_GADGET/configs/c.1# 配置 configuration mkdir $CVI_GADGET/configs/c.1/strings/0x409 echo "config1"> $CVI_GADGET/configs/c.1/strings/0x409/configuration echo 120> $CVI_GADGET/configs/c.1/MaxPower# 使能 Gadget 设备 echo 4340000.usb > /tmp/usb/usb_gadget/cvitek/UDC
-
术语缩写
| 术语 & 编写 | 说明 | 备注 |
|---|---|---|
| UDC | USB设备控制器(UDC)驱动指的是作为其他USB主机控制器外设的USB硬件设备上底层硬件控制器的驱动,该硬件和驱动负责将一个USB设备依附于一个USB主机控制器上。 | |
| NCM | USB NCM,属于USB-IF定义的CDC(Communication Device Class)下的一个子类:Network Control Model,用于Host和Device之间交换以太网帧。 | |
| MSC | Mass Storage Class, USB大容量存储设备,通常指的是像U盘这样的存储设备。 | |
| UAC | USB Audio Class,USB音频类,例如电话,音乐回放,录音等音频功能等; | |
| ACM | Abstract Control Model, 主要用于支持模拟调制解调器(Modem)设备在 USB 总线上的通信。 |
参考
-
第16章 USB主机、设备与Gadget驱动之USB UDC与Gadget驱动(一)
-
通过configfs配置的Linux USB gadget
-
Configfs - 用户空间驱动的内核对象配置
-
简单几步,让自定义USB设备也能免驱动运行
-
使用微软系统描述符1.0制作免驱动自定义USB设备
-
使用微软系统描述符2.0制作免驱动自定义USB设备
-
WinUSB提供的相关USB结构体
相关文章:
USB 驱动开发 --- Gadget 设备连接 Windows 免驱
环境信息 测试使用 DuoS(Arm CA53, Linux 5.10) 搭建方案验证环境,使用 USB sniff Wirekshark 抓包分析,合照如下: 注:左侧图中设备:1. 蓝色,USB sniff 非侵入工 USB 抓包工具;2. …...
计算机网络之---数据链路层的功能与作用
数据链路层概念 数据链路层(Data Link Layer)是计算机网络中的第二层,它位于物理层和网络层之间,主要负责数据在物理链路上的可靠传输。其基本功能是将网络层传来的数据分成帧,并负责在物理链路上可靠地传输这些数据帧…...
前端 图片上鼠标画矩形框,标注文字,任意删除
效果: 页面描述: 对给定的几张图片,每张能用鼠标在图上画框,标注相关文字,框的颜色和文字内容能自定义改变,能删除任意画过的框。 实现思路: 1、对给定的这几张图片,用分页器绑定…...
为什么HTTP请求后面有时带一个sign参数(HTTP请求签名校验)
前言 最近在开发过程中,发现前端有很多的接口发送请求时都会携带signxxxx参数,但是后端明明没有写,也不需要这个参数,后面才知道,这个前面是为了给http请求签名,主要是为了防止请求体和请求参数被拦截篡改…...
第二十八周机器学习笔记:PINN求正反解求PDE文献阅读——反问题、动手深度学习
第二十八周周报 一、文献阅读题目信息摘要Abstract网络架构实验——Data-driven discovery of partial differential equations(偏微分方程的数据驱动发现)1. Continuous time models(连续时间模型)例子:(Navier–Stok…...
计算机毕业设计hadoop+spark知网文献论文推荐系统 知识图谱 知网爬虫 知网数据分析 知网大数据 知网可视化 预测系统 大数据毕业设计 机器学习
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
C#Struct堆栈
Struct若其内部含有堆对象,Struct的该对象放在堆上; Struct当做参数传递时,其堆属性作为引用传递,值属性还是作为值传递; struct TS { public int[] t1; public int t2; } public void TF1(TS t) { int[] t1 t.t1; …...
页面转 PDF 功能的实现思路与使用方法
引言 在 Web 开发中,有时我们需要将页面的特定部分转换为 PDF 格式,以便用户下载和保存。本文将详细介绍如何使用 html2canvas 和 jspdf 这两个强大的库来实现这一功能,并且结合实际代码讲解其实现思路与使用方法。完整源码(src/…...
【保姆级教程】基于OpenCV+Python的人脸识别上课签到系统
【保姆级教程】基于OpenCVPython的人脸识别上课签到系统 一、软件安装及环境配置1. 安装IDE:PyCharm2. 搭建Python的环境3. 新建项目、安装插件、库 二、源文件编写1. 采集人脸.py2. 训练模型.py3. 生成表格.py4. 识别签到.py5. 创建图形界面.py 三、相关函数分析1.…...
docker-compose部署下Fastapi中使用sqlalchemy和Alembic
本篇介绍使用Fastapi sqlalchemy alembic 来完成后端服务的数据库管理,并且通过docker-compose来部署后端服务和数据库Mysql。包括: 数据库创建,数据库用户创建数据库服务发现Fastapi 连接数据库Alembic 连接数据库服务健康检查 部署数据…...
Oracle:ORA-00904: “10“: 标识符无效报错详解
1.报错Oracle语句如下 SELECT YK_CKGY.ID,YK_CKGY.DJH,YK_CKGY.BLRQ,YK_CKGY.ZBRQ,YK_CKGY.SHRQ,YK_CKGY.YT,YK_CKGY.ZDR,YK_CKGY.SHR,YK_CKGY.BZ,YK_CKGY.JZRQ,YK_CKGY.ZT,YK_CKGY.CKLX,(case YK_CKGY.CKLXwhen 09 then药房调借when 02 then科室退药when 03 then损耗出库when…...
C语言#define定义宏
目录 一、什么是宏以及宏的声明方式 1.宏常量: 2.宏函数: 二、宏的替换原则 三、宏设计的易犯错误 ERROR1:尾部加分号(当然有些特定需要加了分号,这里说明一般情况) ERROR2:宏函数定义时&…...
SpringBoot操作spark处理hdfs文件
SpringBoot操作spark处理hdfs文件 1、导入依赖 <!-- spark依赖--><dependency><groupId>org.apache.spark</groupId><artifactId>spark-core_2.12</artifactId><version>3.2.2</version></dependency><depend…...
消息队列架构、选型、专有名词解释
私人博客传送门 消息队列专有名词解释 | 魔筝炼药师 MQ选型 | 魔筝炼药师 MQ架构 | 魔筝炼药师 MQ顺序消息 | 魔筝炼药师...
用OpenCV实现UVC视频分屏
分屏 OpencvUVC代码验证后话 用OpenCV实现UVC摄像头的视频分屏。 Opencv opencv里有很多视频图像的处理功能。 UVC Usb 视频类,免驱动的。视频流格式有MJPG和YUY2。MJPG是RGB三色通道的。要对三通道进行分屏显示。 代码 import cv2 import numpy as np video …...
Allure 集成 pytest
Allure 是一个强大的测试报告工具,与 pytest 集成可以生成详细的测试报告,包括测试步骤、测试数据、截图、错误堆栈等。 1. 安装 Allure 和相关依赖 安装 pytest-allure-adaptor 插件: pip install allure-pytest确保本地已安装 Allure 工具。…...
【Python】构建智能语音助手:使用Python实现语音识别与合成的全面指南
随着人工智能技术的迅猛发展,语音助手已成为人们日常生活中不可或缺的一部分。从智能手机到智能家居设备,语音交互提供了便捷高效的人机交互方式。本文旨在全面介绍如何利用Python编程语言及其强大的库——SpeechRecognition和gTTS,构建一个基…...
在 Arthas 中调用 Spring Bean 方法
获取 Spring 应用上下文 使用工具类 如果你的项目中有一个工具类实现了 ApplicationContextAware 接口,如 cn.shutdown.pf.utils.SpringContextUtils,可以使用该类获取 ApplicationContext: Component public final class SpringContextUt…...
Nginx入门笔记
Nginx入门笔记 一、Nginx基本概念二、代理1、正向代理2、反向代理 三、准备工作1、CentOS 7安装nginx(1). 安装必要的依赖(2)下载nginx(3)编译安装(4)编译并安装 Nginx(5)启动nginx …...
【单片机】实现一个简单的ADC滤波器
实现一个 ADC的滤波器,PT1 滤波器(也称为一阶低通滤波器),用于对输入信号进行滤波处理。 typedef struct PT1FilterSettings PT1FilterSettings; struct PT1FilterSettings {//! last Filter output valueuint32_t filtValOld;//…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
