环境信息
测试使用 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 通路和电源配置外,初始化过程大致分为两个阶段:
- 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_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设备》方案,实施步骤如下:
- 先读取设备描述符和配置描述符,判断设备描述符中的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.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 免驱的快速验证。
方案总结
- 当前环境(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结构体