坑!uboot升级过程遇到的两个bug

嵌入式ARM 2020-09-11 00:00

背景

之前做过一次 uboot的升级,当时留下了一些记录,本文摘录其中比较有意思的两个问题。

启动失败问题

问题简述

uboot代码中用到了一个库,考虑到库本身跟 uboot版本没什么关系,就直接把旧的库文件拷贝过来使用。结果编译链接是没问题,启动却会卡住。

消失的打印

为了明确卡住的位置,就去修改了库的源码,添加一些打印(此时还是在旧版本 uboot下编译的),结果发现卡住的位置或随着添加打印的变化而变化,且有些打印语句,添加后未打印出来。
我决定先从这些神秘消失的打印入手。
分析下 uboot中的 printf实现,最底层就是写寄存器,是一个同步的函数,也没什么可疑的地方。
为了确认打印不出来的时候,到底有没有调用到 printf,我决定给 printf增加一个计数器,在 gd结构体中,增加一个 printf_count字段,初始化为 0,每次打印时执行 printf_count++并打印出值。
设计这个试验,本意是确认未打印出来时是否确实也调用到了 printf,但却有了别的发现,实验结果中 printf_count值会异常变化,不是按打印顺序递增,而是会突变成很大的异常值。
printf_countgd结构体的成员,那就是 gd的问题了。进一步将 uboot全局结构体 gd的地址打印出来。确认了原因是 gd结构体的指针变化了。
这也可以解释部分打印消失的现象,原因是我们在 gd中有另一个字段,用于控制打印等级。当 gd被改动了, printf就可能解析出错,误以为打印等级为 0而提前返回。

gd的实现

那么好端端的, gd为什么会被改了呢?这就要先看看 gd到底是怎么实现的了。
uboot中维护了一个全局的结构体 gd。在代码中加入

    
DECLARE_GLOBAL_DATA_PTR;
即可使用 gd指针访问这个全局结构体,许多地方都会借助 gd来保存传递信息。
进一步看看这个宏的定义

    
旧版本uboot:
#define DECLARE_GLOBAL_DATA_PTR        register volatile gd_t *gd asm ("r8")

新版本uboot:
#define DECLARE_GLOBAL_DATA_PTR        register volatile gd_t *gd asm ("r9")
居然不一样,一个是将 gd的值放到 r8寄存器,一个是放在 r9寄存器。
那么就可以猜测到,库是在旧版本 uboot中编译出来的,可能使用了 r9,那么放到新版本 uboot中去,就会破坏 r9寄存器中保存的 gd值,导致一系列依赖 gd的代码不能正常工作。

验证改动

为了求证,将库反汇编出来,发现确实避开了 r8寄存器,但使用了 r9寄存器。
说明 uboot在指定 gd寄存器的同时,还有某种方法让其他代码不使用这个寄存器。
那是不是把旧 uboot中的这个 r8改成 r9,重新编译库就可以了呢?试一下,还是不行。
那么禁止其他代码使用 r8寄存器肯定就是通过别的方式实现的了。简单粗暴地在旧版本 uboot下搜索 r8,去掉 .c .h等类型后,很容易发现了

    
./arch/arm/cpu/armv7/config.mk:24:PLATFORM_RELFLAGS += -fno-common -ffixed-r8 -msoft-floa
-ffixed-r8修改为 -ffixed-r9,重新编译出库,这回就可以正常工作了,打印正常,启动正常。反汇编出来也可以看到,新编译出来的库用了 r8没有用 r9
当然更好的改法,是直接在新版本的 uboot中编译,这是最可靠的。

追本溯源

话说回来,为什么两个版本的 uboot,会使用不同的寄存器呢?难道有什么坑?
这就得去翻一下 git记录了。

    
commit fe1378a961e508b31b1f29a2bb08ba1dac063155
Author: Jeroen Hofstee <jeroen@myspectrum.nl>
Date:   Sat Sep 21 14:04:41 2013 +0200

    ARM: use r9  for gd
    
    To be more EABI compliant and as a preparation  for building
    with clang, use the platform-specific r9 register  for gd
    instead of r8.
    
    note: The FIQ is not updated since it is not used  in u-boot,
    and under discussion  for the time being.
    
    The following checkpatch warning is ignored:
    WARNING: Use of volatile is usually wrong: see
    Documentation/volatile-considered-harmful.txt
    
    Signed-off-by: Jeroen Hofstee <jeroen@myspectrum.nl>
    cc: Albert ARIBAUD <albert.u.boot@aribaud.net>
git记录中,也可以确认完整地将 r8切换到 r9,都需要做哪些修改

    
diff --git a/arch/arm/config.mk b/arch/arm/config.mk
index  16c2e3d1e0..d0cf43ff41  100644
--- a/arch/arm/config.mk
+++ b/arch/arm/config.mk
@@  -17, 7 + 17, 7 @@ endif
 
 LDFLAGS_FINAL += --gc-sections
 PLATFORM_RELFLAGS += -ffunction-sections -fdata-sections \
-                     -fno-common -ffixed-r8 -msoft- float
+                     -fno-common -ffixed-r9 -msoft- float
 
 # Support generic board on ARM
 __HAVE_ARCH_GENERIC_BOARD := y
diff --git a/arch/arm/cpu/armv7/lowlevel_init.S b/arch/arm/cpu/armv7/lowlevel_init.S
index  82b2b86520. .69e3053a42  100644
--- a/arch/arm/cpu/armv7/lowlevel_init.S
+++ b/arch/arm/cpu/armv7/lowlevel_init.S
@@  -22, 11 + 22, 11 @@ ENTRY(lowlevel_init)
        ldr     sp, =CONFIG_SYS_INIT_SP_ADDR
        bic     sp, sp, # 7  /* 8-byte alignment for ABI compliance */
 #ifdef CONFIG_SPL_BUILD
-       ldr     r8, =gdata
+       ldr     r9, =gdata
 # else
        sub     sp, #GD_SIZE
        bic     sp, sp, # 7
-       mov     r8, sp
+       mov     r9, sp
 #endif
         /*
         * Save the old lr(passed in ip) and the current lr to stack
diff --git a/arch/arm/include/asm/global_data.h b/arch/arm/include/asm/global_data.h
index 79a9597419..e126436093 100644
--- a/arch/arm/include/asm/global_data.h
+++ b/arch/arm/include/asm/global_data.h
@@ -47,6 +47,6 @@ struct arch_global_data {
 
 #include <asm-generic/global_data.h>
 
-#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")
+#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r9")
 
 #endif /* __ASM_GBL_DATA_H */

diff --git a/arch/arm/lib/crt0.S b/arch/arm/lib/crt0.S
index  960d12e732..ac54b9359a  100644
--- a/arch/arm/lib/crt0.S
+++ b/arch/arm/lib/crt0.S
@@  -69, 7 + 69, 7 @@ ENTRY(_main)
        bic     sp, sp, # 7       /* 8-byte alignment for ABI compliance */
        sub     sp, #GD_SIZE     /* allocate one GD above SP */
        bic     sp, sp, # 7       /* 8-byte alignment for ABI compliance */
-       mov     r8, sp           /* GD is above SP */
+       mov     r9, sp           /* GD is above SP */
        mov     r0, # 0
        bl      board_init_f
 
@@  -81, 15 + 81, 15 @@ ENTRY(_main)
  *  'here' but relocated.
  */
 
-       ldr     sp, [r8, #GD_START_ADDR_SP]      /* sp = gd->start_addr_sp */
+       ldr     sp, [r9, #GD_START_ADDR_SP]      /* sp = gd->start_addr_sp */
        bic     sp, sp, # 7       /* 8-byte alignment for ABI compliance */
-       ldr     r8, [r8, #GD_BD]                 /* r8 = gd->bd */
-       sub     r8, r8, #GD_SIZE                 /* new GD is below bd */
+       ldr     r9, [r9, #GD_BD]                 /* r9 = gd->bd */
+       sub     r9, r9, #GD_SIZE                 /* new GD is below bd */
 
        adr     lr, here
-       ldr     r0, [r8, #GD_RELOC_OFF]          /* r0 = gd->reloc_off */
+       ldr     r0, [r9, #GD_RELOC_OFF]          /* r0 = gd->reloc_off */
        add     lr, lr, r0
-       ldr     r0, [r8, #GD_RELOCADDR]          /* r0 = gd->relocaddr */
+       ldr     r0, [r9, #GD_RELOCADDR]          /* r0 = gd->relocaddr */
        b       relocate_code
 here:
 
@@  -111, 8 + 111, 8 @@ clbss_l:cmp r0, r1                   /* while not at end of BSS */
        bl red_led_on
 
         /* call board_init_r(gd_t *id, ulong dest_addr) */
-       mov     r0, r8                   /* gd_t */
-       ldr     r1, [r8, #GD_RELOCADDR]  /* dest_addr */
+       mov     r0, r9                   /* gd_t */
+       ldr     r1, [r9, #GD_RELOCADDR]  /* dest_addr */
         /* call board_init_r */
        ldr     pc, =board_init_r        /* this is auto-relocated! */

启动慢问题

问题简述

填了几个坑之后,新的 uboot可以启动到内核了,但发现启动速度非常慢,内核启动速度慢了接近 10倍!明明是同一个内核,为什么差异这么大。

排查寄存器

初步排查了下设备树配置,以及 uboot跳转内核前的一些关键寄存器,确实在两个版本的 uboot中有所不同,但具体去看这些不同,发现都不会影响速度,将一些驱动对齐之后寄存器差异基本就消失了。

差异的分界

那再细看, kernel的速度有差异, uboot呢?在哪个时间点之后,速度开始产生差异?
尝试在两个版本的 uboot中插入一些操作,对比时间戳,发现两个 uboot在某个节点之后的速度确实有区别。
进一步排查,原来是在打开 cache操作之后,旧 uboot的速度就会比新 uboot快。尝试将旧 ubootcache关掉,则二者基本一致。尝试将旧 uboot操作 cache的代码,移植到新 uboot,未发生改变。
此时可确认新 uboot的开 cache有问题。但觉得这个跟 kernel启动慢没关系。因为 uboot进入 kernel之前都会关 cache,由 kernel自己去重新打开。
也就是不管是用哪份 uboot,也不管 uboot中是否开了 cache,对 kernel阶段都应该没有影响才对。
于是记录下来 uboot的这个问题,待后续修复。先继续找 kernel启动慢的原因。(注:现在看来当时的做法是有问题的,这里的异常这么明显,应该设法追踪下去找出原因才对)

锁定uboot

uboot的嫌疑非常大,但还不能完全确认,因为 uboot之前还有一级 spl。是否会是 spl的问题呢?
尝试改用 新spl+旧uboot,启动速度正常。而新 spl+新uboot的启动速度则很慢,其他因素都不变,说明问题确实出在 uboot阶段。

多做or少做

当时到这一步就卡住了,直接比较两份 uboot的代码不太现实,差异太大了。
后来我就给自己提了个问题,到底新 uboot是多做了某件事情,还是少做了某件事情?
换个说法,目前已知

    
spl --> 旧uboot --> kernel(速度快)
spl --> 新uboot --> kernel(速度快)
但到底是以下的情况 A还是情况 B呢?

    
A: spl(速度慢) --> 旧uboot(做了某个会提升速度的操作) --> kernel(速度快)
   spl(速度慢) --> 新uboot(少做了某个会提升速度的操作) --> kernel(速度慢)

B: spl(速度快) --> 旧uboot(没做特殊操作) --> kernel(速度快)
   spl(速度快) --> 新uboot(多做了某个会限制速度的操作) --> kernel(速度慢)
为了验证,我决定让 spl直接启动内核,看看内核到底是快是慢。
支持过程碰到了一些小问题
1. spl没有能力加载这么大的 kernel
解决:此时不需要 kernel能完全启动,只需要能加载启动一段,足以体现出启动速度是否正常即可,于是裁剪出一个非常小 kernel来辅助实验。
2. kernel需要 dtb
解决:内核有一个 CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE选项。选上重新编译。编译后再用 ddkerneldtb拼接到一起,作为新的 kernel。这样, spl就只需要加载一个文件并跳转过去即可。
试验结果, spl启动的 kernel和使用新 uboot启动的 kernel速度一致,均比旧 uboot启动的 kernel慢。
说明,旧 uboot中做了某个关键操作,而新 uboot没做。

找出关键操作

那接下来的任务就是,找出旧 uboot中的这个关键操作了。
怎么找呢?有了上一步的成果,我们可以使用以下方法来排查
  1. spl加载kernel和旧uboot

  2. spl跳转到旧uboot,此时kernel其实已经在dram中准备好了,随时可以启动

  3. 在旧uboot的启动流程各个阶段,尝试直接跳转到kernel,观察启动速度

  4. 如果在旧ubootA点跳转kernel启动慢,B点跳转启动快,则说明关键操作位于AB点之间。

方法有了,很快就锁定到 start.S,进一步在 start.S中揪出了这段代码

    
#if defined(CONFIG_ARM_A7)
@ set SMP bit
    mrc     p15,  0, r0, c1, c0,  1
    orr        r0, r0, #( 1<< 6)
    mcr        p15,  0, r0, c1, c0,  1
#endif
ubootstart.S中没有这段代码,尝试在新 ubootstart.S中添加此操作,速度立马恢复正常了。
再全局搜索下,原来这个新版本 uboot中,套路是在 board_init中进行此项设置的,而这个平台从旧版本移植过来,就没有设置 SMP bit, 补上即可。

SMP bit是什么

SMP 是指对称多处理器,看起来这个 bit 会影响多核的 cache一致性,此处没有再深入研究。
但可以知道,对于单处理器的情况,也需要设置这个 bit才能正常使用 cache
贴下 arm的图和描述:

    
[6] SMP 

Signals  if the Cortex-A9 processor is taking part  in coherency or not.

In uniprocessor configurations,  if this bit is  setthen Inner Cacheable Shared is treated as Cacheable. The reset value is zero.
搜下 kernel的代码,发现也是有地方调用了的。不过这个芯片是单核的,根本就没配置 CONFIG_SMP

    
#ifdef CONFIG_SMP
 ALT_SMP(mrc p15,  0, r0, c1, c0,  1)
 ALT_UP(mov r0, #( 1 <<  6))  @ fake it  for UP
 tst r0, #( 1 <<  6)   @ SMP/nAMP mode enabled?
 orreq r0, r0, #( 1 <<  6)  @ Enable SMP/nAMP mode
 orreq r0, r0, r10   @ Enable CPU-specific SMP bits
 mcreq p15,  0, r0, c1, c0,  1
#endif

总结

整理出来一方面是记录这两个 bug,另一方面也是想记录下当时的一些操作。
毕竟同样的 bug可能以后都不会碰到了,但解 bug的方法和思路却是可以积累复用的。

-END-


本文授权转载自qb杂货铺,作者:瞎折腾的zqb




推荐阅读



【01】C语言内存泄露很严重,如何应对?
【02】编译C语言程序,使用 gcc 指令,而C++程序则推荐使用 g++指令!
【03】C语言:优雅的字符串函数库
【04】在C 语言中,请一定记得初始化局部变量!
【05】嵌入式编程是否应该用C++替代C语言


免责声明:整理文章为传播相关技术,版权归原作者所有,如有侵权,请联系删除
嵌入式ARM 关注这个时代最火的嵌入式ARM,你想知道的都在这里。
评论 (0)
  •   天空卫星健康状况监测维护管理系统:全方位解析  在航天技术迅猛发展的当下,卫星在轨运行的安全与可靠至关重要。整合多种技术,实现对卫星的实时监测、故障诊断、健康评估以及维护决策,有力保障卫星长期稳定运转。  应用案例       系统软件供应可以来这里,这个首肌开始是幺伍扒,中间是幺幺叁叁,最后一个是泗柒泗泗,按照数字顺序组合就可以找到。  一、系统架构与功能模块  数据采集层  数据处理层  智能分析层  决策支持层  二、关键技术  故障诊断技术  
    华盛恒辉l58ll334744 2025-04-10 15:46 76浏览
  • 什么是车用高效能运算(Automotive HPC)?高温条件为何是潜在威胁?作为电动车内的关键核心组件,由于Automotive HPC(CPU)具备高频高效能运算电子组件、高速传输接口以及复杂运算处理、资源分配等诸多特性,再加上各种车辆的复杂应用情境等等条件,不难发见Automotive HPC对整个平台讯号传输实时处理、系统稳定度、耐久度、兼容性与安全性将造成多大的考验。而在各种汽车使用者情境之中,「高温条件」就是你我在日常生活中必然会面临到的一种潜在威胁。不论是长时间将车辆停放在室外的高
    百佳泰测试实验室 2025-04-10 15:09 77浏览
  • 背景近年来,随着国家对资源、能源有效利用率的要求越来越高,对环境保护和水处理的要求也越来越严格,因此有大量的固液分离问题需要解决。真空过滤器是是由负压形成真空过滤的固液分离机械。用过滤介质把容器分为上、下两层,利用负压,悬浮液加入上腔,在压力作用下通过过滤介质进入下腔成为滤液,悬浮液中的固体颗粒吸附在过滤介质表面形成滤饼,滤液穿过过滤介质经中心轴内部排出,达到固液分离的目的。目前市面上的过滤器多分为间歇操作和连续操作两种。间歇操作的真空过滤机可过滤各种浓度的悬浮液,连续操作的真空过滤机适于过滤含
    宏集科技 2025-04-10 13:45 80浏览
  • 行业痛点:电动车智能化催生语音交互刚需随着全球短途出行市场爆发式增长,中国电动自行车保有量已突破3.5亿辆。新国标实施推动行业向智能化、安全化转型,传统蜂鸣器报警方式因音效单一、缺乏场景适配性等问题,难以满足用户对智能交互体验的需求。WT2003HX系列语音芯片,以高性能处理器架构与灵活开发平台,为两轮电动车提供从基础报警到智能交互的全栈语音解决方案。WT2003HX芯片技术优势深度解读1. 高品质硬件性能,重塑语音交互标准搭载32位RISC处理器,主频高达120MHz,确保复杂算法流畅运行支持
    广州唯创电子 2025-04-10 09:12 174浏览
  • 技术原理:非扫描式全局像的革新Flash激光雷达是一种纯固态激光雷达技术,其核心原理是通过面阵激光瞬时覆盖探测区域,配合高灵敏度传感器实现全局三维成像。其工作流程可分解为以下关键环节:1. 激光发射:采用二维点阵光源(如VCSEL垂直腔面发射激光器),通过光扩散器在单次脉冲中发射覆盖整个视场的面阵激光,视场角通常可达120°×75°,部分激光雷达产品可以做到120°×90°的超大视场角。不同于传统机械扫描或MEMS微振镜方案,Flash方案无需任何移动部件,直接通过电信号控制激光发射模式。2.
    robolab 2025-04-10 15:30 105浏览
  • 文/Leon编辑/侯煜‍关税大战一触即发,当地时间4月9日起,美国开始对中国进口商品征收总计104%的关税。对此,中国外交部回应道:中方绝不接受美方极限施压霸道霸凌,将继续采取坚决有力措施,维护自身正当权益。同时,中国对原产于美国的进口商品加征关税税率,由34%提高至84%。随后,美国总统特朗普在社交媒体宣布,对中国关税立刻提高至125%,并暂缓其他75个国家对等关税90天,在此期间适用于10%的税率。特朗普政府挑起关税大战的目的,实际上是寻求制造业回流至美国。据悉,特朗普政府此次宣布对全球18
    华尔街科技眼 2025-04-10 16:39 107浏览
  • 政策驱动,AVAS成新能源车安全刚需随着全球碳中和目标的推进,新能源汽车产业迎来爆发式增长。据统计,2023年中国新能源汽车渗透率已突破35%,而欧盟法规明确要求2024年后新能效车型必须配备低速提示音系统(AVAS)。在此背景下,低速报警器作为车辆主动安全的核心组件,其技术性能直接关乎行人安全与法规合规性。基于WT2003H芯片开发的AVAS解决方案,以高可靠性、强定制化能力及智能场景适配特性,正成为行业技术升级的新标杆。WT2003H方案技术亮点解析全场景音效精准触发方案通过多传感器融合技术
    广州唯创电子 2025-04-10 08:53 201浏览
  • 由西门子(Siemens)生产的SIMATIC S7 PLC在SCADA 领域发挥着至关重要的作用。在众多行业中,SCADA 应用都需要与这些 PLC 进行通信。那么,有哪些高效可行的解决方案呢?宏集为您提供多种选择。传统方案:通过OPC服务器与西门子 PLC 间接通信SIMATIC S7系列的PLC是工业可编程控制器,能够实现对生产流程的实时SCADA监控,提供关于设备和流程状态的准确、最新数据。S7Comm(全称S7 Communication),也被称为工业以太网或Profinet,是西门
    宏集科技 2025-04-10 13:44 95浏览
  • 行业变局:从机械仪表到智能交互终端的跃迁全球两轮电动车市场正经历从“功能机”向“智能机”的转型浪潮。数据显示,2024年智能电动车仪表盘渗透率已突破42%,而传统LED仪表因交互单一、扩展性差等问题,难以满足以下核心需求:适老化需求:35%中老年用户反映仪表信息辨识困难智能化缺口:78%用户期待仪表盘支持手机互联与语音交互成本敏感度:厂商需在15元以内BOM成本实现功能升级在此背景下,集成语音播报与蓝牙互联的WT2605C-32N芯片方案,以“极简设计+智能交互”重构仪表盘技术生态链。技术破局:
    广州唯创电子 2025-04-11 08:59 136浏览
  •   海上电磁干扰训练系统:全方位解析      海上电磁干扰训练系统,作为模拟复杂海上电磁环境、锻炼人员应对电磁干扰能力的关键技术装备,在军事、科研以及民用等诸多领域广泛应用。接下来从系统构成、功能特点、技术原理及应用场景等方面展开详细解析。   应用案例   系统软件供应可以来这里,这个首肌开始是幺伍扒,中间是幺幺叁叁,最后一个是泗柒泗泗,按照数字顺序组合就可以找到。   一、系统构成   核心组件   电磁信号模拟设备:负责生成各类复杂的电磁信号,模拟海上多样
    华盛恒辉l58ll334744 2025-04-10 16:45 122浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦