USBIP源码分析

简介在普通的电脑上,想使用USB设备,必须将插入到主机。USBIP却可以通过网络,让主机访问其他主机上的外部设备,而用户程序完全感知不到区别。usbip的文章在这里:https://pdfs.semanticscholar.org/c7c4/cb054d75810fdb0a2affa11c288b7687267f.pdf本文基于linux内核4.4.3,分析USBIP这部分的源码。先介绍整体结构,...

USBIP源码分析

简介

在普通的电脑上,想使用USB设备,必须将插入到主机。USBIP却可以通过网络,让主机访问其他主机上的外部设备,而用户程序完全感知不到区别。

usbip的文章在这里:https://pdfs.semanticscholar.org/c7c4/cb054d75810fdb0a2affa11c288b7687267f.pdf

本文基于linux内核4.4.3,分析USBIP这部分的源码。先介绍整体结构,然后分两部分介绍USBIP源码

USBIP整体架构

从体系机构的角度上来说,USB的设备和总线都是通过Host分出,host叫做主机控制器,一个主机控制器会有一个root hub,然后root hub再通过接口分出多个hub,最后,一个hub的一个端口都可以用来连接一个外部设备或是宁一个hub,形成一个以host为跟的树状结构。而Host最终是作为一个PCI的设备,连接在PCI总线上。

在linux内核中,USB驱动的架构可以分成3层。

  • 最靠近用户层的是USB设备驱动,这是每个USB设备有独有的,在它之上可以直接与VFS交互,比如键盘和鼠标这些设备,USB设备驱动就足以处理外部设备的交互任务,因为他们发送过来的就只有是一些很简单的数据,但是如果是一个插在USB上的网卡等设备,则需要在USB设备驱动之上再覆盖一层网卡等驱动,用来解析USB总线传输过来的数据。它与下层的USB Core通过urb交换数据。
  • USB Core则是承上启下的一层,USB设备驱动通过urb的抽次昂概念来和底层交互,USB Core就用来解析urb,同时,还会包含Hub驱动等等一些USB驱动框架的其他主要部分。
  • 最下层的是主机控制器驱动,前面看到USB外部设备最终通过Host和总线、CPU交互,也就是说最底层还需要有一个主机控制器的驱动,它主要负责Host的相关工作,将USB设备的信息通过主机控制器向上传递软件层。这部分会有一个PCI驱动,然后控制Host硬件

理解了USB驱动的框架,USBIP的架构就比价容易了,主要部分也是两个,读取设备的主机端,设置一个虚拟的主机控制器接口VHCI,它不操纵底层的主机控制器,而是将上层的消息通过网络转发到另一个主机,在另一侧,实现一个USB设备驱动,它不是将USB Core的内容向上传递,同样是通过网络发送出去,叫做Stub端。

在Linux内核中,USBIP的源码写在drivers/usb/usbip目录下,在这些文件中,以stub开头的都是server端的代码,vhci开头的是client端的代码,其余是公共部分的代码。

下面从Stub和VHCI的角度来分析。

Stub端

主要结构体

usbip_common中有一个usbip_device结构,这是stub和vhci两边设备从后向出来的公共部分。这里有3个内核线程,一个socket,以及和eh相关的等待队列和操作集合。usbip_common中其他几个结构就不再叙述,比较简单

struct usbip_device { enum usbip_side side; enum usbip_device_status status; /* lock for status */ spinlock_t lock; struct socket *tcp_socket;//用来通信的socket struct task_struct *tcp_rx;//收消息的线程 struct task_struct *tcp_tx;//发送消息的线程 unsigned long event;//记录事件 struct task_struct *eh;//内核线程 wait_queue_head_t eh_waitq;//eh等待队列 struct eh_ops {  void (*shutdown)(struct usbip_device *);  void (*reset)(struct usbip_device *);  void (*unusable)(struct usbip_device *); } eh_ops;};

stub.h中定义了关键的结构体,他们的内容和含义如下:

stub_device是stub端的设备抽象,代表以一个外部设备

struct stub_device { struct usb_interface *interface;//接口描述符指针 struct usb_device *udev; struct usbip_device ud;//对于stub和vhci两端的设备都做了抽象,用来表示两端交互中都需要的内容 __u32 devid; spinlock_t priv_lock; struct list_head priv_init;//urb初始队列 struct list_head priv_tx;//urb被提交之后 struct list_head priv_free;//urb的内容被发送给了chci struct list_head unlink_tx;//unlink请求队列 struct list_head unlink_free;//unlink请求被处理 wait_queue_head_t tx_waitq;//等待队列};

stub_priv被赋值给urb->priv字段,主要是给stub端来管理urb结构体

struct stub_priv { unsigned long seqnum;//给每个urb都有一个序列号 struct list_head list; struct stub_device *sdev; struct urb *urb; int unlinking;//是否被unlink};

stub_unlink表示一个urb的unlink请求

struct stub_unlink { unsigned long seqnum;//和stub_priv对于的序列号 struct list_head list; __u32 status;//unlink是否成功};

stub_main中有一个全局变量的数组,用来管理所有的设备

static struct bus_id_priv busid_table[MAX_BUSID];

其中一个设备用bus_id_priv来表示

struct bus_id_priv { char name[BUSID_SIZE]; char status; int interf_count; struct stub_device *sdev; struct usb_device *udev; char shutdown_busid;};

模块的初始化函数

先看stub_main中最后几行,有模块的的初始和卸载函数

module_init(usbip_host_init);module_exit(usbip_host_exit);

usbip_host_init中主要函数有4个,做的工作如下

1、初始化全局变量busid_table
2、分配一个slab用来做stub_priv的分配工作
3、注册一个usb驱动,这里是stub的初始化,这里注册的驱动也是位于设备端,USB核心上层的USB驱动
4、创建两个sysfs文件,这两个文件的操作就在上面,rebind_store和store_match_busid、show_match_busid。

下面的退出函数usbip_host_exit同理,只是做了这几个函数的清理工作

先看看它创建的两个sysfs文件。这是给用户态的接口。一个是match_busid

static DRIVER_ATTR(match_busid, S_IRUSR | S_IWUSR, show_match_busid,store_match_busid);

涉及的函数是show_match_busid和store_match_busid,show_match_busid用来输出名字,store_match_busid则用来增加或是删除busid_table中的条目

另一个文件则是用来设置设备绑定的驱动。

这几个文件的操作都是围绕busid_table和它的几个操作函数开展开,也比较简单。整个文件的内容也分析完了。下面就还有一个关键的usb驱动,也就是Stub驱动,很显然,主要的工作都是通过这个驱动来开展的。

Stub驱动

stub_dev文件最底部有这个驱动

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,};

从probe函数开始,这里只说一些和Stub驱动自身逻辑相关的重要部分,proe函数通过stub_device_alloc分配了一个stub_device函数。而这个stub_device_alloc函数中还有一个usbip_start_eh函数。用来创建了一个内核线程eh。

int usbip_start_eh(struct usbip_device *ud){ init_waitqueue_head(&ud->eh_waitq); ud->event = 0; ud->eh = kthread_run(event_handler_loop, ud, "usbip_eh"); if (IS_ERR(ud->eh)) {  pr_warn("Unable to start control thread\n");  return PTR_ERR(ud->eh); } return 0;}EXPORT_SYMBOL_GPL(usbip_start_eh);

前面看到usbip_device结构中有3个内核线程,这是其中一个,二且eh_waitq就是用来给eh睡眠的,出发事件写在usbip_event_happened函数中,只是检查usbip_device上是否有事件产生,event字段会否为0。

好,接着回到probe函数,里面有一个stub_add_files函数。可以知道这个函数同样创建了几个sysfs文件,他们为dev_attr_usbip_status、dev_attr_usbip_sockfd、dev_attr_usbip_debug。

其中比较关键的是dev_attr_usbip_sockfd,当写入数据为-1时执行关闭操作,另一条分支则会通过写入数据打开socket,赋值给tcp_socket字段,接着创建了两个内核线程。

看看这个stub_dev文件,剩下的代码都是处理断开和释放的函数了,这里就不分析,剩下的两个文件stub_rx和stub_tx可以知道这是和内核线程相关的了。所以说剩下的主要内容就是这两个内核线程。

rx和tx两个内核线程和stub_device中的5个链表

先看rx,主函数如下,沿着这个逻辑往下看

int stub_rx_loop(void *data){ struct usbip_device *ud = data; while (!kthread_should_stop()) {  if (usbip_event_
源文地址:https://www.guoxiongfei.cn/cntech/18391.html
0