linux usb usbip驅動詳解(四)

      我們先講解vhci-hcd驅動(linux-4.20.14的usbip驅動)。

      usb主機控制器驅動hcd學習心得:可以閱讀某款SOC的主機控制器驅動代碼,譬如TI的am3358芯片,可以看musb驅動代碼(drivers/usb/musb/musb_host.c),或者閱讀虛擬主機控制器(代碼在drivers/usb/gadget/udc/dummy_hcd.c),該虛擬USB主機控制器驅動內還包含一個虛擬USB設備控制器驅動(dummy_udc)供寫gadget驅動時測試使用。但無論你閱讀hcd、udc還是gadget都好,都要閱讀usb core代碼。

      vhci_hcd是一個platform驅動:

static struct platform_driver vhci_driver = {
    .probe    = vhci_hcd_probe,
    .remove    = vhci_hcd_remove,
    .suspend = vhci_hcd_suspend,
    .resume    = vhci_hcd_resume,
    .driver    = {
        .name = driver_name,
    },
};
...
...
ret = platform_driver_register(&vhci_driver);
//爲了方便說明,代碼做了簡化,vhci_num_controllers固定設爲1,錯誤處理忽略
static int __init vhci_hcd_init(void)
{
  int i, ret;
  if (usb_disabled())
   return -ENODEV;

  vhci_num_controllers = 1;

  vhcis = kcalloc(vhci_num_controllers, sizeof(struct vhci), GFP_KERNEL);
  if (vhcis == NULL)
    return -ENOMEM;

  vhcis->pdev = platform_device_alloc(driver_name, i);
  if (!vhcis->pdev) {
    ...
  }

  ret = platform_device_add_data(vhcis->pdev, &vhcis, sizeof(void *));
  ...
  
  ret = platform_driver_register(&vhci_driver);
  ...
  
  ret = platform_device_add(vhcis->pdev);
	...

	return ret;
}

      爲了方便說明,vhci_hcd_init()代碼做了簡化,vhci_num_controllers固定設爲1,錯誤處理忽略。但platform驅動不是本文的重點,我們可以認爲,在vhci_hcd_init()中先malloc一塊內存,用來存儲struct vhci結構,該結構是記錄了本驅動平臺設備指針(struct platform_device)和本驅動的重要數據結構指針(struct vhci_hcd),用於相互關聯,便於利用container_of()找到對方:

struct vhci {
	spinlock_t lock;

	struct platform_device *pdev;

	struct vhci_hcd *vhci_hcd_hs;
	struct vhci_hcd *vhci_hcd_ss;
};

        因爲struct vhci結構是創建“平臺設備”(platform_device_alloc())時添加進去作爲“私有數據”(platform_device_add_data)的,所以能很方便與平臺驅動關聯起來,有現成接口獲取平臺私有數據。底層platform驅動框架會利用driver_name進行匹配(match),找到具體的平臺驅動實例,也就是我們的vhci-hcd驅動,最後框架會回調我們定義的vhci_hcd_probe(struct platform_device *pdev),其中參數pdev就是我們自己在vhci_hcd_init()中分配的,從pdev->dev我們可以很輕易拿到struct vhci結構體指針,這一步就是“從平臺設備找struct vhci對象”,反過來更容易,struct vhci結構本身包含平臺設備指針pdev,初始化時填充進去就ok了。Linux內核是面向對象的,實例(對象)不止一個,而且是分層的,有很多core層或者框架層,經常被“某驅動框架”回調,所以閱讀linux內核代碼最主要的是搞清楚各個結構體指針究竟是從哪裏分配的,到哪裏去,怎麼根據這個結構體找另外一個關聯的結構體等。

       我們從入口函數vhci_hcd_probe()開始分析:

static int vhci_hcd_probe(struct platform_device *pdev)
{
	struct vhci             *vhci = *((void **)dev_get_platdata(&pdev->dev));
	struct usb_hcd		*hcd_hs;
	struct usb_hcd		*hcd_ss;
	int			ret;

	usbip_dbg_vhci_hc("name %s id %d\n", pdev->name, pdev->id);

	/*
	 * Allocate and initialize hcd.
	 * Our private data is also allocated automatically.
	 */
	hcd_hs = usb_create_hcd(&vhci_hc_driver, &pdev->dev, dev_name(&pdev->dev));
	if (!hcd_hs) {
		pr_err("create primary hcd failed\n");
		return -ENOMEM;
	}
	hcd_hs->has_tt = 1;

	/*
	 * Finish generic HCD structure initialization and register.
	 * Call the driver's reset() and start() routines.
	 */
	ret = usb_add_hcd(hcd_hs, 0, 0);
	if (ret != 0) {
		pr_err("usb_add_hcd hs failed %d\n", ret);
		goto put_usb2_hcd;
	}
        
        //USB super speed本文不關注,有興趣自己閱讀linux-4.20.14源碼
	hcd_ss = usb_create_shared_hcd(&vhci_hc_driver, &pdev->dev,
				       dev_name(&pdev->dev), hcd_hs);
	
	...
        ...
}

usbip_dbg_vhci_hc()是用於調試使用的,可在make menuconfig時開啓usbip的debug功能,開啓了之後,會影響usb數據傳輸的速度,僅作調試使用。

hcd_hs = usb_create_hcd(&vhci_hc_driver, &pdev->dev, dev_name(&pdev->dev));

這個就是hcd的重要接口。用於註冊一個USB主機控制器驅動實例:

static const struct hc_driver vhci_hc_driver = {
	.description	= driver_name,
	.product_desc	= driver_desc,
	.hcd_priv_size	= sizeof(struct vhci_hcd),

	.flags		= HCD_USB3 | HCD_SHARED,

	.reset		= vhci_setup,
	.start		= vhci_start,
	.stop		= vhci_stop,

	.urb_enqueue	= vhci_urb_enqueue,
	.urb_dequeue	= vhci_urb_dequeue,

	.get_frame_number = vhci_get_frame_number,

	.hub_status_data = vhci_hub_status,
	.hub_control    = vhci_hub_control,
	.bus_suspend	= vhci_bus_suspend,
	.bus_resume	= vhci_bus_resume,

	.alloc_streams	= vhci_alloc_streams,
	.free_streams	= vhci_free_streams,
};

因爲框架已經有了,我們的工作歸結爲:“爲struct hc_driver對象註冊回調函數”,以實現多態,不同實例有不同實現。

我們只關注vhci-hcd使用到的、比較重要的成員函數和成員變量:

.flags        = HCD_USB3 | HCD_SHARED代表vhci-hcd支持usb3.0設備,同時指定usb2.0和usb3.x的兩個hcd使用相同的硬件。因爲此處(上面的vhci_hcd_probe)定義了兩個hcd:/* Each VHCI has 2 hubs (USB2 and USB3), each has VHCI_HC_PORTS ports */

hcd_hs = usb_create_hcd(&vhci_hc_driver, &pdev->dev, dev_name(&pdev->dev));
...
hcd_ss = usb_create_shared_hcd(&vhci_hc_driver, &pdev->dev,
				       dev_name(&pdev->dev), hcd_hs);

hcd_hs->has_tt = 1;開啓root hub的TT(Integrated TT),TT應該是高速HUB也能支持傳輸低速和全速的usb設備,詳情閱讀CH11章節,看hub底層數據包的傳輸原理。

      很例牌,調用完usb_add_hcd()後,系統就認爲存在一個主機控制器了,我主要講usb2.0部分(hcd_hs),usb3.0部分有興趣的朋友自行閱讀代碼。

      我們發現整個vhci_hcd_probe函數都沒有看到創建的struct usb_hcd  *hcd_hs保存到哪,畢竟我們後面肯定會用到這個usb主機控制器驅動,但怎麼找回來呢?它沒有單純使用全局變量保存,而是利用了平臺驅動的“私有數據”來傳遞,以後用到struct usb_hcd對象也能找回來。它是在usb_create_hcd()中調用了dev_set_drvdata(dev, hcd);

static inline void dev_set_drvdata(struct device *dev, void *data)
{
	dev->driver_data = data;
}

      放到平臺驅動的driver_data中了,到時可以根據struct vhci找到平臺設備(struct platform_device *pdev),就能找到對應的平臺驅動,最後找到struct usb_hcd ,或者反過來,即可以從struct usb_hcd找到struct vhci指針,container_of()功能強大。

      我們繼續struct hc_driver結構體的其他成員說明,看usb_add_hcd()的官方註釋可以知道,reset和start這個成員函數會在usb_add_hcd()中被回調,用於基本的初始化工作。其中,reset比start先執行。

/**
 * usb_add_hcd - finish generic HCD structure initialization and register
 * @hcd: the usb_hcd structure to initialize
 * @irqnum: Interrupt line to allocate
 * @irqflags: Interrupt type flags
 *
 * Finish the remaining parts of generic HCD initialization: allocate the
 * buffers of consistent memory, register the bus, request the IRQ line,
 * and call the driver's reset() and start() routines.
 * If it is an OTG device then it only registers the HCD with OTG core.
 *
 */
int usb_add_hcd(struct usb_hcd *hcd,
		unsigned int irqnum, unsigned long irqflags)

因爲框架(drivers/usb/core/hcd.c)比較複雜,就不跟進去usb_add_hcd()分析了,有興趣的朋友可以深入研究。

      那對於vhci-hcd驅動實例,它的reset和start究竟做了什麼呢?下面我們來一一分析。我們先看reset回調:

static int vhci_setup(struct usb_hcd *hcd)
{
	struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller));
	if (usb_hcd_is_primary_hcd(hcd)) {
		vhci->vhci_hcd_hs = hcd_to_vhci_hcd(hcd);
		vhci->vhci_hcd_hs->vhci = vhci;
		/*
		 * Mark the first roothub as being USB 2.0.
		 * The USB 3.0 roothub will be registered later by
		 * vhci_hcd_probe()
		 */
		hcd->speed = HCD_USB2;
		hcd->self.root_hub->speed = USB_SPEED_HIGH;
	} else {
		...
	}
	return 0;
}

      上面也說了,struct vhci結構可以由struct usb_hcd找到,struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller));

我們主要看primary_hcd那個分支,因爲從vhci_hcd_probe()中我們知道hcd_hs是作爲primary_hcd,而hcd_ss作爲shared_hcd的,usb3.0我們不看。

      這裏有個比較有意思的函數hcd_to_vhci_hcd():

static inline struct vhci_hcd *hcd_to_vhci_hcd(struct usb_hcd *hcd)
{
	return (struct vhci_hcd *) (hcd->hcd_priv);
}

其中hcd_priv在struct usb_hcd中是一個0數組:

/* The HC driver's private data is stored at the end of
	 * this structure.
	 */
	unsigned long hcd_priv[0]
			__attribute__ ((aligned(sizeof(s64))));

早在vhci_hcd_probe()中註冊vhci_hc_driver(.hcd_priv_size    = sizeof(struct vhci_hcd))時,就已經分配好空間了。所以在vhci_setup()被回調時,是可以訪問該空間的,在這裏初始化了vhci_hcd_hs(struct vhci_hcd結構):

vhci->vhci_hcd_hs = hcd_to_vhci_hcd(hcd);
vhci->vhci_hcd_hs->vhci = vhci;

這樣就關聯起來了struct vhci結構中的vhci_hcd_hs,同時也關聯了struct vhci_hcd結構與struct vhci結構,方便後續相互找對方。

    ok,我們再來看start回調:

static void vhci_device_init(struct vhci_device *vdev)
{
	memset(vdev, 0, sizeof(struct vhci_device));

	vdev->ud.side   = USBIP_VHCI;
	vdev->ud.status = VDEV_ST_NULL;
	spin_lock_init(&vdev->ud.lock);

	INIT_LIST_HEAD(&vdev->priv_rx);
	INIT_LIST_HEAD(&vdev->priv_tx);
	INIT_LIST_HEAD(&vdev->unlink_tx);
	INIT_LIST_HEAD(&vdev->unlink_rx);
	spin_lock_init(&vdev->priv_lock);

	init_waitqueue_head(&vdev->waitq_tx);

	vdev->ud.eh_ops.shutdown = vhci_shutdown_connection;
	vdev->ud.eh_ops.reset = vhci_device_reset;
	vdev->ud.eh_ops.unusable = vhci_device_unusable;

	usbip_start_eh(&vdev->ud);
}
...
...
static int vhci_start(struct usb_hcd *hcd)
{
	struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd);
	int id, rhport;
	int err;

	usbip_dbg_vhci_hc("enter vhci_start\n");

	if (usb_hcd_is_primary_hcd(hcd))
		spin_lock_init(&vhci_hcd->vhci->lock);

	/* initialize private data of usb_hcd */

	for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) {
		struct vhci_device *vdev = &vhci_hcd->vdev[rhport];

		vhci_device_init(vdev);
		vdev->rhport = rhport;
	}

	atomic_set(&vhci_hcd->seqnum, 0);

	hcd->power_budget = 0; /* no limit */
	hcd->uses_new_polling = 1;

#ifdef CONFIG_USB_OTG
	hcd->self.otg_port = 1;
#endif

	id = hcd_name_to_id(hcd_name(hcd));
	if (id < 0) {
		pr_err("invalid vhci name %s\n", hcd_name(hcd));
		return -EINVAL;
	}

	/* vhci_hcd is now ready to be controlled through sysfs */
	if (id == 0 && usb_hcd_is_primary_hcd(hcd)) {
		err = vhci_init_attr_group();
		if (err) {
			pr_err("init attr group\n");
			return err;
		}
		err = sysfs_create_group(&hcd_dev(hcd)->kobj, &vhci_attr_group);
		if (err) {
			pr_err("create sysfs files\n");
			vhci_finish_attr_group();
			return err;
		}
		pr_info("created sysfs %s\n", hcd_name(hcd));
	}

	return 0;
}

      vhci_start()函數主要是繼續初始化本驅動的兩個重要結構體struct vhci_hcd和struct vhci_device:

/* a common structure for stub_device and vhci_device */
struct usbip_device {
	enum usbip_side side;
	enum usbip_device_status status;

	/* lock for status */
	spinlock_t lock;

	int sockfd;
	struct socket *tcp_socket;

	struct task_struct *tcp_rx;
	struct task_struct *tcp_tx;

	unsigned long event;
	wait_queue_head_t eh_waitq;

	struct eh_ops {
		void (*shutdown)(struct usbip_device *);
		void (*reset)(struct usbip_device *);
		void (*unusable)(struct usbip_device *);
	} eh_ops;
};

struct vhci_device {
	struct usb_device *udev;

	/*
	 * devid specifies a remote usb device uniquely instead
	 * of combination of busnum and devnum.
	 */
	__u32 devid;

	/* speed of a remote device */
	enum usb_device_speed speed;

	/* vhci root-hub port to which this device is attached */
	__u32 rhport;

	struct usbip_device ud;

	/* lock for the below link lists */
	spinlock_t priv_lock;

	/* vhci_priv is linked to one of them. */
	struct list_head priv_tx;
	struct list_head priv_rx;

	/* vhci_unlink is linked to one of them */
	struct list_head unlink_tx;
	struct list_head unlink_rx;

	/* vhci_tx thread sleeps for this queue */
	wait_queue_head_t waitq_tx;
};

/* for usb_hcd.hcd_priv[0] */
struct vhci_hcd {
	struct vhci *vhci;

	u32 port_status[VHCI_HC_PORTS];

	unsigned resuming:1;
	unsigned long re_timeout;

	atomic_t seqnum;

	/*
	 * NOTE:
	 * wIndex shows the port number and begins from 1.
	 * But, the index of this array begins from 0.
	 */
	struct vhci_device vdev[VHCI_HC_PORTS];
};

包括初始化裏面的隊列和自旋鎖,原子序列號等,以及最重要的是通過sysfs_create_group(&hcd_dev(hcd)->kobj, &vhci_attr_group)註冊了一個sysfs界面,上層就可以用usbip工具進行配置vhci-hcd驅動了,在sysfs中,usbip工具主要配置瞭如下幾個參數:

devid
speed
sockfd

後面再說這幾個參數的作用。我們只需要知道vhci_start()只是一個初始化函數即可。

終於把vhci_hcd_probe()流程講清楚了。完成了vhci-hcdq驅動的全部初始化過程!

 

下篇文章講解“usbip attach -r <server端ip地址> -b <busid>”命令下發後,vhci-hcd驅動做了什麼,導致能共享遠端的真實U盤。

 

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