xHCI 上 USB 读写分析
系列文章目录
xHCI 简单分析
USB Root Hub 分析
USB Hub 检测设备
usb host 驱动之 urb
xHCI那些事儿
PCIe MMIO、DMA、TLP
PCIe配置空间与CPU访问机制
PCIe总线协议基础实战
文章目录
- 系列文章目录
- 一、xHCI 初始化
- 二、xHCI 驱动识别根集线器(RootHub)
- 三、发送 USB URB 包
- 1、设备描述符获取步骤
- 步骤 1:建立事务(Setup Phase)
- 步骤 2:数据事务(Data Phase)
- 步骤 3:状态事务(Status Phase)
- 步骤 4:二次获取完整描述符(可选)
- 关键包类型总结
- 补充说明
- 2、主机发送 SETUP 令牌包
- usb_get_device_descriptor
- usb_submit_urb
- rh_urb_enqueue
- rh_call_control
- xhci_urb_enqueue
- 3、事件处理
- (1)HCD 中断注册
- (2)中断处理
- (3)usb_hcd_giveback_urb
- (4)软中断处理
- (5)__usb_hcd_giveback_urb
一、xHCI 初始化
在文件 drivers/usb/host/xhci-pci.c
中有 module_init(xhci_pci_init);
函数,其实现 xHCI 的初始化。
二、xHCI 驱动识别根集线器(RootHub)
xHCI 驱动调用 xhci_pci_probe
函数识别并初始化根集线器。xhci_pci_probe
函数最终会调用 usb_add_hcd
函数来添加 HCD ,该函数中有如下代码来初始化工作队列。
// drivers/usb/core/hcd.c
int usb_add_hcd(struct usb_hcd *hcd,
unsigned int irqnum, unsigned long irqflags)
{
// ...
init_giveback_urb_bh(&hcd->high_prio_bh);
init_giveback_urb_bh(&hcd->low_prio_bh);
// ...
}
static void init_giveback_urb_bh(struct giveback_urb_bh *bh)
{
spin_lock_init(&bh->lock);
INIT_LIST_HEAD(&bh->head);
INIT_WORK(&bh->bh, usb_giveback_urb_bh);
}
其初始化两个有优先级的工作队列,工作队列执行函数为 usb_giveback_urb_bh
,该函数实现了成功发送完 URB 后的清理及通知工作。
三、发送 USB URB 包
USB 可以进行批量传输、中断传输、实时传输、控制传输。我们选取控制传输 usb_get_device_descriptor
函数来说明传输响应过程。
1、设备描述符获取步骤
在 USB 设备枚举过程中,获取设备描述符 的传输包流程分为以下步骤(以标准控制传输为例):
步骤 1:建立事务(Setup Phase)
- 主机发送 SETUP 令牌包
- 包类型:
SETUP
(PID=0xD) - 数据方向:主机→设备
- 内容:包含标准请求
GET_DESCRIPTOR
的请求头(8 字节),例如:bmRequestType = 0x80 // (Host→Device, 标准请求, 接收者为设备) bRequest = 0x06 // (GET_DESCRIPTOR) wValue = 0x0100 // (描述符类型为设备描述符) wIndex = 0x0000 // (语言 ID 为 0) wLength = 0x0012 // (请求 18 字节,但首次可能仅返回前 8 字节)
- 包类型:
步骤 2:数据事务(Data Phase)
- 设备返回数据包
- 包类型:
DATA0
(PID=0x3) - 数据方向:设备→主机
- 内容:设备描述符的前 8 字节(包含
bLength
和bDescriptorType
等关键字段)。 - 握手包:设备发送
ACK
(PID=0x2)确认传输完成。
- 包类型:
步骤 3:状态事务(Status Phase)
- 主机发送 ACK 握手包
- 包类型:
ACK
(PID=0x2) - 数据方向:主机→设备
- 作用:确认设备描述符数据接收正确。
- 包类型:
步骤 4:二次获取完整描述符(可选)
若首次仅获取前 8 字节(因端点 0 最大包长限制),主机需重新发送请求:
-
主机发送 SETUP 令牌包
- 包类型:
SETUP
(PID=0xD) - 内容:
wLength=0x0012
(请求完整 18 字节)。
- 包类型:
-
设备返回数据包
- 包类型:
DATA1
(PID=0xB,交替使用 DATA0/DATA1) - 内容:完整设备描述符(18 字节)。
- 包类型:
-
主机发送 ACK 握手包
- 包类型:
ACK
(PID=0x2) - 作用:确认完整描述符接收完成。
- 包类型:
关键包类型总结
步骤 | 包类型 | 方向 | 作用 |
---|---|---|---|
建立事务 | SETUP | Host→Dev | 发送 GET_DESCRIPTOR 请求 |
数据事务 | DATA0/DATA1 | Dev→Host | 返回描述符数据 |
状态事务 | ACK | Host→Dev | 确认数据接收完成 |
补充说明
- 首次获取限制:设备端点 0 的最大包长可能仅为 8 字节,因此首次传输可能仅返回前 8 字节。
- 地址分配:获取设备描述符后,主机分配新地址(通过
SET_ADDRESS
请求),后续通信使用新地址。 - 错误处理:若设备未响应,主机会重试或标记设备为不可用。
【USB笔记】 设备描述符Device Descriptor
USB主机是如何获取设备描述符
2、主机发送 SETUP 令牌包
主机发送 SETUP 令牌包在系统中调用大致如下:
usb_get_device_descriptor
// drivers/usb/core/message.c
usb_get_device_descriptor();
usb_get_descriptor();
usb_control_msg();
usb_internal_control_msg();
usb_alloc_urb(); // 分配 URB 空间
usb_fill_control_urb(); // 填充 URB
usb_start_wait_urb();
usb_submit_urb(); // 发送 URB 包
wait_for_completion_timeout(&ctx.done, expire); // 等待设备返回设备描述符
usb_submit_urb
usb_submit_urb()
是 USB 主机控制器驱动(HCD)与核心层交互的核心函数,负责将 URB(USB 请求块)提交给具体的主机控制器(如 EHCI、xHCI)进行实际传输。
usb_submit_urb(); // 发送 URB 包
usb_hcd_submit_urb();
rh_urb_enqueue(hcd, urb); // 发送给 RootHub,直接获取
rh_call_control(); //control urb
rh_queue_status(hcd, urb); //int urb 循环检测 hub 的状态
hcd->driver->urb_enqueue(hcd, urb, mem_flags); // 即调用 xhci_urb_enqueue,从设备中获取
usb_hcd_submit_urb()
代码如下:
int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags) {
// ...
if (is_root_hub(urb->dev)) { // 发送给 RootHub,调用 rh_urb_enqueue 获取
status = rh_urb_enqueue(hcd, urb);
} else {
status = map_urb_for_dma(hcd, urb, mem_flags); // 分配 DMA
if (likely(status == 0)) {
// 即调用 xhci_urb_enqueue,从设备中获取
status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
if (unlikely(status))
unmap_urb_for_dma(hcd, urb);
}
}
// ...
}
rh_urb_enqueue
rh_urb_enqueue
是 根集线器(Root Hub)的 URB 处理函数,专门用于处理与根集线器相关的 URB 请求。其核心功能是 将 URB 提交给根集线器对应的硬件控制器,并触发控制传输或中断传输的流程。
- 核心功能
-
URB 提交
:将 URB 传递给根集线器的主机控制器驱动(HCD)。 -
传输类型分发
:根据 URB 的端点类型(控制传输或中断传输)选择不同的处理路径。 -
硬件状态同步
:管理根集线器的电源状态和远程唤醒功能。
// drivers/usb/core/hcd.c
static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb)
{
if (usb_endpoint_xfer_int(&urb->ep->desc))
return rh_queue_status (hcd, urb);
if (usb_endpoint_xfer_control(&urb->ep->desc))
return rh_call_control (hcd, urb);
return -EINVAL;
}
其中:
- 中断传输:调用
rh_queue_status
,通过定时器轮询根集线器状态。 - 控制传输:调用
rh_call_control
,构造控制请求并触发硬件操作。
rh_call_control
rh_call_control(); //control urb
usb_hcd_link_urb_to_ep(hcd, urb);
list_add_tail(&urb->urb_list, &urb->ep->urb_list); // 添加到端点的 urb_list 链表中
urb->hcpriv = hcd;
hcd->driver->hub_control(); // 即调用 xhci_hub_control 函数
usb_hcd_unlink_urb_from_ep(hcd, urb);
list_del_init(&urb->urb_list);
usb_hcd_giveback_urb(hcd, urb, status);
list_add_tail(&urb->urb_list, &bh->head); // 添加到工作队列中的链表里
queue_work(system_bh_highpri_wq, &bh->bh); // 触发执行 usb_giveback_urb_bh 函数
// 或调用 queue_work(system_bh_wq, &bh->bh);
rh_call_control
函数用来处理送给根集线器的主机控制器驱动(HCD)的控制传输。其通过 MMIO 直接获取相关的信息,如此处的设备描述符。同时启动工作队列,触发 usb_giveback_urb_bh
函数来对 URB 清理,同时通知 wait_for_completion_timeout
函数已完成了数据获取。
xhci_urb_enqueue
对于外接的 USB 设备(如 U 盘)的设备描述符获取,则走 hcd->driver->urb_enqueue(hcd, urb, mem_flags)
⇒ xhci_urb_enqueue
这条链路。
xhci_urb_enqueue();
xhci_queue_ctrl_tx(); // 控制传输处理
struct xhci_ring *ep_ring = xhci_urb_to_transfer_ring(xhci, urb);
// 即:xhci->devs[slot_id]->eps[ep_index]
queue_trb(); // URB 转 TRB,并入传输环
giveback_first_trb();
xhci_ring_ep_doorbell(); // 按响门铃寄存器,此时 xHCI 固件会处理此命令
__le32 __iomem *db_addr = &xhci->dba->doorbell[slot_id];
writel(DB_VALUE(ep_index, stream_id), db_addr);
xhci_queue_bulk_tx();
xhci_queue_intr_tx();
xhci_queue_isoc_tx_prepare();
xhci_ring_ep_doorbell()
会按响门铃寄存器,此时 xHCI 固件会处理此命令,处理好后其会生成传输事件 TRB(Transfer Event TRB
) 并插入事件环,同时给主机产生中断。主机会在中断处理函数 xhci_irq()
中处理此传输事件。
3、事件处理
(1)HCD 中断注册
xHCI 驱动识别 HCD 时,其会调用 usb_add_hcd
函数初始化并添加主机控制器,在此函数中会获取中断号,并进行注册。
xhci_pci_probe();
xhci_pci_common_probe(dev, id);
usb_hcd_pci_probe(dev, &xhci_pci_hc_driver);
usb_add_hcd(hcd, hcd_irq, IRQF_SHARED);
usb_hcd_request_irqs(hcd, irqnum, irqflags);
hcd->driver->start(hcd); // => xhci_pci_run
usb_hcd_request_irqs
// drivers/usb/core/hcd.c
static int usb_hcd_request_irqs(struct usb_hcd *hcd,
unsigned int irqnum, unsigned long irqflags) {
if (hcd->driver->irq) {
retval = request_irq(irqnum, &usb_hcd_irq, irqflags,
hcd->irq_descr, hcd);
hcd->irq = irqnum;
}
}
xhci_pci_run
// drivers/usb/host/xhci-pci.c
static int xhci_pci_run(struct usb_hcd *hcd) {
int ret;
if (usb_hcd_is_primary_hcd(hcd)) {
ret = xhci_try_enable_msi(hcd);
if (ret)
return ret;
}
return xhci_run(hcd);
}
static int xhci_try_enable_msi(struct usb_hcd *hcd) {
// 注册中断
ret = request_irq(pci_irq_vector(pdev, 0), xhci_msi_irq, 0, "xhci_hcd",
xhci_to_hcd(xhci));
}
// drivers/usb/host/xhci-ring.c
irqreturn_t xhci_msi_irq(int irq, void *hcd)
{
return xhci_irq(hcd);
}
(2)中断处理
xhci_irq(hcd);
xhci_handle_events(xhci, xhci->interrupters[0]);
xhci_handle_event_trb(xhci, ir, ir->event_ring->dequeue);
// 处理命令完成事件 TRB(Command Completion Event TRB)
handle_cmd_completion(xhci, &event->event_cmd);
// 处理 Port Status Change Event TRB ?
handle_port_status(xhci, event);
// 处理 传输事件 TRB(Transfer Event TRB)
handle_tx_event(xhci, ir, &event->trans_event);
// 处理 Device Notification Event TRB ?
handle_device_notification(xhci, event);
handle_vendor_event(xhci, event, trb_type);
handle_tx_event
函数正是处理获取从设备返回的设备描述符的地方。
// drivers/usb/host/xhci-ring.c
static int handle_tx_event(struct xhci_hcd *xhci,
struct xhci_interrupter *ir,
struct xhci_transfer_event *event) {
// ...
struct xhci_td *td = list_first_entry(&ep_ring->td_list, struct xhci_td, td_list);
if (usb_endpoint_xfer_control(&td->urb->ep->desc)) // control
process_ctrl_td(xhci, ep, ep_ring, td, ep_trb, event);
else if (usb_endpoint_xfer_isoc(&td->urb->ep->desc)) // isoc
process_isoc_td(xhci, ep, ep_ring, td, ep_trb, event);
else
process_bulk_intr_td(xhci, ep, ep_ring, td, ep_trb, event); // 中断传输
}
usb_get_device_descriptor 属于控制传输,所以这里走的是 process_ctrl_td
这条路径。
process_ctrl_td();
td->urb->actual_length = requested - remaining; // 实际接收到的数据长度
finish_td(xhci, ep, ep_ring, td, trb_comp_code);
xhci_td_cleanup(xhci, td, ep_ring, td->status);
xhci_unmap_td_bounce_buffer(xhci, ep_ring, td); // 清理反弹缓冲区(Bounce Buffer)
xhci_giveback_urb_in_irq(xhci, td, status);
xhci_giveback_urb_in_irq
执行一些必要的清理工作,包括把 URB 从端点链表中脱离,同时调用 usb_hcd_giveback_urb
函数执行软中断进行清理。
// drivers/usb/host/xhci-ring.c
static void xhci_giveback_urb_in_irq(struct xhci_hcd *xhci,
struct xhci_td *cur_td, int status) {
struct urb *urb = cur_td->urb;
struct urb_priv *urb_priv = urb->hcpriv;
struct usb_hcd *hcd = bus_to_hcd(urb->dev->bus);
xhci_urb_free_priv(urb_priv);
usb_hcd_unlink_urb_from_ep(hcd, urb); // 从端点链表中脱离
trace_xhci_urb_giveback(urb);
usb_hcd_giveback_urb(hcd, urb, status); // 启动工作队列,执行软中断,做必要的清理工作
}
回顾上文讲述 rh_call_control
时,当 URB 是发送给根集线器的主机控制器驱动(HCD)时,其最后同样会调用 usb_hcd_giveback_urb
函数进行清理工作。
(3)usb_hcd_giveback_urb
// drivers/usb/core/hcd.c
void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb, int status)
{
// 没在 bh 中并且不是 HCD,则开始进行清理工作
if (!hcd_giveback_urb_in_bh(hcd) && !is_root_hub(urb->dev)) {
__usb_hcd_giveback_urb(urb);
return;
}
// 同步端点和中断端点走高优先级中断
if (usb_pipeisoc(urb->pipe) || usb_pipeint(urb->pipe))
bh = &hcd->high_prio_bh;
else
bh = &hcd->low_prio_bh;
spin_lock(&bh->lock);
list_add_tail(&urb->urb_list, &bh->head); // 加入 bh 链表中
running = bh->running;
spin_unlock(&bh->lock);
if (running) // 运行中则等待
;
else if (bh->high_prio) // 启动工作队列
queue_work(system_bh_highpri_wq, &bh->bh);
else
queue_work(system_bh_wq, &bh->bh);
}
(4)软中断处理
软中断触发流程大致如下,其最后调用处理函数 usb_giveback_urb_bh
。
__do_softirq();
handle_softirqs(false);
h->action(); => tasklet_action()
workqueue_softirq_action(false);
bh_worker
process_scheduled_works(worker);
process_one_work(worker, work);
worker->current_func(work);
// INIT_WORK(&bh->bh, usb_giveback_urb_bh);
usb_giveback_urb_bh();
usb_giveback_urb_bh
是 USB 子系统的底半部(Bottom Half)处理函数,负责将完成传输的 URB(USB Request Block)返回给设备驱动。其核心作用是将 URB 的状态同步到用户空间或驱动层,并触发驱动注册的回调函数。
- 核心功能如下:
URB 完成通知
:将 URB 的传输结果(如成功、错误、取消)传递给设备驱动。-
资源回收
:释放 URB 占用的 DMA 缓冲区、减少引用计数等。 -
中断上下文切换
:在硬件中断上下文中触发异步回调,避免阻塞中断处理。
- 底半部处理逻辑
// drivers/usb/core/hcd.c
static void usb_giveback_urb_bh(struct work_struct *work)
{
struct giveback_urb_bh *bh =
container_of(work, struct giveback_urb_bh, bh);
struct list_head local_list;
spin_lock_irq(&bh->lock);
bh->running = true;
list_replace_init(&bh->head, &local_list); // 原子替换链表
spin_unlock_irq(&bh->lock);
// 循环处理已发送好的 URB
while (!list_empty(&local_list)) {
struct urb *urb;
urb = list_entry(local_list.next, struct urb, urb_list);
list_del_init(&urb->urb_list);
bh->completing_ep = urb->ep;
__usb_hcd_giveback_urb(urb); // 真正的清理与通知处理的地方
bh->completing_ep = NULL;
}
/*
* giveback new URBs next time to prevent this function
* from not exiting for a long time.
*/
spin_lock_irq(&bh->lock);
// 如果还有需要处理的 URB 则开启下一轮处理
if (!list_empty(&bh->head)) {
if (bh->high_prio)
queue_work(system_bh_highpri_wq, &bh->bh);
else
queue_work(system_bh_wq, &bh->bh);
}
bh->running = false;
spin_unlock_irq(&bh->lock);
}
注意: usb_hcd_giveback_urb
中也调用 __usb_hcd_giveback_urb(urb)
函数进行清理与通知。
(5)__usb_hcd_giveback_urb
__usb_hcd_giveback_urb(urb);
unmap_urb_for_dma(hcd, urb);
usb_unanchor_urb(urb);
urb->complete(urb); // => usb_api_blocking_completion
urb->complete(urb)
中 complete
函数会对等待进行通知,其赋值一般是在初始化 URB 时进行的,如 usb_fill_control_urb
。
// drivers/usb/core/message.c
usb_fill_control_urb(urb, usb_dev, pipe, (unsigned char *)cmd, data,
len, usb_api_blocking_completion, NULL);
usb_fill_int_urb(urb, usb_dev, pipe, data, len,
usb_api_blocking_completion, NULL,
ep->desc.bInterval);
usb_fill_bulk_urb(urb, usb_dev, pipe, data, len,
usb_api_blocking_completion, NULL);
usb_api_blocking_completion
实现如下,其会对如 usb_start_wait_urb
函数进行通知,传输已完成。
// drivers/usb/core/message.c
static void usb_api_blocking_completion(struct urb *urb) {
struct api_context *ctx = urb->context;
ctx->status = urb->status;
complete(&ctx->done); // 发送
}
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;
retval = usb_submit_urb(urb, GFP_NOIO);
expire = timeout ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT;
if (!wait_for_completion_timeout(&ctx.done, expire)) { // 等待
usb_kill_urb(urb);
retval = (ctx.status == -ENOENT ? -ETIMEDOUT : ctx.status);
} else
retval = ctx.status;
out:
if (actual_length)
*actual_length = urb->actual_length;
usb_free_urb(urb);
return retval;
}
至此,整个发送与接收流程大致已分析完成。关于 DMA 分配与释放请参考此链接。
☆