USB设备驱动开发之远程访问USB设备( 三 虚拟USB控制器和根集线器)

By Fanxiushu 2016-05-25 转载或引用请注明原始作者

接上文,
通过应用层程序发送 CreatePDO IOCTL命令到总线驱动,让总线驱动直接创建一个虚拟USB设备的PDO,所有发送到
这个PDO的URB请求最终被转发到应用层程序,由应用层程序通过网络转发到真正的USB设备端进行数据处理,
并且最终把处理的数据结果回传给总线驱动。
采用这样的工作模式的虚拟USB设备,因为缺少USB控制器和根集线器的支持,某些软件尤其是工作在底层的软件,
比如USBLyzer这样的USB抓包工具,再比如vmware虚拟机程序等是无法识别出我们的虚拟USB设备的。
而我实现远程访问USB设备的初衷就是为了能让vmware虚拟机识别出虚拟USB设备,
并且再次把虚拟USB设备成功转向到vmware的虚拟机系统里边去。
因此目标尚未没达到,还需继续努力,得让USB虚拟系统是个完善的USB设备栈。

于是通过Google全世界范围内搜索关于windows平台下USB设备栈的相关资料
(感谢Google强大的搜寻能力,尤其是对这些比较难查询的技术资料,国内的搜索引擎就显得捉襟见肘了)
通过查询资料,查看源代码,尤其是ReactOS的关于USB设备栈实现的源代码(也得感谢ReactOS提供的接近windows内核的源代码),
慢慢的理解了windows平台下USB设备栈的层次结构。
基本的层次结构其实在第一章介绍数据采集端的时候已经介绍过了。

电脑中存在基本的PCI总线,USB控制器(也即是某个芯片)通过PCI总线接入电脑。
电脑启动时候,PCI总线驱动会枚举到USB控制器,并且加载USB控制器的功能驱动。
接下来USB控制器的功能驱动会枚举出它所带的RootHUB(根集线器)设备,并告知PnP管理器。
于是PnP管理器接着加载RootHUB的功能驱动。
RootHUB的功能驱动启动后开始监控它的PORT状态,一旦发现有USB设备连接上来,
就创建这个USB设备的PDO,初始化相关信息,并且告知PnP管理器,PnP管理器于是加载这个USB设备对应的功能驱动。
USB设备的功能驱动加载成功后,就开始通过URB包与USB设备通讯。
所有的URB数据包被发送到RootHUB功能驱动创建的这个USB设备的PDO上边。
对于硬件来说,URB数据包是通过USB控制器统一管理和处理的,因此RootHUB功能驱动会接着把发给这个USB设备的PDO的URB包
转发到USB控制器得功能驱动里边,由USB控制器负责真正的硬件级别的数据通信。

整个过程起码存在三种类型的功能驱动,而且三种驱动是父,子,孙的关系:
一,PCI总线驱动,负责枚举USB控制器,(父亲)
二,USB控制器驱动,负责枚举RootHUB,并且处理URB数据包(儿子)
三,RootHUB驱动,负责创建USB设备的PDO,并且转发URB包给USB控制器。(孙子)

要实现完整的USB设备栈,得实现以上三种驱动,对于我们的虚拟设备来说,
PCI总线驱动等同于我们的虚拟总线驱动,它专门负责枚举出虚拟USB控制器。
当我们的虚拟总线驱动枚举出虚拟USB控制器后,接着加载我们的虚拟USB控制器驱动,
虚拟USB控制器驱动负责枚举出虚拟的RootHUB设备,并且实现某些微软定义好的IOCTL通讯,
这些IOCTL必须实现,否则上层的软件是无法识别我们的驱动的。
当我们的虚拟USB控制器驱动枚举出虚拟RootHUB设备后,接着加载我们的虚拟RootHUB驱动,
虚拟RootHUB驱动负责创建虚拟的USB设备,并且实现某些微软定义好的IOCTL通讯,
最后RootHUB驱动还得跟我们的应用层程序通讯,负责把发送到虚拟USB设备PDO上的URB转发到应用层进行处理。

也就是把前篇文章介绍的虚拟USB 设备的数据处理移交到虚拟RootHUB功能驱动去处理,
然后还得实现两个驱动来完善windows平台下的整个USB设备栈,这样才能被某些底层软件比如vmware程序识别。

我们的虚拟USB控制器驱动和RootHUB驱动与真正的USB控制器和RootHUB是有区别的,
主要的区别是URB数据包并不是转发到虚拟USB控制器去处理,而是在虚拟RootHUB驱动里边直接发给应用层处理,
并且每个虚拟USB设备的PDO就对应一个应用层接口。
这点跟硬件的USB控制器不同的,硬件的USB控制器驱动实现核心的URB数据包处理过程,
所有USB设备的URB数据都会汇集到USB控制器里处理。
而我们的虚拟USB控制器就是个摆设,他存在的目的就是为了欺骗上层软件。

至于为何要实现这么一个处理结构而非完全按照windows平台的处理USB设备的URB包的方式,
是经过一翻折腾才最终这么做的,
这么实现最终也达到了我所需要的效果,并且付出的代价比完全按照硬件模式集中处理URB包少。

在开发完成USB采集端和USB虚拟设备端,
并且整套系统都能正常运行,也能看到虚拟设备的模拟效果。
然后才发现vmware,USBLyzer等软件无法识别出USB虚拟设备,
查找原因是因为他们按照USB的层次结构枚举存在于系统中的USB设备的。
具体的说,就是通过 GUID_DEVINTERFACE_USB_HOST_CONTROLLER 接口查询所有USB控制器,
再通过控制器查询根集线器,然后再最终查询到USB设备,具体查询代码可查阅WDK的例子工程usbview。

因此必须实现这么一个层次结构,想法是在已经实现了的源代码基础上增加这些功能,并且对已经实现了的框架尽量少做调整。
首先想到的是在原有的总线驱动再枚举出两个特殊的PDO,一个作为USB控制器,一个作为RootHUB。
这个时候我们的总线驱动就存在三种类型的PDO,一种是模拟USB设备,一种是模拟USB控制器,一种是模拟根集线器,
三种PDO处于平行结构,各自不属于谁,正是这个原因,再次造成vmware无法识别我们的虚拟USB设备,下面会说到。

这样就有了虚拟USB控制器和虚拟RootHUB的物理设备对象了。
接着就是他们的功能驱动问题了,USB控制器没现成的,得自己实现一个虚拟USB功能驱动。
虚拟RootHUB的功能驱动打算直接使用微软的usbhub.sys驱动,这个是windows的USB2的通用集线器驱动框架,
接入usbhub.sys本来是想节省点事情,谁知道反而是增加麻烦。
usbhub会要求RootHUB提供 USB_BUS_INTERFACE_HUB_V 接口,
这个接口有非常多的版本, 在WinXP是V5版本,到了win7,起码需要V6,V7版本,
到了win10 ,已经达到了V9版本,
接口要求提供的回调函数非常之多,光USB_BUS_INTERFACE_HUB_V5 就已经要求15个回调函数了。
USB_BUS_INTERFACE_HUB_V9 要求的接口函数更是多得不得了。

HUB设备依然是个特殊的USB设备,依然有设备,配置,接口和端点描述符;还有个HUB描述符,描述PORT信息的。
还得提供一个中断类型的Pipe,用于当某个USB设备接入到某个PORT的时候,通知给usbhub.sys。
这些都是标准HUB设备必须提供的。当我把这一切数据在自己创建的虚拟RootHUB的PDO上伪造好之后,
然后辛辛苦苦的老老实实的实现了 USB_BUS_INTERFACE_HUB_V5必须提供的15个接口函数,
之后兴奋的打算先在winXP系统下调试,然后再实现V9版本的所有接口函数,
也幸好是先在winxp做测试,没再老老实实的实现V9的所有接口函数。
结果发现一个严重问题,问题来源还是自己对usbhub.sys的运行框架的不甚理解造成的。
因为我的总线驱动是通过应用层程序发送CreatePDO IOCTL控制码来直接创建虚拟USB设备的PDO的,
也就是虚拟USB设备的PDO是在我的总线驱动中直接创建的,
我在 USB_BUS_INTERFACE_HUB_V5的CreateUsbDevice回调函数中通过某种映射关系,
把在总线驱动中已经创建好的虚拟USB设备的PDO跟RootHUB的PORT关联起来,
以为这样就算是实现CreateUsbDevice的功能了。
而事实却是所有的USB设备的PDO都是在usbhub.sys驱动中创建的,USB_BUS_INTERFACE_HUB_V5接口提供的CreateUsbDevice
函数只是告诉我们开辟一块自己的数据结构来描述新到来的USB设备,并且提供一个USB handle句柄,
用于在其他回调函数中识别我们的数据结构,当调用完成CreateUsbDevice和其他一些初始化接口函数之后,
usbhub.sys就开始创建新到来的USB设备的PDO。
这些是在我发现我问题之后,通过查询ReactOS关于USB设备栈的源代码,尤其是关于usbhub部分的源代码,
才知道usbhub的工作流程,然后才恍然大悟。
如果真要使用usbhub.sys来作为我们的RootHUB的功能驱动,就得颠覆我之前的工作,修改量不是一般的小。
关键是以前实现的功能都得重做,实在不划算。因此只好放弃usbhub.sys,自己开发RootHUB的功能驱动。
虚拟RootHUB和控制器,都是不存在的,没必要非得在底层都按照硬件的模式来伪造数据,只要能欺骗上层驱动和程序就行了。
欺骗上层,关键是实现微软定义的必须实现的一些IOCTL接口,
这些IOCTL定义可查看 https://msdn.microsoft.com/en-us/library/windows/hardware/ff537421%28v=vs.85%29.aspx 连接。
想到这点,又开发了两个功能驱动,一个是虚拟USB控制器,一个是RootHUB,并且实现对上层的IOCTL接口。
结果一安装运行,USBLyzer这样的软件能正常识别和抓包了。
但是vmware还是无法识别,经过思索,同时反复比较注册表中我的驱动和别的正常的驱动有什么不同之处,
结果在注册表中发现别的驱动中有个字段ParentIdPrefix ,在我的驱动中不存在。
于是想到,可能是因为我并没按照层次结构来创建PDO,在系统中没有形成设备树结构。
因此再次修改驱动,这次我把USB控制器得功能驱动,RootHUB的功能驱动等全部集成到一个源代码中,
通过不同的标示来判断究竟属于那种驱动类型,这样看起来更简洁。

这样修改之后,奇迹般的被vmware识别出我们的虚拟设备了。
兴奋过后,虽然vmware识别出虚拟设备,但是还是没法接入到虚拟机系统里边去,再次通过查阅各种资料,
以及vmware打印的错误日志,查询到vmware论坛说什么vmware会做底层检测,究竟做什么检测,我也不甚了解。
反正是如果不让它做检测,只要在注册表的 hcmon 服务中添加Parameters子项,
在Parameters子项里添加一个 DWORD键值 DisableDriverCheck 并且设置为1 ,
然后重启 vmware-usbarbitrator64.exe 程序,再重新打开vmware虚拟机就不会在做检测了。
经过这么设置,并且在经过修改BUG之后,虚拟USB设备终于神奇的被vmware再次重定向到虚拟机系统里边去。
有时不能发现虚拟USB设备,可能是驱动中哪个接口没处理好造成的,暂时也懒得去找具体原因了,
只要再把 vmware-usbarbitrator64.exe 重启一下,再重新打开vmware虚拟机,基本上都能被识别到。
用它把从远端虚拟的iPhone设备经过vmware再次接入到 Mac OS操作系统中,并且能在Xcode开发环境中,正常使用。

于是我的终极目标终于完成了。

CSDN上提供的部分源代码工程有完整的例子程序,驱动安装效果如下图:
图片

下图是运行USBlyzer抓包软件之后的虚拟USB设备栈的效果:
图片

虚拟设备栈中“插入”了三个USB设备,一个是iPhone,一个是摄像头,一个是USB键盘。

CSDN上源代码工程下载地址:

http://download.csdn.net/detail/fanxiushu/9538188




猜你喜欢

转载自blog.csdn.net/fanxiushu/article/details/51559720