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;//…...

label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

【C++】纯虚函数类外可以写实现吗?
1. 答案 先说答案,可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...
Python实现简单音频数据压缩与解压算法
Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中,压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言,提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...