记一次uboot升级过程的两个坑

背景

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

启动失败问题

问题简述

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

消失的打印

为了明确卡住的位置,就去修改了库的源码,添加一些打印(此时仍是在旧版本uboot下编译的),结果发现卡住的位置或随着添加打印的变化而变化,且有些打印语句,添加后未打印出来。ide

我决定先从这些神秘消失的打印入手。函数

分析下uboot中的printf实现,最底层就是写寄存器,是一个同步的函数,也没什么可疑的地方。ui

为了确认打印不出来的时候,到底有没有调用到printf,我决定给printf增长一个计数器,在gd结构体中,增长一个printf_count字段,初始化为0,每次打印时执行printf_count++并打印出值。this

设计这个试验,本意是确认未打印出来时是否确实也调用到了printf,但却有了别的发现,实验结果中printf_count值会异常变化,不是按打印顺序递增,而是会突变成很大的异常值。url

printf_countgd结构体的成员,那就是gd的问题了。进一步将uboot全局结构体gd的地址打印出来。确认了缘由是gd结构体的指针变化了。.net

这也能够解释部分打印消失的现象,缘由是咱们在gd中有另外一个字段,用于控制打印等级。当gd被改动了,printf就可能解析出错,误觉得打印等级为0而提早返回。设计

gd的实现

那么好端端的,gd为何会被改了呢?这就要先看看gd究竟是怎么实现的了。3d

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 set, then 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的方法和思路倒是能够积累复用的。

blog: http://www.javashuo.com/article/p-tylcsvns-hk.html
公众号:https://sourl.cn/shT3kz

相关文章
相关标签/搜索