多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

uboot移植实战:从源码索引到DTS配置,构建开发板引导全流程

uboot移植实战:从源码索引到DTS配置,构建开发板引导全流程 刚拿到开发板uboot移植这件事我都是先做索引再做代码先给同样被uboot折腾过的朋友说句实在话uboot移植的难点从来不在“改代码”本身而在于面对一块新板子时你不知道该从哪里下手、哪些文件动不得、哪些配置改了之后要连带改哪里。我做了这么多年嵌入式总结下来uboot移植本质上就是一次“信息索引”的建立过程——你要在庞大的源码树、芯片手册、厂商BSP和网络热词堆里把跟自己板子相关的信号线、时钟、内存、外设驱动全部找出来打成一张可查的对照表。这篇内容就是聊这件事适合正在做uboot移植的嵌入式工程师、学生以及手上有一块不常见开发板却想跑起Linux引导的人。我第一次拿到一款国产主控的评估板时串口完全没有输出点灯程序勉强能跑可一进引导就黑屏。后来发现问题根本不是电路图错了而是我压根没把DTS里的内存节点、串口引脚和实际板子的走线对应上。这就是uboot移植里最典型的“信息盲区”。所以这篇文章的核心内容就是教你怎样快速建立自己的移植索引从源码梳理、DTS调整、板级初始化到交叉编译、烧录和调试每一步都讲清楚“为什么这样做”而不是只给你一组命令。别看网上搜“uboot移植”能出来一堆教程真到了你的板子上十有八九都对不上号。原因很简单uboot的版本差异、芯片差异、板级配置差异导致每一套代码都是“半定制”。你能用的只能是思路和方法。这篇文章就是我把这些通用思路和方法整理成一份可复用的“索引表”希望能帮你少走几个月的弯路。1. 移植前先建索引整体思路与准备工作1.1 选对uboot版本比改代码更重要很多新手拿到板子之后第一件事就是去找“最新版”的uboot源码然后开始改。这其实是移植路上第一个坑。uboot并不是越新越好越是新的版本代码结构变化越大很多老板子的厂商补丁根本打不进去。我自己做移植的时候更倾向于选择一个“稳定且资料多”的版本比如u-boot 2018系列或者厂商BSP自带的版本而不是盲目追主线。有个很现实的例子网上常有人问“wr703n刷uboot用什么版本”“itop4412移植uboot2017怎么弄”这些问题的共性就是版本和板子强相关。u-boot 2018这个版本之所以推荐是因为它的设备树DTS机制已经非常成熟不像早期版本要填一堆平台宏定义。你可以直接把厂商提供的dts文件丢进去改起来清晰得多。就算后面要升级到更新的uboot先基于2018把硬件驱动跑通再研究差异点也是可行的路线。选版本的时候建议你先查三样东西厂商BSP是否自带uboot源码如果带了优先用它因为驱动适配都是验证过的。主线uboot中是否已经包含你主控芯片的SoC支持如果没有要么找厂商补丁要么移植相近SoC的配置。你手上的Linux内核版本因为uboot和内核之间通过设备树传递硬件信息两者的dts最好来自同一套体系避免节点格式对不上。1.2 建立“交叉工具链 手册 源码”的铁三角索引uboot移植要准备的东西其实不算多但每一样都可能成为卡壳点。我给自己立了一个标配清单每次移植前都会确认一遍交叉编译器比如arm-linux-gnueabihf-gcc版本别太老至少支持gnu11标准否则有些dts编译脚本会报错。源码树uboot源码本体配上厂商提供的补丁或BSP分支。芯片手册至少要有SoC的datasheet、开发板原理图、DDR颗粒手册、PHY芯片手册。没有原理图做移植等于盲人摸象。调试工具串口转USB模块TTL电平、J-Link或厂商烧录器、网线用于tftp下载内核、SD卡读卡器。提示严格来说一个能出u-boot.bin的交叉工具链就够了。但我强烈建议你把cscope或ctags建立好源码符号索引直接用source insight或者vscode的“全局搜索”也行。grep源码符号的效率决定了你排查问题的速度。我见过太多人还在用文本编辑器逐个目录翻翻到吐。1.3 把“索引”思维用到源码里config、dts、board三层定位uboot移植涉及的文件多但真正需要你动手的只有三层第一层是config也就是板级配置头文件比如include/configs/myboard.h。这里面定义了内存大小、启动参数、命令行环境变量等。第二层是dts也就是设备树源文件描述硬件拓扑。第三层是board也就是board/vendor/myboard/目录下的板级初始化代码包含board_init、board_late_init等函数。我自己的习惯是拿到一份uboot源码后不急着看代码而是先在文档里画一张表把自己板子的关键硬件和源码中的对应关系列出来。比如硬件模块原理图标注源码搜索关键字涉及文件串口UART0TX/RX引脚PE8/PE9CONFIG_CONS_INDEXuart0include/configs、arch/arm/mach-xxxDDRDDR3颗粒容量512MBCONFIG_NR_DRAM_BANKSSDRAM0board/xxx/ddr.c以太网PHY地址0x01CONFIG_PHY_ADDRphy_iddrivers/net/phy存储SD卡SDMMC1CONFIG_MMCsdmmcdrivers/mmc做完这张表你已经建立了自己的“移植索引”骨架。后面的时间基本就是在往这张表格里填充细节而不是毫无头绪地改代码。这一步看似费时间实际上能帮你省掉至少一周的迷茫期。2. 移植核心流程与实操步骤2.1 从“最接近的配置”起步而不是从零开始uboot移植最快的方法就是找一块“已经能跑”的板子做参照。如果你的SoC跟某个开发板同系列那就直接复制这块板的defconfig改一改。几乎没有一个成功的uboot移植是从空目录开始写起的行业内都是“改配置 改dts 补驱动”。具体操作是这样的在configs/目录下找跟你的板子最接近的defconfig比如xxx_defconfig。复制一份改成你自己的板级名称比如myboard_defconfig。打开这个文件看关键配置项比如CONFIG_SYS_SOC、CONFIG_SYS_BOARD、CONFIG_SYS_CONFIG_NAME这些决定了uboot会去include/configs/目录下找哪个头文件。执行make myboard_defconfig然后make先看看能不能编译通过。我第一次做移植是拿一款老平台wr703n的配置作为起点它的代码量小结构简单非常适合理解uboot的编译流程。后来的itop4412移植项目我也是借助厂商原有config开始而不是直接去追主线几次尝试之后确认是把uboot2017跑通了。编译之前记得先配置环境变量export ARCHarm export CROSS_COMPILEarm-linux-gnueabihf- make myboard_defconfig make -j8如果defconfig里的名字没改对make的时候会出现找不到配置项的错误。不要慌打开configs/myboard_defconfig把CONFIG_SYS_CONFIG_NAME改成你在include/configs下创建的头文件名这是最常见的问题。2.2 DTS设备树调整uboot 2018时代的核心工作从u-boot 2016开始DTS已经是uboot的标准配置方式。你打开arch/arm/dts/目录会看到一票以SoC型号命名的dts文件比如hi3798m100.dtsi、zynq-zc702.dts等。uboot移植过程中dts是你改动最频繁的文件。DTS结构分三层dtsiSoC级、dts板级、dtsi的include链。SoC级dtsi里定义的是这个芯片的所有外设节点比如uart、mmc、ehci、mac它们默认是disabled的板级dts中把用到的节点改为okay并补充引脚、时钟、复位等属性。下面是我移植时基本都会改的几个节点写出来给你参考uart0 { status okay; clocks clk_uart0; pinctrl-names default; pinctrl-0 uart0_pins_a; }; mmc0 { status okay; bus-width 4; cd-gpios pio 4 0 GPIO_ACTIVE_LOW; }; mdio { phy0: ethernet-phy0 { reg 0; }; }; gmac { status okay; phy-mode rgmii; phy-handle phy0; };这里有几个容易踩的坑pinctrl节点名必须和dtsi中pinctrl定义的子节点名一致否则uboot虽然能编译但引脚不会复用。clocks属性里的clk_uart0这些宏要去dtsi或者include/dt-bindings/clock/下确认定义写错数字会导致驱动初始化失败。以太网的phy-mode具体值要跟电路实际连接一致rgmii还是rmii千万不能凭感觉填。注意DTS编译的报错信息在uboot里比较“抽象”常见的是“syntax error”或者“FDT_ERR_BADMAGIC”。前者是格式问题后者通常是dtsi被include进了两次。我建议每次改完dts之后先用make dtbs单独编译设备树不要直接整包编译。2.3 板级初始化把“点灯”跑通再说uboot移植是否成功的第一个里程碑不是看到命令行提示符而是能控制一个GPIO点灯。这个目标很小但它验证了时钟、引脚复用、GPIO控制器驱动三个最基础的模块全都正常。板级初始化代码通常放在board/myboard/myboard.c里。你需要实现以下几个关键函数board_init初始化串口、GPIO、时钟等。board_late_init在uboot启动后期做额外配置。dram_init告诉uboot板上有多少内存、分几个bank。dram_init_banksize填写gd-bd-bi_bankstart供后续内存分配使用。dram_init是个重灾区。很多板子明明有512MB内存uboot却只识别到128MB登录后free一片混乱。常见原因是configured using CONFIG_NR_DRAM_BANKS不对或者DDR控制器OPM操作模式设置错误。我一般会在dram_init里临时加一句printf打印gd-ram_size用它来对照理论计算值。这里也提一下“移植freertos移植lvgl”这类gpio相关项目。很多人做完uboot之后会继续做GUI或RTOS其实底层都是引脚初始化那套逻辑思路完全相通。你要做的就是先在uboot阶段把GPIO读写函数跑通上层随便怎么玩。2.4 编译和烧录用起来像样的镜像uboot编译产物有u-boot.bin、u-boot.img、u-boot-spl.bin等。具体烧哪个取决于你的启动方式。如果你的SoC内有ROM引导一般会把SPL放到偏移地址然后SPL把主uboot加载到DDR里。如果你的板子是直接从NOR或SD卡启动那可能只需要一个完整的u-boot.bin。我用得最多的烧录方式有两种SD卡启动把生成的u-boot.bin写入SD卡特定扇区偏移。tftp下载在uboot命令行中用tftp命令加载镜像。SD卡写入的典型命令sudo dd ifu-boot.bin of/dev/sdb bs512 seek1 convfsync注意那个seek1很多板子是跳过了第一个扇区的MBR。至于烧到eMMC的地址要参考厂商的烧写脚本每家的偏移都不一样不要照搬。烧录完成后上电看串口log。如果串口没有任何输出优先检查三件事串口工具波特率是否正确、uboot里配置的调试串口引脚是否对、u-boot.bin有没有真的写到启动介质上。八成问题出在这三个的某一个里。3. 调试方法与工程实录从“点不亮”到“跑通系统”3.1 串口无log的排查逻辑uboot移植第一个拦路虎就是串口无输出。有些板子还好至少能看到ROM code打印的乱码有些干脆全黑。我把排查顺序总结成一个固定流程先用示波器或逻辑分析仪测uart TX引脚看有没有波形。没有波形就说明uboot根本没执行到串口初始化。有波形但全是乱码检查波特率。uboot默认115200还是厂商改成了别的值你要跟bootrom的打印保持一致。有波形也有像样的log但停在某一行不动那才是uboot代码运行阶段的bug。这里要特别提一句“linux摄像头移植教程”这些热词里常见的调试手法先把串口作为最基本硬件资源它的初始化要放到board_early_init_f阶段比任何驱动都早。如果你在board_init里才开串口那前面的SPL阶段日志全丢调试会很吃力。对于带MMU的SoC还要确认一个细节页面映射是否覆盖了UART寄存器物理地址。如果dcache开启而UART寄存器没被map到虚拟地址中写寄存器可能被缓存吞掉表现就是串口偶尔输出、偶尔不输出。3.2 网络调试tftp下载是移植中期的最强工具uboot阶段没有文件系统跟主机交互最常用的方式就是网络。启动Linux内核时我们常常用uboot的tftp命令把内核镜像和设备树拉到DDR里再bootm启动。这样反复调试内核都不用反复拔插SD卡。网络驱动的调试多半卡在PHY芯片上。PHY的地址、是否内置、MAC与PHY的连接模式MII/RMII/RGMII全靠板级dts中的phy-handle和phy-mode字段控制。我在一块板子上遇到过ping不通查了半天发现是PHY地址写错了芯片实际是地址0x03我代码里写的0x01导致每次都是在一个不存在的MDIO设备上读ID。调网络的时候可以先用mii命令看PHY状态比如mii device # 查看当前MDIO总线上探测到的PHY mii info # 读取PHY ID和链路状态如果mii info显示PHY ID全是ffff说明MDIO总线没通多半是引脚或时钟问题不是uboot配置问题。3.3 从tftp到启动Linux验证uboot对内核传递真的没问题uboot移植的成功标准最后还是要落到“能引导Linux内核”上。这里有个跟热词“zynq移植busybox”很搭的经典组合uboot加载好内核跟文件系统再用bootargs指定root/dev/nfs或者root/dev/ram0。我自己调试时经常用RAM版本的busybox根文件系统作为最小验证环境这样不需要碰存储介质排查起来最干净。启动命令大致是setenv ipaddr 192.168.1.10 setenv serverip 192.168.1.1 setenv bootargs consolettyS0,115200 root/dev/ram0 rw tftp 0x40000000 zImage tftp 0x44000000 myboard.dtb bootz 0x40000000 - 0x44000000有内核起来说明你的uboot已经把内存大小、设备树地址、串口参数正确传递给了内核。如果内核起来后又panic在“Unable to handle kernel paging request”那就要回头检查uboot的dram_bank配置很可能是内存大小没传对。3.4 git diff是最好的移植日志移植过程中改了多少文件、哪些配置动了如果不做记录两周之后你自己都记不住。我强烈建议每完成一个功能模块就提交一次commitcommit message写清楚改动点和验证方法。比如arm: myboard: enable Ethernet PHY support - add PHY address for myboard - set phy-mode to rgmii - verified with tftp download Signed-off-by: yourname youexample.com这样整个移植过程的diff就成了一份天然的“索引文件”后续换人维护、升级uboot版本、移植到其他板子都能快速定位影响范围。我自己后来回看几个月前的移植项目就是靠commit log和数据手册建索引比重新翻代码高效太多。4. 常见问题排查速查表与避坑建议4.1 我把移植过程中踩过的坑整理成了一张速查表问题现象可能原因排查思路编译报undefined reference缺少board目录下的.o文件执行make mrproper后重新make myboard_defconfigDTS节点编译报syntax error缺少逗号或引号不匹配用dtc单独编译dts文件定位报错行u-boot烧录后无串口log串口引脚复用配置错误检查dts的pinctrl节点是否对应当前板子内存识别只有一半dram_init里gd-ram_size设置不对打印gd-ram_size对照芯片手册的片选映射tftp下载超时网线没插好或PHY驱动没起来用mii info命令查看PHY状态启动内核后乱码内核与uboot串口波特率不一致检查bootargs里的console参数保存env后重启丢失未定义CONFIG_ENV_IS_IN_MMC/NOR按启动介质选择环境变量存储驱动启动时卡在Starting kernel...设备树地址不在RAM内确认bootm/bootz后跟的dtb地址落在RAM范围且未被覆写这张表我自己每次移植都会翻一遍。上面这些坑我至少各踩过两回。尤其是“DTS节点编译报错”和“启动时卡在Starting kernel”这两个排查起来最耗时因为它们的问题往往不是表面报错位置而是前一个环节的信息错误。4.2 避坑建议加打印、做备份、拆步骤我自己总结出的三条避坑经验每一条都来自切实的教训第一不要贸然删除打印信息。uboot里大量printf看起来“没用”但它们能告诉你当前执行到哪个函数。移植早期我建议把debug等级提升到最高比如在board_init_f处加#define DEBUG让系统把每个initcall打印出来。这样你能用最小的成本掌握uboot的执行流。第二烧录前备份原始优盘镜像。有的开发板出厂自带一个能用的bootloader我见过不少人一上来就覆盖烧写结果uboot没调通旧的也丢了只能拆芯片上编程器。正确做法是先把原始镜像用dd完整备份到电脑再开始折腾。第三把一个大目标拆成三个小里程碑。第一个小里程碑是板级点灯第二个是串口打印uboot命令行第三个是tftp引导内核。每完成一个就验证一次出了问题能明确知道是哪个阶段引入的不会一堆问题混在一起无法定位。4.3 一套可以在多块板子上复用的“索引模板”做完几次移植之后我发现自己不再需要从头翻代码了。原因是手头有了一套通用的索引模板每次只需要往里填新板子的信息。这里分享一个精简版硬件资源索引记录每一位引脚连接的模块GPIO号、控制器base地址、中断号。外设驱动索引记录每个外设UART、MMC、GMAC、USB在dts中的status节点和对应驱动文件路径。编译打印索引记录哪些defconfig关键宏会影响编译过程哪些宏影响运行行为。调试记录索引记录每次故障的现象、根因、修改方案按日期排列。这套索引一建立原本要花一两个月的uboot移植工作基本可以压缩到一周内完成大半。特别是换板子的时候对比新旧索引差异一目了然能快速确定哪些配置可以沿用哪些必须重做。建立在“信息可检索”基础上的操作才是可复现的工程经验而不是一次性手艺活。我个人的体会是uboot移植这件事做多了你会慢慢发现它不再可怕甚至有点“无聊”——因为所有问题都有迹可循所有坑都有文档记录。困难的地方不在于敲多少命令而在于你是否系统地整理过手上的信息。每次动工前先把索引建好你就成功了一半。最后再分享一个小技巧组里如果有多个人同时做不同板子的移植可以在共享文档里维护一份公共问题库谁踩了坑就把现象和解决方案贴进去。这个库积累起来之后新同事上手速度会快非常多。毕竟uboot移植不像应用编程很多报错信息反人类靠的就是“前面有人趟过路”。
返回列表