linux usb usbip驱动详解(六)

        我们开始讲解usbip-host驱动原理。

        usbip-host驱动源文件大多以stub_*命名,我们先看stub_main.c的usbip_host_init()函数:

static int __init usbip_host_init(void)
{
	int ret;

	init_busid_table();

	stub_priv_cache = KMEM_CACHE(stub_priv, SLAB_HWCACHE_ALIGN);
...
	ret = usb_register_device_driver(&stub_driver, THIS_MODULE);
...
	ret = driver_create_file(&stub_driver.drvwrap.driver,
				 &driver_attr_match_busid);
...
	ret = driver_create_file(&stub_driver.drvwrap.driver,
				 &driver_attr_rebind);
...
	return ret;
...
}

        该函数就是初始化一下用到的自旋锁,创建一个高速缓存块,以及调用usb_register_device_driver(&stub_driver, THIS_MODULE);注册一个usb设备驱动(这个是真正的USB设备驱动,不是诸如U盘驱动、usb-skeleton.c这种USB接口驱动),你全局搜索,会发现整个linux内核就只有两个地方调用到usb_register_device_driver()函数,一个是usb core核心模块,还有一个就是我们的stub_main.c,自豪吧!《linux那些事之我是USB》书籍有USB设备驱动于USB接口驱动的详细介绍。   

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

       注册 usb设备驱动时,我们填充struct usb_device_driver stub_driver结构体,以供core框架回调,我主要分析stub_probe()函数,其余stub_disconnect()等感兴趣的话可自行阅读代码。

        有点困了,不写了,有空再回来补!

        usbip_host_init()函数还通过driver_create_file(&stub_driver.drvwrap.driver, &driver_attr_rebind)在sysfs下(/sys/bus/usb/drivers/usbip-host)产生相应的目录。

/sys/bus/usb/drivers/usbip-host # ls 
bind         match_busid  module       rebind       uevent       unbind

        我们只看“rebind”属性:

static ssize_t rebind_store(struct device_driver *dev, const char *buf,
				 size_t count)
{
	int ret;
	int len;
	struct bus_id_priv *bid;

	/* buf length should be less that BUSID_SIZE */
	len = strnlen(buf, BUSID_SIZE);

	if (!(len < BUSID_SIZE))
		return -EINVAL;

	bid = get_busid_priv(buf);
	if (!bid)
		return -ENODEV;

	ret = device_attach(&bid->udev->dev);
	if (ret < 0) {
		dev_err(&bid->udev->dev, "rebind failed\n");
		return ret;
	}

	return count;
}

        它调用了device_attach(&bid->udev->dev),使得usb设备(如,U盘/鼠标等)从原来的驱动(U盘驱动/HID鼠标驱动)中脱掉,然后改为挂到usbip-host驱动下,这样server这端的u盘就能跟usbip-host驱动通信了,自然就能获取到跟U盘通信的urb对象了,为后续通过tcp传输urb打下了坚实的基础!

       当用户态使用工具“usbip bind -b <busid>”时,usbip-host驱动就会调用上述的rebind_store(),这时,初始化函数static int stub_probe(struct usb_device *udev)就会被“usb core框架”回调,我们再来简单分析一下stub_probe():

static int stub_probe(struct usb_device *udev)
{
	struct stub_device *sdev = NULL;
	const char *udev_busid = dev_name(&udev->dev);
	struct bus_id_priv *busid_priv;
	int rc = 0;

	dev_dbg(&udev->dev, "Enter\n");

	/* check we should claim or not by busid_table */
	busid_priv = get_busid_priv(udev_busid);

	if (udev->descriptor.bDeviceClass == USB_CLASS_HUB) {
		dev_dbg(&udev->dev, "%s is a usb hub device... skip!\n",
			 udev_busid);
		return -ENODEV;
	}

	if (!strcmp(udev->bus->bus_name, "vhci_hcd")) {
		dev_dbg(&udev->dev,
			"%s is attached on vhci_hcd... skip!\n",
			udev_busid);

		return -ENODEV;
	}

	/* ok, this is my device */
	sdev = stub_device_alloc(udev);
...

	busid_priv->shutdown_busid = 0;

	/* set private data to usb_device */
	dev_set_drvdata(&udev->dev, sdev);
	busid_priv->sdev = sdev;
	busid_priv->udev = udev;

	/*
	 * Claim this hub port.
	 * It doesn't matter what value we pass as owner
	 * (struct dev_state) as long as it is unique.
	 */
	rc = usb_hub_claim_port(udev->parent, udev->portnum,
			(struct usb_dev_state *) udev);

	rc = stub_add_files(&udev->dev);

	busid_priv->status = STUB_BUSID_ALLOC;

  ...

	return 0;
}

        为了说明问题,代码上我们也做了简化,譬如去掉错误处理等细节。probe函数检查该bind的设备是否是所要支持的设备,创建相应的数据结构,如果要bind的是vhci_hcd或者usb hub则返回错误。usb_hub_claim_port(udev->parent, udev->portnum, (struct usb_dev_state *) udev);也是比较重要的接口,用于霸占一个usb hub的port。最后,stub_add_files()主要是为了在/sys下创建能读写的属性文件,譬如能将busid从用户态传递进usbip-host驱动、show一下当前状态、传递由应用层建立连接的socket句柄进来驱动等等功能(说明用户态和内核态的交互除了可以用ioctrl外还能借助虚拟文件系统sysfs/procfs)。我们主要来看其中的usbip_sockfd_store()函数:

/*
 * usbip_sockfd gets a socket descriptor of an established TCP connection that
 * is used to transfer usbip requests by kernel threads. -1 is a magic number
 * by which usbip connection is finished.
 */
static ssize_t usbip_sockfd_store(struct device *dev, struct device_attribute *attr,
			    const char *buf, size_t count)
{
	struct stub_device *sdev = dev_get_drvdata(dev);
	int sockfd = 0;
	struct socket *socket;
	int rv;

...

	rv = sscanf(buf, "%d", &sockfd);

	if (sockfd != -1) {

		socket = sockfd_lookup(sockfd, &err);

		sdev->ud.tcp_socket = socket;
		sdev->ud.sockfd = sockfd;

		sdev->ud.tcp_rx = kthread_get_run(stub_rx_loop, &sdev->ud,
						  "stub_rx");
		sdev->ud.tcp_tx = kthread_get_run(stub_tx_loop, &sdev->ud,
						  "stub_tx");

...

	} else {
...
	}

	return count;
}

        从用户态获取到socket后,转换成内核适用的socket对象,并保存到本驱动的描述结构体(struct stub_device)中,后面读写socket时会用到。跟vhci-hcd驱动类似,它也是利用kthread_get_run()宏创建内核线程:stub_rx_loop()和stub_tx_loop(),在用户态使用shell输入top命令,能看到"stub_rx"和"stub_tx"进程的cpu占用情况,就是在这里创建的!这两个线程一个是接收来自vhci-hcd驱动的urb命令消息,另一个是发送本地usb交互产生的urb消息给对端的vhci-hcd。

        就不详细展开stub_rx_loop()和stub_tx_loop()了,因为它们主要是队列操作以及根据usbip协议格式组包和拆包操作。我们主要关注, 从对端的vhci-hcd驱动获取到USBIP_CMD_SUBMIT命令并且已经根据这些命令组装好urb结构体后,usbip-host驱动究竟怎样利用该组装好的urb跟U盘交互的,这个才是我们关注的内容!

        我们发现是在static void stub_recv_cmd_submit(struct stub_device *sdev, struct usbip_header *pdu)函数里操作的:

static void stub_recv_cmd_submit(struct stub_device *sdev,
				 struct usbip_header *pdu)
{
	int ret;
	struct stub_priv *priv;
	struct usbip_device *ud = &sdev->ud;
	struct usb_device *udev = sdev->udev;
	int pipe = get_pipe(sdev, pdu);

	priv = stub_priv_alloc(sdev, pdu);

	/* setup a urb */
	if (usb_pipeisoc(pipe))
		priv->urb = usb_alloc_urb(pdu->u.cmd_submit.number_of_packets,
					  GFP_KERNEL);
	else
		priv->urb = usb_alloc_urb(0, GFP_KERNEL);

	/* allocate urb transfer buffer, if needed */
	if (pdu->u.cmd_submit.transfer_buffer_length > 0) {
		priv->urb->transfer_buffer =
			kzalloc(pdu->u.cmd_submit.transfer_buffer_length,
				GFP_KERNEL);
		if (!priv->urb->transfer_buffer) {
...
		}
	}

	/* copy urb setup packet */
	priv->urb->setup_packet = kmemdup(&pdu->u.cmd_submit.setup, 8,
					  GFP_KERNEL);
...

	/* set other members from the base header of pdu */
	priv->urb->context                = (void *) priv;
	priv->urb->dev                    = udev;
	priv->urb->pipe                   = pipe;
	priv->urb->complete               = stub_complete;

	usbip_pack_pdu(pdu, priv->urb, USBIP_CMD_SUBMIT, 0);


	if (usbip_recv_xbuff(ud, priv->urb) < 0)
		return;

	if (usbip_recv_iso(ud, priv->urb) < 0)
		return;

	/* no need to submit an intercepted request, but harmless? */
	tweak_special_requests(priv->urb);

	masking_bogus_flags(priv->urb);
  
	/* urb is now ready to submit */
	ret = usb_submit_urb(priv->urb, GFP_KERNEL);
...
}

         首先它是通过usb_alloc_urb分配好一个空白的urb对象,当然iso等时传输和其他诸如bulk传输等是不一样的,iso可以用同一个urb对象发好几个packets。然后根据usbip协议得到的transfer_buffer_length长度分配usb通信数据缓冲区,以及分配固定8字节的用于控制传输的setup_packet,然后关键的来了,priv->urb->complete = stub_complete;注册回调函数,最后通过我们的好朋友usb_submit_urb()提交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 - completion handler of a usbip urb
 * @urb: pointer to the urb completed
 *
 * When a urb has completed, the USB core driver calls this function mostly in
 * the interrupt context. To return the result of a urb, the completed urb is
 * linked to the pending list of returning.
 *
 */
void stub_complete(struct urb *urb)
{
	struct stub_priv *priv = (struct stub_priv *) urb->context;
	struct stub_device *sdev = priv->sdev;
	unsigned long flags;

	usbip_dbg_stub_tx("complete! status %d\n", urb->status);

	switch (urb->status) {
	case 0:
		/* OK */
		break;
	case -ENOENT:
		dev_info(&urb->dev->dev,
			 "stopped by a call to usb_kill_urb() because of cleaning up a virtual connection\n");
		return;
	case -ECONNRESET:
		dev_info(&urb->dev->dev,
			 "unlinked by a call to usb_unlink_urb()\n");
		break;
	case -EPIPE:
		dev_info(&urb->dev->dev, "pipe = %#x, %s endpoint %d is stalled\n",
			 urb->pipe, usb_pipein(urb->pipe)? "IN":"OUT", usb_pipeendpoint(urb->pipe));
		break;
	case -ESHUTDOWN:
		dev_info(&urb->dev->dev, "device removed?\n");
		break;
	default:
		dev_info(&urb->dev->dev,
			 "urb completion with non-zero status %d\n",
			 urb->status);
		break;
	}

	/* link a urb to the queue of tx. */
	spin_lock_irqsave(&sdev->priv_lock, flags);
	if (sdev->ud.tcp_socket == NULL) {
		usbip_dbg_stub_tx("ignore urb for closed connection %p", urb);
		/* It will be freed in stub_device_cleanup_urbs(). */
	} else if (priv->unlinking) {
		stub_enqueue_ret_unlink(sdev, priv->seqnum, urb->status);
		stub_free_priv_and_urb(priv);
	} else {
		list_move_tail(&priv->list, &sdev->priv_tx);
	}
	spin_unlock_irqrestore(&sdev->priv_lock, flags);

	/* wake up tx_thread */
	wake_up(&sdev->tx_waitq);
}

        上面的stub_complete()函数没有删减,但不打算细讲,主要功能就是将承载有urb的链表节点(node)pop出去,然后改为放到发送链表priv_tx中,用于就绪从usbip-host发送到vhci-hcd,最后调用wake_up(&sdev->tx_waitq)来唤醒发送线程,进行tcp组包和发送。这样就完成了urb从vhci_hcd到usbip-host的处理过程(当然这里只是tcp发出去,真正完成整个过程在vhci_hcd那边,毕竟那边还有收尾要跟,自行阅读我前面的文章)。在完成函数的urb包含有状态(urb->status)、u盘传来的数据(urb->transfer_buffer),U盘数据的实际长度(urb->actual_length),以及ISO传输相关的等等。

urb的状态(urb->status)意义参考:
usb_submit_urb的完成函数中通过URB结构体的status成员可以获知其原因,如0表示传输成功,
-ENOENT表示被usb_kill_urb()杀死,
-ECONNRESET表示被usb_unlink_urb()杀死,
-EPROTO表示传输中发生了bitstuff错误或者硬件未能及时收到响应数据包,
-ENODEV表示USB设备已被移除,
-EXDEV表示等时传输仅完成了一部分等。

        上面的stub_recv_cmd_submit()函数中,我漏了两个重要的函数没有讲解,tweak_special_requests(priv->urb)和masking_bogus_flags(priv->urb),前者则主要用于利用usb core的接口处理一些“标准请求”,如clear_halt、set_interface和 set_configuration等。譬如有些U盘不支持一些SCSI命令,U盘的IN端点可能会halt,这时候client端(安装了vhci-hcd驱动的PC端)的U盘驱动就会根据bulk only协议的CSW状态返回知道这一情况,于是对0端点下发clear_halt控制传输,以清u盘的halt标志,否则u盘将无法工作,等urb通过tcp传到usbip-host驱动后,就是在tweak_special_requests()处理清halt的:

/*
 * clear_halt, set_interface, and set_configuration require special tricks.
 */
static void tweak_special_requests(struct urb *urb)
{
	if (!urb || !urb->setup_packet)
		return;

	if (usb_pipetype(urb->pipe) != PIPE_CONTROL)
		return;

	if (is_clear_halt_cmd(urb)){
		/* tweak clear_halt */
		printk("---is_clear_halt_cmd\n");
		tweak_clear_halt_cmd(urb);
	}

	else if (is_set_interface_cmd(urb)){
		/* tweak set_interface */
		printk("---is_set_interface_cmd\n");
		tweak_set_interface_cmd(urb);
	}
	
	else if (is_set_configuration_cmd(urb)){
		/* tweak set_configuration */
		printk("---is_set_configuration_cmd\n");
		tweak_set_configuration_cmd(urb);
	}
	
	else if (is_reset_device_cmd(urb)){
		printk("---is_reset_device_cmd\n");
		tweak_reset_device_cmd(urb);
	}
	else
		usbip_dbg_stub_rx("no need to tweak\n");
}

而,后者是为了合理填充urb->transfer_flags成员变量:

static void masking_bogus_flags(struct urb *urb)
{
	int				xfertype;
	struct usb_device		*dev;
	struct usb_host_endpoint	*ep;
	int				is_out;
	unsigned int	allowed;

	if (!urb || urb->hcpriv || !urb->complete)
		return;
	dev = urb->dev;
	if ((!dev) || (dev->state < USB_STATE_UNAUTHENTICATED))
		return;

	ep = (usb_pipein(urb->pipe) ? dev->ep_in : dev->ep_out)
		[usb_pipeendpoint(urb->pipe)];
	if (!ep)
		return;

	xfertype = usb_endpoint_type(&ep->desc);
	if (xfertype == USB_ENDPOINT_XFER_CONTROL) {
		struct usb_ctrlrequest *setup =
			(struct usb_ctrlrequest *) urb->setup_packet;

		if (!setup)
			return;
		is_out = !(setup->bRequestType & USB_DIR_IN) ||
			!setup->wLength;
	} else {
		is_out = usb_endpoint_dir_out(&ep->desc);
	}

	/* enforce simple/standard policy */
	allowed = (URB_NO_TRANSFER_DMA_MAP | URB_NO_INTERRUPT |
		   URB_DIR_MASK | URB_FREE_BUFFER);
	switch (xfertype) {
	case USB_ENDPOINT_XFER_BULK:
		if (is_out)
			allowed |= URB_ZERO_PACKET;
		/* FALLTHROUGH */
	default:			/* all non-iso endpoints */
		if (!is_out)
			allowed |= URB_SHORT_NOT_OK;
		break;
	case USB_ENDPOINT_XFER_ISOC:
		allowed |= URB_ISO_ASAP;
		break;
	}
	urb->transfer_flags &= allowed;
}

        我们发现无论vhci-hcd或者是本文的usbip-host驱动都没有讲解与unlink相关的代码,各位有兴趣可以阅读,流程类似的。unlink的urb是U盘驱动下发int usb_unlink_urb(struct urb *urb)、void usb_kill_urb(struct urb *urb)等等时发出的,unlink通常是出现在卸载驱动前,回收之前通过usb_submit_urb但还没有complete的urb,或者出现异常了,需要kill调之前的urb等等情况。

        终于讲解完usbip的全部驱动了,usbip驱动分析暂告一段落!

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值