当前位置: 首页 > news >正文

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)

  1. 主机发送 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)

  1. 设备返回数据包
    • 包类型DATA0(PID=0x3)
    • 数据方向:设备→主机
    • 内容:设备描述符的前 8 字节(包含 bLengthbDescriptorType 等关键字段)。
    • 握手包:设备发送 ACK(PID=0x2)确认传输完成。

步骤 3:状态事务(Status Phase)

  1. 主机发送 ACK 握手包
    • 包类型ACK(PID=0x2)
    • 数据方向:主机→设备
    • 作用:确认设备描述符数据接收正确。

步骤 4:二次获取完整描述符(可选)

若首次仅获取前 8 字节(因端点 0 最大包长限制),主机需重新发送请求:

  1. 主机发送 SETUP 令牌包

    • 包类型SETUP(PID=0xD)
    • 内容wLength=0x0012(请求完整 18 字节)。
  2. 设备返回数据包

    • 包类型DATA1(PID=0xB,交替使用 DATA0/DATA1)
    • 内容:完整设备描述符(18 字节)。
  3. 主机发送 ACK 握手包

    • 包类型ACK(PID=0x2)
    • 作用:确认完整描述符接收完成。

关键包类型总结

步骤包类型方向作用
建立事务SETUPHost→Dev发送 GET_DESCRIPTOR 请求
数据事务DATA0/DATA1Dev→Host返回描述符数据
状态事务ACKHost→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 提交给根集线器对应的硬件控制器,并触发控制传输或中断传输的流程。

  1. ​核心功能
  • 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
  1. 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;}
}
  1. 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 的状态同步到用户空间或驱动层,并触发驱动注册的回调函数。

  1. 核心功能如下:
  • URB 完成通知:将 URB 的传输结果(如成功、错误、取消)传递给设备驱动。
  • 资源回收:释放 URB 占用的 DMA 缓冲区、减少引用计数等。
  • 中断上下文切换:在硬件中断上下文中触发异步回调,避免阻塞中断处理。
  1. 底半部处理逻辑
// 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 分配与释放请参考此链接。


   
 


http://www.mrgr.cn/news/97892.html

相关文章:

  • openharmony—release—4.1开源鸿蒙源码编译踩坑记录
  • Backtrader从0到1——第一个回测策略
  • ubuntu20.04在mid360部署direct_lidar_odometry(DLO)
  • 如何通过前端表格控件实现自动化报表?1
  • Cursor Agent 模式实现复杂工作流的编排与执行
  • 百度地图小区边界爬取
  • 创建型模式究竟解决了什么问题
  • Vue Router(2)
  • 机器学习 | 强化学习方法分类汇总 | 概念向
  • 【教学类-102-07】剪纸图案全套代码07——Python点状虚线优化版本+制作1图2图6图
  • 【GDB】调试程序的基本命令和用法(Qt程序为例)
  • STM32硬件IIC+DMA驱动OLED显示——释放CPU资源,提升实时性
  • IAP Firmware Upload Tools.exe IAP 网络固件升级教程
  • Vue3+Vite+TypeScript+Element Plus开发-12.动态路由-配置
  • 用Java写一个MVCC例子
  • 蓝桥杯C++组算法知识点整理 · 考前突击(上)【小白适用】
  • Linux vagrant 导入Centos到virtualbox
  • Android 中支持旧版 API 的方法(API 30)
  • VS Code 的 .S 汇编文件里面的注释不显示绿色
  • 【网络安全 | 项目开发】Web 安全响应头扫描器(提升网站安全性)