记一次uboot升级过程的两个坑
2020-06-21 22:38
274 查看
## 背景
之前做过一次`uboot`的升级,当时留下了一些记录,本文摘录其中比较有意思的两个问题。
## 启动失败问题
### 问题简述
`uboot`代码中用到了一个库,考虑到库本身跟`uboot`版本没什么关系,就直接把旧的库文件拷贝过来使用。结果编译链接是没问题,启动却会卡住。
### 消失的打印
为了明确卡住的位置,就去修改了库的源码,添加一些打印(此时还是在旧版本`uboot`下编译的),结果发现卡住的位置或随着添加打印的变化而变化,且有些打印语句,添加后未打印出来。
我决定先从这些神秘消失的打印入手。
分析下`uboot`中的`printf`实现,最底层就是写寄存器,是一个同步的函数,也没什么可疑的地方。
为了确认打印不出来的时候,到底有没有调用到`printf`,我决定给`printf`增加一个计数器,在`gd`结构体中,增加一个`printf_count`字段,初始化为`0`,每次打印时执行`printf_count++`并打印出值。
设计这个试验,本意是确认未打印出来时是否确实也调用到了`printf`,但却有了别的发现,实验结果中`printf_count`值会异常变化,不是按打印顺序递增,而是会突变成很大的异常值。
`printf_count`是`gd`结构体的成员,那就是`gd`的问题了。进一步将`uboot`全局结构体`gd`的地址打印出来。确认了原因是`gd`结构体的指针变化了。
这也可以解释部分打印消失的现象,原因是我们在`gd`中有另一个字段,用于控制打印等级。当`gd`被改动了,`printf`就可能解析出错,误以为打印等级为`0`而提前返回。
### gd的实现
那么好端端的,`gd`为什么会被改了呢?这就要先看看`gd`到底是怎么实现的了。
`uboot`中维护了一个全局的结构体`gd`。在代码中加入
```
DECLARE_GLOBAL_DATA_PTR;
```
即可使用`gd`指针访问这个全局结构体,许多地方都会借助`gd`来保存传递信息。
进一步看看这个宏的定义
```c
旧版本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
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
cc: Albert ARIBAUD
```
从`git`记录中,也可以确认完整地将`r8`切换到`r9`,都需要做哪些修改
```c
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
-#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`快。尝试将旧`uboot`的`cache`关掉,则二者基本一致。尝试将旧`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`选项。选上重新编译。编译后再用`dd`将`kernel`和`dtb`拼接到一起,作为新的`kernel`。这样,`spl`就只需要加载一个文件并跳转过去即可。
试验结果,`spl`启动的`kernel`和使用新`uboot`启动的`kernel`速度一致,均比旧`uboot`启动的`kernel`慢。
说明,旧`uboot`中做了某个关键操作,而新`uboot`没做。
### 找出关键操作
那接下来的任务就是,找出旧`uboot`中的这个关键操作了。
怎么找呢?有了上一步的成果,我们可以使用以下方法来排查
1. `spl`加载`kernel`和旧`uboot`
2. `spl`跳转到旧`uboot`,此时`kernel`其实已经在`dram`中准备好了,随时可以启动
3. 在旧`uboot`的启动流程各个阶段,尝试直接跳转到`kernel`,观察启动速度
4. 如果在旧`uboot`的`A`点跳转`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
相关文章推荐
- springboot 1.5.10 升级到2.0.4需要修改的两个地方
- 在连续两个平台的uboot和Linux系统移植过程中,在千兆网口调试这块都遇到了很大的麻烦。由于寄存器数量庞大,千兆网口MAC和PHY内部结构复杂,MAC和PHY接口种类多,千兆以太网驱动的调试成
- U-BOOT的两个阶段启动过程与第二阶段的board_init_f和board_init_r
- U-BOOT的两个阶段启动过程与第二阶段的board_init_f和board_init_r
- U-BOOT的两个阶段启动过程与第二阶段的board_init_f和board_init_r
- 任意一个5位数,比如:34256,把它的各位数字打乱,重新排列,可以得到一个最大的数:65432, 一个最小的数23456。求这两个数字的差,得:41976,把这个数字再次重复上述过程(如果不足5位,
- Android系统Recovery工作原理之使用update.zip升级过程分析(三)---Android系统的三种启动模式
- u-boot-2009.08工程编译过程分析
- 升级Android支持库版本遇到的两个问题
- Android系统Recovery工作原理之使用update.zip升级过程分析(一)---update.zip包的制作
- U-Boot移植(二)——U-Boot编译过程分析(3)
- U-Boot 启动过程和源码分析(第二阶段)
- 总结一下springboot项目整合log4j2框架过程中遇到的问题
- [转]ubuntu10.10升级到11.04过程中python2.7升级出错的解决方法(安装wine过程中出错)
- Apollo升级系统 - uboot 环境变量的组织
- U-Boot工作过程
- Android系统Recovery工作原理之使用update.zip升级过程分析(一)---update.zip包的制作
- Android系统Recovery工作原理之使用update.zip升级过程分析(六)---R...
- Solaris boot过程详解
- SpringBoot 定时任务升级篇(动态修改cron参数)