ARTICLE DETAIL

资讯详情

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

U-Boot移植的索引化思路与排错指南

U-Boot移植的索引化思路与排错指南 搞嵌入式Linux开发这些年我经手过的板子少说也有十几种从ARM9到Cortex-A53从老掉牙的AT91到新出的国产双核每次都躲不开U-Boot移植这道坎。说是移植其实大多数时候并不是从零写代码而是把厂商SDK、参考板源码、芯片手册和硬件原理图这些素材重新组合再一点点调通。这个过程最磨人的地方在于知识点太散今天卡在DDR参数明天卡在设备树节点后天又卡在网络PHY的复位时序。如果每次都是现查现用等于把同一个坑踩了十遍。所以我后来养成一个习惯用“索引”的方式来组织整个移植过程。文章里我要讲的就是这套U-Boot移植索引该怎么建、关键路径上有哪些坑、不同平台怎么对照以及一张可以直接抄作业的排查速查表。适合正被U-Boot折磨的、想系统梳理移植流程的嵌入式开发者也适合准备从裸机转Linux的朋友先建立整体认知。1. 我为什么建议用“索引”思路做U-Boot移植1.1 移植的真正难点不是编译而是“不知道下一步该看哪里”很多第一次接触U-Boot移植的朋友以为最难的是编译。实际上编译出错反而是最友好的问题编译器会告诉你文件路径、行号、缺失的宏定义照着改就行。真正让人崩溃的是程序烧进去之后串口一个字符都不出或者打印停在某个地方再也不动了。这时候你面对的是一大团黑盒可能是CPU没跑起来可能是SPL没加载可能是串口引脚没复用也可能是DDR初始化直接挂掉。U-Boot移植涉及的知识面比普通单片机项目宽得多启动汇编、链接脚本、时钟树、DDR控制器、设备树、驱动模型、环境变量、启动命令每个模块单独拎出来都能写一篇很长的文档。人的工作记忆是有限的不可能把所有细节都记在脑子里。我见过不少工程师移植卡住之后把厂商手册从头翻一遍效率低还容易漏关键项。反过来如果一开始就把所有知识点和排查路径整理成“索引”遇到问题就能先定位到某个模块再顺着索引去查具体原因效率完全不一样。1.2 一份合格的移植索引应该包含哪几个维度我常用的索引结构可以分成六个维度每个维度对应一类问题。硬件层记录芯片型号、板卡版本、DDR颗粒、启动介质、串口引脚源码层记录U-Boot版本、参考板、board目录、defconfig、dts路径启动流程记录从BootROM到SPL再到U-Boot再到内核的每个阶段外设驱动记录网卡、存储、USB、显示等模块的设备和驱动匹配关系环境变量记录默认bootcmd、bootargs以及实际验证过的启动命令排错记录则把每次故障的现象、原因、解决方法按时间线写下来。这个索引不一定要做成复杂的工具我最初就是用Markdown维护一个表格后来内容多了再拆成多个文件。关键是建立“先查索引再动手”的条件反射而不是每次都在网上重新搜索。比如有一天你发现网卡不通如果索引里已经记录了“这款PHY需要GPIO0拉低复位”这样的结论十分钟就能解决如果没有记录又要从看原理图开始重新走一遍半天就没了。2. U-Boot移植前的准备板级信息和物料清单2.1 拿到开发板后先收集哪些资料很多人拿到新板子第一件事就是git clone U-Boot源码开始编译这是最不推荐的做法。我一般先花半天时间把资料收集齐放进索引的“硬件层”里。最基础的是SoC芯片手册重点看memory map、启动模式、时钟树和GPIO复用表然后是原理图至少要把串口、DDR、电源、启动介质、网卡PHY这几页看明白接着是原厂SDK或者参考板U-Boot源码这是移植最重要的素材绝大多数板子都能在官方参考板的基础上改出来。除了文档工具也得提前准备好。串口模块必须要有建议带TTL电平的USB转串口线烧录工具要看启动介质SD卡就用写卡工具NAND/NOR Flash就要用厂家提供的烧录器或者U-Boot自己的tftp烧写。调试器有条件也可以备一个JTAG没有也没关系串口加打印基本能覆盖大部分问题。最后还要准备一张空白的记录表把板卡型号、芯片型号、DDR型号、串口引脚、启动拨码开关状态这些信息登记好这些内容就是索引的骨架。2.2 源码版本与交叉编译工具链的选择U-Boot版本选择是个容易被忽视的坑。新版本U-Boot对设备树和驱动模型Driver Model的依赖很强这不完全是好事。如果SoC厂家SDK用的是U-Boot 2017而你直接拉最新的U-Boot 2024源码大概率会遇到大量驱动API不兼容的问题比如dm_i2c_read这样的函数签名改了或者原有平台文件被完全重构。我的建议是优先用SoC厂家SDK配套的U-Boot版本在这个基础上做板级适配。除非你有明确需求比如要支持新文件系统、需要修复安全漏洞才考虑版本升级而且升级时要把官方release note里的迁移点过一遍。交叉编译工具链也要跟着SDK走。32位ARM平台一般用arm-linux-gnueabihf-64位平台用aarch64-linux-gnu-但具体版本有讲究。工具链太新可能引入编译错误太老可能不支持某些新指令或新特性。我踩过一回用系统自带的gcc 12编老版本U-Boot结果在链接阶段报错换回SDK自带的gcc 7就一切正常。所以索引里一定要记录“哪一个版本源码配哪一个工具链”这行字能省掉无数折腾时间。3. U-Boot移植关键路径从串口到系统的第一盏灯3.1 最小系统先让U-Boot在串口上开口说话移植U-Boot我自己习惯拆成几个最小目标。第一个目标不是启动到内核而是让U-Boot在串口上输出完整启动日志。CPU上电后固化在芯片内部的BootROM代码会先运行根据启动引脚电平决定从SD、NAND或USB等介质加载代码。这个阶段CPU厂商已经写好我们不需要动但必须确认启动介质选择正确。如果拨码开关拨错位置U-Boot根本不会被加载自然没有任何输出。接下来的代码就是你自己的地盘了。U-Boot早期启动会运行start.S里面的汇编然后是lowlevel_init、board_init_f这些函数再往后才进入C语言世界。串口能输出的前提是UART控制器时钟打开了、引脚复用正确、波特率设置对、驱动代码被编译进当前板子。我的经验是先不要一次修改一整套配置而是只保证串口输出。比如从参考板的defconfig开始只改串口相关的宏定义和引脚配置编译烧录后看打印。如果没输出优先怀疑引脚复用和时钟可以用示波器或者逻辑分析仪测量串口引脚是否有电平翻转。提示有些芯片原厂会提供预编译的固件第一次拿到板子时先用原厂固件验证硬件和串口是否正常再烧自己编译的U-Boot。这样可以排除“板子本身有问题”的干扰项。3.2 DDR初始化的索引式排查DDR初始化是U-Boot移植的第一大难关也是初学者最容易劝退的地方。U-Boot运行到一定阶段需要把DDR控制器配置好才能把代码搬进内存里继续执行。如果DDR参数不对CPU一访问内存就异常表现就是串口输出到某个位置后戛然而止或者压根没有输出。DDR参数包括频率、列地址位宽、行地址位宽、bank数量、各种时序参数如tRCD、tRP、tRAS这些数值都要根据DDR颗粒手册和SoC的DDR控制器手册一起换算。解决DDR问题最有效的路径就是去参考板源码里找现成的DDR初始化代码。很多SoC的DDR参数是通过结构体配置的比如三星平台的mem_ctl、全志平台的dram_para原厂已经把经过验证的颗粒参数写好了。我们要做的是对照自己板子上的DDR颗粒型号和参考板的颗粒差异通常只需要修改容量大小和位宽时序参数一般能通用。改完之后不要急着继续往下做先用U-Boot自带的mtest命令或者厂商提供的内存测试代码测一下读写稳定性多跑几轮确认没有数据错误。这个环节非常适合做索引。把每一块板子的DDR颗粒型号、频率、控制器寄存器基地址、关键参数来源记成一张表。下次换DDR颗粒或者换新板子直接查表就能定位到可以复用的参数块不用重新翻手册。3.3 设备树DTS板级信息的索引文件现代U-Boot已经把大量板级信息放进设备树设备树本身就像一本硬件索引。我们在u-boot源码里的arch/arm/dts目录下往往能找到芯片厂家提供的参考板dts文件移植时要做的第一件事就是复制一份改成自己的板子名。设备树里常见需要修改的地方包括model和compatible字符串这两个要跟代码里的板级匹配逻辑对应memory节点下的reg属性改成实际DDR容量和起始地址serial节点要确认当前使用的串口号和时钟频率ethernet、mmc、usb等控制器节点要确认status属性为“okay”。设备树节点并不是写了就生效它需要和驱动代码匹配。U-Boot驱动模型通过compatible字符串把设备节点和驱动绑定起来如果dts里写的compatible和驱动里面的of_match表对不上设备就不会被识别。所以移植时如果某个外设没工作除了查硬件还要检查dts节点是否完整、compatible是否匹配。我维护的索引里专门有一页记录每个外设对应的dts节点路径、compatible字符串和驱动文件位置调试驱动时打开这页对照比在源码里grep半天快得多。设备树编译也不难U-Boot编译时会自动把dts编译成dtb并打包进镜像或者单独生成一个dtb文件。只要确保dts文件被包含进当前板子的Makefile里编译产物里就能找到它。3.4 从U-Boot到内核启动参数与引导命令U-Boot本身跑起来只算完成了一半另一半是把它配置成能引导内核。U-Boot通过环境变量决定启动流程最核心的是bootcmd和bootargs。bootcmd是一串命令U-Boot启动后自动执行比如从MMC加载内核镜像和dtb再bootm启动bootargs是传给内核的命令行参数包括consolettyS0,115200、root、rootfstype等等。移植时必须根据板子实际的存储介质和内核位置来设置这两个环境变量。我一般会先在U-Boot命令行手动敲命令确认每一步都能成功再把命令串成bootcmd写进环境变量。比如从SD卡启动就先敲mmc info确认卡识别然后mmc part、fatls mmc 0:1看分区和文件再用fatload加载uImage和dtb最后bootm。全部验证没问题后用setenv bootcmd fatload mmc 0:1 0x42000000 uImage; fatload mmc 0:1 0x44000000 board.dtb; bootm 0x42000000 - 0x44000000保存下来。使用saveenv把环境变量写入存储介质下次上电就能自动引导。启动参数这块同样要索引化我习惯把所有试过的启动方式记录清楚从SD卡怎么启动、从网络tftp怎么启动、从NAND怎么启动每条命令都留一行备注。这样哪怕过了一个月再回来看这个项目也能照着索引快速恢复上下文。4. 不同芯片平台的移植差异对照4.1 经典ARM32平台从参考板改起来最省事经典的ARM32平台比如Cortex-A9、Cortex-A7系列U-Boot移植路径已经非常成熟。最典型的做法是在源码里找一颗相似SoC的参考板以它为蓝本修改。比如早期的S5PV210、Exynos 4412这些平台网上能找到大量基于旧版本U-Boot的移植教程它们的board目录、configs目录和dts文件都被前人翻烂了。如果你手头正好是这类芯片第一步应该去对应厂家的开发板SDK里翻出旧版U-Boot对照着移植到新版本U-Boot比如从U-Boot 2017起步重点核对时钟初始化、GPIO复用、DDR配置和网卡驱动。这类平台移植时要注意board头文件里的配置宏比如CONFIG_SYS_TEXT_BASE是U-Boot的运行地址CONFIG_SYS_SDRAM_BASE是DDR基地址CONFIG_SYS_INIT_SP_ADDR是初始栈指针。这些宏定义错了程序根本跑不对位置。旧版U-Boot大量使用宏来配置板级信息而新版可能已经改成设备树和驱动模型移植过程中要对齐这两套体系不能漏项。4.2 64位平台与ARMv8启动流程到了ARMv8 64位平台启动流程变成长链条。典型的ARMv8启动会经历BootROM、BL1、BL2、BL31这些阶段ATFArm Trusted Firmware负责管理EL3和PSCI电源管理U-Boot通常作为BL33运行在EL2或者EL1。这对移植影响很大U-Boot不再直接接在BootROM后面而是由ATF跳转过来U-Boot可以通过PSCI接口调用底层电源管理能力。很多新手拿着64位板子还在按老思路找“启动到U-Boot的汇编完整流程”结果发现代码被分到了多个独立工程里容易懵。移植64位平台时我强烈建议不要自己从头搭ATF链直接使用SoC厂家提供的ATF和引导整套镜像。U-Boot这边主要关注自己作为BL33的部分包括串口、DDR、时钟这些基础外设是否已经由前级初始化好还是需要在U-Boot里重新初始化。另外64位平台几乎都依赖设备树节点写法也要比32位平台更规范比如CPU节点要包含enable-method、clocks等属性。索引里把“启动链BootROM未动 - ATF - U-Boot”这个顺序写清楚排错时先判断当前停在哪一波能节约大量时间。4.3 常见国产SoC的注意事项最近几年国产SoC用得越来越多不少芯片原厂提供了完整的U-Boot SDK但往往是某个旧版本改造而来并且会夹带大量私有驱动代码。移植这类平台时要注意几个问题。第一个是版本兼容厂家代码可能修改了U-Boot的核心框架如果你试图升级到新版本U-Boot必须把厂家所有补丁重新移植一遍工作量非常大。第二个是设备树完整性问题有些厂家的dts只写了SDK里用到的外设其他控制器节点没写你每接一个新外设都要自己补节点。第三个是驱动闭源或半开源部分驱动以独立库形式存在需要按厂家文档集成。对国产平台索引的价值尤其明显因为很多坑不是公开资料里能查到的。我会记录每个外设的调试结论比如“USB PHY供电GPIO必须在probe之前拉高”“网卡MDIO需要延时50ms再访问”这些都来自实际排查是真正的第一手经验。后续如果有人接手这个项目先看索引再动代码能少走很多弯路。5. U-Boot移植常见问题与排查速查表5.1 系统毫无输出怎么办系统完全没有输出是所有问题里最让人头疼的。按照我的排查习惯会沿着启动链从前到后逐段排除。先确认电源和时钟用示波器检查各主要电源轨电压是否正常晶振是否起振复位引脚是否在正确的电平状态。接着确认启动介质选择对照芯片手册看boot引脚电平确保U-Boot镜像确实被加载到了正确位置。然后是串口本身确认串口引脚没有接反板子上是否有RS232电平转换芯片有些调试口还要确认跳线是否连接。如果硬件基本确认无误那就进入软件排查。先检查串口初始化的底层代码比如samsung平台的uart_base地址、引脚复用配置、波特率分频值。这里最容易出问题的是引脚复用因为SoC的UART引脚往往和多功能引脚冲突如果没有在代码里设置GPIO复用寄存器数据根本送不到芯片外面。我遇到过UART1默认复用成GPIO导致串口全无输出修改pinctrl寄存器后立刻打印正常这个案例后来也进了我的索引。注意代码里如果定义了CONFIG_SYS_EARLY_PRINT通常可以提前输出一些早期打印信息。开启这个宏调试无输出问题非常有用但要注意它可能依赖当前的ARM架构实现不同平台支持程度不一样。5.2 U-Boot启动到一半卡死串口输出了几行但打印停在一个固定的上下文这种问题比完全无输出好查一些。打印停止的位置本身就是索引的关键信息。比如停在一行网卡初始化问题多半在MII时钟或者PHY复位停在DDR相关打印问题多半在DDR时序参数不稳定停到解压内核部分可能是内核镜像加载地址不对或者设备树有问题。我特别想提的是重定位relocation这个坑。U-Boot早期代码在flash或者SRAM里运行之后会把自己从存储介质搬到DDR的高地址区继续运行。如果CONFIG_SYS_TEXT_BASE和实际加载地址不一致或者DDR没有完全初始化重定位后代码就飞了。这种问题表现很典型串口正常打印一段接着出现乱码或者全卡死。排查时可以关掉重定位相关功能或者把CONFIG_SYS_TEXT_BASE调整到别的地址再观察输出变化。另一个常见原因是环境变量损坏。U-Boot会从存储介质的某个固定偏移位置读取默认环境变量如果读取到了魔数错乱的数据可能导致启动过程异常。解决办法是在U-Boot命令行执行env default -a然后重新设置必要的变量。如果板子上有清除环境变量的按键或者能直接擦除相应Flash分区也可以从硬件层面清零。5.3 网卡/存储/USB识别不到外设识别不到首先要在U-Boot命令行人工检查设备是否存在。网卡可以先执行dhcp或者ping网关如果失败就看网卡驱动有没有报错比如PHY address不对、MDIO总线通信超时、没有发现PHY芯片。然后检查硬件确认PHY芯片的地址引脚、复位引脚和时钟。很多时候PHY不工作的原因就是复位脚拉高时序不对或者晶振没起振。存储设备识别不到要分情况。MMC设备先确认mmc list有没有输出如果设备都没枚举到检查dts里mmc节点和SD卡检测引脚。USB设备类似先看usb start后控制器枚举到的设备列表如果为空大概率是控制器电源或者dts节点status有问题。把每个外设的排查结果记进索引会形成一套非常高效的“现象-原因”速查手册。5.4 一张可直接抄作业的速查表我自己整理速查表一般用三列结构现象、可能原因、解决路径。下面是移植U-Boot时最常遇到的一批问题可以直接复制到自己的索引里继续扩展。现象可能原因解决路径串口完全无输出启动介质选择错误、串口引脚复用错误、波特率不对、DDR挂死检查boot引脚/拨码开关核对UART引脚mux和时钟用原厂固件验证硬件输出乱码波特率不一致、串口电平不匹配、主频和分频配置错误核对U-Boot和终端波特率检查UART时钟源和分频系数打印停在一行后卡死时钟或DDR初始化有问题、重定位失败、环境变量损坏记录卡住位置查对应模块排查DDR时序检查CONFIG_SYS_TEXT_BASEmtest命令报地址错误DDR容量配置超出芯片实际大小地址总线有问题核对bank选择脚和地址线连接减少DDR容量测试范围网络不通PHY地址不对、复位时序不对、MDIO通信异常、dts节点不完整测量PHY时钟和复位确认MDIO引脚连接检查compatible匹配SD卡识别不了供电不足、SD卡检测引脚配置错误、mmc节点status异常检查卡座引脚核对dts节点换一张低速卡排除兼容性内核启动崩溃bootargs传参错误、dtb地址错误、内存冲突确认mtdparts和console参数检查dtb加载地址与内核解压地址这张表不是死的每次遇到新问题我都会往表里加一行。半年下来它就是一份完全属于自己的排错工具比任何网上教程都管用。6. 把索引思维带走从U-Boot到其他系统移植6.1 内核移植、RTOS移植、应用移植的通用套路U-Boot移植的索引思维其实可以平移到很多类似场景。比如FreeRTOS移植LVGL看起来和U-Boot八竿子打不着但思路完全相通第一步先梳理硬件和驱动确认显示接口的初始化代码第二步建立图形库适配层搞清楚LVGL需要哪些底层函数第三步做性能调优比如帧率低就查DMA加速和缓冲策略。这些环节同样可以做成索引记录“我改了什么文件、哪些函数是必须实现的、踩过什么性能坑”。Linux内核移植也不例外。很多工程师移植内核的第一步不是改代码而是先确认设备树和内核config基线这本质上就是在建索引。应用层移植比如把Android Studio项目从一个版本迁移到另一个版本要记得记录依赖库版本差异、API变更点和构建配置这也是索引思维。所以我在带新人的时候经常讲你掌握多少具体接口很重要但你组织知识的方式更重要。6.2 我的个人习惯和工具推荐最后分享几个我自己长期坚持的习惯。第一每次编译和烧录后把Git commit message写清楚注明“为什么这么改”而不只是“修改dts”。时间久了commit log本身就是一份极佳的索引。第二所有验证过的启动命令、DDR参数、外设初始化的坑都统一记录在项目根目录的docs/porting_index.md里用Markdown表格组织方便全文搜索。第三每次遇到问题先想一下索引里有没有相关记录没有的话解决后立刻补充形成“遇到问题-记录问题-索引复用”的闭环。我也理解有些人觉得维护文档浪费时间但真实情况是移植U-Boot这种重资产、长周期的事情索引带来的回报极其可观。我后来接手新板子三天内能让U-Boot正常起串口并引导内核靠的不是记忆力而是索引已经给我铺好了一条从硬件到软件的快速通路。有时候年轻人问我移植有没有捷径我都会说把零零散散的知识点连成一个索引这就是我走过最有效的捷径。
返回列表