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 实现原理

serverclient两边在恰当的时机分别隔断各自系统的 usb 通信流程,然后巧妙地交换数据,各自系统都察觉不到。

C/S模式中基本都是client发出请求,阅读usbip工具代码可以知道,tcp 的创建和建立连接均在应用程序usbip工具上创建,至于驱动需要使用 socket 时,是采取将 socket 的描述符传入内核,内核利用这个句柄,就能直接利用这个已打开的socket进行通信,无需再在内核建立连接之类的,linux内核会使用kernel_sendmsgkernel_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相关推荐

  1. USB驱动之USB网络共享

    一 编译USB/IP组件 USB/IP组件包含两部分:USB/IP协议栈和USB/IP驱动模块 USB/IP协议栈源码位于linux_kernel/tools/usb/usbip USB/IP驱动模块 ...

  2. 2008年12月13日上海USB驱动开发深度解析讲座PPT

    讲座PPT:宋宝华2008年12月13日上海USB驱动开发深度解析讲座PPT [url]http://www.linuxdriver.cn/200812/20081213172619_836.rar[ ...

  3. 嵌入式Linux USB驱动开发之教你一步步编写USB驱动程序

    2019独角兽企业重金招聘Python工程师标准>>> 编写与一个USB设备驱动程序的方法和其他总线驱动方式类似,驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标 ...

  4. MF Porting之USB驱动开发

    花费了近三个礼拜的时间,终于完成了TI开发板的USB驱动开发,现在回头想一想,其实也没有什么,具体硬件方面的通信由DM355实现了,软件层面的数据交互由MF Porting实现了,所做的也就是熟悉了解 ...

  5. Linux USB 驱动开发(五)—— USB驱动程序开发过程简单总结

    http://blog.csdn.net/zqixiao_09/article/details/51057086 设备驱动程序是操作系统内核和机器硬件之间的接口,由一组函数和一些私有数据组成,是应用程 ...

  6. linux usb驱动

    0.usb协议     usb的版本:     硬件         usb 1.0     OHCI        微软                 硬件 > 软件         usb ...

  7. android 最新usb驱动程序下载,安卓手机USB驱动官方下载、安装教程

    如果您想要成功地将安卓手机连接到电脑端进行文件传输.手机ROOT,或者更新手机固件.就不得不需要一个合适的USB驱动.为了方便大家,我们特意收集并整理了比较大众的安卓手机机型USB驱动的下载链接(所有 ...

  8. USB学习5---android usb驱动源代码目录说明

    kernel\msm-3.18\drivers\usb下目录内容 我们msm8937+android7.1平台编译out目录下usb目录下有编译到的目录如下: 我们先参考kernel\msm-3.18 ...

  9. USB基础---Linux USB驱动层次

    在Linux系统中,提供主机侧和设备侧视角的USB驱动框架,从主机侧看到的USB主机控制器和设备驱动,以及从设备侧看到的设备控制器和Gadget驱动. Linux系统中USB驱动的整体视图 图1 (1 ...

  10. 【.Net Micro Framework PortingKit(补) – 1】USB驱动开发

    在前段时间我连续写了15篇关于[.Net Micro Framework PortingKit–?]的系列文章,初步介绍了.Net Micro Framework在Cortex-M3平台上的移植过程, ...

最新文章

  1. FPGA的LVDS电平以及LVDS25电平能在HR Bank上使用吗?
  2. Makefile文件和shell脚本
  3. Jenkins中的邮件设置
  4. oracle 中增加行,Oracle中实现FORM表单插入、锁定、更新行、删除行的包
  5. linux 网络内核 ko文件,编译内核模块 .ko文件缺少:mmzone.h bounds.h
  6. PCA相关 PCL库和Matlab对比
  7. 【风电功率预测】基于matlab粒子群算法优化LSTM风电功率预测【含Matlab源码 941期】
  8. Java中PreparedStatement和Statement区别
  9. 爱的十个秘密--7.舍弃的力量
  10. mac电脑如何装双系统Linux,苹果电脑双系统怎么装【详细教程分享】
  11. Easy ip 简单配置实验
  12. 表达式(四则运算)计算的算法代写 essay代写
  13. Passenger简介
  14. 糖友控糖是在控什么糖呢
  15. 工作经验这样写,面试就有了!
  16. “整合”还是“混合”——多因子组合的构建
  17. Maven项目自动更新/修复Javadoc
  18. 怎么在Excel2003版中查找重复值
  19. 50TB ExaDrive SSD投入商用,EB级容量闪存系统来日可期
  20. 我的python错题本

热门文章

  1. PHP开发环境准备,PHPWAMP使用,图文教程
  2. 学计算机专业你后悔吗?为什么?
  3. 麻将游戏简介firefly游戏框架介绍
  4. 超全的Linux基础知识思维导图(1)
  5. 设置cookie存活时间_js中如何设置cookie的保存时间呢?
  6. ai神经网络滤镜安装包
  7. 【裴礼文数学分析】例1.1.1
  8. git SSH密钥生成及部署
  9. 数据库优化相关面试题
  10. 聚类算法之密度聚类方法