我们开始讲解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
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驱动分析暂告一段落!