1.簡介
整個 USB 系統的通訊模型如上圖所示,本文詳細解析其中 Host 各模塊的架構和原理 (圖中彩色部分)。
2. Usb Core 驅動設備模型
由前幾節可知USB將Device進一步細分成了3個層級:Configuration 配置、Interface 接口、Endpoint 端點。
Usb Core 為其中兩個層次提供了 Device + Driver 的設備驅動模型,這兩個層次分別是 Usb Device Layer 和 Usb Interface Layer 層,一個Usb Device包含一個或多個Usb Interface。其中:
-
Usb Device Layer層。這一層的 Device 由 Hub 創建,Hub 本身也是一種 Usb Device;這一層的 Driver 完成的功能非常簡單,基本就是幫 Usb Device 創建其包含的所有子 Usb Interface 的 Device,大部分場景下都是使用 usb_generic_driver。
-
Usb Interface Layer層。這一層的 Device 由上一級 Usb Device 在驅動 probe() 時創建;而這一層的 Driver 就是普通的業務 Usb 驅動,即 Usb 協議中所說的 Client Software。
2.1 Usb Device Layer
2.1.1 device (struct usb_device)
Usb Device Device 對應的數據結構為 struct usb_device,會在兩種情況下被創建:
1、roothub device。在 HCD 驅動注冊時創建:
/* (1) 首先創建和初始化 `usb_device` 結構:*/
usb_add_hcd() → usb_alloc_dev():
struct usb_device *usb_alloc_dev(struct usb_device *parent,
struct usb_bus *bus, unsigned port1)
{
/* (1.1) dev 總線初始化為 usb_bus_type */
dev->dev.bus = &usb_bus_type;
/* (1.2) dev 類型初始化為 usb_device_type,標明自己是一個 usb device */
dev->dev.type = &usb_device_type;
dev->dev.groups = usb_device_groups;
}
/* (2) 然后注冊 `usb_device` 結構:*/
usb_add_hcd()→register_root_hub()→usb_new_device()→device_add()
2、普通 usb device。在 Hub 檢測到端口有設備 attach 時創建:
/* (1) 首先創建和初始化 `usb_device` 結構:*/
hub_event() → port_event() → hub_port_connect_change() → hub_port_connect() → usb_alloc_dev()
/* (2) 然后注冊 `usb_device` 結構:*/
hub_event()→port_event()→hub_port_connect_change()→hub_port_connect()→usb_new_device()→device_add()
2.1.2 driver (struct usb_device_driver)
Usb Device Driver 對應的數據結構為 struct usb_device_driver,使用 usb_register_device_driver() 函數進行注冊:
int usb_register_device_driver(struct usb_device_driver *new_udriver,
struct module *owner)
{
/* (1) 設置for_devices標志為1,表面這個驅動時給 usb device 使用的 */
new_udriver->drvwrap.for_devices = 1;
new_udriver->drvwrap.driver.name = new_udriver->name;
new_udriver->drvwrap.driver.bus = &usb_bus_type;
new_udriver->drvwrap.driver.probe = usb_probe_device;
new_udriver->drvwrap.driver.remove = usb_unbind_device;
new_udriver->drvwrap.driver.owner = owner;
new_udriver->drvwrap.driver.dev_groups = new_udriver->dev_groups;
retval = driver_register(&new_udriver->drvwrap.driver);
}
注冊的 Usb Device Driver 驅動非常少,一般情況下所有的 Usb Device Device 都會適配到 usb_generic_driver。因為這一層次驅動的目的很單純,就是給 Usb Device 下所有的 Interface 創建對應的 Usb Interface Device。
usb_init() → usb_register_device_driver() :
static int __init usb_init(void)
{
retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
}
struct usb_device_driver usb_generic_driver = {
.name = "usb",
.match = usb_generic_driver_match,
.probe = usb_generic_driver_probe,
.disconnect = usb_generic_driver_disconnect,
.suspend = usb_generic_driver_suspend,
.resume = usb_generic_driver_resume,
.supports_autosuspend = 1,
};
驅動 probe() 過程:
usb_probe_device() → usb_generic_driver_probe() → usb_set_configuration():
int usb_set_configuration(struct usb_device *dev, int configuration)
{
/* (1) 創建和初始化 `struct usb_interface` */
for (i = 0; i < nintf; ++i) {
/* (1.1) dev 總線初始化為 usb_bus_type */
intf->dev.bus = &usb_bus_type;
/* (1.2) dev 類型初始化為 usb_if_device_type,標明自己是一個 usb interface */
intf->dev.type = &usb_if_device_type;
intf->dev.groups = usb_interface_groups;
}
/* (2) 注冊 `struct usb_interface` */
for (i = 0; i < nintf; ++i) {
ret = device_add(&intf->dev);
}
}
2.1.3 bus (usb_bus_type)
可以看到 struct usb_device 和 struct usb_interface 使用的總線都是 usb_bus_type。他們是通過字段 dev.type 來區分的:
/* (1) `struct usb_device` 的 `dev.type` 值為 `usb_device_type`:*/
usb_add_hcd() → usb_alloc_dev():
struct usb_device *usb_alloc_dev(struct usb_device *parent,
struct usb_bus *bus, unsigned port1)
{
dev->dev.type = &usb_device_type;
}
/* (2) `struct usb_interface` 的 `dev.type` 值為 `usb_if_device_type` */
usb_probe_device() → usb_generic_driver_probe() → usb_set_configuration():
int usb_set_configuration(struct usb_device *dev, int configuration)
{
for (i = 0; i < nintf; ++i) {
intf->dev.type = &usb_if_device_type;
}
}
static inline int is_usb_device(const struct device *dev)
{
/* (3) 判斷當前 Device 是否為 Usb Device */
return dev->type == &usb_device_type;
}
static inline int is_usb_interface(const struct device *dev)
{
/* (4) 判斷當前 Device 是否為 Usb Interface */
return dev->type == &usb_if_device_type;
}
另外 struct usb_device_driver 和 struct usb_driver 使用的總線都是 usb_bus_type。他們是通過字段 drvwrap.for_devices 來區分的:
/* (1) `struct usb_device_driver` 的 `drvwrap.for_devices` 值為 1:*/
int usb_register_device_driver(struct usb_device_driver *new_udriver,
struct module *owner)
{
new_udriver->drvwrap.for_devices = 1;
}
/* (2) `struct usb_driver` 的 `drvwrap.for_devices` 值為 0:*/
int usb_register_driver(struct usb_driver *new_driver, struct module *owner,
const char *mod_name)
{
new_driver->drvwrap.for_devices = 0;
}
/* (3) 判斷當前 Driver 是適配 Usb Device 還是 Usb Interface */
static inline int is_usb_device_driver(struct device_driver *drv)
{
return container_of(drv, struct usbdrv_wrap, driver)->
for_devices;
}
在 usb_bus_type 的 match() 函數中利用 dev.type 進行判別分開處理:
struct bus_type usb_bus_type = {
.name = "usb",
.match = usb_device_match,
.uevent = usb_uevent,
.need_parent_lock = true,
};
static int usb_device_match(struct device *dev, struct device_driver *drv)
{
/* devices and interfaces are handled separately */
/* (1) Device 是 `Usb Device` 的處理 */
if (is_usb_device(dev)) {
struct usb_device *udev;
struct usb_device_driver *udrv;
/* interface drivers never match devices */
/* (1.1) 只查找 `Usb Device` 的 Driver */
if (!is_usb_device_driver(drv))
return 0;
udev = to_usb_device(dev);
udrv = to_usb_device_driver(drv);
/* If the device driver under consideration does not have a
* id_table or a match function, then let the driver's probe
* function decide.
*/
if (!udrv->id_table && !udrv->match)
return 1;
return usb_driver_applicable(udev, udrv);
/* (2) Device 是 `Usb Interface` 的處理 */
} else if (is_usb_interface(dev)) {
struct usb_interface *intf;
struct usb_driver *usb_drv;
const struct usb_device_id *id;
/* device drivers never match interfaces */
/* (2.1) 只查找 `Usb Interface` 的 Driver */
if (is_usb_device_driver(drv))
return 0;
intf = to_usb_interface(dev);
usb_drv = to_usb_driver(drv);
id = usb_match_id(intf, usb_drv->id_table);
if (id)
return 1;
id = usb_match_dynamic_id(intf, usb_drv);
if (id)
return 1;
}
return 0;
}
2.2 Usb Interface Layer
2.2.1 device (struct usb_interface)
如上一節描述,Usb Interface Device 對應的數據結構為 struct usb_interface,會在 Usb Device Driver 驅動 probe() 時 被創建:
usb_probe_device() → usb_generic_driver_probe() → usb_set_configuration():
int usb_set_configuration(struct usb_device *dev, int configuration)
{
/* (1) 創建和初始化 `struct usb_interface` */
for (i = 0; i < nintf; ++i) {
/* (1.1) dev 總線初始化為 usb_bus_type */
intf->dev.bus = &usb_bus_type;
/* (1.2) dev 類型初始化為 usb_if_device_type,標明自己是一個 usb interface */
intf->dev.type = &usb_if_device_type;
intf->dev.groups = usb_interface_groups;
}
/* (2) 注冊 `struct usb_interface` */
for (i = 0; i < nintf; ++i) {
ret = device_add(&intf->dev);
}
}
2.2.2 driver (struct usb_driver)
Usb Interface 這一層次的驅動就非常的多了,這一層主要是在 USB 傳輸層之上,針對 USB Device 的某個功能 Function 開發對應的 USB 功能業務驅動,即常說的 USB Client Software。在 USB 定義中,一個 Interface 就是一個 Function。
Usb Interface Driver 對應的數據結構為 struct usb_driver,使用 usb_register_driver() 函數進行注冊:
int usb_register_driver(struct usb_driver *new_driver, struct module *owner,
const char *mod_name)
{
/* (1) 設置for_devices標志為0,表面這個驅動時給 usb interface 使用的 */
new_driver->drvwrap.for_devices = 0;
new_driver->drvwrap.driver.name = new_driver->name;
new_driver->drvwrap.driver.bus = &usb_bus_type;
new_driver->drvwrap.driver.probe = usb_probe_interface;
new_driver->drvwrap.driver.remove = usb_unbind_interface;
new_driver->drvwrap.driver.owner = owner;
new_driver->drvwrap.driver.mod_name = mod_name;
new_driver->drvwrap.driver.dev_groups = new_driver->dev_groups;
spin_lock_init(&new_driver->dynids.lock);
INIT_LIST_HEAD(&new_driver->dynids.list);
retval = driver_register(&new_driver->drvwrap.driver);
}
一個最簡單的 Usb Interface Driver 是 usb_mouse_driver:
static const struct usb_device_id usb_mouse_id_table[] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);
static struct usb_driver usb_mouse_driver = {
.name = "usbmouse",
.probe = usb_mouse_probe,
.disconnect = usb_mouse_disconnect,
.id_table = usb_mouse_id_table,
};
module_usb_driver(usb_mouse_driver);
在后面的章節中會進一步詳細分析這個驅動的實現。
2.2.3 bus (usb_bus_type)
Usb Interface 這一層次總線也是 usb_bus_type,上一節已經分析,這里就不重復解析了。
3. USB Request Block
Usb Core 除了提供上一節描述的設備驅動模型以外,另一個重要的作用就是要給 Usb Interface Driver 提供讀寫 USB 數據的 API,這一任務是圍繞著 USB Request Block 來完成的。
Usb Interface Driver 適配成功以后,會從配置信息中獲取到當前 Interface 包含了多少個 Endpoint,以及每個 Endpoint 的地址、傳輸類型、最大包長等其他信息。Endpoint 是 USB 總線傳輸中最小的尋址單位,Interface Driver 利用對幾個 Endpoint 的讀寫來驅動具體的設備功能。
3.1 urb
對某個 Endpoint 發起一次讀寫操作,具體工作使用 struct urb 數據結構來承擔。
以下是一個對 Endpoint 0 使用 urb 發起讀寫的一個簡單實例:
static int usb_internal_control_msg(struct usb_device *usb_dev,
unsigned int pipe,
struct usb_ctrlrequest *cmd,
void *data, int len, int timeout)
{
struct urb *urb;
int retv;
int length;
/* (1) 分配一個 urb 內存空間 */
urb = usb_alloc_urb(0, GFP_NOIO);
if (!urb)
return -ENOMEM;
/* (2) 填充 urb 內容,最核心的有3方面:
1、總線地址:Device Num + Endpoint Num
2、數據:data + len
3、回調函數:usb_api_blocking_completion
*/
usb_fill_control_urb(urb, usb_dev, pipe, (unsigned char *)cmd, data,
len, usb_api_blocking_completion, NULL);
/* (3) 發送 urb 請求,并且等待請求完成 */
retv = usb_start_wait_urb(urb, timeout, &length);
if (retv < 0)
return retv;
else
return length;
}
↓
static int usb_start_wait_urb(struct urb *urb, int timeout, int *actual_length)
{
struct api_context ctx;
unsigned long expire;
int retval;
init_completion(&ctx.done);
urb->context = &ctx;
urb->actual_length = 0;
/* (3.1) 把 urb 請求掛載到 hcd 的隊列當中 */
retval = usb_submit_urb(urb, GFP_NOIO);
if (unlikely(retval))
goto out;
expire = timeout ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT;
/* (3.2) 當 urb 執行完成后,首先會調用 urb 的回調函數,然后會發送 completion 信號解除這里的阻塞 */
if (!wait_for_completion_timeout(&ctx.done, expire)) {
usb_kill_urb(urb);
retval = (ctx.status == -ENOENT ? -ETIMEDOUT : ctx.status);
dev_dbg(&urb->dev->dev,
"%s timed out on ep%d%s len=%u/%u ",
current->comm,
usb_endpoint_num(&urb->ep->desc),
usb_urb_dir_in(urb) ? "in" : "out",
urb->actual_length,
urb->transfer_buffer_length);
} else
retval = ctx.status;
out:
if (actual_length)
*actual_length = urb->actual_length;
usb_free_urb(urb);
return retval;
}
3.2 normal device urb_enqueue
對普通的 Usb device 來說,urb 最后會提交到 Host Controller 的收發隊列上面,由 HC 完成實際的 USB 傳輸:
usb_submit_urb() → usb_hcd_submit_urb():
int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
{
/* (1) 如果是 roothub 走特殊的路徑 */
if (is_root_hub(urb->dev)) {
status = rh_urb_enqueue(hcd, urb);
/* (2) 如果是普通 device 調用對應的 hcd 的 urb_enqueue() 函數 */
} else {
status = map_urb_for_dma(hcd, urb, mem_flags);
if (likely(status == 0)) {
status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
if (unlikely(status))
unmap_urb_for_dma(hcd, urb);
}
}
}
3.3 roothub device urb_enqueue
特別需要注意的是 roothub 它是一個虛擬的 usb device,實際上它并不在usb總線上而是在 host 內部,所以相應的 urb 需要特殊處理,而不能使用 hcd 把數據發送到 Usb 總線上去。
usb_submit_urb() → usb_hcd_submit_urb() → rh_urb_enqueue():
static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb)
{
/* (1) 對于 int 類型的數據,被掛載到 hcd->status_urb 指針上面
通常 roothub 驅動用這個 urb 來查詢 roothub 的端口狀態
*/
if (usb_endpoint_xfer_int(&urb->ep->desc))
return rh_queue_status (hcd, urb);
/* (2) 對于 control 類型的數據,是想讀取 roothub ep0 上的配置信息
使用軟件來模擬這類操作的響應
*/
if (usb_endpoint_xfer_control(&urb->ep->desc))
return rh_call_control (hcd, urb);
return -EINVAL;
}
|→
static int rh_queue_status (struct usb_hcd *hcd, struct urb *urb)
{
/* (1.1) 將 urb 掛載到對應的 ep 鏈表中 */
retval = usb_hcd_link_urb_to_ep(hcd, urb);
if (retval)
goto done;
/* (1.2) 將 urb 賦值給 hcd->status_urb
在 hcd 驅動中,會通過這些接口來通知 roothub 的端口狀態變化
*/
hcd->status_urb = urb;
urb->hcpriv = hcd; /* indicate it's queued */
if (!hcd->uses_new_polling)
mod_timer(&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));
}
|→
static int rh_call_control (struct usb_hcd *hcd, struct urb *urb)
{
/* (2.1) 軟件模擬對 roothub 配置讀寫的響應 */
}
4. Usb Hub Driver
普通的 Usb Device 通過內部的 Interface 提供各種業務功能。而 Hub 這類特殊的 Usb Device 功能就一種,那就是監控端口的狀態變化:
-
在端口上有設備 attach 時,創建新的 usb device,給其適配驅動。如果是 hub device,子 usb 驅動會進一步掃描端口。
-
在端口上有設備 deattach 時,移除掉對應的 usb device。如果是 hub device 進一步移除其所有的子 usb device。
Hub 也是標準的 Usb Device,它也是標準的流程被上一級設備發現后創建 Usb Device → 創建 Usb Interface,然后被 Usb Hub Interface Driver 給適配到。系統中只有一個 Hub 驅動:
static const struct usb_device_id hub_id_table[] = {
{ .match_flags = USB_DEVICE_ID_MATCH_VENDOR
| USB_DEVICE_ID_MATCH_PRODUCT
| USB_DEVICE_ID_MATCH_INT_CLASS,
.idVendor = USB_VENDOR_SMSC,
.idProduct = USB_PRODUCT_USB5534B,
.bInterfaceClass = USB_CLASS_HUB,
.driver_info = HUB_QUIRK_DISABLE_AUTOSUSPEND},
{ .match_flags = USB_DEVICE_ID_MATCH_VENDOR
| USB_DEVICE_ID_MATCH_INT_CLASS,
.idVendor = USB_VENDOR_GENESYS_LOGIC,
.bInterfaceClass = USB_CLASS_HUB,
.driver_info = HUB_QUIRK_CHECK_PORT_AUTOSUSPEND},
{ .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS,
.bDeviceClass = USB_CLASS_HUB},
{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,
.bInterfaceClass = USB_CLASS_HUB},
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, hub_id_table);
static struct usb_driver hub_driver = {
.name = "hub",
.probe = hub_probe,
.disconnect = hub_disconnect,
.suspend = hub_suspend,
.resume = hub_resume,
.reset_resume = hub_reset_resume,
.pre_reset = hub_pre_reset,
.post_reset = hub_post_reset,
.unlocked_ioctl = hub_ioctl,
.id_table = hub_id_table,
.supports_autosuspend = 1,
};
hub_driver 驅動啟動以后,只做一件事情發送一個查詢端口狀態的 urb :
hub_probe() → hub_configure():
static int hub_configure(struct usb_hub *hub,
struct usb_endpoint_descriptor *endpoint)
{
/* (1) 分配 urb */
hub->urb = usb_alloc_urb(0, GFP_KERNEL);
if (!hub->urb) {
ret = -ENOMEM;
goto fail;
}
/* (2) 初始化 urb,作用就是通過 ep0 查詢 hub 的端口狀態
urb 的回調函數是 hub_irq()
*/
usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,
hub, endpoint->bInterval);
/* (3) 發送 urb */
hub_activate(hub, HUB_INIT);
}
↓
static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
{
/* (3.1) 提交 urb */
status = usb_submit_urb(hub->urb, GFP_NOIO);
}
4.1 normal hub port op
在普通的 hub 中,端口操作是通過標準的 urb 發起 usb ep0 讀寫。分為兩類:
-
1、通過輪詢讀取 Hub Class-specific Requests 配置來查詢端口的狀態:
-
2、設置和使能端口也是通過 Hub Class-specific Requests 中相應的命令實現的:
4.2 rootHub port op
而對于 roothub 來說,對端口的操作的 urb 都需要特殊處理 (以 EHCI 的驅動為例):
-
1、端口狀態的變化可以通過 HCD 觸發中斷再上報:
ehci_irq() → usb_hcd_poll_rh_status() :
void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
{
/* (1) 獲取端口狀態的變化 */
length = hcd->driver->hub_status_data(hcd, buffer);
if (length > 0) {
/* try to complete the status urb */
spin_lock_irqsave(&hcd_root_hub_lock, flags);
/* (2) 通過回復 hcd->status_urb 來進行上報 */
urb = hcd->status_urb;
if (urb) {
clear_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);
hcd->status_urb = NULL;
urb->actual_length = length;
memcpy(urb->transfer_buffer, buffer, length);
usb_hcd_unlink_urb_from_ep(hcd, urb);
usb_hcd_giveback_urb(hcd, urb, 0);
} else {
length = 0;
set_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);
}
spin_unlock_irqrestore(&hcd_root_hub_lock, flags);
}
}
↓
hcd->driver->hub_status_data() → ehci_hub_status_data():
static int
ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
{
/* (1.1) 通過 HCD 驅動,獲取 roothub 端口的狀態 */
}
-
2、設置和使能端口需要嫁接到 HCD 驅動相關函數上實現:
→ rh_urb_enqueue() → rh_call_control() → hcd->driver->hub_control() → ehci_hub_control():
int ehci_hub_control(
struct usb_hcd *hcd,
u16 typeReq,
u16 wValue,
u16 wIndex,
char *buf,
u16 wLength
{
(1) 通過 HCD 驅動,設置 roothub 的端口 */
}
4.3 device attach
hub_event() → port_event() → hub_port_connect_change() → hub_port_connect():
static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
u16 portchange)
{
for (i = 0; i < PORT_INIT_TRIES; i++) {
/* (1) 給端口上新 Device 分配 `struct usb_device` 數據結構 */
udev = usb_alloc_dev(hdev, hdev->bus, port1);
if (!udev) {
dev_err(&port_dev->dev,
"couldn't allocate usb_device ");
goto done;
}
/* (2) 給新的 Device 分配一個新的 Address */
choose_devnum(udev);
if (udev->devnum <= 0) {
status = -ENOTCONN; /* Don't retry */
goto loop;
}
/* reset (non-USB 3.0 devices) and get descriptor */
usb_lock_port(port_dev);
/* (3) 使能端口,并且調用 hub_set_address() 給 Device 配置上新分配的 Address */
status = hub_port_init(hub, udev, port1, i);
usb_unlock_port(port_dev);
/* (4) 注冊 `struct usb_device` */
status = usb_new_device(udev);
}
}
4.4 device deattach
hub_event() → port_event() → hub_port_connect_change() → hub_port_connect():
static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
u16 portchange)
{
/* (1) 移除端口上的 `struct usb_device` */
if (udev) {
if (hcd->usb_phy && !hdev->parent)
usb_phy_notify_disconnect(hcd->usb_phy, udev->speed);
usb_disconnect(&port_dev->child);
}
}
5. Usb Host Controller Driver
Usb Host Controller 是主機側的硬件實現,主要分為以下種類:
-
Usb1.0 有兩種控制器標準:OHCI 康柏的開放主機控制器接口,UHCI Intel的通用主機控制器接口。它們的主要區別是UHCI更加依賴軟件驅動,因此對CPU要求更高,但是自身的硬件會更廉價。
-
Usb2.0 只有一種控制器標準:EHCI。因為 EHCI 只支持高速傳輸,所以EHCI控制器包括四個虛擬的全速或者慢速控制器。EHCI主要用于usb 2.0,老的Usb1.1用OHCI和UHCI。EHCI為了兼容Usb1.1,將老的OHCI和UHCI合并到EHCI規范里。
-
Usb3.0 控制器標準:XHCI。XHCI是Intel最新開發的主機控制器接口,廣泛用戶Intel六代Skylake處理器對應的100系列主板上,支持USB3.0接口,往下也兼容USB2.0。XHCI英文全稱eXtensible Host Controller Interface,是一種可擴展的主機控制器接口,是Intel開發的USB主機控制器。Intel 系列芯片的USB協議采用的就是XHCI主控,主要面向USB 3.0標準的,同時也兼容2.0以下的設備。
我們以應用最廣泛的 EHCI 為例,分析其軟硬件實現的架構。
5.1 ehci hardware
5.1.1 compatible usb1.0
對 EHCI 來說,它向下兼容的方案是非常有特點的。因為 EHCI 只支持 Usb2.0 高速傳輸,為了向下兼容 Usb1.1,它直接在內部集成最多4個全速或者慢速控制器 OHCI。在 EHCI 協議內稱這種伴生的 OHCI 控制器為 companion host controllers。
由 EHCI 驅動根據端口速率情況來決定由誰來處理:
-
每個端口有一個 Owner 屬性,用來決定是 EHCI 管理還是 OHCI 管理。就是一個 Switch 開關,決定 USB 數據切到哪邊處理。
-
初始狀態時端口默認屬于 OHCI 管理。所以對于硬件上從 OHCI 升級到 EHCI,而軟件上只有 OHCI 驅動而沒有 EHCI 驅動的系統來說是透明的,它繼續把 EHCI 當成 OHCI 硬件來使用就行了,保持完美的向前兼容。
-
如果系統軟件上啟用了 EHCI 驅動,它首先會把所有端口的Owner配置成 EHCI 管理。如果 EHCI 驅動發現端口連接且速率是全速或者慢速,則把端口的Owner配置成 OHCI 管理。
對于 EHCI 這種包含兩種控制器的兼容方式,軟件上需要同時啟動 EHCI Driver 和 OHCI Driver,才能完整的兼容 Usb1.0 和 Usb2.0:
5.1.2 Periodic Schedule
EHCI 把數據傳輸分成了兩類來進行調度:
-
Periodic Schedule。用來傳輸對時間延遲要求高的 Endpoint 數據,包括 Isochronous Transfer 和 Interrupt Transfer。
-
Asynchronous Schedule。用來傳輸對時間延遲要求不高的 Endpoint 數據,包括 Control Transfer 和 Bulk Transfer。
Periodic Schedule 內部實現如上圖所示,核心是兩級鏈表:
-
1、第一級鏈表如上圖綠色所示。是各種傳輸結構的實際描述符,主要包含以下幾種類型的描述符:
-
2、第二級鏈表如上圖橙色所示。是一個指針數組,數組中保存的是指向第一級鏈表的指針。這里每個數組成員代表一個時間分片 Frame/Micro-Frame 的起始位置,每個時間片會根據指針傳輸第一級鏈表中的數據,直到第一級鏈表的結尾。指針的格式如下:
這里的調度思想就是:第一級鏈表是一個傳輸數據全集,第二級鏈表決定了某個時間片里要傳輸的數據。這樣合理的安排二級鏈表的指針,比如間隔8次指向同一位置這部分數據的interval就是8,間隔4次指向同一位置這部分數據的interval就是4。 第一級鏈表也是要根據interval排序的。
Periodic Schedule 中幾個核心的描述符如下:
1、Isochronous (High-Speed) Transfer Descriptor (iTD)
2、Queue Head
2.1、Queue Element Transfer Descriptor (qTD)
5.1.3 Asynchronous Schedule
Asynchronous Schedule 內部實現非常的簡單就只有一級鏈表,鏈表中只有Queue Head類型的描述符。每個時間片內傳輸完 Period 數據以后,再盡可能的傳輸 Asynchronous 數據即可。
5.2 ehci driver
ehci driver 負責把 echi 功能封裝成標準的 hcd 驅動。它主要完成兩項工作:
-
1、注冊標準的 hcd 驅動。把 Client Software 傳說下來的 urb 映射到 EHCI 的鏈表中進行傳輸。
-
2、創建一個虛擬的根 hub 設備,即 roothub。
5.2.1 urb transfer
ehci 注冊 hcd 驅動:
static int ehci_platform_probe(struct platform_device *dev)
{
/* (1) 分配 hcd,并且把 hcd->driver 初始化成 ehci_hc_driver */
ehci_init_driver(&ehci_platform_hc_driver, &platform_overrides);
hcd = usb_create_hcd(&ehci_platform_hc_driver, &dev->dev,
dev_name(&dev->dev));
/* (2) 注冊標準的 hcd 驅動 */
err = usb_add_hcd(hcd, irq, IRQF_SHARED);
}
hcd 驅動向上提供了標準接口,最終的實現會調用到 ehci_hc_driver 當中。
static const struct hc_driver ehci_hc_driver = {
.description = hcd_name,
.product_desc = "EHCI Host Controller",
.hcd_priv_size = sizeof(struct ehci_hcd),
/*
* generic hardware linkage
*/
.irq = ehci_irq,
.flags = HCD_MEMORY | HCD_DMA | HCD_USB2 | HCD_BH,
/*
* basic lifecycle operations
*/
.reset = ehci_setup,
.start = ehci_run,
.stop = ehci_stop,
.shutdown = ehci_shutdown,
/*
* managing i/o requests and associated device resources
*/
.urb_enqueue = ehci_urb_enqueue,
.urb_dequeue = ehci_urb_dequeue,
.endpoint_disable = ehci_endpoint_disable,
.endpoint_reset = ehci_endpoint_reset,
.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
/*
* scheduling support
*/
.get_frame_number = ehci_get_frame,
/*
* root hub support
*/
.hub_status_data = ehci_hub_status_data,
.hub_control = ehci_hub_control,
.bus_suspend = ehci_bus_suspend,
.bus_resume = ehci_bus_resume,
.relinquish_port = ehci_relinquish_port,
.port_handed_over = ehci_port_handed_over,
.get_resuming_ports = ehci_get_resuming_ports,
/*
* device support
*/
.free_dev = ehci_remove_device,
};
在 urb transfer 過程中,最核心的是調用上述的 ehci_urb_enqueue() 和 ehci_urb_dequeue() 函數。
5.2.2 roothub
首先創建虛擬的 roothub:
/* (1) 首先創建和初始化 `usb_device` 結構: */
ehci_platform_probe() → usb_add_hcd() → usb_alloc_dev():
struct usb_device *usb_alloc_dev(struct usb_device *parent,
struct usb_bus *bus, unsigned port1)
{
/* (1.1) dev 總線初始化為 usb_bus_type */
dev->dev.bus = &usb_bus_type;
/* (1.2) dev 類型初始化為 usb_device_type,標明自己是一個 usb device */
dev->dev.type = &usb_device_type;
dev->dev.groups = usb_device_groups;
}
/* (2) 然后注冊 `usb_device` 結構: */
usb_add_hcd()→register_root_hub()→usb_new_device()→device_add()
然后因為 roothub 并不是在 Usb 物理總線上,所以對它的查詢和配置需要特殊處理。詳見Usb Hub Driver這一節。
6. Usb Client Software
這里再詳細分析一下典型的 Usb Client Software 即 usb mouse 驅動,看看它是怎么利用 urb 讀取 usb 設備數據的。
static const struct usb_device_id usb_mouse_id_table[] = {
/* (1) 驅動可以適配的 interface 列表 */
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);
static struct usb_driver usb_mouse_driver = {
.name = "usbmouse",
.probe = usb_mouse_probe,
.disconnect = usb_mouse_disconnect,
.id_table = usb_mouse_id_table,
};
module_usb_driver(usb_mouse_driver);
1、首先根據得到的 endpoint 準備好 urb,創建好 input 設備:
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
struct usb_mouse *mouse;
struct input_dev *input_dev;
int pipe, maxp;
int error = -ENOMEM;
interface = intf->cur_altsetting;
if (interface->desc.bNumEndpoints != 1)
return -ENODEV;
/* (1) 得到當前 interface 中的第一個 endpoint,mouse設備只需一個 endpoint */
endpoint = &interface->endpoint[0].desc;
if (!usb_endpoint_is_int_in(endpoint))
return -ENODEV;
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);
/* (2.1) 分配 input device */
input_dev = input_allocate_device();
if (!mouse || !input_dev)
goto fail1;
mouse->data = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &mouse->data_dma);
if (!mouse->data)
goto fail1;
/* (3.1) 分配 urb */
mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
if (!mouse->irq)
goto fail2;
mouse->usbdev = dev;
mouse->dev = input_dev;
if (dev->manufacturer)
strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));
if (dev->product) {
if (dev->manufacturer)
strlcat(mouse->name, " ", sizeof(mouse->name));
strlcat(mouse->name, dev->product, sizeof(mouse->name));
}
if (!strlen(mouse->name))
snprintf(mouse->name, sizeof(mouse->name),
"USB HIDBP Mouse %04x:%04x",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
usb_make_path(dev, mouse->phys, sizeof(mouse->phys));
strlcat(mouse->phys, "/input0", sizeof(mouse->phys));
/* (2.2) 初始化 input device */
input_dev->name = mouse->name;
input_dev->phys = mouse->phys;
usb_to_input_id(dev, &input_dev->id);
input_dev->dev.parent = &intf->dev;
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);
input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |
BIT_MASK(BTN_EXTRA);
input_dev->relbit[0] |= BIT_MASK(REL_WHEEL);
input_set_drvdata(input_dev, mouse);
input_dev->open = usb_mouse_open;
input_dev->close = usb_mouse_close;
/* (3.2) 初始化 urb */
usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,
(maxp > 8 ? 8 : maxp),
usb_mouse_irq, mouse, endpoint->bInterval);
mouse->irq->transfer_dma = mouse->data_dma;
mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* (2.3) 注冊 input device */
error = input_register_device(mouse->dev);
if (error)
goto fail3;
usb_set_intfdata(intf, mouse);
return 0;
fail3:
usb_free_urb(mouse->irq);
fail2:
usb_free_coherent(dev, 8, mouse->data, mouse->data_dma);
fail1:
input_free_device(input_dev);
kfree(mouse);
return error;
}
2、在 input device 被 open 時提交 urb 啟動傳輸:
static int usb_mouse_open(struct input_dev *dev)
{
struct usb_mouse *mouse = input_get_drvdata(dev);
mouse->irq->dev = mouse->usbdev;
/* (1) 提交初始化好的 usb,開始查詢數據 */
if (usb_submit_urb(mouse->irq, GFP_KERNEL))
return -EIO;
return 0;
}
3、在傳輸完 urb 的回調函數中,根據讀回的數據上報 input 事件,并且重新提交 urb 繼續查詢:
static void usb_mouse_irq(struct urb *urb)
{
struct usb_mouse *mouse = urb->context;
signed char *data = mouse->data;
struct input_dev *dev = mouse->dev;
int status;
switch (urb->status) {
case 0: /* success */
break;
case -ECONNRESET: /* unlink */
case -ENOENT:
case -ESHUTDOWN:
return;
/* -EPIPE: should clear the halt */
default: /* error */
goto resubmit;
}
/* (1) 根據 urb 讀回的數據,上報 input event */
input_report_key(dev, BTN_LEFT, data[0] & 0x01);
input_report_key(dev, BTN_RIGHT, data[0] & 0x02);
input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);
input_report_key(dev, BTN_SIDE, data[0] & 0x08);
input_report_key(dev, BTN_EXTRA, data[0] & 0x10);
input_report_rel(dev, REL_X, data[1]);
input_report_rel(dev, REL_Y, data[2]);
input_report_rel(dev, REL_WHEEL, data[3]);
input_sync(dev);
resubmit:
/* (2) 重新提交 urb 繼續查詢 */
status = usb_submit_urb (urb, GFP_ATOMIC);
if (status)
dev_err(&mouse->usbdev->dev,
"can't resubmit intr, %s-%s/input0, status %d ",
mouse->usbdev->bus->bus_name,
mouse->usbdev->devpath, status);
}
1.Enhanced Host Controller Interface Specification
2.USB 2.0 Specification
審核編輯 :李倩
-
usb
+關注
關注
60文章
7963瀏覽量
265270 -
Host
+關注
關注
0文章
32瀏覽量
34644
原文標題:Linux usb Host 詳解
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論