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

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控制器得开发过程。

时间: 2024-10-05 19:46:59

USB设备驱动开发之远程访问USB设备(二 USB设备虚拟端)的相关文章

USB设备驱动开发之远程访问USB设备(一)

By Fanxiushu 2016 05-15  转载或引用本文,请注明原始作者. 使用过vmware的人都应该知道,vmware虚拟机有这样的一个功能, 当在宿主机上插入一个USB设备的时候,通过设置,可以在vmware的虚拟机系统里边能访问到这个USB设备, 而且访问这个USB设备,就跟真的把这个USB设备插入到这个虚拟系统中一样,跟真实的几乎没任何区别. 再看一种情况,假设有两台机器C和S,C 机器是你正在使用的机器, S机器在远端,你只能通过远程控制S. S机器的配置和功能都很强大,大部

Linux设备驱动开发学习(2):Linux设备驱动简介(未完)

(未完待续......)

Hasen的linux设备驱动开发学习之旅--linux设备驱动中的并发与竞态

/** * Author:hasen * 参考 :<linux设备驱动开发详解> * 简介:android小菜鸟的linux * 设备驱动开发学习之旅 * 主题:linux设备驱动中的并发与竞态 * Date:2014-11-04 */ 1.并发与竞态 并发(concurrency)指的是多个执行单元同时.并行被执行,而并发的执行单元对共享资源(软件上的全 局变量,静态变量等)的访问则很容易导致竞态(race conditions). 主要的竞态发生在以下几种情况: (1)对称多处理(SMP)

《Linux设备驱动开发详解(第3版)》海量更新总结

本博实时更新<Linux设备驱动开发详解(第3版)>的最新进展. 2015.2.26 几乎完成初稿. [F]是修正或升级:[N]是新增知识点:[D]是删除的内容 第1章 <Linux设备驱动概述及开发环境构建>[D]删除关于LDD6410开发板的介绍[F]更新新的Ubuntu虚拟机[N]添加关于QEMU模拟vexpress板的描述 第2章 <驱动设计的硬件基础> [N]增加关于SoC的介绍:[N]增加关于eFuse的内容:[D]删除ISA总线的内容了:[N]增加关于SP

Linux设备驱动开发基础

1.驱动概述和开发环境搭建 1.1驱动设备的作用 对设备驱动最通俗的解释就是"驱动硬件设备行动".驱动与底层硬件直接打交道,按照硬件设备的具体工作方式,读写设备的寄存器,完成设备的轮训.中断处理.DMA通信,进行物理内存向虚拟内存的映射等,最终让通信设备能收发数据,让显示设备能显示文字和画面,让存储设备能记录文件和数据. 由此可见,设备驱动充当了硬件和应用软件之间的纽带,他使得应用软件只需要调用系统软件的应用编程接口(API)就可让硬件去完成要求的工作.在系统中没有操作系统的情况下,工

《Linux设备驱动开发具体解释(第3版)》进展同步更新

本博实时更新<Linux设备驱动开发具体解释(第3版)>的最新进展. 2015.2.26 差点儿完毕初稿. 本书已经rebase到开发中的Linux 4.0内核,案例多数基于多核CORTEX-A9平台. [F]是修正或升级:[N]是新增知识点:[D]是删除的内容 第1章 <Linux设备驱动概述及开发环境构建>[D]删除关于LDD6410开发板的介绍[F]更新新的Ubuntu虚拟机[N]加入关于QEMU模拟vexpress板的描写叙述 第2章 <驱动设计的硬件基础> [

Hasen的linux设备驱动开发学习之旅--异步I/O

/** * Author:hasen * 参考 :<linux设备驱动开发详解> * 简介:android小菜鸟的linux * 设备驱动开发学习之旅 * 主题:异步I/O * Date:2014-11-11 */ linux中最常用的输入/输出(I/O)模型是同步I/O.在这个模型中,请求发出后,应用就会阻塞,知道请求满足 为止.但是在某些情况下,I/O请求可能需要与其他的进程进行交叠.可移植操作系统接口(POSIX)异步I/O(AIO) 应用程序接口(API)就提供了这种功能. AIO基本

Linux设备驱动开发学习(1):前言

虽然网络上已经有很多Linux设备驱动开发学习的文章和博客,更是有很多经典的Linux设备驱动开 发的书籍,写这些博文似乎意义不大,但把自己的学习过程.学习心得记录下来,一方面有着强化巩固的 意义,另一方面也是把所学知识转化为自己所得的必要途径之一,这是我写这些的博客的原始动力.

Hasen的linux设备驱动开发学习之旅--异步通知

/** * Author:hasen * 参考 :<linux设备驱动开发详解> * 简介:android小菜鸟的linux * 设备驱动开发学习之旅 * 主题:异步通知 * Date:2014-11-05 */ 一.异步通知的概念和作用 阻塞和非阻塞访问.poll()函数提供了较好地解决设备访问的机制,但是如果有了异步通知整套机制就更 加完整了. 异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这 一点非常类似于硬件上"中断"的概