By fanxiushu 2022-06-01 转载或引用请注明原始作者。
CSDN上前面多篇文章都讲述过USB虚拟总线驱动,也可以叫做USB主机控制端驱动。
包括linux平台和windows平台的。
linux平台的USB主机控制器驱动相对而言比较容易开发。
因为linux内核设计了一个很容易入门的总体框架,使得我们不用花太多精力去处理与linux内核衔接的问题。
以下连接阐述的就是linux平台下的USB主机控制器驱动的开发过程。
linux平台实现USB虚拟总线驱动一(原理以及开发流程)_雨中风华的博客-CSDN博客_linux虚拟usb驱动
而windows平台就很糟糕了。而要达到所有的windows通用的虚拟USB总线驱动就更加糟糕了。
尤其要支持 WINXP,WIN7,WIN8平台,因为这些平台的内核并没有提供一个总体框架来方便的开发USB虚拟总线驱动。
因此我们必须要处理各种细节,需要仔细处理与windows操作系统衔接的各种问题。
CSDN上以前的文章也阐述过通用的windows平台的USB虚拟总线驱动的开发过程,有兴趣可以自行去查阅。
而到了WIN10以上的内核中,微软似乎发现了这样的问题,终于实现了一个UCX基础框架,方便USB虚拟总线驱动的开发。
当然,也只能支持WIN10平台,因此下面的内容都是针对win10平台的阐述(当然也同样适用WIN11及以后的升级版本)
UCX是 USB Controller Extension的简称,意思是USB主机控制器扩展,其实就是个接口库,与我们应用层理解的dll本质上没有区别。
在CSDN上前面的文章阐述WIN10 平台的UDE框架(一个纯粹的模拟虚拟USB总线的驱动的框架),地址如下:
WIN10平台基于UDE框架的的虚拟USB总线驱动开发_雨中风华的博客-CSDN博客_usb驱动开发 win10
其实也是从UCX基础框架扩展而来,只是当时在讲述UDE的时候,最后提到UDE问题:
UDE框架并没有把所有USB 命令请求下发下来,最糟糕的就是USB的 SET_CONFIGURATION 和SET_INTERFACE等重要命令。
这样造成的直接结果,就是无法实现复杂USB设备的远程访问模拟。因此最终也放弃了使用UDE来实现USB远程访问的功能。
使用UDE来模拟一些非USB到USB设备,到也是一个选择,
这本身也是UDE这个名字的定义:(USB emulated device )USB仿真设备。
我们再来简单看看微软自带的 USBXHCI.SYS ,这是微软自己实现的基于 USB3 以上的主机控制器驱动。
也就是在WIN10以上,我们几乎所有USB设备插入到电脑中,都在这个USB主机控制器驱动的管辖范围内。
USBXHCI其实也是基于UCX框架的基础上实现的 XHCI(也就是USB3.0以上的协议)主机控制器驱动。
这点也可以从泄露的 windows_10_shared_source_kit 源码中找到实际证明。
UCX框架的官方地址如下:
Overview of developing Windows drivers for USB host controllers - Windows drivers | Microsoft Docs
虽然官方描述的有点简单,
但是你若具备 USB协议基础知识, windows平台的USB设备驱动开发知识,KMDF,WDM驱动等知识,
开发UCX框架的USB总线驱动,也不会太难,
否则就如我在阐述UDE框架的时候说的一样,也会如同看天书一般。
我在以前的文章中,都有提到过,windows中 usbhub.sys (或者usbhub3.sys) 驱动,是个灵魂人物。
在WIN7,WIN8等还没出现UCX框架的时候,就已经存在了,它构建起了整个USB设备堆栈的基础。
当我们在没有UCX这类基础框架的系统中开发USB host Contrller 驱动的时候,首先创建FDO 挂载到 PCI 总线上
(当然如果是虚拟的,那就没PCI总线),然后创建PDO,让 usbhub (usbhub3) 挂载到这个PDO上,
然后usbhub(usbhub3)负责创建和管理真实USB设备的PDO,
并且再次把这些设备的USB请求转发到我们的 USB Controller驱动中来
这样在我们的驱动中处理具体的USB请求。
以上流程虽然说得挺简单,但实际操作起来却是很不容易,首先usbhub(usbhub3)提供的接口太多,版本也太多。
要实现每个版本和接口,是个非常麻烦的事情。
因此在以前的文章中,也特别提到过:
如果是实现虚拟USB控制器,使用IOCTL命令造假,让上层以为下面衔接的是 usbhub驱动。
其实UCX框架也是基于USBHUB3.SYS驱动,其实际的流程和上面提到的通过usbhub直接开发USB Host Controller驱动没什么区别。
它其实就是封装了 usbhub3.sys的各种接口行为,并且提供了简洁的接口,和更加接近通用的USB协议 的接口。
为何这么说呢?
其实我们在开发windows的驱动的时候,都有这个感受,大部分都是windows自己搞一套,很多是把通用的USB协议封装了起来。
比如 URB_FUNCTION_XXX 命令,有几十个,其实绝大部分都是 控制传输的 setuppacket 的8个字节配置码中分离出来的。
usbhub的接口也是把通用的 USB hub本身的协议封装了起来。
而UCX提供的接口,终于还原了 USB hub本该通用的 USB 命令,下面会再次提到。
UCX框架的官网,其实主要阐述了几个基本和核心函数:
1, UcxControllerCreate,
2,UcxRoothubCreate,
3,UcxUsbDeviceCreate
4,UcxEndpointCreate
其实就以上几个函数就能实现一个完整的 USB Host Controler, 是不是很简单。
不过也别高兴的太早,因为这些函数提供的回调函数也是挺多的,要实现真正的 USB Host Controller,
还是跟前面说的一样,具备windows下各种驱动开发知识,USB协议知识等。
同所有KMDF驱动一样,我们在DriverEntry中需要调用 WdfDriverCreate 初始化,并且提供 EvtDriverDeviceAdd 回调函数,
也就是对应WDM驱动中的AddDeivce回调函数。
在 EvtDriverDeviceAdd 回调函数中调用 UcxControllerCreate创建USB控制器。接着再调用UcxRoothubCreate创建 根集线器。
如果这两个函数创建成功,并且完整提供了对应的回调函数,基本上这个USB Host Controller驱动的总体框架就完成了。
在UcxControllerCreate 调用前,需要提供 :
EvtControllerUsbDeviceAdd ,USB设备添加的回调函数
EvtControllerGetCurrentFrameNumber, 获取当前 FrameNumber,主要用于 ISO传输
EvtControllerReset, 主控制器重置
EvtControllerQueryUsbCapability,主控制能提供的某些处理能力。
最重要的是 EvtControllerUsbDeviceAdd 回调函数,我们会在这个回调函数中调用 UcxUsbDeviceCreate 创建USB设备,
并且进行后续一系列的处理,从而建立起完整的USB通信过程。
至于EvtControllerUsbDeviceAdd 函数如何被触发,则需要从 UcxRoothubCreate提供的回调函数中进行解释。
接着就是在EvtDriverDeviceAdd 中调用UcxRoothubCreate,这个函数需要提供:
1, EvtRootHubClearHubFeature,清除hub的feature
2, EvtRootHubClearPortFeature,清除某个端口的feature
3, EvtRootHubGetHubStatus,获取hub的状态
4, EvtRootHubGetPortStatus,获取某个端口的状态
5, EvtRootHubSetHubFeature, 设置hub的feature
6, EvtRootHubSetPortFeature,设置某个端口的feature
7, EvtRootHubGetPortErrorCount,获取错误数
8, EvtRootHubInterruptTx, 查看发生状态改变的端口
9, EvtRootHubGetInfo,获取hub信息,通过它设置我们的端口数
10,EvtRootHubGet20PortInfo, 获取USB2的端口的信息
11,EvtRootHubGet30PortInfo 获取USB3的端口信息。
其中从1 到7 这7个回调函数,都是对应 USB标准协议的内容,这里也不再做深入讲述,
有兴趣可去查阅 USB2和USB3的 usbspec 协议阐述。
EvtRootHubInterruptTx 回调函数是用来获取哪些端口状态发生了变化。
比如简单的说,
PORT 1,我们的host controller驱动检测到 有设备连接上来,我们在PORT 1设置 C_PORT_CONNECTION这个feature,
接着调用 UcxRootHubPortChanged 通知UCX框架,我们的PORT状态发生了变化,于是 EvtRootHubInterruptTx 回调函数被调用。
接着我们在 EvtRootHubInterruptTx 回调函数中,检测到 PORT 1确实发生了 C_PORT_CONNECTION 变化,
于是设置为PORT 1 发生了改变。
再接下来,我们的 EvtRootHubSetPortFeature回调函数会接收到 PORT_RESET 等命令,
经过一系列处理,这些处理流程请查阅通用的USB 协议,USB设备连上来的流程。
EvtControllerUsbDeviceAdd 回调函数被调用,告诉我们USB连上了,
该调用 UcxUsbDeviceCreate创建UCX框架的 USB设备了。
EvtRootHubGetInfo 回调函数获取我们的host controller 打算创建多少个端口,包括多少个 usb2的端口和usb3的端口。
EvtRootHubGet20PortInfo 和 EvtRootHubGet30PortInfo 就是这些端口的具体信息,这些都好理解。
至此,开发ucx框架的 host controler 基本就能跑起来了。
但是要让某个具体USB设备插上后,能正常工作,还需要进行更多的处理过程。
上面简单阐述如何触发EvtControllerUsbDeviceAdd 回调函数,
在EvtControllerUsbDeviceAdd 回调函数中,我们该创建USB设备了,需要调用UcxUsbDeviceCreate函数。
在UcxUsbDeviceCreate 调用前,我们需要提供:
1,EvtUsbDeviceEndpointsConfigure, 端口配置情况,就是哪些端口使能,哪些别禁用
2,EvtUsbDeviceEnable,USB设备启用,默认端口启用
3,EvtUsbDeviceDisable,USB设备禁用,默认端口禁用
4,EvtUsbDeviceReset,USB设备重置
5,EvtUsbDeviceAddress, 获取USB设备地址
6,EvtUsbDeviceUpdate,USB设备更新情况
7,EvtUsbDeviceHubInfo,如果当前端口连接的是个子hub,需要提供相关信息
8,EvtUsbDeviceDefaultEndpointAdd,默认端口添加,就是我们常说的控制传输端口
9,EvtUsbDeviceEndpointAdd,普通端口添加。
这里主要讲述 8和9,其他的应该都好理解。
8和9其实都差不多,唯一区别是一个是控制传输的 ep,一个是 普通的 ep
控制传输的ep需要提供以下回调函数:
1,EvtEndpointPurge, ep清除
2,EvtEndpointStart ,ep启用
3,EvtEndpointAbort, ep中断
4,EvtEndpointOkToCancelTransfers,传输取消
5,EvtDefaultEndpointUpdate, 更新
普通ep除了需要提供上1到4之外,还需要提供:
1,EvtEndpointReset, ep发生了重置,
2,EvtEndpointStaticStreamsAdd,EvtEndpointStaticStreamsEnable,EvtEndpointStaticStreamsDisable
主要对应USB3以上的bulk传输的stream传输的,如果是USB2或者不敢兴趣,
可以在这三个回调函数中直接返回 STATUS_NOT_IMPLEMENTED 了事。
接着我们调用UcxEndpointCreate 创建具体的 endpoint。
看起来是完成了ep的创建,但是有个主要的问题,usb中ep的一个重要目的,就是为了通信,传输URB请求的。
因此我们必须把 ep和具体IO关联起来,如下伪代码实现:
WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchParallel);//
queueConfig.EvtIoInternalDeviceControl = UcxIoQueueSubmitUrb;
if (bCtrl_EP) {
queueConfig.EvtIoInternalDeviceControl = default_endpoint_UcxIoQueueSubmitUrb; ///控制传输,查看是否有缓存
}
status = WdfIoQueueCreate(devctx->controller->device,
&queueConfig, &oa2,
&io_queue);
其中UcxIoQueueSubmitUrb和default_endpoint_UcxIoQueueSubmitUrb 就是IO回调函数,对应的就是
IOCTL_INTERNAL_USB_SUBMIT_URB 这个命令请求,我们知道,windows的这个命令,承载了所有的 URB 请求。
接着就是把 io_queue和ucxEndpointCreate创建的UCXENDPOINT关联起来,调用:
UcxEndpointSetWdfIoQueue(ucx_ep, io_queue); //关联起来
这样,这个ep的所有URB请求被发送到 io_queue 队列的回调函数中了。
这里强调的是所有URB命令,包括 UDE模拟中忽略的SET_INTERFACE,SET_CONFIGURATION等命令。
接着还有一些命令,比如 URB_FUNCTION_RESET_PIPE 和URB_FUNCTION_ABORT_PIPE
其实这两个命令我们可以从 创建endpoint中提供的回调函数中找到答案。
对应 UcxEndpointCreate回调函数中的 EvtEndpointReset和 EvtEndpointAbort 回调函数。
IOCTL_INTERNAL_USB_RESET_PORT这个命令,对应着 roothub中的 set port feature回调函数中 PORT_RESET命令。
IOCTL_INTERNAL_USB_CYCLE_PORT 对应着roothub中的clear port feature回调函数中的 PORT_ENABLE 命令。
以上这些都是在实际调试中得出来的。
实现了以上内容,基本就实现了 UCX框架的 host controller driver,
至于 具体的URB数据如何处理,则是根据个人的需求,自行处理。
比如你如果是实现自己特殊硬件的 HOST控制器,根据以上的通信流程,传输USB数据到你自己的硬件即可。
如果你是实现usb远程访问,则把URB请求转发到应用层,然后再进行一堆处理即可。
总体来说,UCX框架比起自己开发原始级别的USB 虚拟总线驱动,轻松了许多。
但是与linux的框架做个比较,还是会发现非常的麻烦。
同时也非常可惜,WIN7,WIN8,WINXP没有这样的框架,鉴于目前WIN7,WIN8使用量也非常多,
很多时候,为了实现通用的windows平台的远程USB访问,我们不得不自己实现虚拟USB总线驱动。
下图是我电脑上已经实现的三个框架的 USB Host Controller:
非常热闹的,
Fanxiushu Virtual Win10-Udecx USB Host Controller 是基于UDE 框架的
Fanxiushu Virtual USB Host Controller 是最早实现的,能运行在通用windows平台,甚至在WINXP也能正常运行。
Fanxiushu Virtual Ucx-Usb Host Controller 则是最近实现的基于UCX框架。
后两种都连上了不同的USB设备,这些其实是通过远程访问来的,
当然也可以是自己模拟的“USB设备“, 比如 Fanxiushu Virtual USB Host Controller中
的 “Virtual USB Camera ” 就是单纯的模拟的USB摄像头。



