我們先講解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盤。