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。

/**
 * 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的處理過程。

        上面的stub_recv_cmd_submit()函數中,我漏了兩個重要的函數沒有講解,tweak_special_requests(priv->urb)和masking_bogus_flags(priv->urb),前者則主要用於利用usb core的接口處理一些“標準請求”,如clear_halt、set_interface和 set_configuration等。譬如有些U盤不支持windows的一些SCSI命令,U盤的IN端點可能會halt,這時候就會對0端點下發控制傳輸:clear_halt,這時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;
}

        終於講解完usbip的全部驅動了,usbip驅動分析暫告一段落!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章