1. 一次U盘读写卡死之后,我开始认真对待NAK
去年调试一块RK3568开发板的Type-C OTG接口,往板载eMMC里烧写系统镜像。电笔试了十几次,每次都在同一个地方卡死:设备枚举一切正常,固件传输到一半,进度条突然不动,过几十秒报“下载失败”。一开始我怀疑是烧写工具问题、数据线问题、甚至是开发板供电不足,全换了一遍问题依旧。后来用逻辑分析仪去抓DP/DM线上的低速信号,才看到真相:主机一遍遍发起批量写入令牌,设备端在擦除Flash期间根本不接收数据,每个事务都返回NAK,而我们的主机库对这个端点没有任何NAK超时与恢复策略,NAK就这样一直刷下去,最后把整个USB总线的调度搅成一团。
这个案例让我彻底改变了看待NAK的方式。很多搞USB驱动和主机协议栈的人,一上来就背协议规范,知道NAK是设备“没准备好”的握手响应,知道硬件控制器一般会自动重试,但真正到了自研OTG主机库、或者要排查设备兼容性问题时,对NAK的理解往往停留在“硬件自动处理”的层面。这篇文章就围绕OTG主机库对NAK的处理展开,把我这几年的实际案例、主机库源码分析方法和工程取舍都摊开来聊。不管你是做嵌入式设备端固件、写USB主机驱动,还是调试Android系统的OTG外设兼容性,这篇内容应该都能给你一点参考。
2. 先弄清NAK到底是什么,为什么主机库不能装看不见
2.1 一次USB事务里的“忙”信号
USB总线通信的最小单位是事务,一次事务由令牌包、数据包、握手包组成。主机向某个端点发出IN或OUT令牌后,设备必须在规定时间内回应握手包。三种常见响应:
- ACK:数据已经正确接收,或者数据已经准备好送出。
- NAK:设备暂时无法接收或提供数据,但功能正常,请主机稍后再试。
- STALL:设备无法支持这个请求,或者端点已被halt,请主机停止访问。
NAK就是字面意思的“Not Acknowledged”,不过它跟网络协议里的NACK不太一样。USB协议中的NAK不是否定应答,更像一个“我正忙着,你过会再来”的排队信号。打个比方:你去窗口打饭,师傅说“稍等,正在备餐”,你退回去重新排队,过一会再问一次。师傅没有说“我管不了你这单”,只是让你等。
NAK出现在哪些传输类型里很关键。控制传输的Data和Status阶段,设备可以NAK;批量输入的IN令牌,设备没有数据时NAK;批量输出的OUT令牌,设备内部缓冲区满时NAK;中断传输的IN令牌,设备没有中断事件要上报时也NAK。等时传输不握手,没有NAK这回事,因为等时传输本身就是“不保证送达”的实时数据通道。
2.2 设备能连续NAK多少次
协议规范里没有限制设备连续NAK的次数。主机的批量端点如果一直往一个没就绪的设备发数据,设备可以无限回复NAK,只要它还没准备好。这个问题在纯协议层面无解,必须靠主机库的策略来终结。
一次NAK事务本身要消耗总线时间。拿高速设备来说,一个成功的批量IN事务大概要几百纳秒到几微秒,而一个被NAK掉的批量IN事务同样要消耗令牌包和NAK握手的时间。如果一个端点上每秒产生几万次NAK,主线带宽被白白浪费一大截。更严重的是,NAK事务会不断打断调度器对其他端点的服务,造成“一个忙设备拖死整条总线”的现象。
但这并不意味着NAK是坏事。NAK是USB流控机制里最基础的手段,设备端背压能力不足时,正是靠NAK保证数据不乱。主机库要做的不是消灭NAK,而是给NAK建立一套有边界的处理机制:允许它发生,但要知道它持续了多久、频次有多高、什么时候该放弃、放弃之后怎么办。
2.3 四种传输类型对NAK的容忍度完全不同
控制传输主要用于设备枚举、获取描述符、设置配置等管理类操作。设备在控制传输过程中NAK是正常现象,但控制管道的NAK不能无限等下去。Linux usbcore中,很多控制URB都有超时参数,比如usb_control_msg的timeout,常见默认值是5秒或30秒。超过时间主机库就返回-ETIMEDOUT,由驱动上层决定是重试还是复位设备。
批量传输对NAK的容忍度最高。U盘里的Flash在做擦写时,一个扇区的写操作可能要好几十毫秒,期间设备一直NAK,主机控制器通常会在硬件层面自动重试。软件层不需要每次NAK都跑一遍,但这不意味着软件可以完全不设防。块存储协议(SCSI/UFI)虽然有自己的命令超时,但USB层如果没有兜底,I/O请求可能挂得比你还急。
中断传输的NAK是常态。一个HID键盘,在没有键按下的时间里,主机每次轮询都会收到NAK。这种NAK不能算错误,甚至不能影响错误计数。主机库只需要按照设备描述符里的bInterval和对应速度下的轮询间隔,按部就班地发起IN事务就好。中断端点的处理原则是“NAK当空气”,既不重试也不报错。
2.4 为什么NAK对OTG主机库尤其麻烦
OTG设备通常既是主机又是外设,同一颗芯片要能在host和device之间切换。相比普通PC上的纯主机控制器,OTG主机库还多了不少麻烦事。
配套的协议栈往往运行在资源受限的MCU或嵌入式SoC上,CPU主频不高,内存也不大。如果NAK完全交给软件处理,每个NAK都触发一次中断,中断风暴能把CPU打满。如果完全交给硬件处理,又没有可以感知NAK状态的手段,出问题时只能干瞪眼。
角色切换时,原有的URB和端点调度队列可能还没清空。主机库从host切到device后,如果残留了一批正在等待重试的NAK队列,等角色切回来时,这些URB可能还卡在“等待设备就绪”的状态里,导致外设无法正常工作。我自己就遇到过一次:OTG口先接了U盘当主机用,拔掉U盘后切换到device模式连电脑,发现电脑端总是枚举失败。后来查下来是主机模式下某个批量端点还在等NAK重试,切到device时没有把该端点的调度队列清干净。
2.5 NAK和STALL要分清,很多Bug就出在这里
排查NAK问题时,最容易混淆的就是NAK和STALL。NAK是“暂时忙”,STALL是“我没法做这件事,别来烦我”。主机协议栈对两者的处理完全相反:NAK应该继续等待或重试,STALL则应该立即终止传输并向驱动返回错误。
很多驱动开发者在日志里看到设备一直回NAK,以为设备坏了,直接复位端口。实际上NAK持续期间设备可能只是正在擦写Flash,一旦擦完就能正常接收。反过来,有些设备对不支持的命令直接回STALL,驱动如果把它当成NAK反复重试,不仅浪费时间,还可能让设备端的主控状态错乱。所以在主机库里设计NAK处理逻辑时,事件上报来源必须能区分NAK与STALL,这是第一优先级。
3. 主机库是怎么处理NAK的:从Linux到自研栈的几种典型方案
3.1 先问清楚:NAK的处理到底在哪一层
USB主机系统的分层大致是这样:USB控制器硬件负责发送令牌、收数据包、响应NAK;主机控制器驱动(HCD)负责管理URB、端点调度、中断处理;USB核心层(usbcore)负责设备枚举、标准请求、设备驱动管理。再往上,才是块存储、HID、网卡这些具体类驱动。
NAK最先发生在控制器硬件这一层。不同控制器硬件对NAK的干预程度完全不同。EHCI/xHCI这类高性能控制器通常会在硬件调度器里自动重试批量和控制传输,NAK不会频繁打断CPU;而很多OTG芯片自带的控制器,比如DWC2、DWC3、CH9、OTG IP核,硬件自动重试的深度有限,有时需要软件介入。主机库的工作就是在“硬件能自动处理的就交给硬件,硬件处理不了的要能及时感知并接管”之间做平衡。
3.2 Linux主机栈的做法:靠URB状态机兜底
Linux内核里,NAK通常不会直接映射为URB的错误状态。USB HCD在硬件层面完成NAK重试,URB只有在超时、取消、设备断开或者控制器错误时才改变状态。驱动开发者能看到的,是一个URB长期处于“pending”状态,最后可能返回-ETIMEDOUT或-ESHUTDOWN。
Linux usbcore对控制传输的超时管理最明确。usb_control_msg()的最后一个参数就是超时时间,很多驱动会传5000或者30000毫秒。批量URB则没有通用的超时机制,一旦提交就无限期等待,直到设备完成传输、出错或者驱动主动取消。所以“NAK无限重试导致卡死”在Linux上不容易被主机栈自己发现,通常要上层驱动或应用程序主动设置超时。
usbmon在Linux调试中很有用,但必须说明一点:usbmon只能看到URB层面的提交与完成事件,看不到物理层每个事务是否NAK。你可以在usbmon日志里看到一个批量URB提交后过了很久才完成,却看不到期间到底发生了多少次NAK。想看到NAK计数,得用协议分析仪,或者支持调试寄存器读取的控制器驱动。这不是usbmon的缺陷,而是USB协议栈分层设计的结果,NAK本来就不属于URB这个概念能表达的层级。
硬件自动重试的大前提下,Linux主机栈真正要管的是“重试到什么时候为止”。HCD的调度器会按带宽预算和端点队列轮询各个端点,假如某个批量端点长期占用async队列,其它端点可能被饿死。某些控制器驱动里会有中断延迟检查,但整体来说,Linux把NAK的“最终超时”责任交给了驱动和上层软件。这对PC Linux没什么问题,因为xHCI硬件本身足够强,但对OTG嵌入式场景,直接套用Linux这套思路就不一定合适了。
3.3 自研OTG主机库的软件轮询模型
很多嵌入式项目不用完整Linux内核,而是用自研的USB主机协议栈,基于FreeRTOS、RT-Thread、Zephyr这类RTOS环境。我自己维护过一段时间的自研OTG主机库,这里把我们对NAK的处理模型拆开讲,比较有参考价值。
核心思路是:每个端点维护一个“传输上下文”,里面包含当前URB状态、重试计数器、首次NAK时间戳、最后NAK时间戳。控制器在完成中断里上报事务结果时,如果结果是NAK,主控驱动并不把NAK当成中断风暴,而是只更新上下文里的统计字段,然后把该端点重新挂到调度队列尾部。伪代码如下:
void host_channel_xfer_complete(endpoint_t *ep, xfer_result_t result) { if (result == XFER_NAK) { ep->ctx->nak_count++; if (ep->ctx->first_nak_ticks == 0) { ep->ctx->first_nak_ticks = now_ms(); } ep->ctx->last_nak_ticks = now_ms(); if (ep_type_is_bulk(ep) || ep_type_is_control(ep)) { uint32_t elapsed = ep->ctx->last_nak_ticks - ep->ctx->first_nak_ticks; if (elapsed > ep->ctx->nak_timeout_ms) { abort_urb(ep->ctx->urb, -ETIMEDOUT); reset_endpoint(ep); return; } } // 中断端点不重试不统计,直接等待下一次调度 if (ep_type_is_interrupt(ep)) { return; } queue_endpoint_for_retry(ep); } }这套模型有几个关键决策。批量端点的NAK要有时间预算,而不是计数预算。连续NAK 1000次和分散NAK 1000次是两回事,时间维度更能反映设备是否真的卡死。中断端点的NAK直接忽略,不重试、不计时间、不报错,等调度器下一轮再查。控制端点的NAK超时要设得短一些,控制管道是管理通道,不能让它被一个外设的忙碌状态拖死。
3.4 从TinyUSB和Zephyr主机栈中能学到的设计
TinyUSB主机模式的实现也值得参考。TinyUSB在底层驱动里会把NAK结果解析出来,传给上层usbh驱动模块。但不同控制器在NAK事件暴露上差别很大,有的控制器在完成中断里明确给出NAK状态位,有的控制器只给一个“传输未完成”事件,需要驱动自己判断是不是NAK。TinyUSB面临的核心挑战跟我们自研栈一样:如何用一套统一的上层接口,屏蔽不同控制器的NAK表达差异。
Zephyr的USB主机栈更偏重设备侧,其host控制器抽象层在NAK处理上同样要分别考虑轮询型和中机型控制器。轮询型控制器要求HCD在NAK后把端点重新放回调度队列,中机型控制器则要小心处理中断风暴。这类设计里最值得借鉴的一点是:把“NAK是否可上报”做成控制器能力标志。支持NAK事件上报的控制器,主机栈可以精确统计NAK频率;不支持的控制器,就只能靠“URB完成时延”这个间接指标来推断设备是否处于反复NAK状态。
3.5 软件层NAK统计:不打断中断,但是要记账
我见过不少OTG主机库设计,完全没有NAK统计这回事。控制器返回NAK,要么直接重试,要么忽略,代码里连一个计数器都没有。这样带来的问题是:设备兼容性出了问题,你根本没有数据支撑去判断是设备端太忙,还是主机调度策略有问题。
我觉得最低限度也要维护三个字段:
- 端点NAK总次数:持续累加,用于观察设备在某段时间内的忙碌程度。
- 当前URB的首次NAK时间:用于判断当前传输是否卡死。
- 连续无进展时间:上次成功传输到当前的时间间隔。
有了这三个字段,主机库可以在NAK率过高时打印警告日志,或者通过调试接口导出。没有硬性要求每个NAK都进中断,可以在完成中断里顺手更新一下计数器,累计到CPU能接受的程度。统计的意义不在于报警,而在于让你在复现问题时有一组可对比的数据。很多“灵异故障”只要看到底层的NAK计数,整个排查方向就清晰了。
4. 实测复盘:OTG场景下三类由NAK引发的“灵异故障”
4.1 案例一:RK3566 Type-C OTG烧写镜像中断
这个案例就是文章开头提到的场景。开发板通过Type-C OTG口连接到PC,PC端用烧写工具把固件发送到板子,板子端进入下载模式接收数据并写入Flash。问题现象是:每次写入到某个固定位置附近卡死,进度条不再前进,设备枚举仍然存在,但主机和板子之间就像失去联系一样。
用逻辑分析仪抓DP/DM信号后发现,主机在反复发送批量OUT令牌,板子端回复的是清一色NAK。进一步对照Flash操作的时序,卡死位置正好对应着Flash在进行大块擦除。设备端固件在处理擦除操作时,USB中断被长时间屏蔽,导致端点的FIFO没有被及时读取和释放。主机的批量OUT事务因此只能收到NAK。
问题根源不在设备端,而在主机侧。烧写工具使用的批量URB没有设置超时,主机控制器硬件一直在重试。重试了大约几十秒后,控制器内部状态出现异常,整个端口需要复位才能恢复。当时的OTG主机库没有“NAK时间预算”机制,也没有在NAK重试超时后主动复位端点的逻辑。
解决办法分两端处理。设备端固件在擦除Flash期间不能完全屏蔽USB中断,至少要周期性地服务一下端点FIFO,哪怕只是把数据收到RAM里暂存。主机端则给批量传输增加了NAK超时阈值,例如连续NAK超过5秒就主动终止该URB并复位端点,而不是让硬件无限重试。这样即使设备端响应不及时,主机侧也能快速恢复,不会拖死整条总线。
这个案例让我意识到一个很反直觉的事实:USB设备“卡死”很多时候不是死机,而是设备处于一个无法及时响应的正常忙状态,NAK处理策略决定了主机是选择等待、放弃,还是把整条总线都拖下水。
4.2 案例二:U盘通过OTG连接手机后变成“写保护”
这个故障在论坛里见过很多人问。U盘通过OTG口插到手机上,复制照片或文件,复制到一半弹窗提示“U盘写保护”,拔下来插到电脑上一看,U盘里的文件系统甚至出现了只读属性。很多人第一反应是U盘坏了,或者OTG转接头有问题。但把U盘插到电脑上,用专业工具一查,硬件本身完全正常。
这类问题的本质,我分析下来是这样:手机端的主机栈在批量写传输中遇到设备反复NAK,持续一段时间后达到某种超时或错误阈值。Android系统的卷管理服务(vold)或者文件系统驱动在检测到块设备I/O错误后,会把挂载状态切换为只读,防止数据进一步损坏。用户在UI上看到的“写保护”,其实是系统自我保护后呈现的结果,不是U盘物理写保护。
为什么U盘会长时间NAK?常见原因有两个。一是OTG口供电不足,U盘主控在写操作时供电电压跌落,Flash写操作失败,主控反复重试,对应的USB端点表现为长时间NAK。二是U盘本身的Flash写速度跟不上,或者文件系统碎片太多导致写放大严重,设备端忙不过来。这两种情况下,NAK是结果,不是原因。
从这个案例可以学到:主机库在处理NAK时,不能只盯着USB层,还要考虑上层文件系统策略。一旦你决定在NAK超时后返回I/O错误,文件系统就有权将设备切换为只读。如果产品不希望看到“写保护”这种用户感知强烈的错误,就得从更早的层面做文章,比如检测到供电不足时主动提示、降低写入速率、或者在NAK率升高时先尝试重试而不是直接报错。
4.3 案例三:Android OTG网络共享(RNDIS/ECM)吞吐量异常
有段时间我在调一块开发板,希望通过OTG口连接手机,用手机共享网络给板子。设备枚举正常,RNDIS网卡也能起来,但ping外网时延很高,下载速度只有几百Kbps。折腾了很多网络参数都不行,回到USB层才发现问题。
手机通过USB共享网络时,上行和下行数据分别走不同的批量端点。当板子向手机请求大量数据时,手机的RNDIS驱动如果来不及从USB控制器读取数据,就会在批量IN端点上返回大量NAK。板子端的主机库如果只用固定的调度策略,比如仍然以很高的频率轮询这个批量端点,这些NAK事务就会占满总线,把其它端点的服务全挤掉。
更隐蔽的一个坑是USB 3.0和USB 2.0之间的切换。现在很多手机在开发者选项里有一个“USB速度/配置”的选项,可以选USB 3.0或USB 2.0。某些OTG线材和连接器本身是USB 2.0的焊盘,主板只接了USB 2.0信号,但手机侧被切到USB 3.0模式以后,链路训练不完整,设备枚举后链路状态不稳定,导致批量传输时频繁出现链路层重试,端点上表现为大量NAK和NRDY。当时排查到这个原因后,把手机端的USB配置固定到USB 2.0,并把主机库对批量端点的调度间隔做了一点降频,吞吐量立刻恢复正常。
这个案例的教训是:OTG主机库不能假设设备端永远处于“即问即答”的状态。NAK的密度和分布,往往能反映链路质量和设备端背压。如果主机库能把NAK率过高的批量端点降权,把总线调度让给中断端点(比如HID),吞云吞吐问题就能缓解很多。
4.4 案例四:USB HID外设枚举后随机断连
这类故障在运行自研主机库的嵌入式设备上很常见。一个USB鼠标或键盘插上去能识别,但用着用着突然断连,过几秒又恢复。排查之后发现,中断端点在无事件时本来就该持续NAK,按照协议这是常态。但某些设备对中断查询的间隔非常敏感,如果主机调度器轮询间隔短于设备端实际处理能力,设备端会一直NAK,甚至可能触发设备端的错误恢复机制。
问题出在主机库对中断端点的间隔配置上。很多初版自研主机库会把所有中断端点都按1ms轮询,但对全速设备来说,USB描述符里的bInterval表示的是帧数,对高速设备则是2^(bInterval-1)微帧,对无线USB等特殊设备还有不同的解析方式。如果解析错误,轮询间隔过短,NAK风暴就会出现。我们把描述符解析逻辑修正,按设备速度正确计算轮询间隔后,断连问题彻底消失。
5. 排查NAK问题,主机库层面有哪些实用工具和手段
5.1 先做可复现性测试,把“灵异”变“必然”
排查NAK问题前,有一件很重要的事情:确认问题能不能稳定复现。不能稳定复现的问题,后面所有分析都可能建立在偶然性上。我在调试NAK相关故障时,通常先按这个顺序走:
- 固定使用同一个USB设备、同一根OTG线、同一个供电方案,先把“设备相关变量”压到最小。
- 用不同的主机侧控制器模式各测一遍,比如同一颗芯片分别跑DWC2和DWC3控制器驱动。
- 在同一台设备上连续跑100次压力测试,记录每次失败时的URB状态、耗时、dmesg日志。
如果问题只在特定设备上复现,那大概率是设备端兼容性问题;如果多台设备都复现,那主机库或硬件设计的嫌疑更大。这个分类能帮你省下大量时间。
5.2 usbmon、dmesg和ftrace的配合打法
Linux环境下,usbmon是最容易上手的工具。开启方式:
加载usbmon模块后,抓取总线1上的URB记录。输出会包含URB的提交、完成、错误事件。重点关注以下几类信息:
- 完成状态s = -110,代表-ETIMEDOUT,说明某个URB被超时终止,背后很可能存在长时间的NAK重试。
- 长时间pending的批量URB,时间戳跨度异常大,说明设备端可能处于忙碌状态。
- 连续多个URB以错误完成,且错误码相同,说明设备或控制器可能已经进入了非正常状态。
usbmon看不到物理层NAK,所以当usbmon显示“URB长时间pending”时,配合dmesg看控制器驱动是否上报了异常事件。例如xHCI驱动在响应超时时,可能打印类似“ERROR Transfer event TRB DMA ...”的信息,这类信息往往和端点的NAK重试超时有关。ftrace用来跟踪hcd驱动内部的端点调度函数耗时和中断处理时间,可以定位NAK风暴时CPU是否被中断处理占满。
5.3 逻辑分析仪和USB协议分析仪怎么选
逻辑分析仪适合抓低速和中速信号。USB 2.0全速和低速信号用24MHz以上采样率的逻辑分析仪就能抓,配合sigrok/PulseView这类开源工具可以直接解码URB层的token、data、handshake。看到NAK包连续出现时,可以统计NAK的间隔和数量,但受限于采样率和解码深度,不适合长时间大数据量抓取。
USB协议分析仪则是专业设备,比如TotalPhase Beagle 480、Ellisys USB Explorer之类。它们能深度解码USB 2.0/USB 3.x协议,直接给出NAK计数、总线利用率、错误统计。调试NAK风暴时,这类工具能直观地告诉你:某个端点上每秒产生了多少NAK,占用了多少总线时间,哪些事务在NAK期间被挤掉。如果公司预算允许,这类设备值得备一台。
我个人的习惯是:先用usbmon和dmesg把问题压缩到某个端点,再用协议分析仪做一轮短时间抓取,确认NAK的分布和频率。逻辑分析仪在涉及低速信号质量、VBUS压降、D+/D-上拉下拉时序的问题时更有用。
5.4 在自己的主机库里增加NAK健康度接口
如果主机库是自研的,强烈建议在早期就加上NAK统计和导出接口。具体做法:
- 在每个端点结构体里维护nak_count、last_nak_time、total_retry_counter。
- 在完成中断里更新这些字段,不打印、不打断正常流程。
- 提供一个调试CLI或API,随时可以查询某个端点上最近一段时间的NAK率。
- 当NAK率超过预设阈值时,在日志里输出警告,但不主动改变传输策略。
有了这些数据,排查兼容性问题时就能直接回答“这个设备到底有多忙”“它的NAK是持续性的还是突发性的”。没有数据支撑,全靠猜,效率差太多。
6. 关于NAK重试策略,我的工程取舍和最终体会
做OTG主机库这几年,踩过不少坑,也积累了一些很实际的取舍经验,这里一次性分享完。
控制传输的NAK,我倾向于“短等待、快失败、再恢复”。控制管道是USB设备的生命线,设备枚举、标准请求、类请求都走这条管道。如果控制端点一直NAK,先别急着无限重试,给一个比较保守的超时阈值,比如2到5秒;超时后直接复位设备并重新枚举,往往比重试更靠谱。因为控制管道不通,通常意味着设备端固件状态异常,再等下去也只是浪费时间。
批量传输的NAK,采用“硬件重试+软件护栏”的双层策略。硬件负责正常的NAK重试,软件只管一件事:设定一个合理的总超时时间。块存储设备写入时,Flash擦写导致的NAK可能要持续几十毫秒甚至上百毫秒,超时设太短会误杀正常操作;设太长又可能出现URB挂死。我用下来比较合适的是给批量URB设30秒到60秒的总超时,具体数值还要根据产品场景调节。如果NAK持续时间超过了总超时,立刻终止URB、复位端点,并向上层返回I/O错误,让上层决定是否重试或降级。
中断传输的NAK,核心原则是“不计错、不重试、按间隔轮询”。HID设备没有事件时本就应该NAK,这是正常工作状态。唯一要细心的是轮询间隔的解析正确,全速设备bInterval的单位是1ms帧,高速设备用的是2^(bInterval-1)微帧,搞错了就会出现NAK风暴。
对于OTG角色切换场景,务必在切换前清空所有正在等待NAK重试的端点队列。切换后主机库要处于一个干净状态,不能带着旧端点的“残留记忆”进入新角色。我的做法是在角色切换函数里显式调用一个endpoint_flush_all(),把所有URB终止,清空调度队列,再重新初始化主机控制器。
调试工具方面,usbmon负责快速定位URB层问题,协议分析仪负责确认NAK的分布和频率,逻辑分析仪负责排查信号质量相关的问题。三者不是替代关系,而是互补关系。如果你只靠usbmon去查NAK,大概率查不出结果,因为NAK层面根本不在usbmon的视野里。
最后说一点个人体会。很多USB主机栈的设计者把NAK当成一个协议层的小细节,觉得交给硬件控制器自动重试就万事大吉。但真正到了OTG嵌入式场景,你会发现NAK处理策略直接决定了设备的兼容性上限和故障恢复速度。我们自研主机库在加入NAK统计、超时预算、端点复位这三板斧之后,兼容性表格从十几款设备扩展到上百款设备,烧写工具再也没有出现那种位置随机、原因不明的卡死。如果你也正在被“USB设备随机卡死”“OTG外设兼容性差”这类问题困扰,不妨先去端点上挂个NAK计数器。大概率你会看到一些意料之外的数据,而这些数据,往往就是问题答案的一半。