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(); // 填充 URBusb_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 urbrh_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 urbusb_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)) // controlprocess_ctrl_td(xhci, ep, ep_ring, td, ep_trb, event);else if (usb_endpoint_xfer_isoc(&td->urb->ep->desc)) // isocprocess_isoc_td(xhci, ep, ep_ring, td, ep_trb, event);elseprocess_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;elsebh = &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);elsequeue_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_workerprocess_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);// 循环处理已发送好的 URBwhile (!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);elsequeue_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);} elseretval = ctx.status;
out:if (actual_length)*actual_length = urb->actual_length;usb_free_urb(urb);return retval;
}
至此,整个发送与接收流程大致已分析完成。关于 DMA 分配与释放请参考此链接。
☆