终于来到usbip驱动代码分析了!
我们在做产品时,通常是先讨论方案、制定协议、编码和测试。
usbip的方案是行得通的,它是从URB对象获取信息,然后从tcp发送出去的,URB是linux usb子系统里面用于抽象usb通信而精心设计的对象,只要server和client两边在恰当的时机分别隔断各自系统的usb通信流程,然后巧妙地交换数据,各自系统都察觉不到,就像黑客利用钩子函数做rookit。如果server和client都是linux系统,就很容易理解了,毕竟部门内的兄弟好说话,跨部门沟通就没那么好了。譬如是linux和windows的话,那么linux的urb就需要转换为windows那边能辨认的的“urb”了,它们的结构体成员肯定不一样,毕竟操作系统都不一样,不过够信息组包就好了。
usbip有一套协议,用于解决上述问题。
协议文档在kernel源码根目录下的drivers/usb/usbip/usbip_protocol.txt,比较新的版本在Documentation/usb/usbip_protocol.txt
在描述usbip原理前,我们先回顾下usb主机控制器和usb驱动(接口驱动)的关系。可以通过阅读usb-skeleton.c代码来学习USB设备驱动是怎么写的。简单来说,当主机控制器驱动里的hub.c检测到root hub的port有插入设备(譬如检测到D-/D+管脚被hub拉低),主机控制器hcd就会发出“获取设备描述符”的请求进行枚举设备,获取到PID/.VID,接口描述符、端口描述符等信息后,报给上层驱动(usb core),usb core会根据PID/VID来match设备驱动,此时假设匹配到usb-skeleton驱动,那么usb-skeleton驱动的probe()被回调,这时就可以进行usb通信了,如U盘读写,键鼠操作等,这些通信是通过填充urb结构体,然后交给usb core模块导出的usb_submit_urb函数,它会回调从主机控制器驱动hcd的struct hc_driver注册到的urb_enqueue回调函数和urb_dequeue回调函数。这两个函数是比较底层的,直接控制usb控制器的寄存器,开启dma传输,最后编码成usb硬件层的“非归零反转码”,用差分电信号传出去。其中usb core模块是linux的大牛们封装好的很多通用的api供我们使用,减少了我们开发usb驱动的难度,因为大部分事情已经交给usb core做了,很多时候就是在框架内注册回调方法。
对于usbip,假设在client端(vhci-hcd)安装有U盘驱动,vhci-hcd的一个重要作用是虚拟出一个usb主机控制器(hcd),linux很多虚拟设备,譬如虚拟网卡(driver/net/dummy.c)等,根据上面的描述,我们可以知道usb主机控制器的作用,就是枚举信息,以及获取U盘驱动组装的usb通信数据转发给底层硬件,最后给到U盘,跟U盘通信。所以虚拟hcd也具有这样的功能,我们在上一篇文章中知道,client端是使用“usbip attach -r 192.168.100.191 -b 2-1.1”把usb设备attach到本地的,其实这个工具的attach操作就是类似于usb的“热插拔”,踢一下vhci-hcd虚拟出来的主机控制器的root hub(任何主机控制器都有一个根hub),这时hub.c就以为有真实的usb设备插入,按照usb hub的协议发出请求,譬如询问root hub究竟是哪个port口插入了设备等,下面是一些hub需要处理的请求,具体功能参考《usb2.0规范》的CH11章节:
ClearHubFeature
ClearPortFeature
GetHubDescriptor
GetHubStatus
GetPortStatus
SetHubFeature
SetPortFeature
只要我们模拟出root hub端口号以及端口状态值给hub.c,就能蒙骗它,让它以为真的有硬件插入,此时hub.c就会发出枚举usb设备的“请求设备描述符”给root hub,最后给到urb_enqueue,vhci-hcd就是实现一个vhci_urb_enqueue,并注册到struct hc_driver对象的.urb_enqueue成员函数里,vhci_urb_enqueue的功能不是像真实主机控制器驱动那样,操控寄存器,操控DMA,而是通过socket发送出去,给server端的真实主机控制器那边接收,由于server端(usbip-host)真的存在有usb主机控制器,所以把从client(vhci-hcd)的socket发出来的usb数据接收到,重新组装好urb,通过usb core模块的usb_submit_urb接口传给真实的主机控制器里,就能跟接在host端pc的U盘通信了,既然链路已经通了,client端的U盘驱动的其他操作(写入U盘数据或者读取U盘里的文件等)就能按照上面的链路走了,能完全操控host端的真实的U盘了!
文字很啰嗦,不知道有没有讲明白,但讲解usbip协议前必须要给大家一个初步的原理介绍。
来源:oschina
链接:https://my.oschina.net/u/4264517/blog/4331820