我們開始講解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驅動分析暫告一段落!