ARTICLE DETAIL

资讯详情

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

uboot移植实战指南:从串口DDR到内核加载的完整流程与避坑总结

uboot移植实战指南:从串口DDR到内核加载的完整流程与避坑总结 1. 为什么uboot移植值得单独写一篇索引式总结搞嵌入式的人几乎绕不开uboot。不管你是做ARM开发板、路由器刷机、还是给国产芯片适配系统uboot移植都是那道必须迈过去的坎。我从最早玩友善之臂的S3C2440到后来折腾iTOP4412、Hi3798M、Zynq再到最近帮朋友在GD32和CH32V305上跑RTOS前前后后移植过不下二十次uboot。每次移植都会踩不同的坑但核心逻辑其实高度一致。这篇索引不是教科书式的教程而是把我这些年积累的移植思路、关键节点、常见故障和排查手段做一次系统梳理。你可以把它当成一个移植地图——遇到问题的时候翻到对应章节找到方向再深入。适合已经有一定嵌入式基础、正在做或准备做uboot移植的开发者也适合刚接触bootloader、想了解整体流程的初学者。uboot移植的本质是什么说白了就是让一段引导程序在你的硬件上跑起来完成DDR初始化、时钟配置、串口输出、存储设备驱动最终把内核加载到内存并跳转执行。听起来简单但每一步都涉及大量硬件细节和配置选项。我见过太多人卡在串口没输出、DDR初始化失败、或者内核启动后挂死这些问题上一卡就是好几天。下面我会从整体设计思路开始逐步拆解到具体实操再到问题排查尽量把每个环节的为什么讲清楚。2. uboot移植的整体设计思路与方案选型2.1 先搞清楚你要移植的是什么版本的ubootuboot的版本差异非常大。2010之前的版本用boards.cfg和include/configs/下的头文件来配置2014之后引入了Kconfig和设备树dts2018之后基本全面转向defconfig dts的模式。如果你拿到的是一份老代码比如uboot 1.1.6或者2010.09那配置方式和最新的2023版本完全不同。我的建议是新项目尽量用2018之后的版本因为设备树机制让硬件描述和代码逻辑分离移植时你主要改dts和defconfig不用到处翻C代码里的宏定义。但如果是维护老项目比如iTOP4412这种经典板子网上大量教程基于uboot 2017甚至更早那就跟着教程的版本走别硬上新版。选版本还要看芯片厂商的支持情况。比如全志、瑞芯微、海思这些国产芯片厂商通常会提供一份定制过的uboot源码里面已经包含了DDR初始化代码和关键驱动。这种情况下你不需要从mainline uboot开始移植而是基于厂商SDK做适配。Hi3798M就是一个典型例子——海思的SDK里uboot已经能跑你主要改的是启动参数、分区表和设备树。2.2 移植路线的选择从零开始还是基于参考板uboot移植有两条路第一条路从mainline找一个最接近的参考板逐步改。这是最正统的做法。比如你的芯片是i.MX6ULL那就找mx6ull_14x14_evk作为起点然后改DDR参数、引脚复用、时钟树。优点是代码干净、可维护性好缺点是需要你对硬件非常了解DDR参数调不好直接起不来。第二条路基于厂商BSP或社区现成移植做增量修改。比如你拿到一块Hi3798M的机顶盒板子网上已经有wr703n刷uboot的类似案例那就基于现有代码改。优点是上手快、风险低缺点是代码可能很乱厂商改动大量核心文件后续升级困难。我的经验是产品开发走第一条路个人折腾和快速验证走第二条路。如果你只是想让板子跑起来没必要追求代码优雅。但如果是量产项目一定要基于mainline做否则后期维护会让你痛不欲生。2.3 关键硬件模块的移植优先级uboot移植不是一口气把所有驱动都搞定而是有明确的优先级优先级模块原因P0串口没有串口输出你什么都看不到P0时钟CPU和总线时钟不对后面全白搭P0DDR内存初始化失败uboot自身都跑不起来P1存储SD/eMMC/NAND需要从存储加载内核P1网络可选用于tftp加载和调试P2USB用于fastboot或U盘升级P2显示如果需要开机logo这个顺序不能乱。我见过有人一上来就调网络结果串口都没通根本不知道问题出在哪。先把P0搞定确保uboot能启动、能打印、能读写内存再往下做。3. 核心细节解析与实操要点3.1 串口配置你的第一双眼睛串口是uboot移植中最重要的调试手段。没有串口你就像蒙着眼睛修车。串口配置涉及三个层面引脚复用、控制器初始化和波特率设置。引脚复用通常在board_init或pinctrl相关代码里配置。以i.MX6ULL为例你需要在设备树里把UART1的TX/RX引脚配置成正确模式uart1 { pinctrl-names default; pinctrl-0 pinctrl_uart1; status okay; }; iomuxc { pinctrl_uart1: uart1grp { fsl,pins MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1 MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1 ; }; };控制器初始化一般由uboot的串口框架自动完成你只需要在defconfig里使能对应的串口驱动CONFIG_DM_SERIALy CONFIG_MXC_UARTy波特率默认是115200但有些板子用的是921600甚至更高。如果你看到乱码先检查波特率是否匹配。另外注意有些芯片的串口时钟源来自PLL如果PLL配置不对波特率也会偏。这时候你需要用示波器或者逻辑分析仪量一下实际波特率。注意串口TX/RX不要接反。我至少有三四次因为TX/RX接反对着终端发呆半小时。另外有些板子的串口电平是1.8V或3.3VUSB转串口模块要选对电平否则可能烧毁芯片。3.2 DDR初始化移植中最容易翻车的环节DDR初始化是uboot移植中最难的部分没有之一。DDR参数包括时序参数、刷新参数、驱动强度、ODT配置等任何一个不对都可能导致系统不稳定甚至完全起不来。以DDR3为例关键参数包括tRCD行激活到列地址选通的延迟tRP行预充电时间tRAS行激活到预充电的最小时间tRFC刷新周期CLCAS延迟这些参数来自DDR芯片的数据手册同时要结合SoC的DDR控制器特性。很多厂商会提供一个Excel表格或者工具来生成这些参数。比如NXP的DDR Stress Test工具、全志的DragonHD工具。如果你用的是厂商BSPDDR参数通常已经调好了你不需要动。但如果你是从mainline开始移植那就必须自己填这些参数。我的建议是先用厂商提供的参数跑通再逐步优化。不要一上来就自己算那样效率太低。DDR初始化失败的典型表现是串口没有任何输出或者输出几行就挂了。这时候你需要检查DDR电源是否正常通常1.5V或1.35V参考时钟是否正常通常24MHz或25MHz参数是否与DDR芯片匹配PCB布线是否有问题等长、阻抗实操心得如果DDR初始化不稳定可以尝试降低频率。比如从533MHz降到400MHz先确保能跑起来再逐步提高。另外有些SoC支持DDR训练traininguboot启动时会自动校准这种情况下参数容错率会高一些。3.3 存储驱动从SD卡到eMMC再到NANDuboot需要从存储设备加载内核所以存储驱动是必须的。常见的存储类型包括SD卡、eMMC、SPI NAND、并行NAND等。SD卡驱动相对简单uboot的MMC框架已经支持大部分控制器。你只需要在设备树里使能对应的MMC节点并在defconfig里打开CONFIG_MMCy CONFIG_DM_MMCy CONFIG_MMC_SDHCIyeMMC和SD卡共用MMC框架但eMMC通常焊接在板子上需要配置正确的总线宽度和速度模式。有些eMMC芯片需要额外的初始化序列比如发送CMD0、CMD1等。NAND驱动就复杂多了因为NAND存在坏块管理、ECC校验、页/块映射等问题。uboot的NAND子系统支持多种NAND控制器和ECC引擎。移植时你需要注意NAND芯片的页大小、块大小、OOB大小ECC模式硬件ECC还是软件ECC坏块管理策略我移植过一块SPI NAND的板子光是ECC配置就调了两天。最后发现是ECC强度设置不对导致读出来的数据全是错的。3.4 网络驱动调试利器网络在uboot里主要用于tftp加载内核和调试。虽然产品最终可能不需要网络启动但调试阶段有网络会方便很多。常见的网络芯片包括Realtek RTL8211、Micrel KSZ9031、TI DP83867等。uboot的PHY框架支持大部分标准PHY你只需要在设备树里配置正确的PHY地址和接口模式RGMII、RMII等。fec1 { pinctrl-names default; pinctrl-0 pinctrl_enet1; phy-mode rmii; phy-handle ethphy0; status okay; mdio { #address-cells 1; #size-cells 0; ethphy0: ethernet-phy0 { reg 0; }; }; };网络调试的常见问题是PHY地址不对、时钟不对、或者RGMII延迟配置错误。如果你ping不通先用mii info命令看看PHY是否被识别到。4. 实操过程与核心环节实现4.1 环境搭建与源码获取首先你需要一个Linux开发环境。我习惯用Ubuntu 20.04或22.04因为工具链兼容性好。交叉编译工具链的选择取决于你的芯片架构ARM32arm-linux-gnueabihf-ARM64aarch64-linux-gnu-RISC-Vriscv64-linux-gnu-源码获取有三种方式从mainline下载git clone https://source.denx.de/u-boot/u-boot.git从芯片厂商获取比如NXP的imx-uboot、全志的u-boot分支从板卡厂商获取比如友善之臂、正点原子提供的BSP我一般会同时保留mainline和厂商版本方便对比。4.2 配置与编译uboot的配置命令是make board_defconfig make -j$(nproc)比如i.MX6ULL EVKmake mx6ull_14x14_evk_defconfig make -j8编译完成后会生成u-boot.bin、u-boot.imxNXP专用、SPL等文件。具体生成哪些文件取决于你的启动方式。如果你需要SPLSecondary Program Loader比如i.MX6系列编译时会自动生成SPL文件。SPL的作用是在DDR未初始化时先运行一小段代码来初始化DDR然后再加载完整的uboot。4.3 烧录与启动烧录方式取决于你的存储介质SD卡dd ifu-boot.imx of/dev/sdX bs1k seek1eMMC通过USB下载工具或fastbootNAND通过JTAG或专用烧录器SPI Flash通过编程器或uboot自身以SD卡为例i.MX6ULL的uboot.imx需要写入偏移1KB的位置sudo dd ifu-boot.imx of/dev/sdb bs1k seek1 convfsync然后插入板子设置启动模式为SD卡启动上电。如果一切正常你会在串口终端看到U-Boot 2023.04 (Jan 01 2024 - 00:00:00 0000) CPU: Freescale i.MX6ULL rev1.1 528 MHz (running at 396 MHz) Model: Freescale i.MX6ULL 14x14 EVK Board DRAM: 512 MiB MMC: FSL_SDHC: 0 In: serial Out: serial Err: serial Net: FEC Hit any key to stop autoboot: 0 看到这个提示符说明uboot已经成功运行。4.4 内核加载与启动uboot的最终目标是加载内核。常见的加载方式包括从存储分区读取fatload mmc 0:1 0x80800000 zImage从网络加载tftp 0x80800000 zImage从USB加载fatload usb 0:1 0x80800000 zImage加载完成后使用bootz或bootm命令启动bootz 0x80800000 - 0x83000000其中0x80800000是内核加载地址0x83000000是设备树地址。启动参数通过bootargs环境变量传递setenv bootargs consolettySTM0,115200 root/dev/mmcblk0p2 rootwait rw注意内核加载地址不能与uboot自身占用的内存冲突。一般来说uboot会把自己重定位到内存高端低端内存留给内核。具体地址要看你的DDR布局。5. 常见问题与排查技巧实录5.1 串口无输出这是最常见的问题。排查步骤检查串口线是否接对TX/RX是否交叉检查波特率是否匹配检查串口电平是否匹配检查芯片是否正常上电量电压检查启动模式是否正确检查DDR是否初始化成功如果以上都没问题可能是uboot根本没有运行。这时候需要用JTAG调试器连接看看CPU是否在跑。5.2 DDR初始化失败表现串口输出几行就挂了或者完全无输出。排查方法用示波器量DDR时钟检查DDR电源降低DDR频率试试检查DDR参数是否与芯片匹配检查PCB布线我遇到过一次DDR初始化失败最后发现是PCB上的一颗电阻焊错了导致参考时钟不对。所以硬件问题也不能忽视。5.3 内核启动挂死表现uboot正常内核加载后卡住。常见原因设备树不对内核加载地址不对bootargs参数错误根文件系统挂载失败排查方法在bootargs里加earlycon和loglevel8检查设备树是否与硬件匹配检查内核是否支持你的存储控制器5.4 常见问题速查表问题可能原因解决方法串口无输出线接反、波特率不对、DDR失败检查接线、波特率、DDR参数DDR不稳定参数不对、电源不稳、频率过高降频、检查电源、调整参数网络不通PHY地址不对、时钟不对用mii info检查PHY内核挂死设备树不对、bootargs错误加earlycon、检查设备树存储读写失败驱动不对、ECC配置错误检查驱动、调整ECC启动模式不对拨码开关设置错误检查启动模式引脚5.5 独家避坑技巧技巧一保留一份能跑的uboot。每次修改之前先备份当前能跑的版本。这样一旦改坏了可以快速回退。技巧二用git管理你的修改。每次修改都提交一次commit message写清楚改了什么。这样出问题可以diff。技巧三串口打印要详细。在关键函数里加printf比如DDR初始化前后、时钟配置前后。这样能快速定位问题。技巧四不要一次改太多。每次只改一个模块验证通过后再改下一个。我见过有人一次性改了DDR、时钟、串口结果出问题根本不知道是哪个引起的。技巧五善用厂商工具。比如NXP的DDR Stress Test、全志的DragonHD、瑞芯微的RKDevTool。这些工具能帮你快速验证硬件和生成参数。6. 不同芯片平台的移植要点差异6.1 i.MX系列i.MX系列i.MX6、i.MX8的uboot移植相对成熟mainline支持很好。关键点需要生成u-boot.imx或u-boot-dtb.imxSPL负责DDR初始化设备树在arch/arm/dts/下启动模式通过BOOT_MODE引脚配置6.2 全志系列全志Allwinner的uboot分为两个阶段boot0和u-boot。boot0负责DDR初始化和加载u-boot。全志的DDR参数通过sys_config.fex配置编译时生成二进制。6.3 海思系列海思HiSilicon的uboot通常由厂商提供比如Hi3798M。移植时主要改启动参数分区表设备树网络配置海思的uboot有自己的一套命令和工具比如hitool用于烧录。6.4 Zynq系列ZynqXilinx的uboot移植涉及FSBLFirst Stage Boot Loader。FSBL由Vivado生成负责初始化PS端和加载uboot。uboot本身基于mainline设备树由Vivado导出。6.5 RISC-V平台RISC-V平台的uboot移植还在发展中mainline支持逐渐完善。关键点工具链选择riscv64-linux-gnu-SPL和OpenSBI的配合设备树描述7. 移植后的验证与优化7.1 功能验证清单uboot跑起来之后需要验证以下功能串口输入输出正常DDR读写正常用mw和md命令测试存储读写正常用mmc read、nand read等网络正常用ping和tftp测试USB正常用usb start测试环境变量保存正常用saveenv测试7.2 启动速度优化uboot启动速度影响产品体验。优化方法关闭不必要的驱动和命令使用SPL直接加载内核跳过完整uboot优化DDR训练时间减少启动延迟bootdelay设为07.3 安全性考虑产品级uboot需要考虑安全启动Secure Boot镜像签名验证环境变量保护调试接口关闭这些内容超出了本文范围但值得后续深入研究。8. 我个人的移植体会移植uboot这件事说难也难说简单也简单。难的是硬件细节太多任何一个参数不对都可能让你卡好几天。简单的是一旦你掌握了套路大部分芯片的移植流程都是相似的。我最大的体会是不要怕失败但要善于记录。每次移植遇到的问题和解决方法都记下来下次遇到类似问题就能快速定位。我有一份自己的移植笔记记录了各种芯片的DDR参数、串口配置、常见故障这些年帮了我大忙。另外社区的力量很重要。uboot的邮件列表、各种开发板的论坛、GitHub上的开源项目都是宝贵的资源。遇到问题先搜一搜很可能已经有人踩过同样的坑。最后分享一个小技巧如果你在调DDR不妨先用厂商的DDR测试工具跑一遍确认硬件没问题再调uboot。这样能排除硬件因素让你专注于软件配置。
返回列表