byfanxiushu2019-11-07转载或引用请标明原始作者。
之前的文章探讨过在windows平台下,怎样实现USB虚拟总线驱动,以及怎样在windows平台采集真实USB设备的数据,
之后通过网路传输,达到”延长“USB电缆的疗效。
相关链接如下:
USB设备驱动开发之远程访问USB设备(一USB设备数据采集端)
USB设备驱动开发之远程访问USB设备(二USB设备虚拟端)
USB设备驱动开发之远程访问USB设备(三虚拟USB控制器和根网桥)
以上都是windows平台下的,其中第一个链接是关于怎样实现采集端的,前面两个链接探讨USB虚拟端。
曾经想要实现USB虚拟驱动的目的比较另类,也在里面的文章中说明了。
由于时常会在macOS系统中做些iOS手机数据备份开发哪些的,而我的macOS系统是装到vmware虚拟机中的,
结果每次都须要把手机USB数据线插到电脑笔记本上,总觉得这跟线多余,于是总想着有没有办法去除linux usb驱动,于是才有了前面的开发的文章。
实现的疗效如右图这样的:
简单解释一下,图中A部份”FanxiushuVirtualUSBHostController“和”FanxiushuVirtualUSBRootHUB“是我实现的USB虚拟总线驱动,
这个总线驱动须要虚拟控制器和根网桥的,否则vmware这样的软件难以辨识的,
而这个驱动下“的Port1端口插入了”iPhone手机的,虽然真实iPhone手机插到我的另一台windows台式机器上,
这儿通过网路传输,之后模拟成”插入“”FanxiushuVirtualUSBHostController“的疗效。
然而Port1端口显示的是”VmwareUSBDevice“,说明这个USB设备早已被vmware接管了。
紧接着看图中的B部份。显示”AppleFanxiushu-USB-Device1“设备已然联接进去vmware虚拟机中,
再看图中的C部份,在虚拟机中macOS系统中,早已辨识到了我的iPhone手机。
这就是当年实现USB虚拟驱动,想要达到的目的。
本文正式描述的就是linux平台下的USB虚拟总线驱动的实现,也可以叫虚拟USB控制器驱动。
同时,也在先前的文章中,探讨过怎样在linux平台中采集真实USB设备的数据,
链接如下:
USB驱动开发之远程访问USB设备扩充(linux平台USB设备数据采集端)
配合本文的虚拟USB总线驱动,可以实现在linux平台之间任意的共享USB设备,
假如再配合windows平台下的实现,则可实现windows,linux平台之间任意共享USB设备。
同时也可以把虚拟总线驱动单独掏出来,用于模拟各种通用的USB设备。
例如模拟USB摄像头,如下链接描述的就是借助USB总线驱动模拟USB摄像头:
USB设备驱动开发之扩充(借助USB虚拟总线驱动模拟USB摄像头)
也可以模拟USB声卡,U盘,USB鼠标键盘,游戏摇杆等,只有想不到的没有办不到的,由于USB插口太通用了。
也可以模拟个别私有合同的USB设备,其实前提是必须晓得这类设备的USB通信合同格式。
linux下实现USB虚拟总驱动并没有windows平台这么有用linux usb驱动,由于使用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使用的虚拟总线长春linux培训,这就是platofrm总线的来历。
我们在开发虚拟USB总线驱动的时侯,就是须要使用platfrom总线。使用它的形式也是很简单。
我们在代码中定义platform_driver和platform_device数据结构,如下代码:
///驱动入口
staticstructplatform_driverhost_driver={
.probe=host_add_device,
.remove=host_remove_device,
.suspend=host_suspend,
.resume=host_resume,
.driver={
.name="usb_host",///和下边的device一样
.owner=THIS_MODULE,
},
};
staticvoidplatform_device_release(structdevice*dev)
{
//donothing,isvirtualhost
printk("--usb_host:platform_device_releasen");
}
staticstructplatform_devicehost_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注册总线设备,如下伪代码:
staticint__inithost_driver_init(void)
{
intret;
。。。
ret=platform_driver_register(&host_driver);
。。。。
//注册一个平台总线设备
ret=platform_device_register(&host_device);
。。。。
printk("--usb_host:driveinitok.n");
return0;
}
在退出函数注销,如下伪代码:
staticvoid__exithost_driver_exit(void)
{
platform_device_unregister(&host_device);
platform_driver_unregister(&host_driver);
printk("---usb_host:driverexit.n");
}
初始化模块:
module_init(host_driver_init);
module_exit(host_driver_exit);
这样,platform总线驱动就完善上去了。是不是比起windows实现虚拟总线驱动简单得多了。
接着我们在host_add_device反弹函数中初始化USB总线驱动,创建HCD,也就是USB主机控制器。
开始之前,先大致来了解linux平台下,USBHost端,也就是主机端驱动的总体框架流程。
主要分为三层:
1,USB设备驱动,这儿就是具体的USB设备,负责主机与USB设备通讯。
|
2,USBCore,负责联接和管理上下两层,而且对里面的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内部会创建一个roothub虚拟根网桥设备,用于管理插到主机上的USB设备或USBHUB,
usb_create_hcd函数会要求传递一个hc_driver数据结构变量。
这儿边定义了所有关于USB数据交换,状态查询,USB控制等反弹函数。用于查询和管理USB设备状态,URB数据传输。
把hc_driver里面相关的反弹函数实现了,就等于是完整的实现了一个USB控制器驱动。
为此,我们的主要任务就是实现hc_driver结构里面的反弹函数。
hc_driver结构比较复杂,这儿只实现我们在虚拟控制器驱动须要实现的内容,如下:
///host主机相关结构和反弹函数
staticstructhc_driver_hc_driver={
.description="usb_host",
.product_desc="FanxiushuVirtualUSBHostController",
.hcd_priv_size=sizeof(structusb_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设备驱动加载以后red hat linux下载,接着会调用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摄像头的疗效。