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.o
	usbip-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-hcd
	obj-$(CONFIG_USBIP_VHCI_HCD) += vhci-hcd.o
	vhci-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 等情况。

  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值