usb 驱动之usbip
1、usbip 框架
参考:https://blog.csdn.net/litao31415/category_9292335.html
1.1 usbip 功能介绍
usbip
驱动提供了linux
下USB 透传(bypass
)的功能,或者说usb over tcp
,利用以太网,将usb 设备共享到另外一端。
usbip
分为两端:server
端和client
端,它们分别运行在不同的linux
主机中,其中运行server
端插入usb 设备(如U盘等),用于共享。此时client
端就可以 attach 到server
端,此时client端就能看到 u盘,它以为真的有一个 u盘插入本机,并为其安装驱动,效果跟在本机上直接插入U盘无异,实现了 u盘共享,或者USB延长器的功效。
1.2 usbip 使用介绍
内核的根目录下tools/usb/usbip
,这个是应用程序的,用来配置(控制)usbip
驱动。
usbip
有一套协议,协议文档在kernel 源码根目录下的drivers/usb/usbip/usbip_protocol.txt
,比较新的版本在Documentation/usb/usbip_protocol.txt
。
1.2.1 server 端
在server
端PC上:
(1)加载驱动,运行应用程序;
sudo modprobe usbip-host //加载server 端驱动obj-$(CONFIG_USBIP_HOST) += usbip-host.ousbip-host-y := stub_dev.o stub_main.o stub_rx.o stub_tx.
usbipd -D //运行usbipd程序,该守护进程用于建立TCP socket连接等
(2)插入一个U盘/鼠标,此时usbip list -l
应该能列举出设备,用于查看busid
:
usbip list -l- busid 2-1.1 (046d:c077)Logitech, Inc. : M105 Optical Mouse (046d:c077)
(3)绑定U盘,以便于共享,有complete
字眼说明绑定成功:
usbip bind -b 2-1.1 //将usb 绑定到usbip-host 驱动下usbip: info: bind device on busid 2-1.1: complete
usbip unbind -b 2-1.1 //解绑,用于关闭usb的共享
1.2.2 client 端
在client
端 PC 上:
(1)加载驱动;
modprobe vhci-hcdobj-$(CONFIG_USBIP_VHCI_HCD) += vhci-hcd.ovhci-hcd-y := vhci_sysfs.o vhci_tx.o vhci_rx.o vhci_hcd.o
(2)列举出server
端已经导出的 usb 设备;
#usbip list –r <server端ip地址>
usbip list -r 192.168.100.191
(3)将server
端的 usb 设备attach
到本地client
;
#usbip attach -r <server端ip地址> -b <busid>
usbip attach -r 192.168.100.191 -b 2-1.1
usbip detach -p 0 #可以断开usb设备
2、usbip 实现
驱动源码目录(server 和 client):\drivers\usb\usbip
2.1 usbip 实现原理
server
和client
两边在恰当的时机分别隔断各自系统的 usb 通信流程,然后巧妙地交换数据,各自系统都察觉不到。
在C/S
模式中基本都是client
发出请求,阅读usbip工具代码可以知道,tcp 的创建和建立连接均在应用程序usbip
工具上创建,至于驱动需要使用 socket 时,是采取将 socket 的描述符传入内核,内核利用这个句柄,就能直接利用这个已打开的socket进行通信,无需再在内核建立连接之类的,linux内核会使用kernel_sendmsg
和kernel_recvmsg
发送和接收 tcp 数据。
client
端是使用“usbip attach -r 192.168.100.191 -b 2-1.1
”把usb 设备 attach 到本地的,其实这个工具的attach 操作就是类似于 usb 的“热插拔”,踢一下 vhci-hcd
虚拟出来的主机控制器的 root hub
(任何主机控制器都有一个根hub),这时 hub.c
就以为有真实的 usb设备插入。
只要我们模拟出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盘了!
2.1 client 端
2.1.1 usbip attach
vhci_start()
函数的最后,注册了sysfs 的用户界面,用于配置 vhci-hcd
驱动。
usbip attach -r 192.168.100.191 -b 2-1.1 // usbip应用程序== attach_store() // 内核驱动接口drivers/usb/usbip/vhci_sysfs.c== vdev->ud.tcp_rx = kthread_get_run(vhci_rx_loop, &vdev->ud, "vhci_rx"); //创建两个内核线程== vdev->ud.tcp_tx = kthread_get_run(vhci_tx_loop, &vdev->ud, "vhci_tx");== vhci_tx_loop(void *data);== wait_event_interruptible(vdev->waitq_tx,(!list_empty(&vdev->priv_tx) ||!list_empty(&vdev->unlink_tx) ||kthread_should_stop())); // 等待唤醒发送数据== rh_port_connect(vdev, speed); // 踢一下vhci-hcd虚拟出来的主机控制器的root hub,让hub.c以为有真实的usb设备插入== usb_hcd_poll_rh_status(struct usb_hcd *hcd);== hcd->driver->hub_status_data(hcd, buffer);== vhci_hub_status(struct usb_hcd *hcd, char *buf); // buf: a bitmap to show which port status has been changed== usb_hcd_unlink_urb_from_ep(hcd, urb); == usb_hcd_giveback_urb(hcd, urb, 0); //vhci_hub_status 有change 发生== urb->complete(urb); //urb 的完成函数 hub_irq== hub_irq() //hub.c usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,== kick_hub_wq(hub);== hub_event() //INIT_WORK(&hub->events, hub_event);== port_event(hub, i);== hub_port_connect_change()== hub_port_connect()== hub_port_init()== usb_new_device(udev);== usb_enumerate_device(udev); //开始枚举== device_add(&udev->dev); //枚举完毕后加载设备驱动
2.1.2 vhci_urb_enqueue
client
端通过 usb-storage
设备驱动与 U盘进行数据交流。
== usb_submit_urb(urb, GFP_KERNEL); // urb就是从上层驱动(usb-skeleton.c或者U盘驱动、hid键鼠驱动等)传下来== usb_hcd_submit_urb(urb, mem_flags);== hcd->driver->urb_enqueue(hcd, urb, mem_flags);== vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags);== usb_hcd_link_urb_to_ep(hcd, urb); // hcd主机控制器把urb放入端点队列中,等待底层发送== vhci_tx_urb(urb, vdev); == list_add_tail(&priv->list, &vdev->priv_tx); // 通过分配 struct vhci_priv 实例,填充 urb 并加入到 priv_tx 队列== wake_up(&vdev->waitq_tx); // 唤醒发送线程 vhci_tx_loop() 进行发送== vhci_tx_loop(); // 发送线程是没有跑的,只有唤醒事件发生才有发送数据== vhci_send_cmd_submit(vdev);== dequeue_from_priv_tx(vdev); // 从 priv_tx 队列提取出urb,并插入到另外一个 priv_rx 队列中,最后将urb通过IP发送(USBIP_CMD_SUBMIT)== kernel_sendmsg(struct socket *sock, struct msghdr *msg,struct kvec *vec, size_t num, size_t size); //发送数据== sock_sendmsg(sock, msg);
// 接收线程是一直在跑的,除非有停止事件发生
== vhci_rx_loop(void *data);== vhci_rx_pdu(ud);== usbip_recv(ud->tcp_socket, &pdu, sizeof(pdu));== sock_recvmsg(sock, &msg, MSG_WAITALL);== vhci_recv_ret_submit(vdev, &pdu);== pickup_urb_and_free_priv(vdev, pdu->base.seqnum); //从priv_rx队列中提取符合帧序号的urb(发送时和接收的均使用同一个urb对象,所以要利用seqnum来找回之前发送时用的那个urb,类似于“回话ID”的概念),并删除在priv_rx的节点== usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci_hcd), urb);== usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci_hcd), urb, urb->status);== urb->complete(urb); //返回给设备驱动程序
2.2 server 端
2.2.1 初始化
驱动初始化,创建sys 节点:
/sys/bus/usb/drivers/usbip-host # ls
bind match_busid module rebind uevent unbind
== usbip_host_init();== usb_register_device_driver(&stub_driver, THIS_MODULE); // 注册一个device_driver== driver_create_file(&stub_driver.drvwrap.driver, &driver_attr_match_busid); // 创建 match_busid 节点== sysfs_create_file(&drv->p->kobj, &attr->attr); // driver 的kobj== driver_create_file(&stub_driver.drvwrap.driver, &driver_attr_rebind); // 创建 rebind 节点
2.2.2 bind
当用户态使用工具“usbip bind -b <busid>
”时,usbip-host 驱动就会调用 rebind_store()
,它调用了device_attach(&bid->udev->dev)
,使得usb 设备(U盘/鼠标等)从原来的驱动(U盘驱动/HID鼠标驱动)中脱掉,然后改为挂到 usbip-host 驱动下,这样 server 这端的 u盘就能跟 usbip-host 驱动通信了,自然就能获取到跟 U盘通信的 urb 对象了,为后续通过 tcp传输 urb 打下了坚实的基础!
== rebind_store(struct device_driver *dev, const char *buf, size_t count);== do_rebind(char *busid, struct bus_id_priv *busid_priv);== device_attach(&busid_priv->udev->dev); // try to attach device to a driver// 驱动重新绑定成功后,执行probe
== stub_probe(struct usb_device *udev);== stub_device_alloc(udev); // 构建 struct stub_device *sdev;== dev_set_drvdata(&udev->dev, sdev); // set private data to usb_device== usb_hub_claim_port(udev->parent, udev->portnum, (struct usb_dev_state *) udev); // 霸占一个usb hub的port
// struct attribute_group **dev_groups; 注意dev_group 的用法
struct usb_device_driver stub_driver = {.name = "usbip-host",.probe = stub_probe,.disconnect = stub_disconnect,
#ifdef CONFIG_PM.suspend = stub_suspend,.resume = stub_resume,
#endif.supports_autosuspend = 0,.dev_groups = usbip_groups, //提供attr 文件系统操作接口
};static ssize_t usbip_sockfd_store(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
{...
}
static DEVICE_ATTR_WO(usbip_sockfd); //对应 dev_attr_usbip_sockfd.attr,static struct attribute *usbip_attrs[] = {&dev_attr_usbip_status.attr,&dev_attr_usbip_sockfd.attr,&dev_attr_usbip_debug.attr,NULL,
};
ATTRIBUTE_GROUPS(usbip); // 对应前面的.dev_groups = usbip_groups,
2.2.3 rx & tx
// 上层sys 节点操作 attr
== usbip_sockfd_store();== socket = sockfd_lookup(sockfd, &err); // 从用户态获取到socket后,转换成内核适用的socket对象== sdev->ud.tcp_socket = socket; // 并保存到本驱动的描述结构体(struct stub_device)中== sdev->ud.sockfd = sockfd;== sdev->ud.tcp_rx = kthread_get_run(stub_rx_loop, &sdev->ud,"stub_rx"); //创建内核线程,接收来自vhci-hcd驱动的urb命令消息== sdev->ud.tcp_tx = kthread_get_run(stub_tx_loop, &sdev->ud,"stub_tx"); //创建内核线程,发送本地usb交互产生的urb消息给对端的vhci-hcd
== stub_rx_loop(void *data);== stub_rx_pdu(ud);== usbip_recv(ud->tcp_socket, &pdu, sizeof(pdu));== sock_recvmsg(sock, &msg, MSG_WAITALL);== stub_recv_cmd_submit(sdev, &pdu);== priv->urbs[0] = usb_alloc_urb(np, GFP_KERNEL); //分配好一个空白的urb对象== priv->urbs[i]->complete = stub_complete; //注册回调函数== usb_submit_urb(priv->urbs[i], GFP_KERNEL); //提交urb,
usb 主机控制器驱动(hcd)的.urb_enqueue
就会收到提交的 urb,最后通过 DMA 把数据传给 SOC 的usb 控制器,给到U盘,U盘如果处理完了,就把数据发给 usb主机控制器驱动(hcd),最后归还端点(譬如调用usb_hcd_unlink_urb_from_ep()
和usb_hcd_giveback_urb()
),这时usb core 就会回调完成函数stub_complete()
,完成一次 U盘交互过程。
== stub_complete(struct urb *urb);== list_move_tail(&priv->list, &sdev->priv_tx); //将承载有urb的链表节点(node)pop出去,然后改为放到发送链表priv_tx中,用于就绪从usbip-host发送到vhci-hcd== wake_up(&sdev->tx_waitq); //唤醒发送线程,进行tcp组包和发送== stub_tx_loop(void *data); // 将数据发送给client 端vhci== stub_send_ret_submit(struct stub_device *sdev);== dequeue_from_priv_tx(vdev); // 从 priv_tx 队列提取出urb== kernel_sendmsg(struct socket *sock, struct msghdr *msg,struct kvec *vec, size_t num, size_t size); //发送数据== sock_sendmsg(sock, msg);
3、unlink 操作
unlink
的 urb 是 U盘驱动下发 usb_unlink_urb(struct urb *urb)
、 usb_kill_urb(struct urb *urb)
等时发出的,unlink 通常是出现在卸载驱动前,回收之前通过usb_submit_urb
但还没有 complete 的urb,或者出现异常了,需要 kill 掉之前的 urb 等情况。
usb 驱动之usbip相关推荐
- USB驱动之USB网络共享
一 编译USB/IP组件 USB/IP组件包含两部分:USB/IP协议栈和USB/IP驱动模块 USB/IP协议栈源码位于linux_kernel/tools/usb/usbip USB/IP驱动模块 ...
- 2008年12月13日上海USB驱动开发深度解析讲座PPT
讲座PPT:宋宝华2008年12月13日上海USB驱动开发深度解析讲座PPT [url]http://www.linuxdriver.cn/200812/20081213172619_836.rar[ ...
- 嵌入式Linux USB驱动开发之教你一步步编写USB驱动程序
2019独角兽企业重金招聘Python工程师标准>>> 编写与一个USB设备驱动程序的方法和其他总线驱动方式类似,驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标 ...
- MF Porting之USB驱动开发
花费了近三个礼拜的时间,终于完成了TI开发板的USB驱动开发,现在回头想一想,其实也没有什么,具体硬件方面的通信由DM355实现了,软件层面的数据交互由MF Porting实现了,所做的也就是熟悉了解 ...
- Linux USB 驱动开发(五)—— USB驱动程序开发过程简单总结
http://blog.csdn.net/zqixiao_09/article/details/51057086 设备驱动程序是操作系统内核和机器硬件之间的接口,由一组函数和一些私有数据组成,是应用程 ...
- linux usb驱动
0.usb协议 usb的版本: 硬件 usb 1.0 OHCI 微软 硬件 > 软件 usb ...
- android 最新usb驱动程序下载,安卓手机USB驱动官方下载、安装教程
如果您想要成功地将安卓手机连接到电脑端进行文件传输.手机ROOT,或者更新手机固件.就不得不需要一个合适的USB驱动.为了方便大家,我们特意收集并整理了比较大众的安卓手机机型USB驱动的下载链接(所有 ...
- USB学习5---android usb驱动源代码目录说明
kernel\msm-3.18\drivers\usb下目录内容 我们msm8937+android7.1平台编译out目录下usb目录下有编译到的目录如下: 我们先参考kernel\msm-3.18 ...
- USB基础---Linux USB驱动层次
在Linux系统中,提供主机侧和设备侧视角的USB驱动框架,从主机侧看到的USB主机控制器和设备驱动,以及从设备侧看到的设备控制器和Gadget驱动. Linux系统中USB驱动的整体视图 图1 (1 ...
- 【.Net Micro Framework PortingKit(补) – 1】USB驱动开发
在前段时间我连续写了15篇关于[.Net Micro Framework PortingKit–?]的系列文章,初步介绍了.Net Micro Framework在Cortex-M3平台上的移植过程, ...
最新文章
- FPGA的LVDS电平以及LVDS25电平能在HR Bank上使用吗?
- Makefile文件和shell脚本
- Jenkins中的邮件设置
- oracle 中增加行,Oracle中实现FORM表单插入、锁定、更新行、删除行的包
- linux 网络内核 ko文件,编译内核模块 .ko文件缺少:mmzone.h bounds.h
- PCA相关 PCL库和Matlab对比
- 【风电功率预测】基于matlab粒子群算法优化LSTM风电功率预测【含Matlab源码 941期】
- Java中PreparedStatement和Statement区别
- 爱的十个秘密--7.舍弃的力量
- mac电脑如何装双系统Linux,苹果电脑双系统怎么装【详细教程分享】
- Easy ip 简单配置实验
- 表达式(四则运算)计算的算法代写 essay代写
- Passenger简介
- 糖友控糖是在控什么糖呢
- 工作经验这样写,面试就有了!
- “整合”还是“混合”——多因子组合的构建
- Maven项目自动更新/修复Javadoc
- 怎么在Excel2003版中查找重复值
- 50TB ExaDrive SSD投入商用,EB级容量闪存系统来日可期
- 我的python错题本