U-Boot-2022-移植调试心得


学习了U-Boot 移植的教程,从网上下载了最新版的源码,尝试着操作一下,发现源码与教 材中的发生了很大变化,主要是 dts 的引入,让源码结构和内容有了调整。想着既然是学 习,肯定要能举一反三,就此进入了艰难的入坑与爬坑的过程。U-Boot 的版本千变万化, 但是遇到坑可能千篇一律,可是在学习研究的过程中在网上搜到的此类信息几乎没有,有的 是各类常规知识,类似于成功学的指引,当然也同成功学一样,好多竟然无法复制,于是记 录我的失败以供参考。

1 U-Boot 调试手段

U-Boot 基本上就属于Arm 裸机编程的范畴,调试的确是困难的,而且如果调试方法不正确 还可能引入新的问题,干扰我们的判断。如果有方便的工具或者提前避开误区必然可以节约 我们大量精力。

U-Boot 的 debug_uart_init 位于 crt0.S 中,况且 debug_uart_init 可能因为时钟设备 错误也无法正确工作,所以,在 debug_uart_init 正确工作之前最好用 led 或者设置串口 来显示相关信息以供调试。

1.1 正确使用 led

Tiny210 有4个 led 灯,可以显示 1到15这几个数,可以在程序中插入代码,判断程序执行 到什么位置。

由于 led 子程序至少需要3个寄存器,没有设置栈保存寄存器的情况下,可能造成与调用过

程寄存器的冲突,引入新的问题。

1.2 正确使用串口

至少需要写3段代码,uart_asm_init,uart_putc,uart_print_hex, 这样既可以完全控制串 口初始化,也可以显示基本字符和内容地址及其中值。

这里同 led 显示子程序一样,可能由于寄存器冲突导致调用者出错。与 led 显示子程序不 同的是,由于我在 uart_print_hex 中使用 bl 调用了 uart_putc 为显示字符,这会更改 lr 寄存器的内容,如果处理不好会导致 uart_print_hex 的调用者无法返回到正确的地方。

1.3 串口的选择

我一度想使用 UART0 为输出信息,但检查 IROM 之后可以看到,IROM 使用 UART2 作为控 制台输出信息,要想看到IROM执行期间的错误信息又不想开出两个串口终端的话,最好是与三星 保持一致,使用UART2。

1.4 正确使用 Superboot 和 MiniTools

友善之臂的 Superboot 和 MiniTools 确实对开发提供了便利,至少不用一遍又一遍的拔插 存储卡。但同时也为调试引入了一些可能让人困惑的地方,比如用 MiniTools 下载运行, 结果正确,但烧写到卡上或者 NAND 中就不能正确运行,反过来也一样,就是使用 MiniTools 不能正确运行,全面排查找不到原因。主要有如下坑:

1.4.1 Superboot 导致 UART2 无法输出

断电检查 UART2 输出线未短路,运行 Superboot 后 UART2 及 UART3 输出线均短接地线, 当然 UART1 正常,所以不要在 Superboot Ram 中调试 UART2 及 UART3。

1.4.2 Superboot 对烧写 Nand flash 造成加载的U-Boot 跑飞

Superboot (我使用的版本比较早,大概是2013年左右的吧) 只保证前20K 的正确读写,超 过20K 其后如果使用 IROM 提供的API读写是读不完整的,当然可以在烧写前对要烧写的镜 像进行修改后再烧写。另一个问题是,不支持烧写大于1M的 bootloader ,我没有深究是 Superboot 的问题还是 MiniTools 的问题。

后面我将专门记录一下如何读 Nand 及排查问题。

1.5 巧用 uart_stdio

这是 Arm 裸机开发中的一个典型应用,这个应用可以作为一个基础应用来验证很多代码, 先验证了 uart ,再逐步加入时钟初始化、内存初始化、Nand 代码加载等功能,这样可以排 除很多干扰,只要将此调试通了后,就可以大致判断 U-Boot 中的问题可能在哪里。要注意 的是链接地址的设置,如果使用 Superboot 和MiniTools 时链接地址可以设置到 DDR 中, 但要是直接从 Nand 或者 SD 卡中加载就需要初始化DDR 并且重定位代码,否则是不能正确 运行的。

1.6 检查 U-Boot 调试信息

当 debug_uart_init 调试正常之后,可以开启调试信息,之后的内容调试就非常方便了。 可以检查过程调用、内存设置等,定位程序在哪里跑飞。

2 串口调试

串口作为重中之重的调试手段,需要作为第一个要调通的内容,否则后续的调试就不用讨论 了,我们遇到的问题主要有串口乱码或无输出两种,但其后的原因有以下好几种。

2.1 硬件短路问题

  • 如果输出线与地线完全短路,会造成无输出;
  • 如果不是完全短路,比如焊油未清洁,输出线与地线之间有轻微短路,会造成输出乱码;
  • 还有一种情况,在未通电情况下检查硬件一切正常,运行 Superboot 后发现UART2和 UART3 输出与地线短接了,此时如果使用 MiniTools下载程序并运行是看不到串口输出的。

2.2 其他硬件问题

  • 电源电流太小,比如只使用电脑的USB 供电可能因为电流不足导致传输乱码。
  • 还有一种可能是网上说频率太高同时使用线缆太长(当然理论应该是对的,USB 线缆屏蔽 不好造成握手和设备发现问题和是遇到的,UART 中所谓的长应该是很长才对)。
  • 芯片质量问题,我在搞 GM8136S 的时候就发现串口总是有点不稳定,很容易因为一些抖 动干扰什么的导致串口传输过程中出现乱码。
  • 共地问题,曾经调试显示模块,模块与主板之间未共地,导致始终不稳定。

2.3 时钟设置问题

与串口相关的设置涉及到如下几个地方。

  1. UART 时钟源(CLK_SRC4)
  2. UART 时钟分频参数(CLK_DIV4)
  3. UART 时钟门(CLK_GATE_IP3)
  4. UART 自己的控制寄存器(UCON)对时钟源的选择(默认使用 PCLK,很重要也容易忽略,这里让 我一度怀疑时钟初始化是不是根本不能起作用)
  5. 如果选择使用PLL 之后输出作为UART 时钟源,那么还需要设置相关 PLL 倍频及分频参 数(LOCK,CON,DIV,SRC)。

其中一个地方设置错误都会导致串口无输出或者乱码。

其中特别要 注意 的是,debug_uart_init UART 控制寄存器配置默认使用的是PCLK,而 不是其他时钟源,所以给出的时钟频率是PCLK 频率。我的教训是,在配置DEBUG串口时钟频 率忽略了 UCON 对时钟源的选择控制,发现在 debug_uart_init 前串口输出正常,之后输 出乱码。

2.4 串口参数错误

  1. 按时钟频率进行计算并选择UBRDIV 和 UDIVSLOT,如果与时钟不匹配,则乱码或者无输出。
  2. PC 上位机要与 ULCON 设置相匹配。

2.5 DTS 配置问题

goni 开发板遵循三星建议使用 uart2 作为 console ,如果 debug_uart_init 使用 uart2 而 dts 又改成了 uart0 或者其他,会造成显示 u-boot 跑起来后控制台没显示后续信息了。

3 程序卡死的几种原因

3.1 程序未加载到内存中

比如 IROM 加载16K到内存的时候,还有些代码位于SD 或者 Nand 中,如果前半段代码要调 用的代码位于后半段,程序就飞了,没有任何响应。

crt0.S 中调用的 board_init_f_alloc_reserve 就可能在后半段,具体要看编译链接情况, 可以使用 objdump u-boot 查看其实际所在位置。 debug-uart-init 也可能是在后半段, 所以初始化串口也可能无法返回。

3.2 SOC 中模块控制器未打开

比如未初始化 DDR 会导致程序在访问相关地址时卡死,可以用汇编代码尝试加载还未初始 化的内在所在的地址范围的数据,对于 s5pv210 可以尝试 0x20000000 ,不出意外应该是 卡死,程序执行不下去了。

3.3 SOC 模块时钟未打开

比如 goni 板默认使用 OneNand ,所以在时钟初始化时将 Nand 控制模块的时钟关掉了, 这会导致自己初始化 Nand 并拷贝 Nand 当中数据到内存的API 不能返回。具体如何排查后 面专门的文章记录。

3.4 调用某个函数或者过程时参数错误

这同第一种情况相关但不完全一样,因为还有另一种情况,U-Boot 在有些时候发现计算的 结果太离谱会直接调用 hang(),让代码陷入死循环,比如后面会讲的 bss 段链接设置的问 题导致程序挂起。

4 DDR SDRAM 调试

4.1 未初始化 DDR 导致程序卡死

可以用汇编代码尝试加载还未初始化的内在所在的地址范围的数据,对于 s5pv210 可以尝 试 0x20000000 ,不出意外应该是卡死,程序执行不下去了。

4.2 内存初始化

主要有4个易错参数。

  • chip_mask,AXI 基址掩码,为1的位表示使用,为0的位表示不使用。
  • chip_row,32位地址映射数据中的行地址位数,根据硬件连接情况,在Datasheet "PAD MUX FOR ADDRESS CONFIGURATION" 这张表的附注中可以查到 row 的数据,也可以 DDR Datasheet 中查询芯片寻址的参数。
  • chip_col,32位地址映射数据中的列地址位数,可以在芯片手册寻址参数中查到(当然我 认为按地址 SOC 芯片的内在地址映射和芯片布局计算也可以,但直接查更简单)。
  • cl ,列地址锁存时间,必须要与发送给内存芯片的命令中的想一致,这里非常 重要.

4.3 内存可访问的最大地址是多少

我专门写段代码进行测试。

//check memory edge
ldr r1,=0x20000000
ldr r0,=0x12345678
str r0,[r1]
ldr r0,[r1]
bl uart_print_hex

ldr r1,=0x3FFFFFF8 //为何最大为此
str r1,[r1]
ldr r0,[r1]
bl uart_print_hex*/

512M 内存最高可访问地址是 0x3ffffff8,我也不是很明确,但推断可能是 arm 指令要求 8字节对齐吧,如果使用 Thumb 的话应该可能达到 0x3ffffffc。

4.4 影响内存配置的头文件

configs/s5p_goni_defconfig 会影响 .config 的生成最终影响程序编译,同时还有一个地 方,include/configs/s5p_goni.h 中的相关参数也会影响相关配置,在内在配置上需要修 改正确才能保证板子被驱动。

5 代码重定位

5.1 U-Boot 的重定位不是我想的那样

我以为的重定位主要是指从 SD 或者 Nand 加载代码到内存并跳转到内存中执行的过程,实 际上 U-Boot 中我没有找到从 SD 或者 Nand 中加载代码的过程,而主要是从内存中 SYS_TEXT_BASE 位置,把代码搬到内存的最高区域,并跳转执行的过程。

5.2 是否需要 SPL

本来是在写从NAND 和 SD 中复制代码内存的代码时遇到困难,猜想 U-Boot 中可能有搬运 代码的功能,是不是在 SPL 中实现了。很多网上的资料显示 s5pc1xx 需要 SPL, 尝试编译 SPL , 修改 Kconfig, 及相关配置后make,发现还要实现几个函数,于是再转头研究,最终 发现不用 SPL 也可以,关键就是在16K代码范围内把代码复制到内存中并跳转成功。

5.3 从 NAND 启动

可以先调试从Nand 启动,这样能使用 Superboot 和 MiniTools 的烧写Nand 功能,极大便 利我们的开发,当然U-Boot 的体量达到一定程度后也会遇到 Superboot 和 MiniTools 带 来的副作用(后文详解)。

5.3.1 要不要使用IROM API

网上的主流方案是使用带 ECC Nand 读写的库,特别是 kangear 改善 ECC 功能的库,而对 于使用 Samsang IROM API 的方式有提到但好像并不是主要的介绍对象。但本人认为,使用 API 的方式可以减少代码量,而且我相信其实现的检验和纠错能力是能达到的极限了。

5.3.2 NF8_ReadPage_Adv 和 NF16_ReadPage_Adv 应该使用哪个

根据三星提供的说明,后者是针对 16 位数据总线的,而前者是针对8位数据总线的。文档 关于API ECC 的说明不清不楚,而且也没有说一次读几Kb。

5.3.3 OM 寄存器的启动介质信息

这个寄存器体现了启动方式,其实也包含了启动介质的基本信息,如果 OM 低5位等于6的话, 就表述的 4Kb 5Cycle 16Bit ECC 启动,也就是说 NF8_ReadPage_Adv 实际上按每页4K,使 用16字节ECC来加载的NAND。我们可以将 OM 的值打印出来看一下来判断 NF8_ReadPage_Adv 的工作方式,这样在烧写 Nand 的引导程序的时候也要按这些参数进行工作。

5.3.4 Nand 要不要初始化

实践表明,如果从Nand 启动,IROM 会对Nand 进行初始化,那么在复制代码到内存前其实 不用再次对NFCONF、NFCON等寄初始化进行初始化。而且发现按照手册设置的参数读取数据 没有原始参数读取数据稳定。

5.3.5 排查 API 卡死故障

如果调用 API 时卡死 ,将Nand 控制器配置寄存器中以及相应 GPIO 功能配置寄存器的内容 读出来看看,发现Nand控制寄存器内容为0,检查发现Nand 时钟未开,相当于控制器未工作。

5.3.6 重定位后的程序跑飞

你是如何保证从Nand 加载的代码与原代码是一致的呢,当然把读到的数据通过串口发到电 脑上查看,简单的方式是每隔固定间隔地址打印一下存着的值,然后与我们期望的内容对比 一下。在使用 Superboot 和 MiniTools 烧写U-Boot 时只有前20K 正确存取,也就是前5页, 而之后的页, NF8_ReadPage_Adv 不能正确读取,每两页只能读一页出来,在继续使用 Superboot 和 MiniTools 的情况下,可能把 mkbl2 (也就是为镜像添加头部的工具)改一 改,从第5页开始,填充1页0,然后再写正确的数据,这样生成的新镜像烧写到 Nand 后可 以使用 NF8_ReadPage_Adv 正确的读出来。

5.3.7 如果生成的镜像大于1M怎么办

这种情况下使用 Superboot 和 MiniTools 是无法烧写Nand 的,那么可以尝试将先不将 CMD 编译进镜像,把U-Boot 调试通了之后再把 CMD 模块加进去。

5.4 从SD卡启动

当从Nand 调通了以后,从SD 卡启动就是改改 API 的事情,超级简单,一遍就过。

6 Debug 信息开启及后续问题解决

6.1 开启 Debug 信息

使用 KCFLAGS=-DDEBUG 参数可能使 debug 函数输出调试信息。

CROSS_COMPILE=arm-cortex_a8-linux-gnueabi- KCFLAGS=-DDEBUG make -j3

6.2 解决内存地址段设置问题

发现起始地址变成了0x30000000, 当然后续出现了访问错误,修改 include/configs/s5p_goni.h 中的相关参数及涉及到的其他代码后解决错误。

6.3 解决 .bss 段地址引起的程序错误

Missing uclass for driver root_driver
dm_init() failed: -12
initcall sequence 3ffe85ac failed at call 34807d98 (err=-12)
### ERROR ### Please RESET the board ###

发现内存地址异常,主要是在计算 mon_len 时使用 __bss_end - _start,追踪 __bss_end 发现 __bss_start=0, 而 __bss_end 是一个很小的值,最终 mon_len 为负数,被当作特 别大的正确值,导致内在地址计算异常。而 __bss_strt 为0的关键是 u_boot.lds 中使用 OVERLAY 与前一个段共用内存造成的,本来他的链接地址应该与前一个段一样才对,但是却 为0,后找到原因,重新编译链接前需要“ make clean ” 否则链接会出问题,U-Boot 跑 起来了。


本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本许可协议 进行许可。

posted on 2022-10-16 22:25  YourTech-WuPeng  阅读(1018)  评论(0编辑  收藏  举报

导航