ARTICLE DETAIL

资讯详情

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

U-Boot移植实战:从参考板选型到内核启动全流程

U-Boot移植实战:从参考板选型到内核启动全流程 1. U-Boot移植的整体思路与方案选型1.1 为什么U-Boot移植是嵌入式Linux的第一道门槛做过嵌入式Linux的人都有一个共识板子能不能跑起来第一关就是Bootloader。U-Boot作为这个领域事实上的标准几乎每一块跑Linux的板子都绕不开它。但很多人第一次拿到一块新板子或者一块参考板的时候面对U-Boot源码里几百个板级配置目录往往不知道从哪里下手。U-Boot移植的本质说白了就是让一段裸机程序在你的硬件上完成三件事初始化最基本的硬件时钟、内存、串口把Linux内核从存储介质搬到内存里然后把控制权交给内核。听起来简单但每一步都涉及大量的板级细节。我个人的经验是U-Boot移植最忌讳的就是“从零开始写”。U-Boot的源码树里已经包含了大量成熟板级的支持正确的做法是找到一块与你目标板最接近的参考板然后在其基础上做增量修改。这个思路贯穿整个移植过程也是后面所有操作的基础逻辑。1.2 参考板选型的三个核心判断维度选参考板不是随便找一个同芯片的就行需要从三个维度来判断。第一是SoC型号的匹配度。同系列芯片的外设寄存器基地址可能不同比如i.MX6ULL和i.MX6Q的GPIO控制器地址就不一样。最理想的情况是参考板用的SoC和你完全一致这样时钟树、引脚复用、DDR控制器这些最麻烦的部分基本可以直接复用。第二是存储介质的类型。你的板子是从eMMC启动、SD卡启动还是SPI NOR启动不同启动介质对应的SPLSecondary Program Loader配置和加载地址完全不同。如果参考板是SD卡启动而你的板子是eMMC启动那至少需要在配置层面做调整。第三是内存颗粒的差异。DDR的型号、容量、位宽都会影响DDR初始化参数。这部分是U-Boot移植中最容易出问题的地方因为DDR初始化失败的表现通常是串口完全无输出连调试信息都看不到。选参考板的时候优先选芯片原厂提供的EVK板配置其次选社区维护活跃的板级配置。原厂EVK的DDR参数通常经过严格验证直接复用能省掉大量调试时间。1.3 移植工作的阶段划分整个U-Boot移植可以分成四个阶段每个阶段有明确的验证标志。第一阶段是编译通过能生成u-boot.bin和SPL。这个阶段只需要配置好交叉编译工具链和基本的板级配置文件。第二阶段是串口有输出能看到U-Boot的启动打印信息。这说明时钟初始化和串口驱动已经正常工作。第三阶段是DDR初始化成功U-Boot能完整启动到命令行。这个阶段需要DDR参数正确否则会出现各种随机崩溃。第四阶段是能加载并启动内核包括存储介质驱动、网络驱动、文件系统支持等全部就绪。每个阶段都有明确的验证方法不要跳阶段调试。我见过太多人在串口还没输出的时候就去调网络驱动结果浪费大量时间在无效调试上。2. 核心细节解析与实操要点2.1 源码目录结构与关键文件定位U-Boot的源码结构经过多年演进现在采用的是Kconfig加设备树的架构。对于移植工作来说需要重点关注以下几个位置。board目录下存放的是板级初始化代码路径通常是board/厂商名/板名/。这里面最重要的是board.c和Makefile前者包含board_init等板级初始化函数后者定义了编译规则。arch目录下是架构相关代码ARM架构在arch/arm/下面。其中arch/arm/mach-xxx/是SoC厂商的通用代码arch/arm/dts/存放设备树文件。configs目录下是板级默认配置文件命名规则是xxx_defconfig。这个文件决定了编译时哪些驱动被编入、哪些功能被开启。include/configs目录下是板级头文件虽然现在很多配置已经迁移到defconfig中但一些板级特有的宏定义仍然放在这里。我通常的操作流程是先在configs/下复制一份参考板的defconfig重命名为自己的板名然后在arch/arm/dts/下复制参考板的设备树文件最后在board/下复制参考板的板级目录。这三步做完一个最基本的移植框架就搭起来了。2.2 交叉编译工具链的配置与验证工具链的选择看似简单实际上有很多坑。ARM架构常用的工具链有Linaro的GCC、ARM官方的GNU Toolchain以及各家SoC厂商提供的定制版本。选择工具链时要注意几个关键点。首先是浮点ABI的匹配硬浮点hard-float和软浮点soft-float编译出来的二进制不兼容。其次是EABI版本现在基本都是EABI5。最后是GCC版本太新的GCC可能对老版本U-Boot的代码兼容性不好太老的GCC又可能不支持新的语法特性。配置工具链的环境变量很简单export ARCHarm export CROSS_COMPILEarm-linux-gnueabihf- export PATH$PATH:/opt/toolchain/bin验证工具链是否可用arm-linux-gnueabihf-gcc -v能正常打印版本信息就说明配置正确。这里有个细节CROSS_COMPILE的值必须以横杠结尾因为U-Boot的Makefile会在后面拼接gcc、ld等命令名。工具链路径中不要包含空格或中文字符否则Makefile解析时会出现莫名其妙的错误。这个问题我踩过不止一次。2.3 板级配置文件的裁剪策略defconfig文件的内容非常丰富但移植初期不需要全部理解。我的建议是先保持参考板的配置不动只修改必要的几项。必须修改的配置项包括CONFIG_TARGET_xxx目标板名称、CONFIG_DEFAULT_DEVICE_TREE设备树文件名、CONFIG_SYS_CONFIG_NAME板级头文件名。可以暂时保留的配置项包括各种外设驱动、文件系统支持、网络协议栈等。这些可以等基本启动成功后再逐步调整。需要特别注意的配置项是CONFIG_SPL相关的如果你的板子需要SPL而参考板不需要或者反过来那这部分配置必须仔细核对。SPL的加载地址、栈指针设置、DDR初始化代码的包含与否都会直接影响启动能否成功。2.4 设备树文件的适配要点设备树是U-Boot和Linux内核共享的硬件描述文件。U-Boot阶段用到的设备树节点比内核阶段少得多主要涉及串口、时钟、引脚控制、存储控制器这几个部分。串口节点是最关键的因为它是你唯一的调试手段。需要确认reg属性中的寄存器基地址和interrupts属性中的中断号与你的硬件一致。时钟频率通过clock-frequency属性指定这个值必须和实际硬件匹配否则串口打印会是乱码。引脚控制节点pinctrl决定了各个外设的引脚功能复用。比如串口的TX/RX引脚需要配置为UART功能如果参考板的引脚分配和你的板子不同这里必须修改。存储控制器节点涉及eMMC、SD卡、SPI Flash等设备的初始化参数。以SD卡为例bus-width属性指定数据线位宽1位或4位cd-gpios指定卡检测引脚这些都需要根据实际硬件填写。3. 实操过程与核心环节实现3.1 从零搭建移植框架的完整步骤假设我们要把U-Boot移植到一块基于某ARM Cortex-A系列芯片的定制板上参考板是芯片原厂的EVK。以下是完整的操作流程。第一步创建板级目录。在board/下找到参考板所在的厂商目录复制一份并重命名cp -r board/vendor/evk board/vendor/myboard然后修改board/vendor/myboard/Makefile把编译目标从evk.o改成myboard.o。同时修改board/vendor/myboard/Kconfig更新板级配置选项的名称和描述。第二步创建默认配置文件。在configs/下复制参考板的配置cp configs/evk_defconfig configs/myboard_defconfig编辑这个文件修改CONFIG_TARGET_EVKy为CONFIG_TARGET_MYBOARDy修改CONFIG_DEFAULT_DEVICE_TREEevk为CONFIG_DEFAULT_DEVICE_TREEmyboard。第三步创建设备树文件。在arch/arm/dts/下复制参考设备树cp arch/arm/dts/evk.dts arch/arm/dts/myboard.dts cp arch/arm/dts/evk-u-boot.dtsi arch/arm/dts/myboard-u-boot.dtsi修改myboard.dts中的model和compatible属性更新为你的板子信息。同时修改arch/arm/dts/Makefile添加myboard.dtb的编译目标。第四步创建板级头文件。在include/configs/下复制参考板的头文件cp include/configs/evk.h include/configs/myboard.h修改头文件中的宏定义主要是环境变量存储位置、启动参数、控制台设备等。第五步编译验证make myboard_defconfig make -j$(nproc)如果编译通过会在根目录生成u-boot.bin和spl/u-boot-spl.bin。3.2 DDR初始化参数的调试方法DDR初始化是U-Boot移植中最关键也最困难的部分。参数不对轻则系统不稳定重则完全无法启动。DDR参数通常由SoC厂商提供的工具生成比如NXP的DDR Tool、瑞芯微的DDR Bin等。这些工具会根据你填写的DDR颗粒型号、容量、位宽、频率等信息计算出一组寄存器配置值。拿到这些参数后需要把它们填入SPL的DDR初始化代码中。以常见的ddr_init函数为例参数通常以数组形式存在static const struct dram_cfg dram_cfgs[] { { 0x020e04b4, 0x00000000 }, { 0x020e04ac, 0x00000000 }, /* ... 更多寄存器配置 ... */ };调试DDR的时候如果串口完全无输出首先检查DDR初始化是否执行到了。可以在DDR初始化前后各加一个GPIO翻转操作用示波器观察。如果DDR初始化前的GPIO有翻转但之后没有说明DDR初始化卡住了。如果串口有输出但打印乱码可能是DDR参数不完全正确导致内存数据不稳定。这时候可以尝试降低DDR频率看是否能稳定运行。如果降频后正常说明时序参数需要微调。DDR调试建议从低频开始比如先跑400MHz确认稳定后再逐步提高到目标频率。这样可以把问题和频率相关的时序参数隔离开来。3.3 串口控制台的配置与验证串口是U-Boot移植过程中最重要的调试工具。配置串口需要关注三个参数波特率、数据格式、引脚映射。波特率通常设为115200这是U-Boot的默认值。数据格式是8N1即8位数据位、无校验位、1位停止位。这些参数在设备树的串口节点中配置uart1 { pinctrl-names default; pinctrl-0 pinctrl_uart1; status okay; };引脚映射通过pinctrl节点配置pinctrl_uart1: uart1grp { fsl,pins MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1 MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1 ; };验证串口是否正常最直接的方法是在board_init函数中添加打印int board_init(void) { printf(Board init start\n); /* 硬件初始化代码 */ printf(Board init done\n); return 0; }如果能看到这两行打印说明串口和基本时钟已经正常。如果看不到需要检查串口引脚是否配置正确、时钟是否使能、波特率是否匹配。3.4 存储介质驱动的适配U-Boot需要从存储介质中读取内核和设备树所以存储驱动的适配是必须的。常见的存储介质有SD卡、eMMC、SPI NOR Flash、NAND Flash等。以eMMC为例设备树中的配置通常如下usdhc2 { pinctrl-names default, state_100mhz, state_200mhz; pinctrl-0 pinctrl_usdhc2_8bit; pinctrl-1 pinctrl_usdhc2_8bit_100mhz; pinctrl-2 pinctrl_usdhc2_8bit_200mhz; bus-width 8; non-removable; status okay; };bus-width指定数据线位宽eMMC通常是8位。non-removable表示这是焊接在板上的设备不需要卡检测。验证存储驱动是否正常可以在U-Boot命令行执行mmc list mmc dev 0 mmc infommc info会打印出存储设备的容量、位宽、速度等信息。如果这些信息正确说明驱动工作正常。3.5 环境变量的存储配置U-Boot的环境变量需要保存在非易失性存储中通常是eMMC的一个独立分区或SPI Flash的一个扇区。配置环境变量存储位置需要修改板级头文件#define CONFIG_ENV_IS_IN_MMC #define CONFIG_SYS_MMC_ENV_DEV 0 #define CONFIG_ENV_OFFSET (1024 * 1024) #define CONFIG_ENV_SIZE (8 * 1024)这段配置的意思是环境变量存储在eMMC设备0的1MB偏移处大小为8KB。验证环境变量是否正常可以在U-Boot命令行执行setenv test_var hello saveenv reset printenv test_var如果重启后能打印出hello说明环境变量存储配置正确。4. 常见问题与排查技巧实录4.1 串口无输出问题的排查思路串口无输出是U-Boot移植中最常见的问题可能的原因非常多。我通常按照以下顺序排查。首先确认硬件连接。TX和RX是否接反了这是最低级但也最容易犯的错误。用示波器或逻辑分析仪抓一下TX引脚看是否有波形输出。如果没有波形说明U-Boot根本没有运行到串口初始化。然后确认启动介质。板子的启动模式引脚是否正确有些板子通过拨码开关选择启动介质如果拨错了SoC根本不会从你预期的介质加载U-Boot。接着确认时钟配置。串口的时钟源是否正确使能如果时钟频率不对串口输出的波特率就会偏差表现为乱码而不是完全无输出。最后确认DDR初始化。如果DDR初始化失败U-Boot的代码可能无法从存储介质加载到内存自然也不会有任何输出。这时候需要检查DDR参数是否正确。排查串口无输出问题时建议先用一个最简单的GPIO翻转程序验证SoC能正常启动。如果GPIO能翻转说明基本的时钟和复位电路没问题问题出在U-Boot本身。4.2 DDR不稳定导致的随机崩溃DDR参数不完全正确时系统可能能启动但运行一段时间后随机崩溃。这种问题的排查比较困难因为崩溃位置不固定。一个有效的排查方法是使用U-Boot自带的内存测试命令mtest 0x80000000 0x90000000这个命令会对指定地址范围进行读写测试。如果测试过程中出现错误说明DDR参数有问题。另一个方法是降低DDR频率。如果降频后系统稳定说明时序参数需要调整。通常需要调整的是tRFC、tRAS、tRP这些时序参数。还有一种情况是DDR的VREF电压不正确。有些SoC支持通过寄存器调整DDR的参考电压这个值需要根据DDR颗粒的规格书来设置。4.3 网络驱动不工作的常见原因网络驱动在U-Boot中主要用于TFTP下载内核和通过网络启动。如果网络不通可以从以下几个方面排查。PHY芯片的地址是否正确设备树中的phy-handle或phy-addr属性需要和硬件实际连接一致。有些板子的PHY地址是通过引脚配置的需要查看原理图确认。PHY的复位引脚是否正确配置有些PHY芯片需要先复位才能正常工作。设备树中的reset-gpios属性指定复位引脚。MDIO总线的时钟频率是否合适频率太高可能导致通信失败。可以通过CONFIG_SYS_MDIO_CLK宏来调整。验证网络是否正常可以在U-Boot命令行执行ping 192.168.1.1如果能ping通说明网络驱动正常。如果不通先用mii info命令查看PHY是否被正确识别。4.4 常见问题速查表问题现象可能原因排查方法解决方案串口无输出启动介质错误检查拨码开关切换到正确的启动介质串口无输出DDR初始化失败检查DDR参数使用厂商工具重新生成参数串口乱码波特率不匹配确认时钟频率调整串口时钟或波特率启动到一半卡死存储驱动问题检查设备树配置修正引脚和总线宽度网络不通PHY地址错误查看原理图修改设备树中的PHY地址环境变量保存失败存储偏移错误检查分区表调整CONFIG_ENV_OFFSET内核启动失败加载地址错误检查bootargs修正内核加载地址和启动参数4.5 几个容易被忽略的细节第一个是引脚复用冲突。同一个引脚可能被多个外设复用如果设备树中同时使能了两个使用同一引脚的外设会导致其中一个工作不正常。排查方法是检查pinctrl配置确认没有引脚冲突。第二个是时钟树配置。有些SoC的外设时钟来源于不同的PLL如果PLL配置不正确外设可能完全无法工作。需要对照芯片手册确认时钟路径。第三个是电源域配置。一些SoC有多个电源域外设所在的电源域如果没有使能外设无法工作。这个在设备树中通常通过power-domains属性配置。第四个是启动参数中的console设置。bootargs中的console参数必须和实际使用的串口一致否则内核启动后看不到打印信息。我在实际项目中遇到过一个问题U-Boot阶段串口正常但内核启动后串口无输出。排查了很久才发现是bootargs中的console设备名写错了。U-Boot用的是ttymxc0内核用的是ttymxc0但设备树中的串口节点别名没有正确设置导致内核找不到控制台设备。4.6 移植完成后的验证清单U-Boot移植完成后建议按照以下清单逐项验证串口能正常打印U-Boot版本和启动信息DDR容量识别正确内存测试通过存储介质能正常读写环境变量能保存和读取网络能ping通能通过TFTP下载文件到内存能从存储介质加载内核和设备树能成功启动到内核命令行这份清单覆盖了U-Boot的主要功能全部通过后基本可以认为移植工作完成。后续如果遇到问题也可以按照这个清单逐项排查快速定位问题范围。最后分享一个我个人习惯每次修改配置后先执行make mrproper清理编译产物再重新配置和编译。这样可以避免旧配置残留导致的奇怪问题。虽然编译时间会长一些但能省掉很多排查灵异问题的时间。
返回列表