linux usb usbip驱动详解(五)

      继续讲解vhci-hcd驱动。

      上一篇文章讲了vhci-hcd的初始化流程,本文讲解usbip attach -r -b 时驱动做了什么内容。

      我们知道vhci_start()函数的最后,注册了sysfs的用户界面,用于配置vhci-hcd驱动,我们直接阅读drivers/usb/usbip/vhci_sysfs.c的attach的操作:

/* Sysfs entry to establish a virtual connection */
/*
 * To start a new USB/IP attachment, a userland program needs to setup a TCP
 * connection and then write its socket descriptor with remote device
 * information into this sysfs file.
 *
 * A remote device is virtually attached to the root-hub port of @rhport with
 * @speed. @devid is embedded into a request to specify the remote device in a
 * server host.
 *
 * write() returns 0 on success, else negative errno.
 */
static ssize_t attach_store(struct device *dev, struct device_attribute *attr,
			    const char *buf, size_t count)
{
	struct socket *socket;
	int sockfd = 0;
	__u32 port = 0, pdev_nr = 0, rhport = 0, devid = 0, speed = 0;
	struct usb_hcd *hcd;
	struct vhci_hcd *vhci_hcd;
	struct vhci_device *vdev;
	struct vhci *vhci;

	/*
	 * @rhport: port number of vhci_hcd
	 * @sockfd: socket descriptor of an established TCP connection
	 * @devid: unique device identifier in a remote host
	 * @speed: usb device speed in a remote host
	 */
	if (sscanf(buf, "%u %u %u %u", &port, &sockfd, &devid, &speed) != 4)
		return -EINVAL;
  ...
	rhport = port_to_rhport(port);
  ...

	hcd = platform_get_drvdata(vhcis->pdev);
  ...
  
	vhci_hcd = hcd_to_vhci_hcd(hcd);
	vhci = vhci_hcd->vhci;

	vdev = &vhci->vhci_hcd_hs->vdev[rhport];

	/* Extract socket from fd. */
	socket = sockfd_lookup(sockfd, &err);
  ..
  
	/* now need lock until setting vdev status as used */

	vdev->devid         = devid;
	vdev->speed         = speed;
	vdev->ud.sockfd     = sockfd;
	vdev->ud.tcp_socket = socket;
	vdev->ud.status     = VDEV_ST_NOTASSIGNED;
  ...
  
	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");

	rh_port_connect(vdev, speed);

	return count;
}

       为了方便描述,删除了代码中的错误处理、自旋锁初始化和usb3.0部分以及默认只有一个hcd控制器等。可以看出从上层(用户态)得到buf字符串数据,使用sscanf格式化后初始化虚拟root hub的端口(usb hub有多个USB口)、usb速度、socket描述符(句柄)以及设备id(devid)。

static inline __u32 port_to_rhport(__u32 port)
{
	return port % VHCI_HC_PORTS;
}

      因为vhci-hcd被设计为最多支持VHCI_HC_PORTS(8)个端口。

      socket = sockfd_lookup(sockfd, &err);则是将用户态下的socket句柄转换成内核适用的socket描述符,供内核调用kernel_recvmsg和kernel_sendmsg时使用。因为前面说过,tcp的建立连接是在应用层做的,底层驱动只是使用已经打开了的socket链路。
      然后使用kthread_get_run()创建了两个内核线程vhci_rx_loop和vhci_tx_loop,在shell中使用top命令能看到进程名称为"vhci_rx"和"vhci_tx"。这两个内核线程很重要,基本上USBIP_CMD_SUBMIT、USBIP_RET_SUBMIT、USBIP_CMD_UNLINK和USBIP_RET_UNLINK命令都是靠这两个线程处理。下文会专门分析这两个线程。

      最后是调用rh_port_connect(vdev, speed);这个函数功能十分重要,就是前文说的“踢一下vhci-hcd虚拟出来的主机控制器的root hub,让hub.c以为有真实的usb设备插入”:

void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed)
{
	struct vhci_hcd	*vhci_hcd = vdev_to_vhci_hcd(vdev);
	struct vhci *vhci = vhci_hcd->vhci;
	int		rhport = vdev->rhport;
	u32		status;
	unsigned long	flags;

	usbip_dbg_vhci_rh("rh_port_connect %d\n", rhport);

	spin_lock_irqsave(&vhci->lock, flags);

	status = vhci_hcd->port_status[rhport];

	status |= USB_PORT_STAT_CONNECTION | (1 << USB_PORT_FEAT_C_CONNECTION);

	switch (speed) {
	case USB_SPEED_HIGH:
		status |= USB_PORT_STAT_HIGH_SPEED;
		break;
	case USB_SPEED_LOW:
		status |= USB_PORT_STAT_LOW_SPEED;
		break;
	default:
		break;
	}

	vhci_hcd->port_status[rhport] = status;

	spin_unlock_irqrestore(&vhci->lock, flags);

	usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci_hcd));
}

      该函数主要是设置了一下状态status |= USB_PORT_STAT_CONNECTION | (1 << USB_PORT_FEAT_C_CONNECTION) | USB_PORT_STAT_HIGH_SPEED,最后调用usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci_hcd));进行触发。usb_hcd_poll_rh_status是usb core的接口,我们不去分析,作用就是刚刚说的“踢一下hcd的root bub”,此时就会去回调struct hc_driver vhci_hc_driver的.hub_status_data,对于vhci-hcd实例为vhci_hub_status():

/*
 * Returns 0 if the status hasn't changed, or the number of bytes in buf.
 * Ports are 0-indexed from the HCD point of view,
 * and 1-indexed from the USB core pointer of view.
 *
 * @buf: a bitmap to show which port status has been changed.
 *  bit  0: reserved
 *  bit  1: the status of port 0 has been changed.
 *  bit  2: the status of port 1 has been changed.
 *  ...
 */
static int vhci_hub_status(struct usb_hcd *hcd, char *buf)
{
	struct vhci_hcd	*vhci_hcd = hcd_to_vhci_hcd(hcd);
	struct vhci *vhci = vhci_hcd->vhci;
	int		retval = DIV_ROUND_UP(VHCI_HC_PORTS + 1, 8);
	int		rhport;
	int		changed = 0;
	unsigned long	flags;

	memset(buf, 0, retval);

	spin_lock_irqsave(&vhci->lock, flags);
	if (!HCD_HW_ACCESSIBLE(hcd)) {
		usbip_dbg_vhci_rh("hw accessible flag not on?\n");
		goto done;
	}

	/* check pseudo status register for each port */
	for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) {
		if ((vhci_hcd->port_status[rhport] & PORT_C_MASK)) {
			/* The status of a port has been changed, */
			usbip_dbg_vhci_rh("port %d status changed\n", rhport);

			buf[(rhport + 1) / 8] |= 1 << (rhport + 1) % 8;
			changed = 1;
		}
	}

	if ((hcd->state == HC_STATE_SUSPENDED) && (changed == 1))
		usb_hcd_resume_root_hub(hcd);

done:
	spin_unlock_irqrestore(&vhci->lock, flags);
	return changed ? retval : 0;
}

      其实作用就是把virtual root hub的端口号上报给“hcd框架”,hcd框架就会回调struct hc_driver vhci_hc_driver的.hub_control,对于我们vhci-hcd实例就是vhci_hub_control()函数:

hub.c的hub_event -> hub_port_init -> usb_control_msg -> usb_alloc_urb被执行,调用usb_submit_urb后最终会回调vhci_urb_enqueue()
上面的流程描述了hub.c检测到设备插入后,做什么。

紧跟着,对于usb_submit_urb则有这样的流程(主要关注“根hub”的urb路径):
usb_submit_urb
  ->usb_hcd_submit_urb(urb, mem_flags);
    ->rh_urb_enqueue //如果判断是根hub,即is_root_hub() is true就走这个分支
      ->rh_call_control 
        ->hub_control回调 //对于vhci-hcd为vhci_hub_control
   |->status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);//不是root hub,而是诸如U盘驱动、usb-skeleton.c等接口驱动产生的urb时就发送到enqueue

      vhci_hub_control()函数比较复杂!目的是回应“hcd框架”针对virtual root hub端口号检测到有设备插入的这件事作出反馈,譬如根据下发hub特有的请求回应相应数据,或者假装设置标志等等,譬如:

        GetHubDescriptor
        DeviceRequest | USB_REQ_GET_DESCRIPTOR
        GetHubStatus
        GetPortStatus
        SetPortFeature等等hub请求,这样才能模拟出一个“接近真实usb hub”的hub,不然“hcd框架”会看出端倪,就虚拟不了hcd了。具体不分析了,有兴趣的读者可以研究一下,因为分析这个必然会牵涉到usb core中hcd.c和hub.c这两块硬骨头,我还是回归主题——继续vhci-hcd驱动本身。

      有个问题,就是当root hub检测到有usb设备插入,最后是怎么加载U盘驱动的?我做了简单的代码走读,通过枚举到的PID/VID信息匹配到U盘驱动或者HID键鼠驱动等,就回调相应驱动的probe驱动入口了,最后就能看到/dev/sda或者/dev/input/even0了:

rh_port_connect
  ->usb_hcd_poll_rh_status //hcd.c
    ->hcd->driver->hub_status_data(hcd, buffer)//vhci_hub_status
    ->usb_hcd_unlink_urb_from_ep(hcd, urb);
    ->usb_hcd_giveback_urb(hcd, urb, 0)
         ->usb_giveback_urb_bh();//tasklet_hi_schedule(&bh->bh);
            ->__usb_hcd_giveback_urb(urb);
              ->urb->complete(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);//枚举完毕后加载设备驱动

device_add函数会出发总线的通知链发送通知,最终会调用总线的match方法
usb设备和驱动一旦match,则会调用驱动的drvwrap.driver.probe方法:
若是设备则通过driver.c的usb_register_device_driver函数调用usb_probe_device方法
若是接口则通过driver.c的usb_register_driver函数调用usb_probe_interface方法
假设是U盘接入,则调用mass_storage驱动的probe,并在probe中使用usb_alloc_urb分配urb,最后usb_submit_urb提交urb。
此时会进入绑定mass_storage驱动的hcd的urb_enqueue,由于此处是vhci-hcd,故会进入vhci_urb_enqueue()回调。     

      最重要的函数到了!

      是struct hc_driver vhci_hc_driver的.urb_enqueue回调。即vhci_urb_enqueue(),我们在前面的文章也简单用文字描述过,就是U盘驱动或者USB骨架驱动(usb-skeleton.c)在读写设备时,离不开urb的创建和提交(usb_submit_urb):

static inline void usb_fill_bulk_urb(struct urb *urb,
				     struct usb_device *dev,
				     unsigned int pipe,
				     void *transfer_buffer,
				     int buffer_length,
				     usb_complete_t complete_fn,
				     void *context)
{
	urb->dev = dev;
	urb->pipe = pipe;
	urb->transfer_buffer = transfer_buffer;
	urb->transfer_buffer_length = buffer_length;
	urb->complete = complete_fn;
	urb->context = context;
}

//简单举例,摘自usb-skeleton.c
        urb = usb_alloc_urb(0, GFP_KERNEL);
	...
	usb_fill_bulk_urb(urb, dev->udev,... );
	urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
        ...
	retval = usb_submit_urb(urb, GFP_KERNEL);

usb_submit_urb()是异步函数,usb_fill_bulk_urb()有注册完成函数,urb提交后usb_submit_urb()立马返回,usb设备处理完后usb core就会回调urb->complete()。

      跟踪usb_submit_urb()代码,我们发现urb最终是去到hcd驱动注册的.urb_enqueue,对于我们vhci-hcd就是static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags)了,看到struct urb *urb参数没?这个urb就是从上层驱动(usb-skeleton.c或者U盘驱动、hid键鼠驱动等)传下来的。也就是说该urb包含了usb通信数据,我们要做usb共享(透传),第一步就是截获上层驱动的usb通信数据,通过tcp发送出去。

      vhci_urb_enqueue()函数里比较关键的函数是:usb_hcd_link_urb_to_ep(hcd, urb)和vhci_tx_urb(urb, vdev),usb_hcd_link_urb_to_ep()是usb core的api,目的是hcd主机控制器把urb放入端点队列中,等待底层发送。而vhci_tx_urb()则是通过分配struct vhci_priv实例,填充urb并加入到priv_tx队列,最后唤醒发送线程(就是上文说的vhci_tx_loop())进行发送。发送线程这边则通过dequeue_from_priv_tx从priv_tx队列提取出urb,并插入到另外一个priv_rx队列中,
最后将urb通过IP发送(USBIP_CMD_SUBMIT)。细节自行阅读代码,尤其是怎么从urb获取到有用数据,然后填充到USBIP_CMD_SUBMIT命令的字段中。

      接收线程(就是上文说的vhci_rx_loop())则通过pickup_urb_and_free_priv,从priv_rx队列中提取符合帧序号的urb(发送时和接收的均使用同一个urb对象,所以要利用seqnum来找回之前发送时用的那个urb,类似于“回话ID”的概念),并删除在priv_rx的节点。紧跟着,通过socket对urb进行接收,接收完成后,调用usb_hcd_unlink_urb_from_ep()usb_hcd_giveback_urb()将urb从usb core归还给设备驱动,完成一次urb的处理:

static void vhci_recv_ret_submit(struct vhci_device *vdev,
				 struct usbip_header *pdu)
{	
...
...
    spin_lock_irqsave(&vhci->lock, flags);
	usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci_hcd), urb);
	spin_unlock_irqrestore(&vhci->lock, flags);

	usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci_hcd), urb, urb->status);

	usbip_dbg_vhci_rx("Leave\n");
}

归还后,usb core就会回调由U盘驱动、usb-skeleton.c等所注册的完成函数(urb->complete())。

      因为urb的发送和接收过程基本上使用“生产者消费者模式”,就以文字描述,不具体分析代码了,读者可自行阅读。注意linux内核的链表操作,譬如list_move_tail()是将一个urb对象从一个发送队列priv_tx中取下来,然后放到接收队列priv_rx里。

      另外,当处于控制传输阶段(使用0号端点),而且设备地址为0时,vhci_urb_enqueue()中对在这个阶段的特殊的“标准请求”(如用于分配usb设备地址的USB_REQ_SET_ADDRESS请求等)并没有发送到远端的server,而是直接本地处理掉了(goto no_need_xmit),马上归还urb给U盘驱动:

/*
 * The enumeration process is as follows;
 *
 *  1. Get_Descriptor request to DevAddrs(0) EndPoint(0)
 *     to get max packet length of default pipe
 *
 *  2. Set_Address request to DevAddr(0) EndPoint(0)
 *
 */
if (usb_pipedevice(urb->pipe) == 0) {
...
...
        switch (ctrlreq->bRequest) {
		case USB_REQ_SET_ADDRESS:
			/* set_address may come when a device is reset */
			dev_info(dev, "SetAddress Request (%d) to port %d\n",
				 ctrlreq->wValue, vdev->rhport);

			usb_put_dev(vdev->udev);
			vdev->udev = usb_get_dev(urb->dev);

			spin_lock(&vdev->ud.lock);
			vdev->ud.status = VDEV_ST_USED;
			spin_unlock(&vdev->ud.lock);

			if (urb->status == -EINPROGRESS) {
				/* This request is successfully completed. */
				/* If not -EINPROGRESS, possibly unlinked. */
				urb->status = 0;
			}

			goto no_need_xmit;

		case USB_REQ_GET_DESCRIPTOR:
			if (ctrlreq->wValue == cpu_to_le16(USB_DT_DEVICE << 8))
				usbip_dbg_vhci_hc(
					"Not yet?:Get_Descriptor to device 0 (get max pipe size)\n");

			usb_put_dev(vdev->udev);
			vdev->udev = usb_get_dev(urb->dev);
			goto out;

		default:
			/* NOT REACHED */
			dev_err(dev,
				"invalid request to devnum 0 bRequest %u, wValue %u\n",
				ctrlreq->bRequest,
				ctrlreq->wValue);
			ret =  -EINVAL;
			goto no_need_xmit;
...
...
...
no_need_xmit:
	usb_hcd_unlink_urb_from_ep(hcd, urb);
no_need_unlink:
	spin_unlock_irqrestore(&vhci->lock, flags);
	if (!ret)
		usb_hcd_giveback_urb(hcd, urb, urb->status);
	return ret;
		}

        也能理解,毕竟我们操作的,其实是远端的U盘,事实上,远端的U盘在插入到它自己的hcd主机控制器时,就经历了“分配usb设备地址”的阶段,无需再下发该请求到远端了。

        最后总结一下,usb_hcd_link_urb_to_ep()usb_hcd_unlink_urb_from_ep()/usb_hcd_giveback_urb()才是关键!当然,还有urb取出数据填充到USBIP_CMD_SUBMIT命令字段,以及从socket接收到USBIP_RET_SUBMIT的命令字段后怎么填充回urb也有细节需要注意

        下一篇文章讲解usbip-host.ko驱动的源码分析。

 

你可能感兴趣的:(Linux驱动,总线协议)