ARTICLE DETAIL

资讯详情

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

u-boot下用U盘加载固件:从USB初始化到fatload的排障详解

u-boot下用U盘加载固件:从USB初始化到fatload的排障详解 最近在调一块基于ARM64平台的核心板遇到的最直接的一个需求就是把几MB的升级固件通过U盘灌进板子。板子的网络接口还没完全调通TF卡驱动处于“能识别但不稳定”的状态剩下最可靠的路径就是USB。u-boot下把U盘里的文件读出来表面上就是一条usb start; fatload usb 0:1 0x82000000 upgrade.bin的命令但从USB控制器初始化到U盘枚举、再到FAT文件系统解析背后其实是一条完整的软件链路。我花了两天时间把这条链路完整跑通过程中踩了不少坑也把u-boot的USB驱动模型、存储协议、文件系统兼容性这些细节都翻了一遍。这篇文章就把从控制器初始化到fatload读U盘的完整过程和排障经验写出来给准备在u-boot阶段用U盘做烧录、做产品升级的朋友一个参考。1. 为什么要在u-boot里折腾U盘场景、需求与整体方案1.1 什么场景下必须在u-boot阶段读U盘很多人觉得u-boot里读U盘是个冷门需求毕竟进了Linux之后挂载U盘太简单了一句mount /dev/sda1 /mnt就完事。但在下面几个场景里u-boot阶段的USB支持是绕不过去的第一种是开发板bring-up阶段。网络驱动、SD/MMC驱动可能都还没稳定但你想先验证kernel镜像、设备树、rootfs能不能正常启动。这时候用U盘在u-boot里把文件load到DDR里再bootm引导是成本最低的方案。第二种是产线批量烧录。很多设备没有网口或者不允许接网口产线工人拿着一个量产母盘插上设备、上电、自动烧录这个流程必须在u-boot阶段完成因为烧录动作就是要往emmc/NAND里写固件还没到Linux那一层。第三种是变砖恢复。emmc分区表损坏、kernel起不来但u-boot还在可以用U盘从外部加载一个恢复镜像把flash重新刷一遍。这三种场景有一个共同点u-boot环境没有完整的驱动模块机制也没有udev帮你自动创建设备节点。你要什么设备就得先把对应的驱动编译进u-boot里。读U盘本质上是“控制器初始化 设备枚举 块设备读取 FAT解析”四个环节缺一不可任何一个环节出问题最终表现都是“fatload失败”。1.2 u-boot读取U盘的完整链路我们可以把这条链路按层次拆开看USB控制器驱动层负责把EHCI/xHCI/DWC2这些控制器硬件初始化起来包括时钟、复位、VBUS供电、PHY配置。USB core枚举层负责端口复位、读取设备描述符、分配地址、读取配置描述符把U盘识别成一个USB设备。设备类匹配层u-boot根据接口描述符里的设备类bInterfaceClass 0x08 Mass Storage Class把设备绑定到usb_storage驱动。传输协议层u-boot的usb_storage驱动使用BOTBulk-Only Transport协议通过CBW/CSW命令与U盘交互底层是基于SCSI命令集的READ/WRITE。块设备层usb_storage注册为blk device提供blk_dread()/blk_dwrite()接口上层不管U盘内部是什么主控、什么闪存统一按块读写。文件系统层u-boot的FAT文件系统驱动通过blk_dread读取U盘扇区解析FAT表最终提供fatload/fatls/fatinfo这些命令。如果只是执行一条fatload就完事你可能感受不到这个层次。但一旦出问题你必须能判断出问题到底出在哪一层——是硬件没起来、枚举失败、存储协议不兼容还是文件系统解析不了。后面第5章我会专门用一个实际案例演示怎么逐层定位。2. u-boot USB驱动架构拆解从host控制器到文件系统层2.1 驱动模型视角下的u-boot USB新版u-boot比如2022.04我这次用的版本已经全面切到driver modelDM架构。USB相关的uclass主要涉及这几个UCLASS_USBUSB控制器主机端设备例如EHCI、xHCI、DWC3。每个控制器在设备树里对应一个节点。UCLASS_USB_HUBUSB Hub设备包括根Hubroot hub和外部Hub。UCLASS_USB_DEVICE实际插入的USB设备U盘就属于这一类。设备树里的USB节点长这样以我手头板卡的OTG口为例usbotg1 { status okay; dr_mode host; vbus-supply reg_vbus_5v; phy_type utmi; };u-boot的USB子系统在DM模型下会自动把设备树里的控制器节点和host驱动绑定起来。这里的dr_mode host很关键——如果控制器同时支持OTG你必须在u-boot阶段明确指定host模式否则初始化方向可能不对。vbus-supply会告诉驱动哪路电源控制5V输出如果缺少这个属性驱动可能不会去拉高VBUSU盘压根就没上电。老版本u-boot没有DM直接在板级文件里用usb_lowlevel_init()注册控制器宏定义也散落在头文件里。虽然我现在用的新版本但排障思路和驱动层次基本一样老项目的经验也能复用。2.2 必须打开的配置项u-boot里读U盘第一个坑就是配置项缺失。不同的配置缺失失败的现象完全不同配置项作用缺失时的现象CONFIG_CMD_USB提供usb命令命令行里敲usb start直接报“未知命令”CONFIG_USB_EHCI_HCD/CONFIG_USB_XHCI_HCD控制器主机驱动usb start看不到bus初始化信息CONFIG_USB_STORAGEMass Storage设备支持usb tree能看到设备但usb storage列表为空CONFIG_DM_USB启用USB驱动模型设备树里的USB节点不生效CONFIG_FS_FAT/CONFIG_CMD_FATFAT文件系统支持没有fatload/fatls命令CONFIG_USB_HOST_ETHERUSB网卡支持与读U盘无关但排查时容易误开基本上读U盘的最低配置组合是CONFIG_CMD_USB CONFIG_USB_EHCI_HCD CONFIG_USB_STORAGE CONFIG_FS_FAT CONFIG_CMD_FAT。如果你的控制器是xHCI或者DWC3再把对应的HCD配置打开。我之前就在一份配置里漏掉了CONFIG_USB_STORAGE结果usb start和usb tree全都正常就是usb storage空荡荡一度误以为U盘坏了。2.3 存储协议BOT与UASP的兼容性这个点是最容易忽略的。u-boot对USB存储设备的支持主要集中在BOT协议Bulk-Only Transport这是USB 2.0时代定义的Mass Storage传输协议。而很多USB 3.0移动固态盘、U盘默认使用的是UASPUSB Attached SCSI Protocol或者自动在UASP和BOT之间切换。当u-boot扫描到U盘时它期望看到的是BOT接口的Mass Storage设备。如果U盘固件选择只暴露UASP接口u-boot的usb_storage驱动就绑定不上表现就是枚举成功了但拿不到块设备。我手里有几块不同主控的U盘实测下来SMI、Phison这类常见主控基本都保留了BOT兼容但一些新出的NVMe桥接方案、高端移动固态盘在u-boot里经常读不到。这不是u-boot有问题是协议层对不上。遇到这种情况最直接的测试方法是换一块普通的USB 2.0 U盘验证。如果普通U盘能读问题基本锁定在UASP/主控兼容性而不是板卡驱动。3. 控制器初始化到设备枚举核心流程与驱动适配细节3.1usb start背后到底发生了什么在u-boot命令行敲usb start实际执行动作是starting USB... Bus usb1c14000: USB EHCI 1.00 scanning bus for devices... 2 USB Device(s) found这个输出里隐藏了几个关键步骤usb_init()遍历系统中所有的USB控制器uclass逐个调用控制器的usb_lowlevel_init()。usb_lowlevel_init()负责控制器硬件初始化——开时钟、解复位、配置PHY、拉起VBUS。控制器起来之后usb_host_scan()对根Hub执行端口扫描也就是枚举流程。枚举成功后再对每个新发现的USB设备调用usb_scan_device()读取描述符、分配地址并尝试绑定驱动。如果你在多个控制器比如一个native EHCI加一个PCIe转USB的xHCI可以敲usb start autou-boot会自动遍历所有控制器并逐个扫描设备。不过我个人习惯用默认usb start因为日志少、定位更清楚。3.2 控制器初始化最容易出问题的三个细节控制器硬件初始化在u-boot里可比Linux简单粗暴得多没有完整的runtime PM没有延迟探测也没有中断驱动。我这次在控制器初始化阶段就遇到了三个典型问题第一时钟和复位。很多SoC的USB控制器时钟默认是关闭的或者USB模块还处在复位状态。u-boot的设备树里如果clk属性不全驱动起不来现象就是usb start之后卡住或者直接显示找不到bus。解决办法是把设备树里USB节点的clocks、resets属性补全去对照SoC的TRM确认USB控制器属于哪个时钟域。u-boot对clk的处理比Linux精简有些平台需要自己加clock-notifier这点在移植时尤其要注意。第二VBUS上电逻辑。主机模式下U盘需要VBUS提供5V电。u-boot不会像Linux那样调用一个完整的regulator框架有的控制器驱动会去读设备树里的vbus-supply有的板子需要你手动把VBUS的GPIO拉高。如果设备枚举总是超时先用万用表量板子USB座的VBUS引脚有没有5V这个判断最快。我这次就是靠量电压定位到VBUS使能GPIO配置错了。第三PHY配置。控制器和USB PHY是两回事很多SoC把USB PHY独立成一个IP。u-boot里PHY的初始化往往不如ATF/SPL阶段完整如果u-boot是从SPL或者ATF之后接管系统的而前级启动代码没初始化PHYUSB就起不来。这种情况下不要在u-boot里死磕先确认前级阶段有没有对PHY做配置或者干脆在板级初始化函数里补上PHY的寄存器配置。3.3 枚举成功的标志与日志解读U盘枚举成功u-boot通常会显示scanning bus for devices... 2 USB Device(s) found然后usb tree能看到层次结构USB device tree: 1 Hub (480 Mb/s, 0mA) |_ 2 Mass Storage (480 Mb/s, 200mA)看到Mass Storage这一行说明设备类匹配成功了u-boot已经认定这是一个存储设备。如果这里显示的是一堆Vendor Specific Class那说明U盘主控暴露的接口描述符不是标准的Mass Storage类存储驱动很可能绑不上。枚举失败的报错也很有讲究。最典型的是unable to get device descriptor (error8)error8对应USB传输层的STALL错误常见原因是U盘上电瞬间电流过大、VBUS电压跌落或者DP/DM线上的上拉电阻有问题。如果出现timeout polling on port一般是端口复位或者高速握手超时这时候要检查USB PHY的终端阻抗配置和时钟稳定性。一个非常容易被忽略的排查顺序先看usb tree再看usb storage。这两条命令之间的差别能直接告诉你问题是在枚举层还是存储驱动层。我在下面第5章的排障案例里会重点用到这个对比。4. fatload命令的实际使用地址规划、分区号与文件系统坑4.1 fatload参数解析等U盘被识别成块设备之后才能执行fatload。命令格式是fatload interface dev[:part] addr filename [bytes]实际使用中fatload usb 0:1 0x82000000 upgrade.bin这里的usb 0:1中间有个冒号含义是“USB控制器0、第1分区”。冒号前的0对应usb start时注册的控制器编号不是U盘编号冒号后的1对应U盘存储设备上的第一个分区。如果U盘上有多个分区读取第二个分区就是usb 0:2。配套的命令还有fatls usb 0:1列出U盘根目录文件。fatinfo usb 0:1显示文件系统信息包括卷标、FAT类型、簇大小。fatwrite usb 0:1 0x82000000 readme.txt 0x100把内存数据写进U盘文件写操作在量产测试里很常用。有个小技巧拿不准分区编号时先用usb storage看设备编号再用fatls usb 0:1试探大多数U盘只有一个FAT分区0:1就够了。4.2 内存地址规划别把固件load到u-boot自己身上很多人以为fatload地址随便填一个就行其实不是。我必须强调加载地址一定要落在有效的DDR范围内并且避开u-boot自身、环境变量、设备树blob这些区域。先跑bdinfo查看DDR布局U-Boot bdinfo DRAM bank 0: start 0x80000000, size 0x40000000这表明DDR从0x80000000开始大小1GB。那么0x82000000就是合理地址离起始地址有32MB偏移足够避开u-boot自身通常占用底部几MB。如果板子的DDR起始地址是0x20000000你往0x82000000写地址根本不在DDR范围内内核会瞬间崩溃或者直接hang死。一个常见的内存规划方案用途地址大小u-boot自身0x80000000 ~ 0x8100000016MBkernel镜像 load地址0x8200000032MBdtb load地址0x830000008MBinitrd/rootfs0x84000000128MB黄色部分是我习惯留出的安全区宁可多留一点也不要让kernel镜像和dtb离得太近很多奇怪的启动问题其实是地址重叠导致的。另外一个坑是DMA buffer对齐。u-boot的FAT驱动每次读取一个簇底层块设备DMA要求地址满足cacheline对齐。如果你用了一个奇奇怪怪的地址可能会遇到FAT: Misaligned buffer address解决办法也很简单地址按64字节对齐0x82000000这种本身就是对齐的不要自作聪明用0x82000100之类带偏移的地址。4.3 FAT文件系统兼容性为什么你的U盘fatload总是失败这是另一个高频雷区。u-boot的Fat驱动对FAT12/16/32支持得好对exFAT的支持要看版本和配置。新版u-boot可以打开CONFIG_FS_EXFAT来支持exFAT但默认配置里经常没开。而NTFS在u-boot里至今没有像样的支持不要指望它。现在出厂的64GB、128GB U盘Windows默认格式化出来的往往就是exFAT。你拿到u-boot里执行fatls usb 0:1很可能直接报Failed to mount. Please check the device.这时候不是U盘坏了是文件系统不认。解决办法是把U盘重新格式化成FAT32。Windows自带的格式化工具对超过32GB的分区比较固执通常不给FAT32选项可以用第三方分区工具或量产工具强制格式化为FAT32。我实测过用Rufus做启动盘时把文件系统改成FAT32也能解决u-boot认不出的问题。还有一个细节是分区表格式。MBR分区表是u-boot兼容性最好的GPT分区在部分老版本u-boot上可能识别不了。如果你U盘之前用某个工具写成了GPT多个分区usb 0:1可能会指向一个不存在的东西报** Bad device usb 0 : 1 **。最稳妥的做法是用磁盘管理把U盘的所有分区删掉新建一个主分区格式FAT32再拷文件。5. “能枚举但读不到盘”的完整排障实录5.1 一个典型问题案例这次调试过程中最有代表性的问题出现在第2天U盘插上后usb start正常枚举也正常但就是读不到文件。完整日志如下U-Boot usb start starting USB... Bus usb1c14000: USB EHCI 1.00 scanning bus for devices... 2 USB Device(s) found U-Boot usb tree USB device tree: 1 Hub (480 Mb/s, 0mA) |_ 2 Mass Storage (480 Mb/s, 200mA) U-Boot usb storage No USB device found注意到没有usb tree里明明显示Mass Storage设备存在但usb storage列表为空。这说明枚举层已经把一个USB设备识别成了存储类设备但usb_storage驱动没有成功拿到块设备接口。问题出在设备类匹配之后、块设备注册之前。5.2 逐步排查链路我按照下面的顺序一层层排查每一步都有明确结果步骤操作结果1usb info查看设备描述符能显示PID/VID说明描述符读取正常2换普通USB 2.0 U盘还是不行排除U盘主控UASP问题3用万用表量VBUS电压5V稳定排除供电问题4检查CONFIG_USB_STORAGE配置配置已打开排除编译选项问题5直接插板卡USB口不经过Hub依然不行排除外置Hub兼容性6开DEBUG级别日志重新u-boot发现usb_stor_get_info()在获取MaxLUN阶段卡住最后定位到问题根源这块U盘实际是读卡器加TF卡读卡器主控支持Multi-LUN上报的MaxLUN大于0而u-boot在某个版本对Multi-LUN设备的处理有兼容性缺陷导致后续的INQUIRY命令没能正常完成块设备注册失败。5.3 解决方案与验证当时我没有去改u-boot源码涉及工作量不小先用一块普通U盘替代确认整条链路是通的。后面为了根治我把u-boot里usb_stor_get_info()中关于MaxLUN部分的逻辑做了小改动如果MaxLUN读取失败或异常强制按单LUN处理。修改之后重新编译同一块读卡器TF组合也能正常fatload了。这个案例给我的启发是排障时一定要分清“枚举成功”和“存储设备注册成功”是两件事。usb tree是最快的信息分层工具它告诉你USB设备层的状态usb storage告诉你块设备层的状态。两个输出对不上直接定位到存储协议和驱动的交界处不要反复去折腾硬件。如果遇到更诡异的情况比如枚举都过不去我建议你用USB抓包工具逻辑分析仪、USB协议分析仪看看枚举过程中的数据传输重点看SET_ADDRESS和GET_DESCRIPTOR有没有STALL。有时候一根看似正常的USB线在高速模式下就是枚举不过去换一根短线就好了这种玄学问题用示波器反而更快。6. 让U盘加载固件成为默认动作bootcmd编排与扩展玩法6.1 bootcmd里编排usb和fatloadu-boot读U盘不是只为了手动测试。产品量产、现场升级都需要上电自动从U盘加载固件。我的习惯是把自动升级流程写进bootcmdsetenv bootcmd usb start; fatload usb 0:1 0x82000000 boot.img; bootm 0x82000000 saveenv这样板子上电后自动执行usb start如果U盘里有boot.img就load到内存并引导。有个细节要注意usb start的执行时间取决于USB扫描流程如果U盘没插扫描会超时耗时会明显拉长。量产场景下我一般这样处理setenv bootdelay 1 setenv bootcmd if usb start; then if fatload usb 0:1 0x82000000 boot.img; then bootm 0x82000000; fi; fi; run distro_bootcmd用两个if嵌套USB初始化失败或U盘文件缺失时自动回退到其他启动设备不会让产线或现场因为U盘没插而卡死在u-boot。6.2 热插拔与量产场景的注意事项u-boot没有中断驱动的热插拔机制它的USB状态只在命令执行时更新。也就是说你先执行了usb start然后插上U盘再执行fatload大概率失败。正确的做法是每次插入U盘后重新执行一次usb reset或者完整usb start。量产烧录的经验我也分享一下U盘质量参差不齐主控兼容性是最大的变量。建议备几块不同主控的U盘做验证不要只测一款。线材越短越好。U盘插在板卡USB口上用短延长线或直接插不要通过前置面板的长线转接高速信号对线缆质量很敏感。供电要稳。如果板卡从USB口给外设供电后级USB Hub或者大电流U盘可能倒灌拉低VBUS导致枚举失败。量产工装建议用独立供电的Hub。固件文件命名固定。比如统一叫firmware.img脚本里写死减少工人手动操作出错。其实在这个场景里u-boot的开发体验很像在做一个“极简操作系统”——没有中断、没有守护进程、没有高可用机制一切靠命令触发。理解了这一点很多奇怪的毛病就能从根上想明白。6.3 从host读U盘到gadget设备的扩展思路读完U盘只是u-boot USB能力的一半。如果你想让PC把板子识别成一个U盘往里面拷文件走的是完全相反的方向——USB gadget。u-boot里对应的命令是umsums 0 mmc 1这条命令把板子的eMMC作为一个U盘挂给PC在调试和生产加载数据时非常有用。但要记住一个USB控制器同一时刻只能工作在host或gadget一种模式设备树里的dr_mode需要在启动前定好。如果你在调host模式读U盘就别同时期待这个口还能当gadget用除非控制器和OTG检测都支持动态切换。我觉得u-boot读U盘这件事掌握了之后最大的收益不是“能load文件了”而是你会真正理解一条存储链路从硬件到文件系统要走多少环节。后面再遇到USB打印机、USB网卡、USB摄像头这些设备的驱动移植本质上都是同一套枚举和设备绑定逻辑只是中间的类驱动不同。最后分享一个我调节了无数次之后的心得u-boot里调试USB先看线再量电压然后用usb tree分层次最后才怀疑代码。这四步走完绝大多数问题都能定位到具体某一层剩下的就是改配置或者换设备的问题了。
返回列表