ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

USB端点和管道:从枚举抓包到驱动排障的完全解读

USB端点和管道:从枚举抓包到驱动排障的完全解读 做嵌入式或者驱动开发的人迟早要跟USB里的两个核心概念硬碰硬端点和管道。我当年第一次用Wireshark抓USB包看着URB结构里那些endpoint 0x81、pipe 0x00000001之类的字段完全不知道它们是怎么对应到手边这把U盘和那根串口线上的。后来啃了几个月的USB 2.0规范把枚举流程、端点描述符、管道机制一条条对到抓包数据里才算真正搞明白。这篇文章就用最直白的方式把这些东西讲透适合正在写USB固件、调驱动、或者被“USB设备无法识别”折磨的工程师。只要你看过一眼lsusb的输出或者听说过CDC虚拟串口、USB转串口驱动这类东西这篇文章就能帮你把疑惑串起来。1. 端点和管道到底解的是什么问题1.1 一条线缆上塞进几十条数据通道USB物理层其实就是4根线VBUS供电、GND地线加上D和D-一对差分数据线。所有设备都挂在同一条总线上靠地址区分。这就相当于一整栋楼的住户共享一条走廊快递员主机不可能同时给所有人送快递必须按门牌号逐个找。端点和管道就是这套“门牌号”和“投递线路”的具体实现。主机要跟设备里的某个功能模块通信比如给虚拟串口发一个字节、向U盘写一块扇区总不能把数据随便丢到总线上。数据必须有个明确的目的地这个目的地就是设备内部的端点Endpoint。而主机侧为了管理这次通信需要建立一条从主机软件到该端点的逻辑通道这条通道就是管道Pipe。很多新手第一次接触这组概念时容易混淆端点是硬件实体还是软件抽象我的理解是端点是设备内部的物理/FIFO/寄存器实体管道的本质是主机侧的逻辑绑定两者通过“端点号方向传输类型最大包大小”这组参数建立对应关系。换句话说端点决定“数据送到哪儿”管道决定“怎么送、按什么规则送”。1.2 管道的核心属性端点号、方向、类型、包大小USB规范里管道不像TCP连接那样需要复杂的握手和状态机它更像是一张“配置单”。管道由四个关键属性决定端点号Endpoint Number设备内部的编号比如EP1、EP2范围是0到15。方向DirectionIN表示设备到主机OUT表示主机到设备。注意这个方向是站在主机视角看的IN就是主机的接收方向。同一个端点号IN和OUT是两个完全独立的端点。传输类型Transfer Type控制、批量、中断、等时四种决定了管道的调度规则。最大包大小wMaxPacketSize管道里每个包最多能装多少字节这个值直接关系传输效率和枚举成败。把这些属性组合起来主机才能为一个端点创建一条正确的管道。你可以在Linux里用lsusb -v看到每个端点描述符也可以在Wireshark抓包时看到URB里携带的端点号和方向——这就是管道在主机软件侧留下的“影子”。2. 四种传输类型对应四种不同脾气2.1 控制传输先聊两句再干正事控制传输是所有USB设备必须具备的传输方式它走的是端点0也就是默认控制管道。控制传输的特点是双向、可靠、带宽有限专门用来做设备枚举和命令控制。每次控制传输分为三个阶段Setup阶段主机发送8字节请求、Data阶段根据请求类型决定数据方向、Status阶段完成握手。比如主机发送GET_DESCRIPTOR请求读取设备描述符就是通过这条控制管道完成的。全速设备的控制端点最大包大小可以是8、16、32或64字节高速设备固定为64字节。这个值写错了设备可能直接无法被识别。控制传输的用途远不止枚举。后续的SET_CONFIGURATION、SET_INTERFACE、类别请求比如CDC虚拟串口设置波特率的SET_LINE_CODING都走这条管道。所以很多虚拟串口上位机在设置波特率时实质就是通过控制管道发了一组特定请求。2.2 批量传输量大管饱但没准点批量传输Bulk Transfer适合传输大量数据但协议上不保证带宽和延迟。U盘读写、虚拟串口收发、USB网卡的数据通道基本都是批量传输。批量传输有CRC校验和自动重传机制可靠性非常高。全速批量端点的最大包大小是8、16、32或64字节高速批量端点固定为512字节。批量传输没有固定的轮询间隔主机有空就来取数据数据量大时可能抢不到带宽所以它不适合音视频这类需要实时性的场景。调试批量端点时最常见的现象是设备端FIFO里没有数据主机发了IN令牌设备返回NAK主机过一会儿再重试。抓包的时候看到一长串NAK基本可以断定设备FIFO没填数据或者填得太慢。这属于正常协议行为不是故障。2.3 中断传输我可是有出勤时间的中断传输Interrupt Transfer名字容易误导人它其实也是主机主动轮询只不过规定了固定的轮询间隔。键盘、鼠标、游戏手柄这类设备用中断传输因为它们的数据包小、对延迟敏感。端点描述符里的bInterval字段决定轮询间隔。低速和全速设备直接以毫秒为单位比如bInterval10表示10毫秒轮询一次高速设备则按2的幂次计算公式是2^(bInterval-1) * 125微秒所以高速中断端点的实际间隔最小是125微秒。很多人的驱动问题就出在这个字段把高速设备的中断轮询间隔写成10结果实际间隔变成了1280微秒延迟高得离谱。中断端点的最大包大小也有明确限制低速1到8字节全速1到64字节高速1到1024字节高带宽模式。2.4 等时传输准点发车丢了不补等时传输Isochronous Transfer专门为音视频这种实时数据流设计。它保证带宽和周期性但不保证可靠性——没有重传机制丢包就丢了反正一帧画面丢了正好赶下一帧。USB摄像头、USB声卡、USB音频设备几乎都用等时端点。全速等时端点的最大包大小是1到1023字节高速等时端点则是1到1024字节并且每个微帧可以执行多次事务通过wMaxPacketSize的高位字段指示。等时端点的描述符比普通端点复杂它的bmAttributes字段里还细分了同步类型异步、自适应、同步和用法类型数据、反馈、隐式反馈这些参数对于实现音频设备特别重要。热词里的esp32直接连接usb相机、usb蓝牙rgb控制器这类场景最终都会落到端点类型的选择上摄像头走等时端点蓝牙控制器的HID部分走中断端点RGB灯的控制通道可能走批量或控制端点具体由固件决定。3. 管道怎么建立从设备插进电脑那一刻说起3.1 默认管道所有USB设备的第一条管道端点0是每个USB设备自带的特殊端点它是双向控制端点主机和设备所有枚举通信都通过这条默认管道完成。设备上电后不需要任何配置端点0就处于可用状态。很多人对“默认管道”这个词没有概念。你可以把它想象成新员工入职时的接待窗口不管你是谁第一天报到都是先到这个窗口填表、领工牌、分配到工位。USB设备也一样任何设备必须先通过端点0完成“报到”其余端点管道才能在配置完成后投入使用。因为端点0是所有USB设备通信的起点它的最大包大小bMaxPacketSize0必须写对。这个值在设备描述符的偏移7处主机第一次读取设备描述符时只能读到一个包的长度所以如果这个值错了整个枚举就直接失败了。热词里那些“stm32无法识别usb设备”“usb设备无法识别”的案例很多问题都出在这个初始握手环节。3.2 一次完整的枚举过程把枚举过程完整走一遍比背规范有效得多设备插入主机检测到D/D-电平变化判断设备是全速、高速还是低速。主机对总线复位设备回到默认地址0。主机通过默认管道端点0向地址0发送GET_DESCRIPTOR请求设备返回设备描述符前8字节其中就包含bMaxPacketSize0。主机发送SET_ADDRESS请求给设备分配一个1到127的地址。主机用新地址再次读取完整设备描述符和配置描述符配置描述符里包含接口和端点信息。主机发送SET_CONFIGURATION选择某个配置。配置生效后该配置下所有非默认端点的管道才真正可以使用。注意第6步很关键即使端点描述符里写得清清楚楚只要主机不执行SET_CONFIGURATION这些端点是不会被激活的。很多设备固件在枚举后忘了准备端点接收数据上位机一连接就超时就是因为设备端认为“配置过了”实际上固件并没有正确初始化端点缓冲区。3.3 配置、接口、端点三级结构决定管道数量管道不是随便建立的它受制于USB的描述符层级结构一个设备可以有多个配置Configuration一个配置下有多个接口Interface一个接口下可以有多个端点Endpoint。这个层级结构在驱动开发里非常重要。比如标准的CDC ACM虚拟串口设备通常有两条接口接口0是通信接口里面有一个中断端点用于上报线路状态接口1是数据接口里面有一个批量IN端点和一个批量OUT端点。Windows的usbser.sys驱动会按接口类和端点类型自动匹配然后把批量端点映射成串口的读写信道。如果固件里的接口数量、端点数量或者端点属性与驱动预期不符就会出现“设备能被识别但驱动无法启动”或“设备感叹号”的现象。我遇到过不少国产USB转串口模块的固件把数据接口的端点写成了中断传输结果在Linux下被识别为HID设备数据收发一塌糊涂。所以认清三级结构是配置管道的第一步。4. 实操把端点和管道“抓出来”看看4.1lsusb -v一行命令看到所有端点在Linux下枚举信息最好用的工具就是lsusb。插上任意一个USB设备先用lsusb找到它的Bus和Device编号再执行详细查询。lsusb # Bus 001 Device 012: ID 0483:5740 STMicroelectronics Virtual COM Port lsusb -d 0483:5740 -v假设你插的是一块STM32做的CDC虚拟串口输出里会有类似这样的关键片段Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0043 bNumInterfaces 2 ... Interface Descriptor: bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 2 Communications ... Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt wMaxPacketSize 0x0008 8 bytes bInterval 10 Interface Descriptor: bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 10 CDC Data ... Endpoint Descriptor: bEndpointAddress 0x81 EP 1 IN bmAttributes 2 Transfer Type Bulk wMaxPacketSize 0x0040 64 bytes bInterval 0 Endpoint Descriptor: bEndpointAddress 0x01 EP 1 OUT bmAttributes 2 Transfer Type Bulk wMaxPacketSize 0x0040 64 bytes bInterval 0bEndpointAddress的0x81怎么读低4位是端点号1bit4是方向1即IN合起来就是EP1 IN。0x01则是EP1 OUT。bmAttributes2表示批量传输3表示中断传输。wMaxPacketSize0x0040即64字节。这些字段直接对应管道的四个关键属性。看这些输出时我建议你把描述符层级跟实物对应起来一个配置描述符管一套“供电模式和工作状态”一个接口描述符管一种“设备功能”端点描述符则定义了这个功能里的数据通道。看多了以后一眼就能判断这个设备挂出来的是串口、网卡还是摄像头。4.2 用Wireshark抓USB流量把管道里的包看穿想真正理解管道机制靠描述符还不够必须抓包。Linux下先加载usbmon模块Windows下装USBPcapWireshark都能直接识别USB流量。sudo modprobe usbmon # 打开Wireshark选择 usbmon0 或对应的usbmonX接口抓包时重点关注几个字段URB id唯一标识一次请求Type表示URB类型Transfer Type表示传输类型Endpoint表示端点号方向通过Direction字段区分IN/OUT。我举一个真实排障经历调试一个自研STM32虚拟串口上位机能打开端口但发送数据后设备毫无反应。用Wireshark抓包发现主机确实在往EP1 OUT发出BULK数据但设备侧一直没有返回ACK反复出现NAK。顺藤摸瓜查到固件里根本没写EP1_OUT的中断处理函数设备端点缓冲区没有及时读走数据主机发多少都被NAK挡回去。改完中断处理函数数据立刻通了。这是典型的“管道已建立、但设备端不受理”问题不抓包很难定位。CTF比赛里的.pcapng usb ctf流量包也是同一个道理。很多题就是键盘或鼠标的USB抓包你要从中断端点里把HID报文还原成按键值遇到U盘镜像题则要分析批量端点里的SCSI命令和扇区数据。掌握端点管道视角处理这类题目会轻松很多。4.3 端点描述符逐字节解读结合原始描述符数据看印象更深刻。标准端点描述符固定7字节比如原始byte流07 05 81 02 40 00 00逐字节解释07描述符长度固定7。05描述符类型0x05表示端点描述符。81bEndpointAddress。低4位是端点号1bit4为1表示IN方向。02bmAttributes。低2位为2表示批量传输。40 00wMaxPacketSize小端表示0x0040即64字节。00bInterval。批量传输没有固定轮询间隔所以为0。如果换成中断端点可能是07 05 82 03 08 00 0A第5字节03表示中断传输第6字节08表示最大包大小8字节第7字节0A表示轮询间隔10毫秒。自己写固件的时候我习惯把配置描述符数组里的这些字节挨个核对一遍尤其注意wMaxPacketSize的字节序和bInterval是否填反了。很多“设备无法识别”其实就是这里字节写错导致主机解析配置描述符时直接崩掉。别问我怎么知道的。5. 真实排障那些被端点和管道坑到崩溃的案例5.1 STM32虚拟串口能枚举但发不出数据用STM32做CDC虚拟串口是最典型的入门项目但很多人栽在同一个坑设备管理器识别出了COM口、驱动也正常但上位机发数据设备收不到或者设备发数据上位机收不到。先确认管道两端的配置用lsusb -v或抓包查看数据接口的批量IN/OUT端点是否正确存在。如果确认端点描述符没问题用Wireshark抓包看BULK OUT的URB到达设备后是否返回NAK。如果一直是NAK说明设备端的端点FIFO没被及时取走大概率是固件里没写OUT端点中断处理函数或写错了回调函数入口。如果看到URB正常ACK但应用层收不到数据那就是中断回调到应用层的通知机制断了属于代码逻辑问题。反过来设备发不出数据先检查BULK IN端点是否在主机发IN令牌之前就已经把数据放进FIFO。USB规范里来自设备的IN事务是主机主动发起的设备FIFO里没数据就会自动回NAK主机就会一直重试。解决办法是把数据准备好再调用端点发送函数让FIFO在IN令牌到达前处于“有货”状态而不是等收到IN令牌才去准备数据。5.2 “USB设备无法识别”先查端点0Windows下最常见的“无法识别的USB设备”十有八九是枚举阶段就失败了跟系统缺不缺驱动关系不大。枚举全靠端点0这条默认管道所以排查重点应该放在端点0相关的描述符和时序上。先查bMaxPacketSize0是否正确。全速设备这个值必须与固件实际配置的端点0包大小一致高速设备必须写64。再查设备描述符里的bcdUSB版本号有些老固件在USB 2.0主机上填了USB 1.1版本虽然也能跑但可能导致主机对端点0包大小有错误预期。最后检查SET_ADDRESS之后的响应时序地址改完后设备必须在主机新地址下正常响应不能仍用地址0应答。如果抓包结果显示主机发了GET_DESCRIPTOR但设备一直没有ACK大概率是设备固件卡在某个初始化流程里没有进入应答状态或者端点0的发送逻辑没写好。注意控制传输的Data阶段可能是IN也可能是OUT设备MCU代码里要区分处理别只写了发送没写接收。5.3 USB转串口装不上驱动可能是芯片没枚举成功网上搜ft231x usb uart驱动、ch340驱动下载这类词的场景通常是插上USB转串口线系统提示“Unknown Device”。很多人第一反应是下驱动但实际上驱动只解决“识别后再匹配”的问题如果枚举都失败装什么驱动都没用。CH340、FT231X、CP2102这些芯片本质上都是带端点的USB设备它们的BULK端点负责收发串口数据控制端点负责配置波特率。芯片通电后如果晶体没起振、D/D-上的上拉电阻不对、或者PCB走线导致信号质量差枚举就会失败。这时候应该先换一根线、换一个USB口、甚至用示波器量一下D/D-波形而不是反复重装驱动。同理热词里的周立功usb转canfd接口卡、usb转485驱动、usb转iic这些工具内部都是不同芯片公司做的USB桥接设备有的遵循CDC ACM类有的是私有类。遇到装不上驱动先确定它在设备管理器里是什么状态如果是“Unknown Device”走枚举排查如果有设备名但感叹号那才轮到驱动版本或PID/VID匹配问题的排查。5.4 虚拟机挂USB设备说到底是整个设备树的映射热词里还有个有意思的方向给物理服务器挂USB硬盘、ESXi挂载USB硬盘、怎么把USB挂载到指定虚拟机上。这个场景跟端点管道有什么关系虚拟化平台把物理USB设备“直通”给虚拟机时本质上就是把设备的整个描述符结构配置、接口、端点映射给虚拟机里的USB控制器虚拟机里的操作系统和驱动面对的就是真实设备的端点和管道。如果虚拟机里识别不到设备先检查虚拟机的USB控制器是否开启再检查宿主机的USB设备是否已被其他占用。设备直通后虚拟机里的驱动匹配逻辑和物理机完全一样照样看PID/VID和接口描述符。5.5 USB抓包时看到一个很长的管道字段怎么读有时候Wireshark里会看到pipe字段像0x0000000b这种十六进制数。它其实是主机侧USB控制器驱动给这条管道分配的句柄具体数值由控制器驱动决定不是USB规范定义的标准字段。如果你需要在Wireshark里按端点筛选直接用usb.endpoint_number、usb.transfer_type、usb.direction这些标准字段更准确。6. 别把这三个“管道”混在一起6.1 USB管道、匿名管道、物理管道“USB端点管道”这个标题搜索量大有很大一部分原因是“管道”这个词在多领域撞车。经常跟USB一起被搜出来的还有操作系统里的匿名管道Anonymous Pipe、命名管道Named Pipe以及管道机器人、燃气管道图像数据集、水下管道裂缝数据集这些完全不同的物理管道。USB协议里的管道Pipe是主机与设备端点之间的逻辑通信通道它不是一个实体。Windows/Linux里的匿名管道是进程间通信机制一个进程往里写、另一个进程往外读数据只在内存里流转。物理管道更不用说了钢管、PE管、PVC管那是真的用来送水送气的。三者完全不是一回事但搜索引擎只认关键词所以总被搅在一起。6.2 “端点”也有同名歧义“端点”这个词也容易误伤。ARCgis里标志地块端点坐标那是地理信息里的多边形节点坐标端点星工程助手是某个软件系统里的服务端点概念Docker、微服务架构里常说的“服务端点”Endpoint指IP地址加端口。做USB开发时说的“端点”特指USB设备内部的数据传输节点。听到别人说“服务端点不可达”或者“配置管理端点”得先分清上下文别一上来就套USB那一套。遇到这种同名不同义的情况我的习惯是先在脑子里把领域范围框死凡是涉及D/D-信号、设备描述符、URB、lsusb、usbmon的都是USB协议域凡是涉及socket、HTTP URL、服务发现的是网络协议域凡是涉及进程间通信、重定向流水线的是操作系统概念。域定了概念自然就不乱了。写到最后分享一点个人经验新手学USB最忌讳一上来就背规范。最好的路径是先抓一次枚举过程的包从GET_DESCRIPTOR到SET_CONFIGURATION一步一步对照着看再拿一个USB转串口模块用lsusb -v把它的端点描述符翻出来然后故意改一个参数比如把端点0的包大小写错观察枚举失败的现场。这套流程走完端点、管道、传输类型、描述符层级这些东西就不再是抽象名词了。我现在拿到一个不认识的USB设备第一件事就是抓枚举包看它挂出来几个接口、哪些端点、什么传输类型心里就有数了。这套路子强烈建议你也试试。
返回列表