Linux usb 3. Host 详解
文章目录
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,
#ifdef CONFIG_PM
.suspend = usb_generic_driver_suspend,
.resume = usb_generic_driver_resume,
#endif
.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\n",
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 配置读写的响应 */
}
3. 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);
}
3.1 normal hub port op
在普通的 hub 中,端口操作是通过标准的 urb 发起 usb ep0 读写。分为两类:
1、通过轮询读取
Hub Class-specific Requests
配置来查询端口的状态:
2、设置和使能端口也是通过
Hub Class-specific Requests
中相应的命令实现的:
3.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 驱动相关函数上实现:
usb_hcd_submit_urb() → 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 的端口 */
}
3.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\n");
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);
}
}
3.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);
}
}
4. 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
为例,分析其软硬件实现的架构。
4.1 ehci hardware
4.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:
4.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)
4.1.3 Asynchronous Schedule
Asynchronous Schedule
内部实现非常的简单就只有一级链表,链表中只有Queue Head
类型的描述符。每个时间片内传输完 Period 数据以后,再尽可能的传输 Asynchronous 数据即可。
4.2 ehci driver
ehci driver 负责把 echi 功能封装成标准的 hcd 驱动。它主要完成两项工作:
1、注册标准的 hcd 驱动。把
Client Software
传说下来的urb
映射到 EHCI 的链表中进行传输。2、创建一个虚拟的根 hub 设备,即 roothub。
4.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()
函数。
4.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
这一节。
5. 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\n",
mouse->usbdev->bus->bus_name,
mouse->usbdev->devpath, status);
}
参考资料
1.Enhanced Host Controller Interface Specification
2.USB 2.0 Specification
Linux usb 3. Host 详解的更多相关文章
- Linux USB 鼠标驱动程序详解(转)
Linux USB 鼠标驱动程序详解 USB 总线引出两个重要的链表!一个 USB 总线引出两个重要的链表,一个为 USB 设备链表,一个为 USB 驱动链表.设备链表包含各种系统中的 USB 设备以 ...
- Linux usb 4. Device 详解
文章目录 1. 简介 2. Platform Layer 2.1 Platform Device 2.2 Platform Driver 3. UDC/Gadget Layer 3.1 Gadget ...
- Linux下usb设备驱动详解
USB驱动分为两块,一块是USB的bus驱动,这个东西,Linux内核已经做好了,我们可以不管,我们只需要了解它的功能.形象的说,USB的bus驱动相当于铺出一条路来,让所有的信息都可以通过这条USB ...
- linux的system () 函数详解
linux的system () 函数详解 system(执行shell 命令)相关函数 fork,execve,waitpid,popen表头文件 #i nclud ...
- (转)Linux 系统设置 : dmesg 命令详解
原文:https://blog.csdn.net/yexiangCSDN/article/details/80683246 https://www.cnblogs.com/duanxz/p/34770 ...
- syslog之一:Linux syslog日志系统详解
目录: <syslog之一:Linux syslog日志系统详解> <syslog之二:syslog协议及rsyslog服务全解析> <syslog之三:建立Window ...
- linux nc命令使用详解(转)
linux nc命令使用详解 功能说明:功能强大的网络工具 语 法:nc [-hlnruz][-g<网关...>][-G<指向器数目>][-i<延迟秒数>][-o& ...
- Linux下桥接模式详解一
注册博客园已经好长时间,一直以来也没有在上面写过文章,都是随意的记录在了未知笔记上,今天开始本着分享和学习的精神想把之前总结的笔记逐步分享到博客园,和大家一起学习,一起进步吧! 2016-09-20 ...
- (转)Linux curl命令参数详解
Linux curl命令参数详解 命令:curl在Linux中curl是一个利用URL规则在命令行下工作的文件传输工具,可以说是一款很强大的http命令行工具.它支持文件的上传和下载,是综合传输工具, ...
随机推荐
- Python生成桌面应用
1.cd进入project所在根目录 2.pyinstaller -F demo.py --noconsole 3.自定义图标 选择ico格式图标发在project目录 4.pyinstaller - ...
- P6076-[JSOI2015]染色问题【组合数学,容斥】
正题 题目链接:https://www.luogu.com.cn/problem/P6076 题目大意 给出\(n*m\)的网格,\(c\)种颜色涂色要求 每个格子可以染色也可以不染 每一行每一列至少 ...
- Serverless 如何在阿里巴巴实现规模化落地?
作者 | 赵庆杰(卢令) 来源 | Serverless 公众号 一.Serverless 规模化落地集团的成果 2020 年,我们在 Serverless 底层基建上做了非常大的升级,比如计算升级到 ...
- nGrinder 参数使用
背景: 性能测试中为了更加接近真实模拟现实应用,对于提交的信息每次都需要提交不同的数据,或使用不同的值,最为典型的就是登录时的账号. 性能测试工具需要提供动态参数化功能,如商业化的LoadRunner ...
- Python | JSON 数据解析(Json & JsonPath)
一.什么是JSON? JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式.它基于 ECMAScript (欧洲计算机协会制定的js规范)的一 ...
- Java基础之(六):变量、运算符与JavaDoc
变量.常量 一.变量的命名规范 首字母只能以字母(A-Z或者a-z)或者美元符($)或者下划线(_)开头,不能以数字开头,首字母之后只能跟字母(AZ或者az)或者数字,不能跟美元符或者下划线 源码 p ...
- [AGC023D] Go Home 题解
题目传送门 Solution 首先排除掉特殊情况:若 \(S\) 在两侧,肯定会顺序/逆序直接走完,答案就是边界减去出发点. 考虑到若 \(P_1\geq P_n\),那么显然 \(1\) 不到家 \ ...
- SpringBoot整合Mabatis
1.导入 MyBatis 所需要的依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> <ar ...
- 【Docker】(10)---详细说说 Dockerfile文件
一.基础概念 1.基本概念 Dockerfile 是一个文本文件,其内包含了一条条的指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建.有了 Dockerfile,当我们需要定制 ...
- 【UE4 设计模式】原型模式 Prototype Pattern
概述 描述 使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.如孙悟空猴毛分身.鸣人影之分身.剑光分化.无限剑制 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象, ...