by fanxiushu 2019-11-07 转载或引用请注明原始作者。

之前的文章阐述过在windows平台下,如何实现USB虚拟总线驱动, 以及如何在windows平台采集真实USB设备的数据,
然后通过网络传输,达到 ”延长“ USB线缆的效果。
 相关链接如下:
https://blog.csdn.net/fanxiushu/article/details/51420096  USB设备驱动开发之远程访问USB设备(一USB设备数据采集端)
https://blog.csdn.net/fanxiushu/article/details/51494169  USB设备驱动开发之远程访问USB设备(二 USB设备虚拟端)
https://blog.csdn.net/fanxiushu/article/details/51559720  USB设备驱动开发之远程访问USB设备( 三 虚拟USB控制器和根集线器)

以上都是windows平台下的,其中第一个链接是关于如何实现采集端的,后面两个链接阐述USB虚拟端。
当初想要实现USB虚拟驱动的目的比较另类,也在上面的文章中说明了。
因为偶尔会在macOS系统中做些iOS手机数据备份开发什么的,而我的macOS系统是装到vmware虚拟机中的,
结果每次都需要把手机USB数据线插到笔记本电脑上,总感觉这跟线多余,于是总想着有没有办法去掉,于是才有了上面的开发的文章。
实现的效果如下图这样的:

简单解释一下,图中A部分”Fanxiushu Virtual USB Host Controller“ 和 ”Fanxiushu Virtual USB Root HUB“ 是我实现的USB虚拟总线驱动,
这个总线驱动需要虚拟控制器和根集线器的,否则vmware这样的软件无法识别的,
而这个 驱动下“的Port1端口插入了” iPhone手机的,其实真实iPhone手机插到我的另一台windows台式机器上,
这里通过网络传输,然后模拟成”插入“ ”Fanxiushu Virtual USB Host Controller“ 的效果。
但是 Port1端口显示的是 ”Vmware USB Device“ , 说明这个USB设备已经被vmware接管了。
紧接着看图中的B部分。显示”Apple Fanxiushu-USB-Device1“设备已经连接进去vmware虚拟机中,
再看图中的 C部分, 在虚拟机中macOS系统中,已经识别到了我的iPhone手机。
这就是当初实现USB虚拟驱动,想要达到的目的。

本文即将描述的就是linux平台下的USB虚拟总线驱动的实现,也可以叫虚拟USB控制器驱动。
同时,也在以前的文章中,阐述过如何在linux平台中采集真实USB设备的数据,
链接如下:
https://blog.csdn.net/fanxiushu/article/details/73478924   USB驱动开发之远程访问USB设备扩展(linux平台USB设备数据采集端)

配合本文的虚拟USB总线驱动,可以实现在linux平台之间任意的共享USB设备,
如果再配合windows平台下的实现,则可实现windows,linux平台之间任意共享USB设备。
同时也可以把虚拟总线驱动单独拿出来,用于模拟各类通用的USB设备。
比如模拟USB摄像头,如下链接描述的就是利用USB总线驱动模拟 USB摄像头:
https://blog.csdn.net/fanxiushu/article/details/52761644  USB设备驱动开发之扩展(利用USB虚拟总线驱动模拟USB摄像头)
也可以模拟USB声卡,U盘,USB键盘鼠标,游戏手柄等,只有想不到的没有办不到的,因为USB接口太通用了。
也可以模拟某些私有协议的USB设备,当然前提是必须知道这类设备的USB通讯协议格式。

linux下实现USB虚拟总驱动并没有windows平台那么有用,因为使用linux的人太少了。
不过考虑在linux服务器下,尤其是作为桌面云服务器的linux宿主机,这个USB虚拟总线驱动用处却是比较大。
远程桌面,需要解决的一个问题就是USB设备的远程共享。
一般是终端的设备采集到USB设备数据,发送到云桌面服务器的宿主机端,linux宿主机使用虚线USB总线驱动模拟出USB设备,
然后就像上图vmware虚拟机把iPhone设备接管到vmware虚拟机那样,把这个模拟的设备转嫁到对应的虚拟机中。
当然这是其中一个办法,还有就是直接从终端设备采集到的USB数据,传输到虚拟机内部,
然后虚拟机内部的操作系统开发出的USB虚拟总线驱动模拟出对应的USB设备。
至于哪个方法比较好,取决于具体的情况。

回到正题.
linux下的USB虚拟总线驱动框架比起windows来说太简单了。
只需调用几个函数,注册几个回调函数,就能实现一个虚拟USB总线驱动框架。

linux内核从2.6版本开始,就实现了一种叫 platform总线的虚拟总线,这是一种通用的虚拟总线框架结构。
为什么会有这么一种架构,
因为在硬件的世界中,有些外设与CPU通讯是使用标准的总线的,比如 USB总线,I2C总线,PCI总线等等,
但是有些外设是与CPU连在一起,这些外设直接扩展到CPU的地址空间,比如SoC。
如果两类设备按照两套逻辑来处理,显然会给系统内核造成不必要的罗嗦和混乱,所以规定所有的设备都具有总线,
只不过Soc使用的虚拟总线,这就是platofrm总线的由来。
我们在开发虚拟USB总线驱动的时候,就是需要使用platfrom总线。使用它的方式也是很简单。

我们在代码中定义  platform_driver 和 platform_device 数据结构, 如下代码:

/// 驱动入口

static struct platform_driver host_driver = {

.probe = host_add_device,

.remove = host_remove_device,

.suspend = host_suspend,

.resume = host_resume,

.driver = {

.name = "usb_host", /// 和下面的device一样

.owner = THIS_MODULE,

},

};

static void platform_device_release(struct device *dev)

{

// do  nothing, is virtual host

printk("-- usb_host: platform_device_release\n");

}

static struct platform_device host_device = {

.name = "usb_host",

.id = -1,

.dev = {

.release = platform_device_release,

},

};

其中host_add_device,host_remove_device,host_suspend,host_resume是回调函数,
如果熟悉windows驱动,也比较好理解 host_add_device和host_remove_device含义,
host_add_deivce相当于windows中的AddDevice回调函数,是虚拟总线驱动加载的时候被调用,
host_remove_deivce是在驱动卸载时候被调用。

定义如上两个结构之后,在驱动初始化入口函数中调用 platform_driver_register注册总线驱动,
调用 platform_device_register 注册总线设备,如下伪代码:

static int __init host_driver_init(void)

{

int ret;

。。。

ret = platform_driver_register(&host_driver);

。。。。

//注册一个平台总线设备

ret = platform_device_register(&host_device);

。。。。

printk("-- usb_host: drive init ok.\n");

return 0;

}
在退出函数注销,如下伪代码:

static void __exit host_driver_exit(void)

{

platform_device_unregister(&host_device);

platform_driver_unregister(&host_driver);

printk("--- usb_host : driver exit.\n");

}
初始化模块:

module_init(host_driver_init);

module_exit(host_driver_exit);

这样,platform 总线驱动就建立起来了。是不是比起windows实现虚拟总线驱动简单得多了。
接着我们在host_add_device回调函数中初始化USB总线驱动,创建HCD,也就是 USB主机控制器。

开始之前,先大致来了解linux平台下,USB  Host端,也就是主机端驱动的总体框架流程。
主要分为三层:
 1, USB 设备驱动, 这里就是具体的USB设备,负责主机与USB设备通信。
            |
2,  USB Core , 负责连接和管理上下两层,并且对上面的USB设备驱动提供API接口,对下面对的USB主机驱动提供API接口。
            |
3, USB 主机控制器驱动,负责控制管理插入的USB设备。
       比如最常见的EHCI(USB2),XCHI(USB3),OCHI(USB1)主机控制器驱动

我们这里需要实现的就是第3个部分,USB主机控制器,同时管理我维护着我们的“虚拟USB设备”。 
在host_add_device回调函数中,调用usbcore提供的 usb_create_hcd 创建主机控制器,
然后调用 usb_add_hcd 函数把主机控制器加入到普拉头发柔美总线设备中,这样一个USB主机控制器就建立起来了。
当然还需要在usb_add_device回调函数做一些其他相关的工作。
其中 usb_add_hcd函数内部的实现很复杂,有兴趣可以去阅读linux内核源代码。
其中一个重要的就是在usb_add_hcd内部会创建一个root  hub 虚拟根集线器设备,用于管理插到主机上的USB设备或USBHUB,
usb_create_hcd函数会要求传递一个hc_driver数据结构变量。
这里边定义了所有关于USB数据交换,状态查询,USB控制等回调函数。用于查询和管理USB设备状态,URB数据传输。
 把hc_driver里边相关的回调函数实现了,就等于是完整的实现了一个USB控制器驱动。
因此,我们的主要任务就是实现hc_driver结构里边的回调函数。

hc_driver结构比较复杂,这里只实现我们在虚拟控制器驱动需要实现的内容,如下:

///host主机相关结构和回调函数

static struct hc_driver _hc_driver = {

.description = "usb_host",

.product_desc = "Fanxiushu Virtual USB Host Controller",

.hcd_priv_size = sizeof(struct usb_host_t),   //

.flags = HCD_USB2, //

.start = usb_host_start,   //主机控制器启动

.stop =  usb_host_stop,  //主机控制器停止

.urb_enqueue = usb_host_urb_enqueue, //上层的USB设备驱动发起了URB请求,递交到主机控制器中了

.urb_dequeue = usb_host_urb_dequeue,  //上层URB请求取消,或者主机检测到USB设备被拔出了

.get_frame_number = usb_host_get_frame_number,

.hub_status_data = usb_host_hub_status,  //查询主机控制器的端口状态,
        .hub_control = usb_host_hub_control,            //设置,清除,查询端口状态。

.bus_suspend = usb_host_bus_suspend, //

.bus_resume = usb_host_bus_resume, //

};

其中hub_status_data和hub_control回调函数的实现,可以查阅usbip的代码,或者借鉴linux内核中其他类似代码。
 无非就是对USB控制器的每个端口状态查询,设置等操作。

重点是urb_enqueue回调函数的实现,这个是USB的通讯核心数据包传递函数。
具体的说,就是上层的USB设备驱动调用usbcore提供的usb_submit_urb 函数的时候,
usb_submit_urb做些其他处理,然后调用usb_hcd_submit_urb函数,
usb_hcd_submit_urb最终进入到我们的主机驱动,调用 urb_enqueue 回调函数, 
如果是真正的USB主机控制器,则在urb_enqueue回调函数中把URB请求递交给USB硬件,
而这里是虚拟主机控制器,因此可以在urb_enqueue中以任何方式传递urb请求数据,
比如在usbip代码中,直接把urb数据通过socket网络传输给对方。
而在我们的代码实现中,为了方便和灵活使用,统一把URB请求数据包传递到应用层,然后在应用层再做其他方面的处理。
当我们的主机驱动处理完这个URB请的时候,调用usbcore提供的usb_hcd_giveback_urb 函数,
通知上层的usb设备驱动,URB请求已经完成。
这时候,上层sub驱动设置的urb回调函数就会被调用,从而上层的usb设备驱动就获取到已经完成的urb数据。

一个完整的urb通讯流程就这样完成了。
现在还有一个问题,如何模拟”插入“和”拔出“USB设备。
在hub_control控制回调函数中,usbcore会查询roothub的设备描述信息,在里边填写我们主机驱动提供的端口数,比如16个。
也就是我们的主机可以提供16个端口同时给16个USB设备。然后每个端口对应一个相应的状态,一开始都是未连接状态。
当我要在某个端口“插入”一个USB设备的时候,改变这个端口状态,然后调用 usb_hcd_poll_rh_status 函数通知usbcore。
usbcore会接着调用 hub_control 回调函数查询端口状态,发现某个端口已经插入了USB设备,
于是调用usb_submit_urb函数获取这个USB设备的设备描述符等相关信息,于是我们的urb_enqueue被调用。
获取描述符,然后根据设备描述符等信息,试图加载对应的USB设备驱动。
USB设备驱动加载之后,接着会调用usb_submit_urb建立起正常的USB设备通信。
在这里,usbcore的行为与windows平台PNP即插即用管理器的行为十分相似。

至此,一个完整的linux平台的USB虚拟控制器驱动内核部分就算实现了,
因为我们的驱动是把URB数据传递到应用层再来处理的。
接着需要处理的就是如何处理URB数据包
1, 如果是模拟一些USB设备,则直接填写相关数据,然后返回给驱动。
2,如果是实现类似usbip功能,则把数据整理打包,再通过网络传递给对方。
这里也就不再赘述。
下图是在 CentOS8系统中(linux内核版本是4.18), 模拟一个USB摄像头的效果图:
USB摄像头的模拟数据是根据以前所写的windows平台的模拟数据,
USB总线驱动不单可以模拟USB摄像头,还能模拟其他通用USB设备,这里为了方便,只模拟了USB摄像头。

未完待续,
下一章主要阐述如何把驱动移植到Android系统中,并且模拟出一个USB摄像头的效果。

linux平台实现USB虚拟总线驱动一(原理以及开发流程)相关推荐

  1. USB虚拟总线驱动开发扩展之(利用虚拟USB总线驱动实现U盘模拟)

    by fanxiushu 2020-03-25 转载或引用请注明原始作者. USB虚拟总线驱动的使用范围是非常广泛的,可以使用它来模拟各种通用的USB设备. 以前的文章阐述过基于windows平台和基 ...

  2. MongoDb Windows linux平台环境及主流编程语言驱动安装同时配置mongoDb的远程连接

    MongoDb Windows linux平台环境及主流编程语言驱动安装同时配置mongoDb的远程连接 <一,>MongoDB 简介篇Ruiy; MongoDB是一个高性能,开源,无模式 ...

  3. win10下安装华为Atals USB虚拟网卡驱动

    1. 用USB连接电脑和HUAWEI Atlas 200 进入设备管理器,查看设备驱动安装情况 2. 安装驱动 (1) 在RNDIS上点击右键,选择更新驱动程序 (2) 接下来选择浏览我的电脑以查找驱 ...

  4. USB设备驱动开发之扩展(利用USB虚拟总线驱动模拟USB摄像头)

    fanxiushu 2016-10-08 转载或引用,请注明原始作者 做这个事情写这篇文章之前,压根没朝模拟USB摄像头这方面去想过. 直到CSDN上一位朋友提出问题,才想到还有这么一个玩意. 因此花 ...

  5. Linux I2C子系统分析-I2C总线驱动

    在drivers/i2c/busses下包含各种I2C总线驱动,如S3C2440的I2C总线驱动i2c-s3c2410.c,使用GPIO模拟I2C总线的驱动i2c-gpio.c,这里只分析i2c-gp ...

  6. linux 虚拟仪器,linux平台下编写虚拟仪器系统设计

    描述 计算机及其接口技术的发展和传统测试测量仪器系统暴露出来的不足,使得基于计算机的虚拟仪器设备越来越成为测试测量仪器的主导.虚拟仪器系统以其平台通用性.可扩充.易升级和高度的智能性获得了广泛的工业应 ...

  7. linux内核配置usb虚拟串口,Linux USB虚拟串口设备

    Linux内核中usb设备侧驱动程序分成3个层次:UDC驱动程序.Gadget API和Gadget驱动程序.UDC驱动程序(USB控制器)直接访问硬件,控制USB设备和主机间的底层通信,向上层提供与 ...

  8. 嵌入式linux 配置usb otg,嵌入式linux系统环境下USB设备的驱动实现

    0  引言 嵌入式linux系统环境以其易于移植裁减.内核小.效率高.完整.原代码开放及性能优异等特点,在嵌入式领域得到了非常广泛的应用.Linux的USB设备端的源代码中主要有USB device的 ...

  9. Linux源码阅读——PCI总线驱动代码(一)整体框架

    目录 一.前言 二.概述 三.整体流程 四.PCI相关入口函数 4.1 pcibus_class_init 4.2 pci_driver_init 4.3 pci_arch_init 4.4 pci_ ...

最新文章

  1. 手把手教你如何制作可视化大屏!
  2. LeetCode 140. 单词拆分 II
  3. GPU处理图像 Shader的入门
  4. 获取网络状态ConnectivityManager
  5. centos7 以上和以下版本设置
  6. 《云计算》学习笔记1
  7. 人工智能?.NetCore一样胜任!
  8. 最大熵对应的概率分布
  9. Docker Installation : Docker 中安装并启动 Kong
  10. Django框架——视图
  11. mac实现ssh的免密远程登录
  12. XVIII Open Cup named after E.V. Pankratiev. Grand Prix of SPb
  13. python的文件夹_Python遍历文件夹和文件
  14. hdu 5120(求两个圆环相交的面积 2014北京现场赛 I题)
  15. Magnet for mac(专业窗口辅助工具)支持m1
  16. springboot+mybatis+druid 多数据源整合
  17. 删除Linux上的用户
  18. 下载篇:程序员修炼之道+从小工到专家(高清、免费)
  19. uygurqa输入法android,uygurqa键盘输入法
  20. 加ing形式的单词有哪些_哪些单词是动词加 -ing 变形容词,在加 -ly 变副词的?能不能列举一些,有十几个就可以了。?...

热门文章

  1. 实战:kali攻击Android手机
  2. 【T2噬菌体】ASP.NET AJAX客户端编程之旅(三)—数据转换序列化
  3. linux系统发行版安装,Linux 发行版和安装的基本知识
  4. Java选择结构分支训练题(if、switch)
  5. 使用maven创建javaSE工程
  6. java链接zookeeper
  7. Oracle表明明存在SQL查询数据提示表不存在异常
  8. StreamSets
  9. 软件安全测试需要更加灵活
  10. Python实现快速排序 易懂