您的位置:首页 > 其它

USB设备驱动开发之远程访问USB设备(二 USB设备虚拟端)

2016-05-24 22:31 573 查看
By Fanxiushu 2016-05-22 转载或引用请注明原始作者

接上文,

在处理好USB数据采集端的问题之后,接下来进入核心的部分,虚拟USB设备端的开发工作。

上文简单介绍过,需要开发虚拟总线驱动来模拟USB设备。

所谓虚拟总线驱动,就是安装于System系统设备下的一个驱动,由PnP管理器创建出一个虚拟的总线PDO设备,

我们的虚拟总线驱动Attach到这个PDO上,形成一个FDO功能设备驱动,

然后在我们的驱动中,根据需要创建出若干个 Child PDO设备,

这些 Child PDO设备就是我们根据需要模拟出来的虚拟设备。

我们的总线驱动每当创建出一个 Child PDO并且初始化之后,

调用 IoInvalidateDeviceRelations函数,通知PnP管理器我们的的Child PDO有变化。

于是PnP管理器接着发送 IRP_MN_QUERY_DEVICE_RELATIONS即插即用消息给我们的驱动,

等我们把新的所有Child PDO列表告诉给PnP管理器,它接着比较他内部维护的新旧的PDO列表,

知道哪些PDO被新添加,哪些已经被移除。

对于新添加的设备,PnP管理器发送查询设备ID的消息IRP_MN_QUERY_ID给我们创建的Child PDO,查询设备的各种ID,

然后PnP管理器根据设备ID从注册表查找是否已经为这个Child PDO安装了功能驱动,

如果已经安装,则加载它,没安装则提示用户安装新的驱动。

这就是虚拟总线驱动的大致框架,原理上来说并不复杂,而且有微软提供的 例子代码,

可以阅读它的例子代码进一步加深对总线驱动原理的理解,或者可以查看我提供在CSDN上的源代码来加深理解。

我们的总线驱动模拟的是USB设备接口,因此Child PDO必须具备USB接口的特性,

USB接口核心部分要处理的,其实就是上文简单介绍过的USB接口的四种数据传输方式:

一,控制传输,二中断传输,三批量传输,四,同步传输。

中断,批量,同步传输都比较好处理,而控制传输牵涉到的命令很多,因此需要处理多种命令。

windows平台把跟USB接口的设备进行数据通讯统一使用URB数据包,每个包都指定一个Function功能号,

也就是URB的功能种类。Function的种类大概有20多个,其实依然是从USB接口的四种通讯方式派生出来的,

比如URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER 这个URB功能就是中断传输和批量传输的合并。

URB_FUNCTION_ISOCH_TRANSFER就是同步传输,

而余下来的20多个 URB_FUNCTION_XXX可以完全理解成控制传输的某个命令。

比如URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE就是控制传输中,从USB设备获取设备描述符。

首先列举中我们的驱动中需要处理的URB_FUNCTION_XXX命令:

(以下是中断,批量,同步传输命令)

URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER(中断或者批量传输)

URB_FUNCTION_ISOCH_TRANSFER(同步传输)

(以下全是控制传输命令)

通用的控制传输命令,当USB设备传输的命令不在微软定义的URB_FUNCTION时候,可以用它进行传输

URB_FUNCTION_CONTROL_TRANSFER

获取设备,接口,端点描述符

URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE

URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE

URB_FUNCTION_GET_DESCRIPTOR_FROM_ENDPOINT

选择配置描述符, 接口可选描述符

URB_FUNCTION_SELECT_CONFIGURATION

URB_FUNCTION_SELECT_INTERFACE

获取USB设备的Class或Vendor信息

URB_FUNCTION_CLASS_DEVICE

URB_FUNCTION_CLASS_INTERFACE

URB_FUNCTION_CLASS_ENDPOINT

URB_FUNCTION_CLASS_OTHER

URB_FUNCTION_VENDOR_DEVICE

URB_FUNCTION_VENDOR_INTERFACE

URB_FUNCTION_VENDOR_ENDPOINT

URB_FUNCTION_VENDOR_OTHER

重置或者中断在某个端点的传输

URB_FUNCTION_RESET_PIPE

URB_FUNCTION_ABORT_PIPE

获取设备,接口,端点状态

URB_FUNCTION_GET_STATUS_FROM_DEVICE

URB_FUNCTION_GET_STATUS_FROM_INTERFACE

URB_FUNCTION_GET_STATUS_FROM_ENDPOINT

URB_FUNCTION_GET_STATUS_FROM_OTHER

获取当前配置,当前接口,当前framenumbber。当前的framehnumber用于同步传输

URB_FUNCTION_GET_CONFIGURATION

URB_FUNCTION_GET_INTERFACE

URB_FUNCTION_GET_CURRENT_FRAME_NUMBER

以下是设置或清除FEATURE,主要用于HUB,当然可能某些USB设备会有用到

URB_FUNCTION_SET_FEATURE_TO_DEVICE

URB_FUNCTION_SET_FEATURE_TO_INTERFACE

URB_FUNCTION_SET_FEATURE_TO_ENDPOINT

URB_FUNCTION_SET_FEATURE_TO_OTHER

URB_FUNCTION_CLEAR_FEATURE_TO_DEVICE

URB_FUNCTION_CLEAR_FEATURE_TO_INTERFACE

URB_FUNCTION_CLEAR_FEATURE_TO_ENDPOINT

URB_FUNCTION_CLEAR_FEATURE_TO_OTHER

windows为何要搞出这么多FUNCTION命令,估计是为了理解和处理USB控制命令的方便。

但是这些控制命令经过 Host Controller进入真正的USB设备前,Host Controller依然要把它转换成 8个字节的SetupPacket控制命令。

这是硬件需求,而我们是虚拟设备,因此没必要非得转成 SetupPacket格式,只要网络通信中适合我们的就可以。

我们的USB数据采集端和虚拟USB端,都属于windows平台,转成Setuppacket再转成FUNCTION,反而麻烦,

因此基本是根据URB_FUNCTION做些简单转换,这样方便也快捷。

但是如果你的采集端和USB虚拟端分别属于不同的平台,比如linux,windows,macos,等各种平台都有,那得使用一个统一的通讯方式。

到时USB通讯协议中规定的格式估计是更好的选择。

知道哪些URB_FUNCTION命令需要处理,可能大家还是不大明白如何处理这些URB,如何完整的模拟一个USB接口,

从而实现把远方的数据采集端的USB设备搬到虚拟端来。

假设你已经熟悉了虚拟总线驱动的框架。

虚拟总线驱动应该与应用层程序有个通讯接口,应用程序使用IOCTL跟驱动通讯。

应用层程序通过网络连接到USB数据采集端,获取到某个需要被远程访问的USB设备的硬件ID,兼容ID等初步信息,

通过CreatePDO IOCTL传递给虚拟总线驱动,虚拟总线驱动根据硬件ID等各种参数创建child PDO设备,

创建成功后调用IoInvalidateDeviceRelations通知PnP管理器,接下来就是PnP管理器该做的事。

当PnP管理器正确加载根据硬件ID对应的功能驱动之后,这个功能驱动就开始工作。这个功能驱动开始构造URB包,

并且发送URB包到我们在虚拟总线驱动中创建的Child PDO设备上,

接下来,我们的总线驱动必须把这些URB数据正确的传递到远端的USB数据采集端,并且得到正确的响应。

至于如何处理这个主要和核心的过程,每个工程师可能有不同的处理办法,我们是采用把URB数据传递到应用层,

然后在应用层通过socket套接字传递给数据采集端,得到采集端的回应数据包之后,再把它传递给驱动,

最后我们的虚拟总线驱动完成从功能驱动发下来的这个URB数据包。

我们在应用层创建一个信号量,传递到驱动,总线驱动使用这个信号量通知应用层程序有新的URB数据包到达。

比如上层的功能驱动有个 URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE 的URB数据包投递给我们的总线驱动,

于是总线驱动的把这个URB包挂载到Child PDO的等待处理的队列中,然后增加信号量,通知应用层有URB数据包到达。

应用层程序有一个或者多个线程调用 WaitForSingleObject 函数等待这个信号量,
当WaitForSingleObject成功返回,说明有URB数据包,于是通过DeviceIoControl函数,投递一个 BEGIN
IOCTL到总线驱动,
我们的总线驱动从Child PDO的等待队列取出一个URB数据包,分析处理这个URB数据包,

然后再把这个URB挂载到Child PDO的忙碌队列中,同时生成一个seqno唯一标识这个URB包,完成这个BEGIN IOCTL。

应用层程序根据从BEGIN IOCTL获取到的请求数据 ,发送到远方的USB数据采集端,等待对方的回应。
USB数据采集端回应这个数据包之后,应用层程序调用 一个 END IOCTL 到总线驱动,

我们的虚拟总线驱动根据seqno从Child PDO的忙碌队列查找对应的URB包,把从 END IOCTL传递的数据,正确的填写到URB数据包中,
最后完成这个URB包。

这个就是我的总线驱动对URB数据包的处理工程,这个跟前几篇文章介绍的

“文件过滤驱动实现目录重定向“(http://blog.csdn.net/fanxiushu/article/details/43845699)的处理框架是一致的。

如果不熟悉这个过程,可以去看看过滤驱动实现目录重定向的章节。

上边介绍过的URB_FUNCTION_XXX非常之多,为了在 BEGIN IOCTL和END IOCTL简化数据包,

都统一使用一个数据结构与驱动交互。

如下

struct ioctl_usbtx_header_t

{

    ULONGLONG        inter_handle;        //  是等待处理的文件IRP 指针

    LONG             inter_seqno;         //  每个IRP的序列号,由驱动产生,和inter_handle一起用来保证请求包的唯一性验证 

    LONG             data_length;         //  数据的长度; 如果是读设备,则读取的字节数; 如果是写数据到设备,写入前是需要写入的字节数,写入成功后,实际写入的字节数,ISO 传输会包括 iso_packet_hdr_t结构大小

    LONG             result;              //  返回是否成功

    LONG             reserved1;           //  保留

    ////

    int        type;  // 1 获取描述符, 2 vendor or class , 3 传输数据,  4 重置, 5 获取状态, 6 操作feature

    int        reserved[ 3 ]; //保留

    /////

    union{

        ///

        struct{

            int          type;     // 1 获取或设置设备描述符, 2 设置配置描述符, 3 获取或设置接口描述符, 4 获取或设置端口描述符

            int          subtype;  // (type=1,3,4) 1 获取设备描述符, 2 获取配置描述符, 3 获取字符串;;;;; (type=2) 1设置config(index=-1 & value=-1 unconfigure), 2 设置 interface

            int          is_read;  // (type=1,3,4) is_read为TRUE获取描述符,FALSE 设置描述符

            int          index;    // 序号

            int          value;    // 值, 获取string时定义成language_id

        }descriptor;

        ////////

        struct{

            int          type;    //1 CLASS请求, 2 VENDOR请求

            int          subtype; //1 device; 2 interface ; 3 endpoint; 4 other

            int          is_read; //是从设备读,还是写入设备

            int          request;

            int          index;

            int          value;

        }vendor;

        ////

        struct {

            int           type; // 1 控制传输,  2 中断或批量传输, 3 同步传输

            int           ep_address; //端口位置   如果 (ep_address &0x80) 则是读,否则写;  控制传输时候,如果为0表示使用默认端口

            int           is_read;    //是从设备读,还是写入设备

            union{

                int               number_packets; //同步传输时候,包个数,如果为0,则组合到一起传输,>0则在头后面跟iso_packet_hdr_t结构,大小为 ISO_PACKET_HDR_SIZE + number_packets*sizeof(iso_packet_t)

                struct{

                    unsigned char setup_packet[8]; /////控制传输时候,发送的8个字节的控制码

                };

            };

            char          is_split; ///中断批量传输,或同步传输是否拆分成多块,

            char          reserved[3]; ///

        }transfer;

        ////////

        struct {

            int           type; /// 1 IOCTL_INTERNAL_USB_RESET_PORT重置设备; 2 IOCTL_INTERNAL_USB_CYCLE_PORT 重置设备; 3 重置端口URB_FUNCTION_RESET_PIPE; 4 中断端口 URB_FUNCTION_ABORT_PIPE

            int           ep_address;

        }reset;

        /////

        struct {

            int           type; /// 1 device; 2 interface ; 3 endpoint; 4 other status; 5 获取当前配置描述符;6 根据interface获取当前接口的alterantesetting; 7 获取current frame number

            int           index; ///

        }status;

        //////

        struct {

            int           type;     //// 1 SET请求, 2 CLEAR请求

            int           subtype;  ///  1 device; 2 interface ; 3 endpoint; 4 other

            int           index;    ///

            int           value;    ///

        }feature;

        ////////

    };

    ////////////

};

看起来似乎有点多,实际上BEGIN IOCTL和END IOCTL都使用 ioctl_usbtx_header_t 来传递各种URB数据,反而方便许多,
到了数据采集端,也使用同样的结构进行处理,因为都是windows平台,处理的各种转换反而少了许多。

数据结构的定义或使用,请下载CSDN上提供的工程。

到此为止,一个基于虚拟总线驱动的实现USB设备远程访问的功能,基本算完成了,
但是有个不太完善的地方,这样的虚拟USB设备像个无主孤魂一样存在于系统中,它既不附着在某个虚拟跟集线器上,

也没有对应的虚拟USB控制器,因此在某些应用层程序看来,会把它当作不存在。
比如某些按照USB设备栈的方式枚举系统中存在的USB设备 ,这样的虚拟USB设备是枚举不出来的,因为他没ROOTHUB,也没USB控制器。
这个概念就跟以前介绍过的虚拟磁盘驱动很类似,使用 微软的ScsiPort或StorePort模型的虚拟磁盘驱动, 会被当成真正的磁盘,
在磁盘管理器能找到我们的虚拟磁盘,而且可以像真正的磁盘那样进行分区,格式化等各种基本的磁盘操作。

而在网上提供的一个类似 filedisk框架的虚拟磁盘驱动,也能提供磁盘访问的功能,但是并不具备StorePort等框架的提供的磁盘驱动功能,

并不被系统视作一个磁盘系统。
 我们现在实现的虚拟USB设备也跟filedisk一样,不会被系统视作一个真正的USB设备。
但是它依然能欺骗大部分软件,就跟filedisk一样。

如何到达我们的虚拟USB尽善尽美呢? 需要实现虚拟ROOTHUB和虚拟USB控制器。

敬请关注下文关于RootHUB和USB控制器得开发过程。

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



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



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

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

http://download.csdn.net/detail/fanxiushu/9538188
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: