ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux USB协议栈深度解析:从枚举流程到URB调度与Gadget框架

Linux USB协议栈深度解析:从枚举流程到URB调度与Gadget框架 1. 从一次USB设备识别失败说起前阵子帮朋友排查一个嵌入式板子的问题现象很典型一块基于ARM Cortex-A的定制板跑的是主线Linux内核板子上焊了一个USB Hub芯片下面挂了一个4G模组和一个U盘。开机之后U盘能认4G模组时好时坏有时候lsusb能看到设备但/dev/ttyUSB*死活出不来。朋友第一反应是驱动问题折腾了两天换驱动版本、改设备树都没解决。我接手之后没急着动代码先让他把dmesg完整打出来。一看日志问题其实很清楚Hub的枚举过程里端口上电之后有一段device descriptor read/64, error -71然后重试了几次才成功。这是典型的USB协议栈在枚举阶段的信号完整性问题跟驱动半毛钱关系没有。后来把Hub的供电电容加大、差分线走线重新处理了一下问题就消失了。这件事让我意识到一个很普遍的现象很多人用Linux用了很多年天天敲lsusb、dmesg、mount但对底下这套USB协议栈到底是怎么运转的其实是没有概念的。一旦出了问题就只能靠换驱动、改设备树、重启试试这三板斧效率极低。所以这篇东西我想把Linux内核里USB协议栈这套框架从头到尾捋一遍。不是那种API手册式的罗列而是按照数据从物理线缆进来到最终变成一个/dev节点这条完整链路来讲。涉及的核心关键词包括Linux、USB协议栈、框架我会覆盖USB Core、HCD、Gadget、OTG、URB机制、枚举流程、sysfs接口这些核心模块也会讲清楚它们之间的分层关系和调用逻辑。适合谁看如果你是做嵌入式Linux驱动开发的这篇能帮你建立完整的USB子系统心智模型如果你是运维或者应用开发平时只是用用USB设备看完至少能明白dmesg里那些日志到底在说什么出问题知道往哪个方向查如果你正在准备Linux相关的面试USB协议栈几乎是必考项这套框架的理解深度直接决定你能不能答到点子上。下面我按照分层架构 → 核心数据结构 → 枚举全流程 → 数据传输机制 → Gadget侧 → 调试手段这个顺序展开每一块都会结合我自己踩过的坑来讲。2. Linux USB子系统的四层架构与职责边界2.1 为什么USB子系统要分成四层先建立一个整体认知。Linux内核里的USB子系统从上到下大致分成四层这个分层不是拍脑袋定的而是严格对应USB协议规范里的逻辑结构。最上面是USB设备驱动层Device Driver Layer比如usb-storage、usbhid、cdc-acm这些。它们关心的是我这个设备是干嘛的比如U盘驱动只关心怎么发SCSI命令不关心底下是USB 2.0还是3.0。往下一层是USB核心层USB Core对应内核源码drivers/usb/core/目录。这一层是整个子系统的中枢神经负责设备枚举、配置管理、驱动匹配、URBUSB Request Block的调度。它向上给设备驱动提供统一的API向下屏蔽不同主控器的差异。再往下是主机控制器驱动层HCDHost Controller Driver对应drivers/usb/host/。这一层直接跟硬件打交道比如xhci-hcd、ehci-hcd、ohci-hcd、uhci-hcd分别对应USB 3.x、2.0高速、1.1全速/低速等不同规格的主控器。最底下是硬件层也就是物理的USB主控器芯片和USB总线本身。这个分层的核心价值在于解耦。你换一个USB主控器芯片只要HCD层适配好上面的USB Core和设备驱动完全不用动你加一个新的USB设备类型只要写一个设备驱动底下的HCD和硬件也不用管。这就是为什么Linux能支持成千上万种USB设备而不会失控。2.2 各层的源码位置与关键文件光说概念太虚直接上源码位置这样你去看内核代码的时候能对得上号。层次源码目录关键文件职责设备驱动层drivers/usb/class/、drivers/usb/storage/等usb-storage.c、usbhid具体设备功能实现USB核心层drivers/usb/core/usb.c、hub.c、message.c、config.c枚举、驱动匹配、URB调度HCD层drivers/usb/host/xhci-hcd.c、ehci-hcd.c主控器寄存器操作、传输调度Gadget层drivers/usb/gadget/udc/core.c、composite.c设备侧从机功能这里要特别提一下hub.c。很多人以为Hub就是个简单的扩展口其实Hub驱动是USB子系统里最复杂也最关键的部分之一。因为所有设备的枚举都要经过HubHub本身还要处理端口状态变化、电源管理、过流检测等等。你dmesg里看到的绝大多数USB相关日志其实都是hub.c打出来的。2.3 USB Core到底做了哪些看不见的事USB Core这一层我觉得是理解整个协议栈的关键因为它做了大量幕后工作平时你根本感知不到。第一件事是设备枚举。当一个USB设备插上来USB Core要负责给它分配地址、读取描述符、选择配置、加载驱动。这个过程在usb.c的usb_new_device()和hub.c的hub_port_connect_change()里。第二件事是驱动匹配。USB Core维护了一张驱动表每个USB设备驱动注册的时候会声明自己支持哪些vendor id和product id。设备枚举完成后USB Core会拿设备的ID去匹配驱动匹配上了就调用驱动的probe()函数。这个机制跟平台设备的总线匹配是一个思路。第三件事是URB管理。URB是USB数据传输的基本单位你可以把它理解成一次USB传输请求的封装。USB Core负责URB的创建、提交、超时处理、完成回调。设备驱动只管填URB、提交URB剩下的调度交给Core和HCD。第四件事是sysfs接口暴露。你在/sys/bus/usb/devices/下面看到的那些目录比如1-1、1-1.2都是USB Core创建的。这些接口对调试极其重要后面我会专门讲怎么用。提示很多人调试USB问题只知道lsusb和dmesg其实/sys/bus/usb/devices/下面的信息量比这两个命令加起来还大。养成看sysfs的习惯能省你一半的排查时间。2.4 HCD层不同主控器的差异到底在哪HCD层是直接操作硬件的所以不同主控器的差异非常大。目前主流的有四种XHCIeXtensible Host Controller InterfaceUSB 3.x的主控器也向下兼容2.0和1.1设备。现在新板子基本都是XHCI。EHCIEnhanced Host Controller InterfaceUSB 2.0高速主控器。OHCIOpen Host Controller InterfaceUSB 1.1全速/低速多见于早期的非x86平台。UHCIUniversal Host Controller InterfaceUSB 1.1Intel早期的方案。这四种主控器的硬件寄存器布局、调度算法、DMA描述符格式都不一样但它们在HCD层被抽象成了统一的接口。这个抽象接口定义在include/linux/usb/hcd.h里的struct hc_driver。struct hc_driver { const char *description; const char *product_desc; size_t hcd_priv_size; irqreturn_t (*irq)(struct usb_hcd *hcd); int flags; int (*reset)(struct usb_hcd *hcd); int (*start)(struct usb_hcd *hcd); int (*urb_enqueue)(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags); int (*urb_dequeue)(struct usb_hcd *hcd, struct urb *urb, int status); // ... 省略若干 };你看urb_enqueue和urb_dequeue这两个函数指针就是HCD层对上层暴露的核心接口。USB Core把URB交给HCDHCD负责把它翻译成硬件能懂的DMA描述符然后启动传输。传输完成后HCD通过中断通知USB CoreCore再回调设备驱动的完成函数。这个设计的好处是USB Core完全不需要知道底下是XHCI还是EHCI它只管调urb_enqueue。这就是分层架构的威力。3. URB、描述符与设备模型三个必须吃透的核心概念3.1 URBUSB传输的快递单如果只能用一个概念来概括Linux USB子系统的数据传输机制那一定是URBUSB Request Block。我习惯把它类比成一张快递单你要寄什么东西数据缓冲区、寄到哪端点、用什么方式寄传输类型、寄到了通知谁完成回调全都写在这张单子上。URB的定义在include/linux/usb.h里结构体很大但核心字段就那么几个struct urb { struct usb_device *dev; // 目标设备 unsigned int pipe; // 端点信息方向端点号类型 int status; // 传输完成状态 unsigned int transfer_flags; // 传输标志 void *transfer_buffer; // 数据缓冲区 u32 transfer_buffer_length; // 缓冲区长度 u32 actual_length; // 实际传输长度 int interval; // 轮询间隔中断传输用 usb_complete_t complete; // 完成回调函数 void *context; // 回调上下文 // ... 省略若干 };用URB的流程是固定的四步创建 → 填充 → 提交 → 在回调里处理结果并释放。这里有个新手最容易踩的坑URB的完成回调是在中断上下文里执行的所以回调函数里不能睡眠、不能调用可能阻塞的函数。我见过有人在回调里直接kmalloc(GFP_KERNEL)结果系统直接崩了。正确的做法是在回调里只做最轻量的处理比如标记状态、唤醒等待队列把耗时的活儿丢到工作队列或者线程里去干。3.2 描述符设备的身份证USB设备插上来之后主机要做的第一件事就是读它的描述符。描述符就是设备的身份证告诉主机我是谁、我能干什么、我需要多少带宽。描述符是分层的从大到小依次是设备描述符Device Descriptor全局信息包括厂商ID、产品ID、支持的配置数。配置描述符Configuration Descriptor一个配置下的信息包括接口数、供电需求。接口描述符Interface Descriptor一个接口的信息包括端点数和设备类。端点描述符Endpoint Descriptor端点的信息包括传输类型、方向、最大包长。字符串描述符String Descriptor可读的文本信息比如厂商名、产品名。这个层级关系很重要因为一个USB设备可以有多个配置一个配置可以有多个接口一个接口可以有多个端点。比如一个USB音频设备可能有一个配置里面有一个音频控制接口和一个音频流接口流接口下面又有等时端点。在Linux里这些描述符被解析后存放在struct usb_device和struct usb_interface里。你可以通过sysfs直接看到它们# 查看设备描述符 cat /sys/bus/usb/devices/1-1/idVendor cat /sys/bus/usb/devices/1-1/idProduct cat /sys/bus/usb/devices/1-1/bDeviceClass # 查看配置和接口 ls /sys/bus/usb/devices/1-1/3.3 设备模型Linux设备驱动框架的统一视角USB设备在Linux里不是孤立存在的它被纳入了统一的设备模型Device Model。这个模型的核心是bus、device、driver三者的关系。USB总线usb_bus_type在drivers/usb/core/driver.c里注册。每个USB设备是一个struct device每个USB驱动是一个struct device_driver。当设备枚举完成内核会调用device_add()把设备挂到总线上然后总线负责匹配驱动。这个机制带来的一个直接好处是你可以通过sysfs的bind和unbind接口手动绑定或解绑驱动。比如你调试一个USB驱动不想每次都拔插设备就可以这样# 解绑 echo 1-1:1.0 /sys/bus/usb/drivers/usb-storage/unbind # 重新绑定 echo 1-1:1.0 /sys/bus/usb/drivers/usb-storage/bind这个技巧在调试驱动probe/remove逻辑的时候特别好用能省掉大量拔插设备的物理操作。注意unbind的时候要确保设备没有被挂载或者正在使用否则可能导致数据丢失或者内核警告。U盘的话先umount再操作。4. 设备插入那一刻枚举流程的完整拆解4.1 从电气信号到Hub中断现在我们把视角切到设备插入这个瞬间看看整个协议栈是怎么联动的。当你把USB设备插到端口上物理层首先发生的是D或D-线上的上拉电阻变化。USB协议规定全速设备上拉D低速设备上拉D-。Hub芯片检测到这个电平变化后会通过中断线通知主控器。主控器的HCD驱动收到中断读取端口状态寄存器发现端口状态从无连接变成了有连接。然后HCD会调用USB Core注册的回调最终走到hub.c里的hub_port_connect_change()。这个函数是整个枚举流程的入口。它会做几件事先给端口上电如果之前是关闭的然后等待电源稳定USB_PORT_FEAT_POWER再复位端口最后调用hub_port_init()开始真正的枚举。4.2 地址分配与描述符读取hub_port_init()里最关键的一步是给设备分配地址。USB协议规定设备刚插入时默认地址是0主机需要通过SET_ADDRESS请求给它分配一个1到127之间的唯一地址。这里有个细节很多人不知道地址分配之后设备要经过一个恢复时间recovery time才能响应新地址的请求。USB 2.0规范里规定这个时间是2ms所以内核里会有一个msleep(10)左右的延时。如果你自己写HCD驱动这个延时不能省否则会出现设备时好时坏的现象。地址分配完成后USB Core会读取设备描述符。第一次读的时候只读前8个字节因为这时候还不知道端点的最大包长bMaxPacketSize0。拿到这8个字节后才知道后续该怎么读完整的18字节设备描述符。这个先读8字节再读全部的流程是USB协议里一个很经典的设计也是面试常考点。为什么要这样因为控制传输的包长必须匹配设备的能力而设备的能力就写在描述符的前8个字节里。4.3 配置选择与驱动匹配读完设备描述符USB Core会继续读配置描述符、接口描述符、端点描述符把整个设备的能力清单摸清楚。然后它会从设备支持的多个配置里选一个通常是第一个除非驱动有特殊要求发送SET_CONFIGURATION请求激活这个配置。配置激活之后设备就进入了已配置状态可以正常工作了。这时候USB Core会调用device_add()把设备注册到USB总线上触发驱动匹配。匹配的过程是这样的USB Core遍历所有已注册的USB驱动拿设备的idVendor和idProduct去跟驱动的id_table比对。匹配上了就调用驱动的probe()函数。如果没匹配上设备会停留在已配置但无驱动的状态你在lsusb里能看到它但/dev下面不会有对应的节点。4.4 枚举失败的常见原因与排查思路枚举阶段是USB问题的高发区。我把常见的失败原因和排查方法整理成一张表现象可能原因排查手段device descriptor read/64, error -71信号完整性差、线缆质量差换线、查差分线阻抗、加共模电感device not accepting address供电不足、上电时序问题测端口电压、加大电容unable to enumerate USB device上述原因的综合结合dmesg逐条看设备识别但无/dev节点驱动未匹配查idVendor/idProduct、看驱动是否加载反复枚举又断开供电不稳、接触不良换端口、换Hub、测电流这里我要强调一个经验error -71EPROTO几乎都是物理层问题不要浪费时间在驱动上。我见过太多人一看到错误码就去改驱动结果查了半天发现是线缆太差。USB 2.0高速信号的差分阻抗要求是90欧姆线缆质量不达标枚举阶段就会各种报错。5. 四种传输类型与URB的实际调度5.1 控制传输枚举和配置的专用通道USB有四种传输类型控制传输、中断传输、批量传输、等时传输。每种类型对应不同的应用场景底层的调度策略也完全不同。控制传输是最基础的一种所有USB设备都必须支持。它主要用于枚举阶段的描述符读取、配置设置以及一些设备特定的控制命令。控制传输的特点是可靠、有握手、有重传但速度慢、有固定开销。控制传输分三个阶段Setup阶段、Data阶段可选、Status阶段。Setup阶段主机发送8字节的请求Data阶段传输数据Status阶段设备返回握手。这个三段式结构在message.c里的usb_control_msg()函数里实现。int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, __u16 value, __u16 index, void *data, __u16 size, int timeout);这个函数是同步的会阻塞直到传输完成或者超时。在驱动的probe()函数里读描述符、设置设备用的都是它。5.2 中断传输小数据、低延迟、周期性中断传输用于数据量小但要求低延迟的场景最典型的就是USB键盘、鼠标。它的特点是主机周期性轮询设备间隔由端点描述符里的bInterval决定。这里有个常见的误解中断传输并不是设备主动中断主机而是主机周期性去问设备有没有数据。这个问的周期就是bInterval。对于全速设备bInterval单位是毫秒对于高速设备单位是2的幂次方微秒。中断传输的URB提交之后HCD会把它挂到一个周期性的调度队列里按照bInterval定时执行。如果设备没有数据会返回NAKHCD会在下一个周期继续问。5.3 批量传输大数据、可靠、无带宽保证批量传输用于数据量大、对时间不敏感但要求可靠的场景比如U盘、打印机。它的特点是利用总线的空闲带宽有错误就重传但不保证带宽和延迟。批量传输的调度相对简单HCD会把URB挂到批量队列里有空闲带宽就发。如果设备返回NAK表示暂时没准备好HCD会稍后重试。U盘读写用的就是批量传输。你在dd一个大文件的时候如果观察/proc/interrupts会看到USB主控器的中断数在飙升那就是批量传输在跑。5.4 等时传输实时性优先允许丢包等时传输用于实时音视频场景比如USB摄像头、USB声卡。它的特点是保证带宽和延迟但不保证可靠性——数据错了不重传丢了就丢了。这个设计是合理的对于实时音频流重传一个已经过期的数据包没有意义还不如把带宽留给后面的数据。等时传输的URB提交后HCD会按照固定的时间间隔调度每个间隔传一个包。等时传输的编程模型跟其他三种不太一样它需要预先提交多个URB形成一个流水线才能保证数据连续。如果你只提交一个URB等它完成了再提交下一个中间就会有间隙音频就会卡顿。5.5 四种传输类型的对比与选型传输类型可靠性带宽保证延迟典型应用控制传输高有重传无中枚举、配置中断传输高有重传有小带宽低键鼠、HID批量传输高有重传无高U盘、打印机等时传输低无重传有低音视频选型的核心逻辑是先看数据量和实时性要求再看可靠性要求。音视频要实时选等时键鼠要低延迟选中断U盘要可靠选批量配置和命令用控制。6. Gadget框架让Linux设备变成USB从机6.1 Gadget与Host的区别前面讲的都是Linux作为USB主机Host的场景也就是Linux去控制USB设备。但很多时候我们的嵌入式板子需要反过来作为USB设备被PC识别比如做一个USB串口、USB网卡、USB存储设备。这就是Gadget框架要解决的问题。Gadget框架的层次结构跟Host侧是对称的UDC驱动USB Device Controller对应Host侧的HCD直接操作USB设备控制器的硬件。Gadget Core对应USB Core提供统一的API。Function驱动对应设备驱动实现具体的功能比如f_serial、f_mass_storage、f_ecm。6.2 ConfigFS现代Gadget的配置方式早期的Gadget配置是写死的编译内核的时候就得选好功能。现在主流的方式是ConfigFS可以在运行时动态配置Gadget的功能组合。一个典型的配置流程是这样的# 挂载configfs mount -t configfs none /sys/kernel/config # 创建一个gadget mkdir /sys/kernel/config/usb_gadget/my_gadget cd /sys/kernel/config/usb_gadget/my_gadget # 设置VID/PID echo 0x1d6b idVendor echo 0x0104 idProduct # 创建配置 mkdir configs/c.1 # 创建一个ACM串口功能 mkdir functions/acm.usb0 ln -s functions/acm.usb0 configs/c.1/ # 绑定UDC echo fe800000.usb UDC执行完这些命令PC那边就能看到一个USB串口设备了。这个流程的核心是把功能function和配置config解耦你可以自由组合多个功能到一个Gadget里比如同时提供串口和存储。6.3 常见Function驱动与实战场景Gadget框架提供了很多现成的Function驱动常用的有f_acmUSB串口最常用调试利器。f_mass_storageUSB存储可以把板子上的一个文件或分区模拟成U盘。f_ecm/f_rndisUSB网卡让板子和PC通过USB组网。f_hidUSB HID设备可以做自定义的键鼠。f_uac1/f_uac2USB声卡。我做过一个项目板子需要同时给PC提供串口和网卡用ConfigFS组合acm和ecm两个function五分钟就配好了。如果用传统方式改内核配置至少得重新编译烧录一次。提示f_mass_storage有个很实用的场景——把板子的日志分区模拟成U盘PC直接挂载就能看日志不用scp也不用串口传文件。配置的时候注意lun.0/ro属性设成1就是只读防止PC误写。7. 调试USB问题的工具箱从dmesg到usbmon7.1 dmesg与sysfs第一手信息来源调试USB问题dmesg永远是第一站。但很多人看dmesg只看最后几行其实USB的日志是有时间线的从设备插入到枚举完成每一步都有记录。你要做的是找到设备插入的那个时间点然后顺着往下看。配合dmesg -w可以实时监控插拔设备的时候观察日志变化能快速定位问题发生在哪个阶段。sysfs是第二站。/sys/bus/usb/devices/下面的每个目录对应一个USB设备目录名就是它的路径比如1-1.2表示总线1上的根Hub的1号端口下的Hub的2号端口。这个命名规则本身就包含了拓扑信息。# 查看设备树状结构 ls -l /sys/bus/usb/devices/ # 查看某个设备的详细信息 cat /sys/bus/usb/devices/1-1/uevent cat /sys/bus/usb/devices/1-1/busnum cat /sys/bus/usb/devices/1-1/devnum cat /sys/bus/usb/devices/1-1/speedspeed这个属性特别有用它告诉你设备实际协商到的速度。如果插的是USB 3.0设备但speed显示480说明它跑在2.0模式可能是线缆或者Hub的问题。7.2 usbmon抓包看协议层到底发生了什么dmesg和sysfs只能看到结果看不到过程。如果你想看USB总线上实际传输的每一个包就需要usbmon。usbmon是内核自带的USB抓包工具用法如下# 加载usbmon模块 modprobe usbmon # 查看有哪些总线可以抓 ls /sys/kernel/debug/usb/usbmon/ # 抓取总线1上的所有流量 cat /sys/kernel/debug/usb/usbmon/1u usb.log抓到的日志可以用wireshark打开分析也可以直接看文本。文本格式里S表示提交C表示完成Ci是中断完成Co是等时完成。通过分析这些包你能精确看到枚举时发了哪些请求、设备返回了什么、哪个环节超时了。我排查过一个USB设备偶发枚举失败的问题用usbmon抓了几次失败的过程发现是GET_DESCRIPTOR请求发出后设备迟迟不响应最后超时。结合硬件同事的测量确认是设备的晶振起振时间太长上电后需要更长时间才能响应。这种问题不看协议层抓包是根本定位不到的。7.3 常见问题速查表最后给一张速查表覆盖我这些年遇到的高频问题问题现象优先排查方向具体手段设备完全不识别供电、物理连接测电压、换线、换端口枚举报错-71信号完整性查差分线、换线缆识别但无驱动驱动匹配查VID/PID、看驱动加载传输超时端点配置、设备固件usbmon抓包、查端点描述符带宽不足等时/中断带宽分配查/sys/kernel/debug/usb/设备频繁掉线供电、电源管理关闭autosuspend、测电流关于电源管理补充一个经验很多设备用一会儿就掉的问题都是USB autosuspend导致的。内核默认会对空闲的USB设备挂起省电但有些设备固件没处理好挂起/恢复就会掉线。临时验证可以这样# 对某个设备禁用autosuspend echo -1 /sys/bus/usb/devices/1-1/power/autosuspend_delay_ms echo on /sys/bus/usb/devices/1-1/power/control如果禁用之后问题消失那就说明是电源管理的问题需要从设备固件或者驱动层面去修。8. 我踩过的几个坑和一点个人体会聊了这么多框架和机制最后说几个我自己踩过的坑都是文档里不会写的。第一个坑是Hub的级联深度。USB规范规定最多7层但实际用的时候级联超过4层就很容易出问题尤其是供电和信号质量。我有个项目用了三级Hub最后一层的设备经常枚举失败后来把中间一层去掉直接两级问题就没了。所以能用一级Hub解决的就别级联。第二个坑是端点最大包长的设置。写Gadget驱动的时候端点描述符里的wMaxPacketSize必须跟UDC硬件的能力匹配。我见过有人设了个超大的值结果传输的时候DMA直接越界。这个值不是越大越好要查UDC的数据手册。第三个坑是URB的复用。URB是可以复用的提交完成后可以重新填充再提交这样能避免频繁分配释放的开销。但复用的前提是你必须在回调里确认上一次传输已经彻底完成否则会出现竞态。我早期写驱动的时候图省事复用URB结果偶发数据错乱查了很久才发现是复用时机不对。第四个坑是**dmesg日志级别**。默认情况下USB Core的很多调试信息是不打印的。如果你需要更详细的日志可以动态调整# 打开USB Core的调试日志 echo 8 /sys/module/usbcore/parameters/old_scheme_first # 或者用动态调试 echo module usbcore p /sys/kernel/debug/dynamic_debug/control动态调试dynamic debug是内核里一个非常强大的工具可以在运行时打开指定文件、指定函数的日志不用重新编译内核。调试USB问题的时候打开drivers/usb/core/下面的日志能看到很多平时看不到的细节。说到底Linux USB协议栈这套框架表面上复杂但它的分层逻辑是非常清晰的。你只要抓住设备驱动 → USB Core → HCD → 硬件这条主线再理解URB这个核心抽象剩下的都是细节。遇到问题的时候先别急着改代码先用dmesg、sysfs、usbmon这三板斧把现象看清楚大部分问题都能定位到具体是哪一层出了状况。真正需要动代码的其实没那么多。
返回列表