ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从上电到内核:4412开发板U-Boot启动全解析

从上电到内核:4412开发板U-Boot启动全解析

聊到嵌入式Linux,很多人把注意力都放在内核和驱动上,uboot属于那种“能跑就行”的东西。但我在讯为4412开发板上折腾过一段时间后,发现uboot启动过程恰恰是最值得搞明白的一环——它决定了一块板子从上电到加载内核之间,谁在干活、按什么顺序干活。这次以4412为例,把uboot从上电到进入Linux的启动链路完整拆一遍。如果你正在学嵌入式Linux、第一次接触bootloader,或者做驱动移植时反复卡在启动阶段,这篇文章应该能帮你把那层窗户纸捅破。

uboot的启动过程并不神秘,无非是“谁加载谁、谁初始化什么、最后把控制权交给谁”。但4412这套方案和经典的ARM9/ARM11不太一样,它多了一层片内固化的iROM引导,导致很多人在看启动日志时对不上号。下面我从整体链路讲起,再拆start.S,再把C语言初始化部分讲透,最后给实操参考和排障经验。

1. 先看清链路:4412从上电到uboot的三级跳

1.1 谁来决定从哪启动:OM引脚的秘密

开发板上电的一瞬间,CPU核心还没跑起来,片内固化代码先接管了芯片。4412处理器在设计时留了一组启动配置引脚,通常叫OM引脚(Operating Mode),板子设计者把这些引脚通过上拉电阻接到高电平或下拉电阻接到低电平,形成一个几位的二进制编号,用来告诉芯片“你要从哪个存储介质读取启动代码”。

4412支持从SD/MMC启动、从eMMC启动、从USB启动、从UART启动等多种模式。不同模式对应不同的OM引脚组合。你在讯为4412开发板上看到的那几个拨码开关,或者原理图上标注OM0~OM5的网络名,就是干这个用的。SD卡启动和eMMC启动是最常用的两种,前者适合调试阶段频繁烧写,后者适合做产品定型。

这里有一个容易被忽略的地方:OM引脚不是单纯的高或低就行,它有一定时序配合要求,而且某些启动模式下引脚是复用的。如果你换了启动设备却发现板子不启动,先去看原理图上OM引脚的接法,而不是怀疑uboot烧坏了。我遇到过几次“烧了镜像还是不启动”的情况,最后都是OM配置跟实际启动介质对不上导致的。

1.2 iROM读完就交棒:BL1的身世

OM引脚决定启动介质之后,片内的那段固化代码就该干活了。4412片内有一块64KB的ROM,出厂时写死了启动引导程序,业内叫它iROM,也叫BL0。这段代码用户碰不到、也没必要碰,它就是用来“启动UBoot之前的那段启动”的。

iROM干的事情很有规律:先是关看门狗,不然看门狗一超时系统就复位了,那启动流程永远走不完;接着配置最基本的时钟,让CPU在一个已知频率下运行;然后使能SRAM(4412内部的iRAM,一共64KB),因为下面要往SRAM里放代码;再往下,iROM读OM引脚配置,初始化对应的存储控制器,比如初始化SD/MMC控制器;随后从存储设备的固定位置把BL1拷贝到iRAM里,跳到BL1执行。

BL1是一段很小的引导程序,通常只有8KB左右。为什么这么小?因为iROM代码是从存储设备前固定扇区读取的,早期拷贝时只能依赖固化在iROM里的逻辑,不可能支持复杂的文件系统,所以按扇区读最可靠,而SRAM空间又有限,于是BL1必须瘦身。

这里我想强调一个概念:BL1不是uboot,也不归你写。对于4412,BL1通常由芯片厂商以二进制形式提供,或者一并在厂商的BSP源码里编译出来。你最终要烧写的文件名可能是BL1.bin,也可能是一个打包好的镜像,但它的作用都一样——把运行环境从“只有片内SRAM可用”过渡到“DDR内存已经能用”。

1.3 BL2才是完整uboot:加载与跳转

BL1接管后,核心任务只有一个:初始化DRAM。因为iRAM一共才64KB,连一个完整uboot的零头都不够,更别说后续解压内核了。所以BL1要先把DDR控制器配置好,让CPU能访问外部的内存颗粒,然后再把真正的uboot镜像从存储设备搬到DDR的高地址处,跳转过去。

从这一刻起,uboot才算真正开始运行。由于BL1是从外部存储按固定偏移动读取镜像,所以uboot镜像在SD卡或eMMC里的存放位置是约定好的。比如SD卡方案里,BL1存放在扇区1开始的位置,uboot本体存放在扇区17或者更靠后的位置。这些偏移量由厂商BSP决定,不能随意挪动,否则BL1按固定地址去读,读到的就是空白数据,系统直接卡死。

完整uboot镜像在内存中运行后,会执行标准的两阶段启动逻辑,英文里叫“Phase 1”和“Phase 2”。第一阶段主要是汇编,完成CPU、时钟、内存、栈等基础环境的准备;第二阶段进入C语言世界,做板级外设初始化和内核引导准备。很多人对“uboot两阶段”的理解是从经典教材里来的,但放在4412上,还需要把它和BL0/BL1/BL2这条链分开看,否则会觉得“怎么既有BL2又有两个阶段”。

1.4 看懂SD卡和eMMC里的镜像分布

搞懂整条链之后,再看存储设备里的布局就一目了然了。以常见的SD卡启动为例,典型布局长这样:

位置内容说明
扇区0MBR/保留区分区表区域,可能无实际功能
扇区1 ~ 16BL1约8KB,由iROM加载到SRAM
扇区17 ~ 48uboot.bin完整uboot镜像,由BL1加载到DDR
后续扇区内核、设备树、根文件系统由uboot读取

注意,上面这个布局是典型参考,具体偏移量以你的开发板BSP文档为准。烧写时不是把uboot.bin直接扔到SD卡根目录那么简单,而是要写到指定扇区,所以一般要借助厂商提供的烧写工具,或者用dd命令按偏移写。

eMMC启动的布局逻辑类似,只是介质变成了eMMC的boot分区。eMMC通常有独立的boot1/boot2分区,容量不大,专门用来存放引导代码。烧写eMMC时还要注意把boot分区使能(enable),否则eMMC上电后不会主动暴露boot分区给外部控制器访问。这个问题在量产时特别常见:明明烧进去了,板子就是不启动,查到最后是boot分区被关了。

2. uboot内部第一棒:start.S的汇编世界

2.1 从reset向量开始

BL1跳转到DDR里的uboot入口后,执行的第一个文件是和SoC型号对应的start.S。它是整个uboot的入口汇编文件,入口符号通常是_start,对应uboot链接脚本里指定的入口地址。

ARM处理器的启动方式是从固定地址取第一条指令。4412属于Cortex-A9架构,复位后CPU会去读取异常向量表。虽然是uboot在DDR里跑,异常向量表依然要放在uboot链接地址的起始位置。_start处通常是一个跳转指令,CPU一上电或复位,就必须能从这里找到第一条指令,然后跳到真正的初始化代码。

异常向量表本身包含了一组固定长度的跳转指令,分别对应复位、未定义指令、软中断、预取中止、数据中止、IRQ、FIQ等异常类型。对uboot来说,当前阶段并不需要处理复杂的异常,但向量表必须存在,否则一旦发生中断,CPU会从异常向量表地址处取一条“垃圾指令”执行,整个系统直接跑飞。

start.S开头还有一段“中断向量表重定位”的过程。这是因为u-boot可能会把自己搬运到其他地址运行,如果向量表还在原地址,中断来了还是跳到旧地址,代码都搬走了自然找不到处理函数。所以在初始化过程中,会把这16字节一份的向量表复制到当前运行地址处。

2.2 为什么一上来就切模式和关中断

进入start.S之后,第一件事通常是修改CPSR寄存器,把CPU切到SVC模式并屏蔽中断。Cortex-A9有用户态、系统态、SVC、IRQ、FIQ等多种模式,uboot作为一个bootloader,必须运行在特权模式下才能操作硬件寄存器,所以常规选择是SVC模式。

屏蔽中断这件事不是“懒得处理”,而是启动早期根本没有能力处理中断。中断控制器还没有初始化,中断向量表虽然有了,但对应的处理逻辑并不完整,任何中断发生都会导致行为不可控。所以通过设置CPSR的I位和F位,分别屏蔽IRQ和FIQ中断,让CPU在整个uboot初始化阶段只有一条主线在执行。

紧接着是关闭MMU和D-Cache。这一点很多初学者不理解:Linux内核里MMU开着跑得好好的,为什么uboot一上来就要关掉?因为uboot启动早期,CPU访问的还是物理地址,页表还没建立,如果贸然开着MMU访问,地址翻译会进入无效页表,直接data abort。而D-Cache的问题在于,启动早期代码可能已经通过Cache预取了一部分数据,但内存控制器还没有完全配置好,Cache读取到的可能是脏数据。所以先把MMU和Cache关掉,让所有访问路径都走最原始的总线访问,稳字当头。

关完MMU之后,如果uboot版本比较新,后面会在board_init阶段重新建立页表和使能Cache,但4412常见的BSP版本一般倾向于用简单直接的物理地址访问,这样做虽然性能不占优,但移植和调试的复杂度低很多。

2.3 时钟与DDR:两个最容易出事的初始化

start.S调用的下一批关键函数就是时钟初始化和内存初始化。4412外部接的晶振一般是24MHz,但CPU运行频率可以达到1.4GHz。从24MHz到1.4GHz,中间不是一步能跳过去的,必须按PLL配置表一步步倍频,经过ARMCLK、ACP等各级分频和锁相环锁定,最后稳定到目标频率。

这个过程里,最麻烦的是不能随便乱配。PLL寄存器里包含了MDIV、PDIV、SDIV等分频因子,配置错误轻则CPU跑在错误频率,重则直接死机。厂商BSP里通常会带一套验证过的配置表,比如在board_init里根据开发板型号选择对应配置,或者直接在lowlevel_init里写死一套标准参数。我们做移植的时候,尽量沿用这套表,不要自己拍脑袋去改,除非你确认掌握了PLL锁定时间的计算。

DDR初始化的地位比时钟更关键。因为从BL1阶段开始,所有代码就都要跑在DDR里了,DDR不初始化,后面所有步骤都无法进行。4412通常搭配DDR3颗粒,初始化时要配置控制器的时序参数,包括CAS延迟、写恢复时间、刷新周期、突发长度等。

这些参数不是随便填的,要和内存颗粒的规格对应。一个很常见的坑是:厂商提供的BSP针对的是1GB内存配置,你换了颗512MB的内存颗粒后如果不更新配置表,uboot能启动,但内存检测只能看到错误容量,或者在高地址访问时随机死机。排查这类问题只能靠对照颗粒手册逐项核对时序配置,没有捷径。

DDR初始化完成后,start.S会逐步切换栈指针指向DDR空间,为调用C函数做准备。这时候bl跳转指令开始调用C语言函数,标志着uboot第一阶段从“纯汇编”进入“C语言主导”的过渡。

2.4 给C语言铺路:栈和重定位

汇编代码里调用C函数之前,必须先设置栈指针sp。因为C语言在函数调用过程中,参数传递、局部变量、返回地址都要压栈。uboot启动早期,栈只能放在SRAM里,等DDR初始化完成后,再把栈挪到DDR的合适位置,给后续C函数留足空间。

栈地址的选择有讲究。不能选在uboot镜像本身的位置,否则函数调用过程中压栈可能把代码覆盖掉。也不能选到内核将来要加载的地址区域,否则后面tftp下载内核时会冲掉栈。常规做法是选择一个“uboot运行地址下方”或“内存高地址的保留区域”,具体位置在链接脚本和板级配置头文件里定义。

完成这些之后,start.S会根据链接地址和当前运行地址的差异,决定是否需要进行代码重定位。简单说,代码在编译时有个链接地址,比如0x43E00000,但它实际被BL1加载到了别的地址,那么所有使用绝对地址的指令都要修正。传统做法是把uboot整体搬运到链接地址处,再跳转过去,后面的代码就当成“运行地址等于链接地址”来跑,省心。也有版本实现了位置无关的编译方式,但如果厂商BSP还是老一套,老老实实重定位更稳。

重定位完成后,代码跳转到board_init_f,正式开始C语言第一批初始化。从这里开始,启动流程就有串口日志“可见”了,前面半段都是黑盒。

3. uboot内部第二棒:板级初始化和内核接棒

3.1 board_init_f到board_init_r:两段式初始化的用意

到这里,我们要区分两个同名函数:board_init_f和board_init_r。它们的“f”是“early/forward”意义上的初始化,“r”是“relocate”之后的重置初始化。这是uboot老版本里非常经典的两段式设计。

board_init_f在代码重定位之前执行,早早期阶段,这时uboot对DRAM的完整布局还没有摸清,所以只做有限的事:初始化串口(靠它打印日志)、初始化DRAM信息、规划内存布局(比如uboot镜像放哪、堆栈放哪)、识别板级型号等。这一阶段结束后,代码完成重定位,然后跳转到board_init_r。

board_init_r就是“UBoot已经搬到新家,接下来可以撒开手脚初始化所有外设”的阶段。它负责初始化完整的板级设备集合,包括eMMC/SD控制器、网络控制器、USB、LCD、按键、环境变量存储、命令解析等等。最后进入一个不会返回的主循环main_loop,等待用户输入命令或执行自动启动流程。

为什么要这么麻烦地分成两段?原因还是那两个字:空间。早期uboot只有几十KB可用空间时,不可能把所有设备的驱动都加载进来,只能先做最小系统,确定内存布局后再扩展。放在4412的场景里,BL1已经把内存弄好了,但uboot自身还要在内存里重新安家,所以两段式逻辑依然保留。

3.2 串口、MMC、网卡等外设初始化的顺序

在board_init_r里,外设初始化是有先后顺序的,不能颠倒。串口永远是第一个,因为它承担了输出调试信息的职责——串口不工作,后面初始化什么都看不到反馈。很多板子启动日志前几行看不到东西,就是这个原因:串口还没开,日志无从谈起。

串口初始化包括GPIO复用配置、UART控制器的波特率设置、收发FIFO使能等。4412的UART控制器支持不同的波特率,默认串口日志通常用115200,8N1。实际开发中为了和终端匹配,发现乱码时先查波特率对不对,别急着怀疑驱动。

串口就绪后,紧接着初始化的是存储设备,比如SD/MMC控制器。因为uboot要将内核和设备树从存储介质里读出来,存储设备不先初始化,后面load mmc命令就无从执行。初始化顺序里还包括网络控制器,tftp下载内核时要用。LED这类简单设备反而更早,因为它是调试时最简单的指示手段。LCD一般放比较靠后的位置,因为它不影响启动成功与否,只影响显示。

这种顺序并非固定的教学模板,而是由“谁被依赖就谁先初始化”的原则决定的。比如板子上网卡用的是USB接口,那么USB控制器必须在网卡之前初始化;如果网卡挂在总线桥上,总线桥也得提前就位。所以看启动日志时,你会发现初始化顺序和硬件拓扑是强相关的。

3.3 环境变量的实质作用

uboot里环境变量看起来就是个字符串表格,实际作用却直接决定启动行为。两个最重要的环境变量是bootcmd和bootargs。

bootcmd是uboot在自动启动时执行的命令序列。开发板出厂时通常配置成从eMMC或SD卡读取内核镜像,比如:

setenv bootcmd 'mmc read 0 0x41000000 0x2000 0x4000; bootm 0x41000000'

这里mmc read是把eMMC里某个偏移位置的数据读进内存,bootm是从内存地址处启动内核。你看到的“Hit any key to stop autoboot”提示,等的那几秒就是在等待用户按键,如果按了键,就进入交互命令行,否则倒计时一结束就执行bootcmd。

bootargs是传给内核的启动参数,内容包含控制台、内存大小、根文件系统所在设备、根文件系统类型等。举个常见例子:

setenv bootargs 'console=ttySAC2,115200 root=/dev/mmcblk0p2 rootwait rw'

console=ttySAC2,115200告诉内核用哪个串口作为调试串口,波特率是多少。root=/dev/mmcblk0p2告诉内核根文件系统在eMMC的第二个分区上,rootwait表示等待设备出现再挂载。这里很多新手栽过跟头:bootargs里的根文件系统分区和实际分区没对上,uboot启动内核没问题,但内核启动到一半就panic了,报的还特别委婉,不仔细看都不知道是分区写错了。

环境变量在出厂时会有一个默认值集合,烧写在存储设备的固定区域。你修改环境变量后,saveenv把它写回去,下次启动继续生效。调试阶段发现反复改的配置没生效,先确认是不是没执行saveenv——这是uboot玩家最常见的低级错误之一。

3.4 bootm是怎么把内核交给Linux的

到了bootm执行这一步,uboot的使命基本快结束了。bootm命令要做的事情分三块:准备内核镜像、准备内核启动参数、跳转执行。

如果是传统uImage格式,uboot会解析头部信息,把真正的内核代码段解压到内存指定位置,然后用寄存器传递机器类型编号和设备树指针。4412属于较新的平台,常用方式是配合设备树(DTB文件),uboot把DTB也放入内存,在跳转内核时把DTB物理地址告诉内核。内核启动早期再通过解析DTB来枚举平台信息、内存大小、串口配置等,极大减少了内核代码对具体板卡的耦合。

跳转内核不是简单地b一下就行,uboot在跳之前要做好最后的收尾:关闭中断、把MMU和Cache置成内核想要的状态、刷新Cache、确保r0、r1、r2三个寄存器分别存放正确的参数。其中r2存放设备树地址,r1存放机器类型,r0通常是0。内核从入口代码开始执行后,uboot就再也没有控制权了。

从代码逻辑上讲,这里有个“唯一交接点”:uboot把CPU放在一个确定的上下文里,内核代码从链接地址入口开始跑。如果跳转后内核一片死寂,往往是三个寄存器传参不对、镜像被解压到了错误地址,或者DTB地址不合法。这类问题用串口看内核日志就能定位,但如果内核首条指令就没跑起来,串口上连一个字都不会有。

4. 实操记录:编译、烧写和串口日志解读

4.1 编译4412 uboot的完整流程

编译uboot看起来复杂,实际上就是普通的交叉编译流程。先准备好交叉编译工具链,比如arm-linux-gnueabihf-gcc,版本选择要贴近厂商BSP的要求,太新或太老都可能出现编译错误。

打开uboot源码根目录,通常第一步是查看板的配置项。厂商BSP的boards.cfg里一般有一行类似itop_4412的配置,用make help能看到支持的目标板列表。配置完成后执行编译:

make itop_4412_config make

最终会在根目录或特定子目录生成u-boot.bin。某些BSP还会额外产出BL1.bin,或者提供一个mkuboot脚本把BL1和uboot.bin拼成一个完整的烧写镜像。如果没有特殊工具,单纯一个u-boot.bin就已经是完整的BL2镜像了。

编译环境里比较坑的是工具链路径和库依赖。如果编译报错缺少标准头文件,先确认交叉编译器的sysroot是否指向正确的目标板库目录,不要拿宿主机的头文件去顶。另外,uboot源码里如果带了厂商补丁,编译前最好先检查补丁是否已经应用,否则很容易出现编译过了但跑不起来的诡异行为。

4.2 烧写到SD卡的具体步骤

编译出来只是第一步,烧写才是动手的关键。针对SD卡启动,假设你的SD卡是/dev/sdb,首先清空卡上原来的内容,确保没有残留分区干扰启动,然后用厂商提供的烧写工具按扇区把BL1和uboot.bin写入指定位置。

使用dd命令时,要特别小心偏移量和输入文件类型。比如写入uboot.bin时,如果它包含完整镜像头部,就直接按BL1给你的偏移来写:

dd if=BL1.bin of=/dev/sdb seek=1 bs=512 conv=fsync dd if=u-boot.bin of=/dev/sdb seek=17 bs=512 conv=fsync

seek=1表示从第1个扇区开始写,bs=512即以512字节为一个扇区。conv=fsync确保数据落盘,防止写入缓存还没来得及刷就拔卡导致镜像不完整。

这里必须再三提醒:写错seek值是灾难性的。如果seek误写成了0,等于从MBR开始覆盖,后面分区表全部报废,而且卡上原来的数据几乎无法恢复。操作前建议把原始卡镜像备份一下,至少记下原来的分区布局。SD卡烧写完成后,把卡插到开发板,拨动OM引脚到SD卡启动,上电看串口。

4.3 串口日志告诉了我们什么

接好串口线(注意交叉连接信号线),打开终端软件,波特率设为115200,开发板上电后,正常启动大概能捕获到这样的日志节奏:

U-Boot 2010.12 (Jun 06 2013 - 15:20:26) CPU: EXYNOS4412 Board: itop-4412 DRAM: 1 GiB ... MMC: SAMSUNG SD/MMC: 0 ... Net: eth0 Hit any key to stop autoboot: 3

看到响应字符串“U-Boot 2010.12”,说明BL2已经完全接管并跑通了串口、CPU、DRAM这几件大事。DRAM: 1 GiB是内存检测结果,如果这里显示的不是板子实际容量,说明DDR配置有问题。MMC:后面列出检测到的存储设备,Net:后面是网络控制器名称。

从“Hit any key to stop autoboot”开始,uboot就进入倒计时。如果你按下任意键,会进入=>命令提示符,可以手动执行help查看命令列表、printenv查看环境变量、load mmc读取内核、bootm启动内核。如果3秒倒计时结束,则会自动执行bootcmd。

串口日志的一个关键技巧是逐行对号:日志出现的顺序、每行内容对应的初始化阶段,基本能帮你判断问题卡在哪一步。比如日志显示DRAM: 1 GiB之后直接没有输出了,那多半是MMC初始化或后续外设初始化卡住,而不是DDR的问题。

5. 启动排查:板子不启动时怎么找问题

5.1 上电无任何输出

最常见也最让人绝望的故障,就是上电后串口一个字符都没有。这时候先不要怀疑uboot,从更底层排查:供电是否正常,核心板上的电源指示灯亮不亮;晶振是否起振,用示波器探一下24MHz晶振是否有波形;OM引脚的启动介质配置是否和你烧写的镜像位置一致;串口线是否接对了,TX/RX有没有交叉。

如果这些都没问题,还有一种可能是BL1根本就没被执行。比如SD卡里BL1被某个工具意外覆盖,或者写BL1时扇区偏移没对,iROM按固定位置怎么读都读不到有效代码,整个系统就停在黑屏状态。解决方法是重新烧写BL1,并且烧完后用工具读一遍确认内容在正确的扇区上。

5.2 卡在DRAM初始化

如果串口上连“U-Boot”字样都没出现,但板子有上电反应,这一阶段大概率是DRAM初始化失败或时钟配置卡死。因为串口初始化是在DRAM可用之前,打印不了任何东西。此时能用的是示波器量内存数据线上的波形,观察是否有读写请求,再对照BSP里的DDR配置逐项检查。

有条件的话,建议直连调试器,在start.S里关键位置下断点,观察PC运行到哪一步停了。没有调试器,就用最笨的办法:临时在start.S里增加串口早期的输出代码,能把日志提前多少就提前多少,一步步缩小范围。比如在调用lowlevel_init之前打印一行,如果这行都没有,说明时钟或栈有问题;如果这行有但下一行没有,说明DDR配置导致总线异常。

5.3 能启动uboot但网络不通或内核挂不上根文件系统

能进uboot命令行,说明整体框架是好的。网络不通,先看Net: eth0这行是否正常出现,如果没有,检查网卡芯片电源、复位引脚、MDIO总线连接,以及uboot源码里对应网卡驱动的配置。如果有但ping不通,再看环境变量里的ethaddr是否有效、网关和服务器地址是否配对。有些网卡芯片必须用Ethernet MAC地址,地址全零会被驱动拒绝,直接现象就是“link up但数据不通”。

内核能起来但挂不上根文件系统,这是更典型的应用层配置问题。kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(179,2)这类日志出现时,先核对bootargs里root=的分区号与实际分区号是否一致。SD卡设备名可能是/dev/mmcblk0p1、/dev/mmcblk0p2,分配对即可。此外还要确认根文件系统镜像格式是否正确,比如内核只支持ext4,但你烧写的是ubifs格式,那就是format mismatch,跑不起来。

5.4 调uboot的实用技巧

调uboot最有效的工具永远是打印。加上printf和puts,把关键函数的入口、寄存器值、状态字都打出来,配合串口日志一点一点缩小问题范围。如果连基础打印都没有,就查bd/board相关信息有没有初始化成功。

其次是合理利用saveenv。在调试过程中,把临时修改的bootcmd和bootargs存下来,下次启动直接生效,能大幅减少重复输入命令的麻烦。但记住,最终的出厂环境变量要记得改回正规配置,否则交付给人家的板子一上电就跑进你自己的调试命令,现场会非常尴尬。

最后一点是善用第二块启动介质。调试过程中如果SD卡启动不稳定,先通过eMMC启动进入uboot,再从uboot里用命令读取SD卡内容。这样即便SD卡镜像写坏了,开发板仍有一块“保底系统”可以操作,能省很多来回拔插的功夫。

经过这几轮折腾,你会发现自己对uboot的整个启动过程有了真正的感觉,而不只是会敲烧写命令。4412的这套启动链在当今不少应用处理器上依然能看到类似思路,只是命名和细节略有差异——搞懂它,再换其他平台的开发板,你会发现启动过程不过是大同小异。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进