尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

OTG U盘兼容性排查:从硬件供电到Android挂载的全链路优化

OTG U盘兼容性排查:从硬件供电到Android挂载的全链路优化 前阵子WK15那批样机交到测试手里之后反馈最多的就是U盘问题。插上U盘没反应、能弹出来但一拷大文件就掉、同一个U盘在这台机器上好好的另一台就不认……单看这些现象大家第一反应肯定是U盘兼容性太差。但我从头到尾查了一轮才发现所谓兼容性差只是个汇总现象真正的原因分散在硬件供电、OTG切换、内核枚举、存储驱动、Android挂载、U盘固件这几个完全不同的层面。这篇文章把我这次在WK15上从零开始做OTG U盘主机兼容性优化的完整思路写出来包括每个环节的排查方法、踩过的坑、最后的落地配置一次性讲透。你在别的平台上遇到类似问题也可以照着这条链路自己定位。1. 先从现象倒推兼容性问题其实是链路问题1.1 那些听起来一样的故障现象根因可能完全不同做兼容性优化之前我习惯先把测试反馈的故障现象收集起来按表现分类。因为“插上U盘没反应”和“能读盘符但打不开”虽然都是“不兼容”但排查路径完全不同。我这次在WK15上收集到的常见现象大概是这几类现象嫌疑最大的环节初步判断手段插入后完全无反应连指示灯都不亮VBUS供电、OTG切换、线材量VBUS电压、查ID/CC状态指示灯亮但系统没有任何枚举日志PHY、USB控制器驱动、D/D-信号dmesg抓usb枚举枚举成功但sda设备不出现usb-storage、SCSI层dmesg抓scsi/sd日志出现/dev/block/sda但vold挂载失败Android存储栈、文件系统支持logcat -s vold能挂载一拷贝大文件就断供电瞬态、USB autosuspend、U盘固件示波器看VBUS、dmesg看reset特定某个U盘不行其他都行U盘主控/固件兼容问题换主控驱动方式、加quirks发现没有同一个用户角度的“U盘用不了”在工程上其实是好几个不同层面的问题。如果不先分层闷着头改内核参数可能调了半天发现是硬件供电不够反过来也一样一上来就怀疑电路最后发现是vold编译时没把exFAT支持编进去。1.2 不分层定位就会越调越乱在WK15这个项目里我采用的排查顺序是固定的基本覆盖了从物理层到应用层的完整链条硬件层VBUS供电、OTG切换逻辑、D/D-信号、Type-C CC状态。内核层USB控制器驱动、PHY、usb-storage、SCSI、电源管理策略。Android系统层vold、卷管理、文件系统支持、权限和广播机制。U盘外设层主控方案、固件行为、文件系统、分区表。这个顺序不是随便定的。硬件层的问题如果不先排除后面内核调优做得再细都是白费。比如VBUS在插入瞬间掉到4.2V很多U盘的主控直接就复位移除了你后面改什么内核参数都没用。而且分层排查还有个好处每次改动都是可回退、可验证的。改一个点测试一轮记录结果下次再改下一个点。这样最后沉淀下来的结论才是可靠的而不是“我调了一堆东西反正现在好了”。2. 硬件排查VBUS供电能力、OTG线材和Type-C信号链路2.1 第一件事是量VBUS在插入瞬间的电压跌落U盘插入瞬间会有一个很大的电流浪涌。因为U盘内部的Flash主控、Flash颗粒、指示灯、DC-DC电路都要上电这个瞬间电流很容易超过500mA。如果VBUS的供电电路设计得比较紧张电压就会被拉低一旦低于U盘主控的掉电阈值那颗U盘就会在枚举刚开始的时候直接复位表现出来就是“插上没反应”或者“偶尔能识别偶尔不行”。所以我的习惯是任何OTG兼容性问题第一件事都是拿示波器量VBUS。测量点就取U盘插座的VBUS引脚和GND之间探头用短地线触发电平设成5V左右触发沿选下降沿然后插U盘看电压跌到多少、恢复时间多长。判断标准我一般这么掌握正常纹波下VBUS最低不能低于4.5V。如果插入瞬间跌破4.4V或者出现明显的振荡再恢复十有八九就是供电问题。解决方向是加大VBUS输出电容、增强限流开关的驱动能力或者检查有没有多余的分压/开关器件串在供电路径上。在WK15上遇到过一种很隐蔽的情况U盘插上去系统能识别但读写大文件到一半就断。量VBUS时发现U盘工作时候的电流一旦冲上去VBUS就会周期性掉到4.2V左右后来查出来是供电路径上有一颗保护IC的限流值设得太低换了一颗高限流的型号就好了。这类问题靠改软件是永远解决不了的。2.2 ID脚和CC逻辑决定了设备到底切没切进主机模式OTG的双角色切换是个容易出幺蛾子的地方。Micro-USB时代靠的是ID脚OTG线把ID对地短路设备检测到ID拉低就切主机模式。Type-C时代靠的是CC引脚上的Rp/Rd配置设备作为主机时CC上要拉Rp告诉对端“我是Host”作为从机时拉Rd告诉对端“我是Device”。很多兼容性问题的根源其实出在切换判断上。比如有的Type-C线或者转接头如果CC引脚处理不对设备会误判角色导致U盘插上去根本不会去枚举。排查方法很简单插入U盘后看系统日志里有没有OTG角色切换的记录。在RK3568这类平台上可以通过/sys/class/usb_role/下的节点查看当前角色也可以直接看dmesg里的usb0/udc相关日志。如果角色都没切到host后面就都不用看了。另外市面上Type-C转USB-A母座的线材质量参差不齐一些劣质线材的CC引脚根本没有接正确的电阻甚至只接了电源线没接数据线。所以排查兼容性问题时一定要备几条质量靠谱的OTG线先用它们排除线材因素再怀疑设备本身。别拿一根10块钱包邮的转接头测试测出问题来分不清是设备还是线的锅。2.3 不能忽视的信号完整性和ESD器件供电和OTG切换排完之后下一步是检查USB信号通道。USB 2.0的D/D-虽然是低速差分信号但如果走线过长、串阻阻值不对、或者ESD保护器件的寄生电容过大同样会导致眼图质量差、枚举失败率高。做兼容性测试时发现同一批WK15板子里部分板子对某些U盘不识别部分板子完全正常。后来量信号发现不识别的那批板子D/D-上多了一颗寄生电容很大的TVS管导致信号上升沿明显变缓。硬件同事把那颗TVS换成了低电容型号之后问题就没再复现。如果平台有USB 3.0接口还需要检查TX/RX差分对的阻抗匹配和连接器质量。USB 3.0的链路训练比USB 2.0苛刻得多线材和连接器稍微差一点就可能出现USB 3.0识别失败然后回落到USB 2.0甚至完全识别不了。这类问题在OTG场景下也经常出现特别是当U盘本身是USB 3.0时。所以硬件排查阶段我一般会做两个小实验一是换不同线材测试二是把同一颗U盘插到普通PC上验证U盘本身没问题。这两个实验能快速把“设备问题”和“外设问题”分离出来。3. 内核侧调优从枚举重试到存储驱动的关键参数3.1 先把USB Host这套配置确认清楚硬件没问题之后回到代码层面。第一步是确认内核里和USB Host相关的配置都对了。在RK3568这类平台上一般需要确认的有USB控制器驱动CONFIG_USB_XHCI_HCD、CONFIG_USB_EHCI_HCD、CONFIG_USB_OHCI_HCD。USB存储驱动CONFIG_USB_STORAGE。SCSI子系统和磁盘驱动CONFIG_BLK_DEV_SD、CONFIG_SCSI。文件系统支持FAT、VFAT、exFAT等。USB PHY驱动CONFIG_PHY_ROCKCHIP_USB等。很多时候方案商给的SDK默认配置已经把Host模式编进去了但为了省功耗或者减小内核体积可能会关掉某些不常用的选项。另外要注意的是设备树里USB控制器的dr_mode到底配的是otg还是host还是peripheral。如果配成了peripheral那不管你怎么插U盘设备都只能当从机。查看当前OTG模式的方法很简单插上U盘后看内核日志里有没有“New USB device found”这行信息。如果连这行都没有基本可以确定是OTG模式没切对或者PHY没起来。3.2 usb-storage的delay_use和quirksUSB存储这块有两个内核参数在兼容性优化中非常有用delay_use和quirks。delay_use是usb-storage模块的一个参数代表枚举成功后要等待多少秒再去访问U盘设备。默认情况下这个值通常是1单位是秒。理论上USB规范要求设备枚举后要有一定时间让U盘主控做好初始化准备但很多U盘的实际响应时间比较长特别是大容量或者低成本主控的U盘1秒不够就会出现“checking for media”超时导致挂载失败。在调试阶段我习惯临时把它调大一点看看情况# 查看当前值 cat /sys/module/usb_storage/parameters/delay_use # 临时改大重启失效 echo 3 /sys/module/usb_storage/parameters/delay_use如果改成3甚至5之后之前识别不了的U盘能识别了说明问题确实出在U盘初始化时间不够可以在内核启动参数里加上usb-storage.delay_use3永久生效。quirks参数更有意思。它允许你为指定的U盘强制设定某些SCSI/UAS行为格式是“vendorid:productid:flags”。最常见的用法是强制关掉UASUSB Attached SCSI协议让设备回落到传统的BOTBulk-Only Transport模式。有些USB 3.0 U盘宣称支持UAS但实际固件实现有问题在UAS模式下会出现指令超时、设备掉线、读写错误等情况。遇到这种U盘直接在启动参数里把它钉死在BOT模式# 假设U盘的VID:PID是1234:5678u标志代表禁用UAS usb-storage.quirks1234:5678:u这个方法在量产产品里很有用。U盘是千奇百怪的做不到每个都支持但针对特定客户常用的几款U盘做点定向优化是很现实的做法。3.3 关掉autosuspend减少随机掉线嵌入式平台为了省电一般都会开USB runtime PM。但对U盘来说autosuspend简直是随机掉线的温床。U盘在空闲一段时间后会被挂起很多U盘的resume实现并不完善唤醒之后直接失联或者需要重新枚举才能恢复。我遇到的情况是U盘插上去用得好好的过一两分钟不动它再访问的时候设备已经没了dmesg里全是reset或者device not responding的报错。调试时可以全局关掉USB autosuspendecho -1 /sys/module/usbcore/parameters/autosuspend如果想只针对某个U盘设备关掉自动挂起echo on /sys/bus/usb/devices/1-1/power/control需要注意的是关掉autosuspend会增加待机功耗。对电池供电的产品这个改动需要整体评估。折中方案是保留suspend但把resume超时时间调大或者给特定U盘设白名单。不过在兼容性优化阶段我建议先全局关掉验证方向确认问题确实出在autosuspend上再细调。还有SCSI层的timeout。默认情况下磁盘超时一般是30秒有些U盘在忙或者bad block重映射时会导致单条指令超过30秒系统就会把设备标记为离线。可以适当调大echo 60 /sys/block/sda/device/timeout4. Android上层的挂载与通知链路vold、exFAT和权限一个都不能少4.1 vold从看到分区到挂载成功的完整流程很多做底层驱动的人容易忽略Android上层的挂载逻辑。U盘被内核识别成/dev/block/sda只是第一步要让用户真正能在文件管理器里看到、访问U盘还要经过voldVolume Daemon的处理。vold的流程大概是这样的内核通过uevent向vold通知磁盘插入。vold对磁盘做分区扫描识别分区表MBR/GPT。vold识别每个分区的文件系统类型。vold把分区挂载到/mnt/media_rw/ 目录。通过FUSE或sdcardfs把/mnt/media_rw/ 映射到/storage/ 。MountService收到卷状态变化广播给上层应用。调试时最常用的命令是logcat -s vold sm list-volumes如果vold日志里没有任何反应说明uevent压根没送到vold如果日志里报“Unsupported filesystem”那问题出在文件系统支持如果日志显示挂载成功但上层文件管理器没反应那就要看广播和应用层了。4.2 exFAT/NTFS这类文件系统的支持问题WK15这个项目上最典型的坑就是exFAT。现在市面上新买的U盘默认格式基本是exFAT因为FAT32单文件不能超过4GB已经不适合高清电影和大型安装包了。但很多Android方案的SDK默认只支持FAT32exFAT要么没编进内核要么vold里没有对应的支持逻辑。处理exFAT需要两部分都到位一部分是内核驱动。新版本内核已经自带exFAT驱动fs/exfat只要打开CONFIG_EXFAT_FS就行。老内核可能需要使用三星的sdfat驱动或者exfat-nofuse。另一部分是vold对exFAT的识别。vold需要能够识别exFAT的blkid信息并正确调用mount命令。如果是自己编译的系统要确认vold构建时把exfat相关支持编进去了。NTFS的情况更复杂一些Linux下NTFS的写入支持要么用ntfs3新内核自带要么用Tuxera这样的商业方案。大部分产品不会默认支持NTFS写入只读倒是可以做到。处理这类问题最快的验证方法准备三颗U盘分别格式化为FAT32、exFAT、NTFS依次插上看vold日志能识别哪种、不能识别哪种一下子就清楚了。4.3 挂载成功不等于用户能访问权限和广播还有一个容易被忽略的点vold挂载成功之后上层应用能不能访问是另一回事。Android 10之后默认启用分区存储普通应用不能像以前那样直接访问/storage/XXXX-XXXX这个路径必须通过Storage Access FrameworkSAF或者系统级文件管理器才能读写。如果你们的自研App是直接拿文件路径去读U盘文件在旧版本系统上可能没问题但在新版本系统上就会报Permission denied。另外如果系统里的文件管理器是自己开发的要确认它有没有监听存储卷变化的广播。Android 9之前是ACTION_MEDIA_MOUNTEDAndroid 10之后是ACTION_MEDIA_MOUNTED已经废弃需要监听VolumeInfo相关的回调。很多自研Launcher或者文件管理器没适配这个变化结果就是U盘在系统设置里明明已经挂载了但界面就是没反应用户以为U盘没识别。5. 建立U盘兼容矩阵主控、容量、文件系统不是玄学5.1 U盘样本库怎么建才有代表性说到兼容性测试最忌讳的是拿手头那两三颗U盘测一测就宣布“没问题”。U盘市场的主控方案很多不同主控对USB枚举、SCSI命令、UAS协议、电源管理的实现细节完全不同兼容性问题往往只出现在特定主控或者特定固件版本的U盘上。建立样本库时我建议覆盖这些维度容量从16GB到256GB都准备一些。大容量U盘128GB以上通常用的Flash颗粒密度高主控的映射算法更复杂对SCSI命令的处理时序差别更大。接口USB 2.0和USB 3.0都要有。USB 3.0 U盘在插入USB 2.0接口或者Type-C接口时链路训练过程和原生USB 2.0 U盘不一样。主控品牌慧荣SMI、群联Phison、银灿Innostor、安国Alcor、芯邦Chipsbank这几个主流方案尽量都覆盖到。文件系统FAT32、exFAT、NTFS各准备一组。质量层次正规品牌U盘和白牌U盘都要有。白牌U盘的固件实现往往比较糙恰恰是兼容性问题的重灾区。怎么查U盘主控Windows下可以用ChipGenius这类工具Linux下可以通过lsusb看VID/PID再查数据库。有些白牌U盘会瞎标VID/PID查出来的结果不一定准确但作为一个参考足够了。5.2 测试项设计和量化标准兼容性测试不能光看“能不能识别”要设计一套可量化的测试项。我这次在WK15上用的测试项大概是这样测试项操作方式通过标准冷插入识别关机状态下插入U盘再开机开机后能自动识别并挂载热插入识别系统运行中插入U盘3秒内弹出存储通知大文件读写拷贝单个2GB以上的文件全程无断连、无校验错误小文件读写拷贝1000个10KB文件完整拷完无漏文件反复插拔快速插拔50次失败次数不超过2次休眠唤醒挂载状态下休眠唤醒后继续读写唤醒后U盘立即可访问写保护U盘打开写保护开关的U盘系统提示只读或正常拒绝写入每项测试都记录PASS/FAILFAIL的还要记录日志。这样一轮测下来哪类U盘、哪个测试项容易出问题数据一目了然。5.3 用矩阵做回归而不是靠“感觉”兼容性优化是个不断迭代的过程改了一个内核参数、换了一颗ESD器件、升级了vold都要重新跑一遍兼容矩阵。如果不做矩阵回归很容易出现“修好了A品牌U盘结果B品牌U盘又不行了”的情况。我这边每个版本的固件都会配一张U盘兼容性测试矩阵表测试人员按表跑研发按结果判断是否达到发布标准。这也是让测试和研发沟通成本最低的方式。很多项目组在兼容性上扯皮本质就是没有一个统一的标准和数据全凭感觉。6. 实操中的定位方法日志、抓包与参数验证6.1 dmesg和logcat怎么判断问题出在第几层排查时我一般同时开两个终端一个跟dmesg一个跟logcat。插上U盘后观察日志的输出顺序就能判断问题出在哪一层。正常流程下dmesg里应该依次出现usb 1-1: new high-speed USB device number 4 using xhci-hcd usb 1-1: New USB device found, idVendorXXXX, idProductXXXX usb 1-1: New USB device strings: Mfr0, Product1, SerialNumber2 usb-storage 1-1:1.0: USB Mass Storage device detected scsi host0: usb-storage 1-1:1.0 sd 0:0:0:0: [sda] 60753920 512-byte logical blocks sd 0:0:0:0: [sda] Write Protect is off如果日志停在“New USB device found”说明枚举成功但usb-storage没接住问题偏向驱动层面。如果连“New USB device found”都没有说明枚举压根没完成问题偏向硬件、PHY或者OTG切换。如果sda设备在dmesg里都出来了但logcat里的vold没有任何反应那问题就在Android层。这个判断方法不用任何特殊工具快速且有效。我强烈建议每个做底层开发的同事先把这套日志对应关系记熟排查效率能提高一大截。6.2 usbmon和协议分析仪怎么用遇到dmesg日志不够细的情况就需要抓USB协议层面的数据了。Linux内核自带的usbmon是最轻量的选择# 加载usbmon模块 modprobe usbmon # 抓取总线0上的USB数据1代表bus号u代表URB cat /sys/kernel/debug/usb/usbmon/1u usb_trace.txt然后复现一次U盘插拔分析抓到的数据里有没有SET ADDRESS、GET DESCRIPTOR、SET CONFIGURATION这些关键请求以及哪一步失败了。比如标准USB设备枚举失败往往卡在GET DESCRIPTOR阶段说明设备没有正确响应控制传输。如果SET CONFIGURATION之后一直发URB失败则可能是端点配置问题。如果手头有USB协议分析仪Total Phase之类那更直接硬件层的问题一眼就能看出来。不过协议分析仪价格不便宜不是每个项目组都有usbmon dmesg已经能解决90%的软件层面问题。6.3 改参数必须配合的验证流程最后一个经验改参数的时候一次只改一个而且每次改动都要完整跑一轮验证。不要因为赶时间“这个问题改了参数A那个问题改了参数B一起试试”那样万一出了问题你根本不知道是哪个改动引入的回退也没法回退。我自己的流程是明确当前要解决的问题记录复现步骤和日志。只改一个参数记录改了什么、为什么改。跑对应的测试项采集日志判断是否解决。如果解决了再跑一遍全量兼容矩阵确认没有引入新问题。确认没问题后把改动落到正式配置或代码里。改完参数之后还需要把内核日志、vold日志按测试时间归档。这些日志是后续分析问题的宝贵数据。很多看似随机的问题回看日志就会发现其实有规律可循。最后分享一个小习惯。做兼容性优化这事耐心比技术重要。U盘种类多问题千奇百怪但如果能把硬件、内核、系统、外设四层分开排查每一步都有数据和日志支撑问题总能定位到具体环节。我这次在WK15上沉淀下来的这套方法和测试矩阵后续切换到其他项目型号也能直接复用。下次遇到类似“兼容性差”的反馈别急着怀疑U盘先按这条链路走一遍很多答案自然就出来了。
返回列表