USB 驱动开发 --- Gadget 设备连接 Windows 免驱

news2025/1/7 5:34:27

环境信息

测试使用 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结构体

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java项目之读书笔记共享平台(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的闲一品交易平台。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 读书笔记共享平台的主要使…

git知识点汇总

git init 初始化一个git仓库&#xff0c;后面可以加仓库名&#xff0c;在当前目录下创建指定名称的目录并在该目录下创建仓库&#xff0c;若不加则直接在当前目录下创建仓库。git仓库的三个区域&#xff1a;工作区&#xff08;当前目录&#xff09;、暂存区&#xff08;.git/in…

探索大型语言模型新架构:从 MoE 到 MoA

探索大型语言模型新架构&#xff1a;从 MoE 到 MoA 当前&#xff0c;商业科技公司纷纷投身于一场激烈的竞赛&#xff0c;不断扩大语言模型的规模&#xff0c;并为其注入海量的高质量数据&#xff0c;试图逐步提升模型的准确性。然而&#xff0c;这种看似顺理成章的发展路径逐渐…

单片机-静动态数码管实验

P0控制数码管 &#xff0c;P0低电平 P1,P2,P3高电平 1、静态数码管 需求&#xff1a;数码管显示0&#xff0c;即让p0端口输出数字0的段码0x3f(共阴) #include "reg52.h" typedef unsigned int u16; typedef unsigned char u8; //数码管显示数字的数组 共阴极 …

Hyperbolic dynamics

http://www.scholarpedia.org/article/Hyperbolic_dynamics#:~:textAmong%20smooth%20dynamical%20systems%2C%20hyperbolic%20dynamics%20is%20characterized,semilocal%20or%20even%20global%20information%20about%20the%20dynamics. 什么是双曲动力系统&#xff1f; A hy…

细说STM32F407单片机轮询方式CAN通信

目录 一、项目介绍 二、项目配置 1、时钟、DEBUG、USART6、NVIC、GPIO、CodeGenerator 2、CAN1 &#xff08;1&#xff09;Bit Timings Parameters组&#xff0c;位时序参数 &#xff08;2&#xff09;Basic Parameters组&#xff0c;基本参数 &#xff08;3&#xff09…

linux装git

前言 以 deepin 深度系统为例&#xff0c;安装命 令行版 Git 非常简单。 安装 注意&#xff1a;需要输入账号密码&#xff0c;否则无法进行。 打开终端&#xff0c;执行如下命令即可。 sudo apt-get install git成功 如下图所示&#xff0c;输入 git &#xff0c;命令识别即…

微信小程序滑动解锁、滑动验证

微信小程序简单滑动解锁 效果 通过 movable-view &#xff08;可移动的视图容器&#xff0c;在页面中可以拖拽滑动&#xff09;实现的简单微信小程序滑动验证 movable-view 官方说明&#xff1a;https://developers.weixin.qq.com/miniprogram/dev/component/movable-view.ht…

Kerberos用户认证-数据安全-简单了解-230403

hadoop安全模式官方文档&#xff1a;https://hadoop.apache.org/docs/r2.7.2/hadoop-project-dist/hadoop-common/SecureMode.html kerberos是什么 kerberos是计算机网络认证协议&#xff0c;用来在非安全网络中&#xff0c;对个人通信以安全的手段进行身份认证。 概念&#…

大麦抢票科技狠活

仅供学习参考&#xff0c;切勿再令您所爱的人耗费高昂的价格去购置黄牛票 ⚠️核心内容参考: 据悉&#xff0c;于购票环节&#xff0c;大麦凭借恶意流量清洗技术&#xff0c;于网络层实时甄别并阻拦凭借自动化手段发起下单请求的流量&#xff0c;强化对刷票脚本、刷票软件以及…

光伏电站的成本估算方式

绿虫仿真设计软件的成本估算功能主要通过以下方式实现&#xff1a; 依据设计方案自动生成材料清单&#xff1a;软件能够根据光伏项目的具体设计&#xff0c;确定所需的各种材料&#xff0c;如光伏组件、逆变器、线缆等。结合市场价格信息&#xff1a;它可以获取实时的市场价格…

结构生物学1-绪论:

请结合图片&#xff0c;详细解释图片中的内容&#xff0c;要求逻辑清晰&#xff0c;并给出整理与答疑1&#xff0c;x射线衍射&#xff1a; 1. X射线与光学显微镜的基本原理对比 X射线的特性&#xff1a;为了解析大约1-5埃&#xff08;0.1-0.5纳米&#xff09;的细小原子结构&…

yolo小damo合集

效果如下&#xff1a;这个是图片检测 效果如下&#xff1a;这个是视频检测 效果如下&#xff1a;这个是摄像头检测 1 相关库 除了yolov11所用库之外&#xff0c;本文所用到的额外库为pyqt5&#xff0c;输入指令进行安装 pip install pyqt5 导入所需要的库 import sys fro…

商标名称仅由常见姓氏构成,缺显驳回!

近日一个江苏网友给普推知产商标老杨发过来的一个商标驳回案例&#xff0c;商标驳回的原因与第一次驳回引证的商标居然是不一样的&#xff0c;引证的商标与第一次引证的商标也是不一样的。 看了下引证的两个商标与申请商标名称明显不太近似&#xff0c;或许还有做复审的机会&am…

Rockect基于Dledger的Broker主从同步原理

1.前言 此文章是在儒猿课程中的学习笔记&#xff0c;感兴趣的想看原来的课程可以去咨询儒猿课堂 这篇文章紧挨着上一篇博客来进行编写&#xff0c;有些不清楚的可以看下上一篇博客&#xff1a; RocketMQ原理简述&#xff08;二&#xff09;-CSDN博客 2.Broker的高可用 如果…

深入Android架构(从线程到AIDL)_08 认识Android的主线程

目录 3、 认识Android的主线程(又称UI线程) 复习&#xff1a; 各进程(Process)里的主线程​编辑 UI线程的责任&#xff1a; 迅速处理UI事件 举例 3、 认识Android的主线程(又称UI线程) 复习&#xff1a; 各进程(Process)里的主线程 UI线程的责任&#xff1a; 迅速处理UI事…

个人博客自我介绍

你好&#xff0c;我是Chiawei&#xff01; 大家好&#xff0c;我是Chiawei&#xff0c;一个热爱编程和探索新知识的人。很高兴能在这里与大家分享我的编程之旅。今天&#xff0c;我想和大家聊聊我的自我介绍、编程目标、学习计划以及一些个人想法。 自我介绍 我是一个对技术充…

logback之自定义过滤器

logback有两种过滤器&#xff0c;一种是context中的过滤器叫TurboFilter&#xff0c;是一个全局的过滤器&#xff0c;会影响所有的日志记录。另一种是Appender中的过滤器&#xff0c;只对所在的append有效。两者大同小异&#xff0c;这里我们以Appender的过滤器为例。 &#x…

AcWing练习题:面积

给定三个浮点数 A&#xff0c;B 和 C。 然后&#xff0c;计算如下图形的面积&#xff1a; 底边为 A&#xff0c;高为 C 的三角形。半径 C 的圆。&#xff08;π3.14159&#xff09;底边为 A 和 B&#xff0c;高为 C 的梯形。边长为 B 的正方形。边长为 A 和 B 的长方形。 输…

三甲医院等级评审八维数据分析应用(一)--组织、制度、管理可视化篇

一、引言 1.1 研究背景与意义 在当今医疗领域,三甲医院作为医疗服务的核心载体,肩负着保障民众健康、推动医学进步的重任。随着信息技术的飞速发展,数据已成为医院运营管理、医疗质量提升以及科学决策的关键要素。三甲医院等级评审作为衡量医院综合实力与服务水平的重要标…