2017-06-20 02:23:51 fanxiushu 阅读数 2472
  • 玩转手机开发,程序员黑科技来袭

    利用手机环境,来架设开发环境,满足在不在电脑情况下的使用。可以在手机平台下进行web程序开发,java软件开发,php程序开发等,远程管理手机和电脑的文件,架设和管理远程FTP服务器。

    5022 人正在学习 去看看 卫永朝

                                                                    by fanxiushu 2017-06-20 转载或引用请注明原始作者.


前面的章节陆续介绍了远程访问USB设备的相关知识,从数据采集端到虚拟总线驱动,到虚拟控制器和根集线器驱动等、

相关文章如下链接:
http://blog.csdn.net/fanxiushu/article/details/51420096   (USB设备驱动开发之远程访问USB设备(一USB设备数据采集端))
http://blog.csdn.net/fanxiushu/article/details/51494169   (USB设备驱动开发之远程访问USB设备(二 USB设备虚拟端))
http://blog.csdn.net/fanxiushu/article/details/51559720   (USB设备驱动开发之远程访问USB设备( 三 虚拟USB控制器和根集线器))
http://blog.csdn.net/fanxiushu/article/details/52761644   (USB设备驱动开发之扩展(利用USB虚拟总线驱动模拟USB摄像头))
介绍的都是windows平台下的USB相关的东西,
这篇文章介绍的是linux平台下的USB数据采集端。

我们一般在”延长“USB设备的时候,设备采集端采用多种操作系统,不一定是windows平台。
比如工业环境中某些使用USB接口的采集设备,采集设备通过USB接到电脑的windows平台,通过专业的软件分析采集到的数据。
这样采集设备不远处就得有一台PC电脑,操作人员在电脑上分析数据。
有些工业生产环境比较恶劣,距离采集设备太近可能会有危险。因此必须延长到一个合适的距离。
各种工业环境采集设备众多,接口也繁杂。
因此可能就有个想法,使用一个小设备(比如嵌入式linux系统)上边挤满各种各样的接口,
比如COM接口,USB接口等等。采集设备通过USB接口插到这个小设备上,小设备采集USB数据,通过网络比如WIFI无线网等,
把工业采集设备“延长”到电脑中,这样就可以在任何地方,通过电脑windows平台的专业软件分析采集设备的工业数据。
再比如在远程桌面或云桌面实现中尤其是云桌面,控制端采用瘦客户端,通常就是定制的一个嵌入式系统的小设备,
而开发商们通常使用嵌入式linux作为这些终端设备的选择,终端设备上布满大大小小的各种接口,尤其是USB接口。
这些USB接口设备都会被重定向到真正的云桌面系统中。要实现重定向,首先需要采集真正USB设备的数据。
因此我们必须在linux平台实现USB设备的采集。
还比如延长银行U盾设备,延长手机USB接口等等。。。
诸如此类的需求应该很多,这主要得益于USB接口的普及率非常高。

以上需求都有一个共同点,就是USB采集端都是小设备,一般都使用嵌入式linux。
如何才能在linux平台实现USB数据的采集呢? 这个需要从linux驱动方面去实现。
linux平台集成有个usbip项目,它实现了USB虚拟和USB采集两个方面, 应该是linux平台解决此类问题的最快捷办法。
usbip有个特点,它的网络通信是在内核层直接通讯,有自己的通讯协议,这可能也是个缺点,
很多时候,我们可能需要采集到的数据需要加密,压缩之类操作,或者通过自己的私有协议传输,这些需求都不得不修改usbip。
本着一向自己造轮子的习惯,这里并不介绍usbip,而是自己全新开发USB采集端驱动。
其实总体来说,比起windows的USB采集驱动简单的多。

http://blog.csdn.net/fanxiushu/article/details/51420096 连接简单介绍了windows平台USB数据采集的流程,
不管是windows还是linux,只要实现USB核心的四个数据传输方式(控制,中断,批量,同步),基本就完成了USB数据处理。

首先我们开发的驱动必须接管USB设备,因为各种各样的USB设备,在操作系统中可能存在默认的驱动,
一旦设备插到系统中,操作系统就会选择默认的驱动安装,
因此我们要采集某个指定的USB设备,就必须先要接管它,让操作系统加载的驱动变成我们的。
在介绍windows端USB数据采集的时候介绍过,接管它的默认驱动很麻烦。
需要开发一个专门的驱动来hook 被接管的USB设备物理对象所在的驱动的IRP_MJ_PNP派遣函数,
处理 IRP_MN_QUERY_ID 子请求,把PID和VID替换成我们驱动的某个固定值。
这样PnP即插即用管理器再次查询到PID和VID匹配的驱动(自然就是我们的驱动了)
然后就成功把指定的USB设备加载到我们的自己的驱动中了。
而在linux平台上做这个事情就变得很容易。
linux提供了udev来专门管理设备节点。这使得对USB设备的管理也变得非常容易,包括如何将USB设备绑定到指定的驱动。
在 /sys/bus/usb/drivers 这个固定目录下有许多驱动名字,这就是具体的USB驱动名,
如果我们为某个USB设备开发一个驱动,我们的驱动程序的名字也会出现在其中。比如我们的采集USB设备数据的驱动名字叫 usbcoll。
再进入 /sys/bus/usb/drivers/usbcoll子目录中,会发现有两个文件名 bind 和 unbind,这个就是绑定和取消绑定的属性名。
如何把已经绑定到其他驱动的设备解绑定然后再绑定到我们的驱动呢? 其实很简单。
每个插入到系统中的USB设备都存在一个总线ID,把它简单称呼为busid,(就是类似 1-1.3,2-1等一类字符串,不同版本稍有不同)
假设busid为 1-1.3 的USB设备原先绑定到usb驱动中,只需简单做如下操作就能绑定到usbcoll
  echo '1-1.3' >> /sys/bus/usb/drivers/usb/unbind    # 解除到USB驱动的绑定
  echo '1-1.3' >> /sys/bus/usb/drivers/usbcoll/bind   # 绑定到我们的usbcoll驱动中。
非常简单,因此佩服这种做法的设计者们,使得复杂问题能尽量简单化。

解决了接管USB默认驱动的问题,接着就是如何进行驱动开发了,
USB设备可以分为只有一个Interface的单一设备,或者具有多个Interface的复合设备。
操作系统会为每个Interface加载一个功能驱动,我们一般都会看到复合USB设备存在多个驱动。
因此我们给USB开发驱动,也会面临两个驱动类型,一个是为USB设备某个Interface专门开发的驱动,
另一个是给整个USB设备开发驱动,在这个驱动里,我们处理所有Interface的通讯。
linux系统默认已经为我们做好了整个USB设备的驱动,因此一般来说,只需要开发具体的Interface驱动就可以了。
在linux内核源代码 drivers/usb/core/generic.c 已经注册了一个通用的 usb_device_driver 数据结构,并且在
drivers/usb/core/usb.c的 usb_init初始化函数中调用 usb_register_device_driver 注册了这个通用的USB设备驱动,
在 generic.c的generic_probe函数中调用usb_choose_configuration和 usb_set_configuration来给每个Interface加载驱动。
这很有点windows平台的usbccgp.sys的味道。
( usbccgp.sys 就是 USB Composite Device,就是windows平台默认的复合设备驱动,
当发现是复合设备,则usbccgp.sys被加载,在usbccgp.sys驱动中接着加载各个Interface的驱动)
这个是linux内核唯一调用 usb_register_device_driver 函数的地方。

我们要采集整个USB设备的数据,包括各个Interface,因此我们的驱动也必须是调用 usb_register_device_driver  注册的,
而不是某个单一Interface的驱动框架。
这就是我们的驱动跟一般针对USB某个功能开发的驱动不同的地方。
usb_register_device_driver 函数注册和使用过程,几乎跟 drivers/usb/core/generic.c 差不多,
不同的是在 generic_probe和generic_disconnect的实现中。
首先在generic_probe获取到USB设备的busid,根据busid确定是不是我们需要采集USB设备,
(在应用层把busid写入 usbcoll的bind属性前,必须把busid预先告知给usbcoll驱动,让generic_probe函数进行判断处理。)
如果不是则简单返回 -ENODEV,让系统接着查找,
否则初始化各种参数,分配资源等操作。这样这个usb设备的任何通讯就能在我们的usbcoll驱动里完成。
在generic_disconnect中释放在generic_probe分配的资源。

接着就是核心的USB数据交换过程,根据我一向做事的习惯,把驱动层的数据通通传递到应用层来处理,
在应用层要加密,要压缩,要使用私有协议传输,或者要做其他什么事,都会比在驱动层里处理方便的多。
而这个处理过程很像在
http://blog.csdn.net/fanxiushu/article/details/52681705
(linux平台用VFS驱动实现目录重定向(文件驱动实现目录重定向 四))
等文章中介绍的处理办法了,
首先创建一个字符设备驱动,用来传递urb数据,
应用层程序调用open函数打开这个设备驱动,然后定义IOCTL,把某个usb的busid绑定到这个 open打开的设备文件。

接着使用write函数,把URB的具体请求发给usbcoll驱动,usbcoll驱动分析write写入的内容,填写 urb数据结构,
然后调用 usb_submit_urb 函数,把 URB请求提交给 usbcore,接着就是linux的usbcore内核跟USB硬件设备的处理,
处理完成之后,预先设置到urb的完成回调函数就会被调用,
然后在此回调函数中,通知应用层程序某个URB请求完成,
于是应用层调用 read函数读取已经完成了URB数据包。
这样整个URB通讯过程就完成了。

为了应用层程序跟驱动和跟其他程序更好的交换数据,一般会定义另一个数据结构来实现跟URB通讯,
我们采用如下数据结构来实现。

#pragma pack(1)

struct ioctl_exchange_header_t
{
    int             type;  // 1 提交URB请求; 2其他请求
    unsigned int    seqnum; ///唯一标识,应用层提供

    ///
    int             trans_type;  //// 0 control transfer; 1 bulk or interrupt ; 2 iso transfer

    int             trans_flags; //// USBDEVFS_URB_SHORT_NOT_OK and so on
    int             direction;   //// 方向: 1 是 IN , 0 是 OUT
    unsigned char   ep_address;  ////端口 如果 (ep_address&0x80) 则是IN, 否则OUT
    unsigned char   ep_interval; ////每个端口对应的时间间隔,bulk,intr有效
    unsigned char   padding[2];  ////
    int             start_frame;       /// ISO 传输有效
    int             number_of_packets; /// ISO传输有效,指示多少ISO数据包

    unsigned char   setup_packet[8]; // 8个字节的设置码

    int             result;          //返回码
    int             transfer_length; //传输数据长度,如果 direction 是 IN,则表示需要读取的字节,否则写入 USB的数据,这时候transfer_length==后面数据长度(ISO传输,包括packets长度)
    //后面接数据; 如果是 ISO传输,后面跟 usb_iso_packet_descriptor结构 大小为 number_of_packets*sizeof(usb_iso_packet_descriptor), 然后再跟数据
};

#pragma pack()

这样应用层的 write和read函数写入驱动和从驱动读取的数据的头部都是 ioctl_exchange_header_t 结构的头。

基本上linux平台下USB数据采集比起windows的驱动来说简单和容易实现。


2016-05-24 22:31:51 fanxiushu 阅读数 7808
  • 玩转手机开发,程序员黑科技来袭

    利用手机环境,来架设开发环境,满足在不在电脑情况下的使用。可以在手机平台下进行web程序开发,java软件开发,php程序开发等,远程管理手机和电脑的文件,架设和管理远程FTP服务器。

    5022 人正在学习 去看看 卫永朝
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




2016-05-15 23:14:51 fanxiushu 阅读数 14376
  • 玩转手机开发,程序员黑科技来袭

    利用手机环境,来架设开发环境,满足在不在电脑情况下的使用。可以在手机平台下进行web程序开发,java软件开发,php程序开发等,远程管理手机和电脑的文件,架设和管理远程FTP服务器。

    5022 人正在学习 去看看 卫永朝

                                                                                                                                    By Fanxiushu 2016 05-15  转载或引用本文,请注明原始作者。


使用过vmware的人都应该知道,vmware虚拟机有这样的一个功能,
当在宿主机上插入一个USB设备的时候,通过设置,可以在vmware的虚拟机系统里边能访问到这个USB设备,
而且访问这个USB设备,就跟真的把这个USB设备插入到这个虚拟系统中一样,跟真实的几乎没任何区别。
再看一种情况,假设有两台机器C和S,C 机器是你正在使用的机器, S机器在远端,你只能通过远程控制S。
S机器的配置和功能都很强大,大部分时间你都通过远程桌面等方式连接到 S机器。
假如你手上有些USB接口的设备,比如iPhone,iPad,USB摄像头等,很想把他们使用起来,
C机器在你身边,你能而且是只能把这些USB设备插入到C机器,但是你肯定是非常希望插入这些设备后,S机器也能正常使用。
这种远程使用更为强大的S机器的办法,就是现在所谓的云桌面,虚拟云桌面之类的概念。
因此对于虚拟云桌面开发商而言,解决远程访问本地设备,也是基本和重要的课题之一。
再看一个对普通人比较陌生,对iOS开发的人比较熟悉的例子,
iOS的app应用安装问题,非常烦,不像windows程序,只要开发出来,可以到处复制,到处运行。
自己开发的app,需要Xcode开发环境部署到手机上,以前做这样的事情,还得花钱买账户,升级到Xcode7才稍微开放了一些。
通过Xcode部署到自己手机到也方便,可是如何部署到别人的手机上,而且那个人也不在同一个地方,无法把它的手机直接接到电脑上。
于是,能不能通过远程方式实现Xcode部署,首先要解决的就是USB的远程访问的问题,
正是基于这样的原因,同时也想掌握USB设备驱动,才开始研究和开发 USB设备驱动,来尝试实现这么一种功能。
(当然有其他更好的方式实现 iOS APP内测,我只是比较另类非要通过Xcode部署App,
我的MacOS是安装到vmware虚拟机中,通过vmware虚拟USB的方式来访问宿主机的USB接口的,
尝试着在windows宿主机中虚拟出USB设备,再尝试让这个虚拟USB被vmware转向到 MacOS中,
希望这一想法最终能实现,我可不想再去做MacOS系统的USB驱动)

以下讨论的都是基于windows平台的USB设备驱动开发。

USB只是接口,是设备和主机进行数据交换的协议接口而已。数据交换无非两个方向,从设备到主机和从主机到设备。
大家所说各种USB设备,其实是具有USB接口的实现自己某种特定功能的硬件设备,
比如USB摄像头,首先这个硬件是摄像头,它是通过USB接口连接到电脑。
这里不讨论USB接口的各种硬件特性,也不是软件开发的范畴。

windows驱动按照种类来分,大致分为总线驱动,功能驱动,过滤驱动三大类。
USB是硬件接口,肯定跟最底层的总线驱动脱离不了关系。
总线驱动负责管理连在某类总线(比如USB总线)上的所有设备,
它负责监控总线上的设备的插入和移除,创建设备的PDO(物理设备对象),并通知PnP管理器有新硬件添加,等等。
再看看USB总线,事实上在电脑基本总线上(比如PCI总线等)应该有一个或者多个USB的控制器,
从硬件上来说,就是集成到主板上的控制器芯片。
每个USB控制器有唯一的一个RootHUb(根集线器),根集线器有多个PORT,简单的说,就是在电脑上看到的USB插口。
因此我们可以简单把USB总线驱动理解成是 USB控制器驱动和RootHUB驱动,
因为USB控制器总是首先被发现,接下来RootHUB设备交给USB控制器驱动处理,
然后RootHUB驱动接着管理自己的PORT和连接到PORT的USB设备。

根集线器上的PORT不单可以接真正的USB设备,也可以再次连接子HUB,
每个子HUB可以接真正的USB设备,或者再接孙子HUB, 这样形成了一颗以RootHUB为根的树,
正是依靠这样的结构,每个USB控制器可以管理最多127个USB设备(理论上是这样)。
当我们把USB设备插入到RootHUB, 或者子HUB,或者孙子HUB等中,这些HUB会上报设备插入通知,
其实是USB控制器轮询这些HUB端口状态,从而获得通知,
RootHUB驱动负责创建这个设备的PDO(物理设备对象),并且通知PnP管理器有新设备添加。
PnP管理器负责根据这个设备的信息,加载这个设备对应的功能驱动程序。
对应的USB设备功能驱动加载成功之后,就开始真正的USB接口通讯了。
对于功能驱动来说,它只需要把数据直接发给RootHUB驱动创建的 PDO就能完成通讯了。

所谓的功能驱动就是这个USB设备是做什么用的,比如是个USB摄像头,或者是个USB键盘等。
功能驱动在总线驱动的上面,USB接口协议是标准通用的协议,凡是具备USB接口的设备,底层通讯都是一样的。
这个就是能实现远程共享各种USB设备基础。

USB功能驱动在windows平台下通讯使用URB(USB Request BLOCK)的方式,
(这个URB是不是跟前几篇文章中介绍的磁盘驱动通讯使用的SRB很相近, 几乎是同一个模子里刻出来的)
windows已经帮我们实现了大部分的USB底层通讯内容,
我们只需构造 适当的URB数据包,就可以跟USB设备进行数据交互(这种URB包的种类大概有20来个)。
直接给 PDO 发送 IRP_MJ_INTERNAL_DEVICE_CONTROL 命令,
命令中包含 URB包就可以跟USB设备完成一次数据交互。

而USB设备的PDO,接收到URB的IRP_MJ_INTERNAL_DEVICE_CONTROL命令之后,
开始真正的跟USB设备进行物理层级别的数据通信,
它得把URB数据转交给RootHUB设备,RootHUB再交给真正的设备。
至于如何完成通讯过程,具体到硬件处理过程。
这就不是这篇文章讨论的内容,除非你想去实现一个真正的USB控制器驱动。

如何实现远程访问USB设备呢?
通过上边的简单介绍,应该对USB通讯过程有个大致的了解,
我们只需在USB总线层中,拦截到某个USB通讯数据,把这些数据通过网络转发到远程机器,
在远程机器上虚拟出一个USB设备,再把数据输入给这个虚拟设备,于是这个虚拟USB设备,就能被正确识别和使用。
而且因为拦截和处理的是USB接口的底层数据,所以凡是具备USB接口的设备,都能被正确识别,
也就是如果是个USB接口的摄像头,远程机器的虚拟USB也被识别成同样的摄像头,
如果是个USB键盘,远程机器的虚拟USB也被当成是USB键盘。
原理并不复杂,得开发一个虚拟总线驱动,由虚拟USB总线驱动模拟出虚拟USB设备,
这个跟以前介绍过的虚拟磁盘很相似,
可惜的是对于虚拟磁盘驱动,微软提供了专门的ScsiPort或StorPort模块来完成类似功能。
而USB虚拟设备驱动得我们自己开发USB总线驱动。
上面还介绍过,USB总线驱动包括USB设备控制器驱动和RootHUB驱动,
按照层次来说,USB控制器管理RootHUB,RootHUB管理USB设备,USB属于最低级的雇员,
如果真要按照真实硬件的层次来实现虚拟USB系统,可够受的。
好在因为是虚拟USB设备,而不是真正的硬件,虚拟环境下,能把不必要的一些东西简化。
可以去掉控制器和RootHUB,只需虚拟USB设备即可,
虽然这对某些特殊程序不能用外,大部分情况都能正常使用。
(至于如何按照真实硬件层次同时实现虚拟USB控制器和虚拟RootHUB,后续章节会介绍到)

总线驱动开发的框架这里就不做过多介绍,下章介绍如何处理虚拟USB设备时候,会做些解释。
详细的可以查看WDK驱动例子里边的toaster例子代码,
WDK7提供了WDM和WDF的例子,WDK8和WDK10把WDM给删除了,只提供了WDF的代码,
你如要研究总线驱动究竟做了些什么,还是最好看他的WDM例子,稍后在CSDN上提供的虚拟USB驱动,
也是采用WDM开发的工程,并且也并不是抄袭WDM的toaster例子在上边填写自己的代码,
而是按照自己的习惯重新组织了代码框架。

要远程访问USB设备,首先我们得把它分成两大模块,
首先,得有采集真实USB设备数据的采集端,为了方便,下文统称为采集端或者服务端。
其次才能在远端虚拟出USB设备,然后输入采集到的数据,完成USB设备访问, 下文统称为客户端或者虚拟USB端。

为了采集USB数据,倒是费了一些周折,首先想到的就是过滤驱动来采集USB设备数据。
我们先看看USB接口的通讯方式,USB接口就是用来数据通信的,通讯方式是它的核心内容之一。
一共有4种通讯方式:
一,控制传输,主要是发送各种控制命令给USB设备,主要使用的是默认端口0进行传输,任何USB设备一旦连接上主机,
      Host都会给他一个默认端口0,否则就没法跟主机通讯了。
二,中断传输,看名字好像是真的硬件中断一样,不然,USB的中断是伪中断,其实就是USB控制器定时查询USB状态,
       看到标记为中断的标志,然后才进行数据传输,中断的相应速度,就得看USB控制器得轮询速度了。
三,批量传输,顾名思义,就是大数据传输,用于非常大数据传输的场所,比如U盘。
四,同步传输,这个传输方式我是比较费解的,主机给一块大的内存块,然后设置一些区块(packet),
       每个区块设置在这块大内存块的偏移以及每个区块读写长度,
       然后给USB设备,USB设备根据packet,同时填充每个区块的数据,可能有些区块传输不到数据或者只一部分数据,
       这是同步传输允许的,它是一种不保证数据完整的传输,主要用于USB视频等要求比较实时但是对数据质量相对不高的情况,
       比如USB摄像头。

不管哪种传输,USB接口的通讯总是主机主动发起的,
即使数据是从设备传输到主机,也依然是主机首先发起传输命令给USB设备,
USB设备再把数据填写到主机发起的这个命令提供的buffer。

一开始想着给USB类驱动挂载 LowerFilters 过滤驱动,想着这样就能拦截所有的USB数据。
但是这样想,总觉得不大对劲(因为所有URB包都是主机主动发起的,过滤驱动的拦截对我们这样的需求没有意义)。
后来明白了其实所有URB包,都是主机主动发起的,那为何干脆不找到某个USB设备在系统中创建的PDO设备,
直接给他发送URB包来采集数据。这么想了,也这么做了。结果数据倒是采集到了,当然也把整个系统给弄蓝屏了。
因为当我发送URB_FUNCTION_SELECT_CONFIGURATION重新选择配置描述符时候,
加载到这个USB设备的他自己的功能驱动还在运行, 因为发送select命令,迫使USB设备重新选择配置描述符,
上边的功能驱动并不知道,还在使用它原来的配置,结果自然得蓝屏罢工了。
而我们要远程访问USB设备,就是在远程的虚拟USB设备完全控制本地的真实USB,它得被独占使用。
否则如果远程虚拟USB设备发送一个类似SELECT等改变设备状态的控制命令过来,加载到真实USB设备上边的功能驱动将无法正常运行。

明白了这个道理,总算知道了该如何采集USB设备数据。
就是给USB设备开发自己的功能驱动,让他替换掉原来的真正的功能驱动。
自己开发的这个功能驱动中,处理所有的URB数据包;
根据USB虚拟设备通过网络传递来的USB请求数据,生成URB包发给USB设备进行数据传输处理。

至于USB的功能驱动如何开发,对我们来说,最主要和唯一要做的就是如何处理USB的四种数据传输方式。
这样的例子,WDK例子代码里也提供了关于批量传输和同步传输的例子,把他们组合起来使用,就是我们需要的。
也可以寻求应用层级别的解决方案,当然首先想到的就是WINUSB,
这个号称是应用层的USB驱动,其实就是把USB的四种传输方式封装到应用层来给不熟悉驱动开发的程序员使用。
不过非常可惜的是老的WINUSB不支持同步传输,在win8.1以上的系统才开始支持同步传输。
如果真采用winsub,现在大量使用的win7,winxp用户就没法用了。
还有开源的libusb,这个工程倒是不错,他主要活跃在linux平台,也有对应的windows版本,
同样比较可惜的是,他虽然处理了同步传输,但是对于同步传输需要传递小区块(packet),它并没实现。
而是一笼统的跟批量传输一样,只提供一个大内存,对于我们的虚拟USB设备,需要根据Packet返回的信息,
确定每个小区块的传输情况。当然也可以适当小修改,让libusb完成这么一个功能。

我是自己开发的驱动,当然得感谢libusb提供的优秀代码,简洁而易懂。
否则也不会这么快掌握和开发出自己的驱动来处理USB设备的数据采集。

采集USB数据的功能驱动倒是开发出来了,可是却有个非常大的麻烦,如何把我们自己的驱动加载到各种USB设备上,
让windows替换掉原来的驱动,而且在不再使用我们的驱动的时候,再换回原来的驱动。

再回来看看PnP管理器如何给某个设备加载功能驱动,
当总线驱动枚举到有某个设备插入进来,创建PDO,并且调用IoInvalidateDeviceRelations 函数通知PnP管理器设备列表有改变,
PnP管理发送IRP_MN_QUERY_DEVICE_RELATIONS给总线驱动查询所有PDOs列表,并且比较新旧列表,知道某个PDO被添加进来
于是,PnP管理器发送IRP_MN_QUERY_ID给这个PDO查询硬件ID,
查询到硬件ID之后,PnP管理器搜索注册表的HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum 下已经安装的驱动,
他根据硬件ID来查找enum下的子项,找到之后,就开始加载驱动。没找到会找兼容ID对应的驱动。
如果都没找到,则弹出需要安装驱动的提示框。

显然,我们只要在我们的自己的功能驱动的inf安装文件中,填写正硬件ID,就能被正确加载。
然而我们的目的是让我们的驱动,能加载到各种USB设备上边,各个USB设备的硬件ID都不一样,
不可能每个设备都制作一个inf安装文件,那可够呛。
也许我们可以给我们的inf生成一个USB通用的兼容ID,
但是PnP管理器首先查询的是硬件ID,如果对应硬件ID的驱动没装,到时可以找到我们的驱动并且安装,
可是大部分USB设备都是有驱动的,有些是windows自己都有的,大部分设备都能被windows识别并且安装他自己的驱动。

如何解决这个问题呢,最好是在应用层找到解决办法,
幸好有vmware,虽然vmware没提供源代码,但是它已经做到这种效果了,可以从它的程序中查找蛛丝马迹。
vmware安装目录中有个vmware-usbarbitrator64.exe程序,看着程序的名字就是处理USB的,
用depends查看这个程序使用了哪些WIN32 API,终于,vmware-usbarbitrator64.exe导入的setupapi.dll动态库中,
使用了一个函数SetupDiSetSelectedDriverW,这个肯定就是跟如何安装驱动有关的,
于是再用Google满世界的搜索这个函数的相关连接,终于找到 http://www.google.com/patents/US8825909
(中国的用户需要翻墙才能访问),原来他们早就解决了这么一个问题,居然还申请了专利,可想而知他们对知识产权的重视。
大致原理就是给原来设备的硬件ID,在注册表中添加我们的自己开发的功能驱动的硬件ID(这个硬件ID可以随意,只要是唯一的就行),
然后利用setupapi动态库中函数,重新构造驱动列表,这时候我们自己的驱动就会在他的列表中,然后使用SetupDiSetSelectedDriver,
SetupDiSetSelectedDevice,InstallSelectedDriver等函数动态加载我们的驱动。
如何给注册表添加我们的硬件ID,主要用到 CM_Add_ID 函数,其实这个函数在底层用得是 SetupDiSetDeviceRegistryProperty函数。
SetupDiSetDeviceRegistryProperty 使用SPDRP_HARDWAREID 参数就可以设置硬件ID,
本来这个功能在win7,winxp,甚至win8都能工作的好好的,到了win10,被微软给禁止了,不允许设置硬件ID,
不过这也说得通,本来硬件ID对每个设备就是唯一的,不允许随意修改。
可是这样可就苦了我,得另外找办法解决win10下动态加载自己的驱动的问题。
也许把vmware的办法稍微做些修改,就又能在win10 正常使用了,总的思路还是得想法修改硬件ID,
这样才能欺骗PnP管理器加载我们提供的驱动程序。
在写这篇文章的时候,我正在忙着研究开发虚拟USB控制器和虚拟RootHUB的功能,
因此没没时间再去查找资料如何解决win10下动态安装驱动的问题,
反正利用 http://www.google.com/patents/US8825909 说的办法已经能在win7下正常处理这个问题了,等以后有时间再来解决。

下章继续虚拟USB设备的开发,敬请关注稍后在CSDN上提供的部分源代码。

                   未完待续。。。


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

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

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

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

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



2016-06-01 22:36:20 fanxiushu 阅读数 7277
  • 玩转手机开发,程序员黑科技来袭

    利用手机环境,来架设开发环境,满足在不在电脑情况下的使用。可以在手机平台下进行web程序开发,java软件开发,php程序开发等,远程管理手机和电脑的文件,架设和管理远程FTP服务器。

    5022 人正在学习 去看看 卫永朝
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




2013-10-28 19:55:07 zhangy555 阅读数 137
  • 玩转手机开发,程序员黑科技来袭

    利用手机环境,来架设开发环境,满足在不在电脑情况下的使用。可以在手机平台下进行web程序开发,java软件开发,php程序开发等,远程管理手机和电脑的文件,架设和管理远程FTP服务器。

    5022 人正在学习 去看看 卫永朝

BBB板子自带一个linux系统,Angstrom,桌面环境是gnome。以下的设置都是在此基础上进行的。

BBB除了板子,只有一个usb线。通过这根usb线,PC可以远程登录到板子上。

下面是两种方式ssh和vnc。

 

准备工作 

1 将micro usb端口插入到BBB板子中,另一端连接PC。

2 下载linux驱动。http://beagleboard.org/static/Drivers/Linux/FTDI/mkudevrule.sh。在PC上执行。

3 通过chrome或firefox测试下网络是否联通http://192.168.7.2

 

ssh远程登录

1 已root用户远程登录到BBB板子的系统上。

$ ssh 192.168.7.2 -l root

2 密码为空,直接回车。这样就可以通过命令行方式,远程控制BBB板子了。

3 可以使用scp,传输文件。

$ scp usr@host:/path/file ~/path/      从远程下载文件
$ scp ~/path/file usr@host:/path/      向远程上传文件

 

vnc远程登录

1 在BBB板子上安装vnc server。确保BBB板子能连结到网络。

# opkg update
# opkg install x11vnc

 

2 在BBB板子上启动vnc server的服务。

# x11vnc -bg -o %HOME/.x11vnc.log.%VNCDISPLAY -auth /var/run/gdm/auth-for-gdm*/database -display :0  -forever

 

3 在PC上运行vnc client。很多选择,很多系统都自带不同的vnc viewer。

这里下载vncviewer。

$ sudo apt-get install vncviewer

 

4 运行vncviewer,就会看到BBB的桌面了。

$ vncviewer 192.168.7.2

 

 参考资料

BeagleBone Black猎兔犬骨头(黑)- 露出单板计算机的原型吧!

SSH to BeagleBone Black over USB

Quick hint for Beaglebone Black user

没有更多推荐了,返回首页