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" in
  start)
    ...
      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 通路和电源配置外,初始化过程大致分为两个阶段:

  1. USB OTG(ID 管脚)控制;
  2. 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_role
	cviusb_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.sh

CVI_DIR=/tmp/usb
CVI_GADGET=$CVI_DIR/usb_gadget/cvitek

case "$1" in
  start)
	start
	;;
  ...
  probe)
	probe

probe() {
  mkdir $CVI_DIR
  if [ ! -d $CVI_DIR/usb_gadget ]; then  
    mount none $CVI_DIR -t configfs  				# 挂载 USB Configfs
    mkdir $CVI_GADGET								# 创建gadget设备:cvitek
    echo $VID             > $CVI_GADGET/idVendor	#	设置设备信息:VID、PID
    echo $PID             > $CVI_GADGET/idProduct
    mkdir $CVI_GADGET/strings/0x409    				# 创建dadget设备 语言信息
    ...
    mkdir $CVI_GADGET/configs/c.1					# 创建gadget设备 配置
    ...
    echo 0xEF             > $CVI_GADGET/bDeviceClass			# 设备类型、子类型、协议信息
    echo 0x02             > $CVI_GADGET/bDeviceSubClass
    echo 0x01             > $CVI_GADGET/bDeviceProtocol  
	...
  if [ "$CLASS" = "ffs.adb" ] ; then ... 
  else
	    mkdir $CVI_GADGET/functions/$CLASS.usb$FUNC_NUM			# 当前为 NCM 功能设备
  if [ "$CLASS" = "ncm" ] ; then
    ln -s $CVI_FUNC/ncm.usb$FUNC_NUM $CVI_GADGET/configs/c.1	# 关联  function 
  ...	    

start() {
  ...
  if [ -d $CVI_GADGET/functions/ffs.adb ]; then ... else
    UDC=`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设备》方案,实施步骤如下:

  1. 先读取设备描述符和配置描述符,判断设备描述符中的bcdUSB字段,检查设备支持的USB版本号是否大于等于2.0;
  2. 如果bcdUSB大于等于 0x0200,主机请求 OS字符串描述符,请求索引 index 值为0xee
  3. 设备应答 OS 字符串描述符(总长度为18,内容unicode编码为 ”MSFT100″,vendor code由厂商自己定义);
  4. 主机对OS字符串描述验证通过后,发出功能描述符请求(两种):
    • 设备应答 扩展兼容ID描述符(请求索引 wIndex 值为 0x04);
    • 设备应答 扩展属性描述符(请求索引 wIndex 值为 0x05);
  5. 完成枚举,免驱使用;

通过兼容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设备》,实施步骤如下:

  1. 先读取设备描述符和配置描述符,判断设备描述符中的 bcdUSB 字段是否大于等于 0x0210
  2. 如果 bcdUSB 大于等于 0x0210,主机请求 BOS 描述符(类型 bDescriptorType 值为0x0f);
  3. 设备应答 BOS 描述符(总长度为33(5 + 28),bDevCapabilityType 值为 0x05,UUID、VendorCode 由厂商自己定义);
  4. 主机对 BOS 描述验证通过后,依据 bVendorCode 发出 OS2.0 描述符集请求(索引 wIndex 值为 0xC0),包含:
    • WCID20 兼容ID 描述符(类型 wDescriptorType 值为 0x03; cCID_8 值为 ”WINUSB“);
    • WCID20 注册表属性 描述符(类型 wDescriptorType 值为 0x04;内容为接口 GUID 键对,其中 GUID 值由厂商自己定义);
  5. 完成枚举,免驱使用;

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.c

CONFIGFS_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);
				else
					cdev->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.o

obj-$(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_GAOYANG

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
@@ -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 免驱的快速验证。

方案总结

  1. 当前环境(Win10 USB 2.0 主机,Linux USB 2.0 设备)下,无法触发 Win10 主机的 “微软系统描述符1.0" 机制,OS 1.0 免驱方案不见效;
  2. 经快速修改{ bcdUSB = 0x0210;应答 ”BOS 描述符“;应答 ”OS2.0 描述符集“}后,可以触发 Win10 主机的 “微软系统描述符2.0" 机制,方案有效;
  3. 完整方案需要参考 gadget 驱动框架,考虑更合理的 “微软系统描述符2.0" 免驱方案实现;

调试关注

  1. 及时删除注册表中的 usbflags下的对应目录,否则设备不再发出BOS描述符请求。

  2. 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
      

术语缩写

术语 & 编写说明备注
UDCUSB设备控制器(UDC)驱动指的是作为其他USB主机控制器外设的USB硬件设备上底层硬件控制器的驱动,该硬件和驱动负责将一个USB设备依附于一个USB主机控制器上。
NCMUSB NCM,属于USB-IF定义的CDC(Communication Device Class)下的一个子类:Network Control Model,用于Host和Device之间交换以太网帧。
MSCMass Storage Class, USB大容量存储设备,通常指的是像U盘这样的存储设备。
UACUSB Audio Class,USB音频类,例如电话,音乐回放,录音等音频功能等;
ACMAbstract Control Model, 主要用于支持模拟调制解调器(Modem)设备在 USB 总线上的通信。

参考

  • 第16章 USB主机、设备与Gadget驱动之USB UDC与Gadget驱动(一)

  • 通过configfs配置的Linux USB gadget

  • Configfs - 用户空间驱动的内核对象配置

  • 简单几步,让自定义USB设备也能免驱动运行

  • 使用微软系统描述符1.0制作免驱动自定义USB设备

  • 使用微软系统描述符2.0制作免驱动自定义USB设备

  • WinUSB提供的相关USB结构体

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/948513.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2025年1月4日蜻蜓q旗舰版st完整开源·包含前后端所有源文件·开源可商用可二开·优雅草科技·优雅草kir|优雅草星星|优雅草银满|优雅草undefined

2025年1月4日蜻蜓q旗舰版st完整开源包含前后端所有源文件开源可商用可二开优雅草科技优雅草kir|优雅草星星|优雅草银满|优雅草undefined 产品介绍&#xff1a; 本产品主要贡献者优雅草科技优雅草kir|优雅草星星|优雅草银满|优雅草undefined-青史留名&#xff0c;时光如川浪淘…

【文献精读笔记】Explainability for Large Language Models: A Survey (大语言模型的可解释性综述)(三)

****非斜体正文为原文献内容&#xff08;也包含笔者的补充&#xff09;&#xff0c;灰色块中是对文章细节的进一步详细解释&#xff01; 3.2 全局解释&#xff08;Global Explanation&#xff09; 与旨在解释模型个体预测的局部解释不同&#xff0c;全局解释提供了对语言模型…

体验谷歌最新Gemini 2.0 Flash原生多模态音视频对话桌面分享功能

Gemini 2.0是谷歌最新推出的原生多模态输入输出的AI模型。Gemini 2.0 Flash是2.0家族第一个模型&#xff0c;以多模态输入输出和Agent技术为核心&#xff0c;速度比 1.5 Pro快两倍&#xff0c;关键性能指标超过 1.5 Pro。模型支持原生工具调用和实时音视频流输入&#xff0c;提…

Leecode刷题C语言之我的日程安排表③

执行结果:通过 执行用时和内存消耗如下&#xff1a; typedef struct {int size;int maxIntersection;int** books;// #ifdef DEBUG// int runCount;// #endif } MyCalendarThree;void insert(MyCalendarThree*, int, int, int, int); int* binarySearch(int*, int, int);MyCal…

C++ 函数名字后面带const

C++中,在函数名后面加上const关键字表示该函数是一个常量成员函数。 常量成员函数,可以在const对象上被调用,并且不会修改对象的状态。 VC6新建一个单文档工程;添加一个一般类; 把类的代码做好; // MyClass.h: interface for the MyClass class. // //#if !defined(AFX_…

SMTP发送邮件的过程

&#xff08;1&#xff09;SMTP客户端首先请求与服务器端的25号端口建立TCP连接(1分)。&#xff08;2&#xff09;连接建立成功后&#xff0c;客户端和服务器通过握手阶段验证双方身份(1分)。&#xff08;3&#xff09;验证成功后&#xff0c;客户端首先向服务器端通告邮件发送…

qml Rectangle详解

1、概述 Rectangle是Qt Quick中的一个基础图形元素&#xff0c;用于在QML界面上绘制一个可带边框和可填充的矩形区域。它继承自Item类&#xff0c;因此具有Item的所有属性和功能&#xff0c;如位置、尺寸、变换等。通过Rectangle&#xff0c;可以创建各种矩形形状&#xff0c;…

软件工程实验-实验2 结构化分析与设计-总体设计和数据库设计

一、实验内容 1. 绘制工资支付系统的功能结构图和数据库 在系统设计阶段&#xff0c;要设计软件体系结构&#xff0c;即是确定软件系统中每个程序是由哪些模块组成的&#xff0c;以及这些模块相互间的关系。同时把模块组织成良好的层次系统&#xff1a;顶层模块通过调用它的下层…

Innodisk iSMART V6使用说明_SSD还能用多久?已经读写了多少次数?……

Innodisk iSMART是一款SSD健康数据读取软件。它能轻松获取大部分SSD内部寄存器中的健康数据&#xff0c;并以简洁的图形界面展示给用户。在程序界面的顶部&#xff0c;是页面标签&#xff0c;点击页面标签就能切换到相应的页面。页面标签的下面是磁盘选择栏。点击磁盘编号&…

windows11(或centos7)安装nvidia显卡驱动、CUDA、cuDNN

本文是我瞎搞时写的问题汇总及参考文献&#xff0c;记录了一些问题解决的进度及对问题的思考。 最近一次更新时间&#xff1a;2025年1月4日 一、安装或更新nvidia显卡驱动 首先&#xff0c;需要确保你的设备安装了最新的显卡驱动。 &#xff08;1&#xff09;centos7安装显…

2、蓝牙打印机点灯-GPIO输出控制

1、硬件 1.1、看原理图 初始状态位高电平. 需要驱动PA1输出高低电平控制PA1. 1.2、看手册 a、系统架构图 GPIOA在APB2总线上。 b、RCC使能 GPIOA在第2位。 c、GPIO寄存器配置 端口&#xff1a;PA1 模式&#xff1a;通用推挽输出模式 -- 输出0、1即可 速度&#xff1a;5…

WPS表格技巧01-项目管理中的基本功能-计划和每日记录的对应

前言&#xff1a; 在项目管理中&#xff0c;一般就是用些项目管理工具来管理这个任务和 task&#xff0c;但是就是要学这些工具很麻烦&#xff0c;比较好的方法&#xff0c;通用的方法就是用 Excel 表格去做&#xff08;这非常适合松散的团队组织&#xff09;&#xff0c;然后…

Vue 项目中实现打印功能:基于目标 ID 的便捷打印方案

一、引言 在 Vue 项目开发中&#xff0c;实现打印功能是一个常见的需求。本文将介绍如何封装一个打印方法&#xff0c;使得用户只需传入需要打印的目标 ID 名称&#xff0c;即可轻松实现预览并打印的功能。这种方法不仅简单易用&#xff0c;还具有一定的通用性&#xff0c;适合…

ARM 汇编基础总结

GNU 汇编语法 编写汇编的过程中&#xff0c;其指令、寄存器名等可以全部使用大写&#xff0c;也可以全部使用小写&#xff0c;但是不能大小写混用。 1. 汇编语句的格式 label: instruction comment label即标号&#xff0c;表示地址位置&#xff0c;有些指令前面可能会有标…

《塑战核心》V1.0.0.9952官方中文版

体验打击感满分的近距离战斗。击败蜂拥而至的敌人&#xff0c;每次击杀都会让你变得更强。 《塑战核心》官方中文版https://pan.xunlei.com/s/VODW7effpagQN1JU0UpBQQ5uA1?pwdmr8g#

综合练习dfs_1

1863. 找出所有子集的异或总和再求和 之前我们就做了到关于找集合子集的问题&#xff0c;但我们不需要记录路径上的数&#xff0c;求路径上数的异或和就可以。 class Solution {int path;int sum0; public:int subsetXORSum(vector<int>& nums) {dfs(nums,0);return …

【Python学习(五)——条件判断】

Python学习&#xff08;五&#xff09;——条件判断 本文介绍了条件判断&#xff0c;仅作为本人学习时记录&#xff0c;感兴趣的初学者可以一起看看&#xff0c;欢迎评论区讨论&#xff0c;一起加油鸭~~~ 心中默念&#xff1a;Python 简单好学&#xff01;&#xff01;&#x…

PPT加页码并改格式

如何快捷插入自定义 1、插入文本框&#xff0c;并处于输入状态 2、点击插入幻灯片编号的图标&#xff0c;就自动生成页码了 3、然后调整这个页码为想要的格式&#xff0c;到需要加页码的页面&#xff0c;将文本框复制过去就行了

Git 入门(一)

git 工作流如下&#xff1a; 命令如下&#xff1a; clone&#xff08;克隆&#xff09;: 从远程仓库中克隆代码到本地仓库checkout &#xff08;检出&#xff09;:从本地仓库中检出一个仓库分支然后进行修订add&#xff08;添加&#xff09;: 在提交前先将代码提交到暂存区com…

windows远程桌面无法连接,报错:“由于没有远程桌面授权服务器可以提供许可证,远程会话被中断。请跟服务器管理员联系”

windows远程桌面无法连接&#xff0c;报错&#xff1a;“由于没有远程桌面授权服务器可以提供许可证&#xff0c;远程会话被中断。请跟服务器管理员联系” 问题描述&#xff1a;解决方法&#xff1a;无法删除条目解决如下&#xff1a;正常激活详见&#xff1a;[RDS远程服务激活…