|
背景 之前做过一次uboot的升级,当时留下了一些记录,本文摘录其中比较有意思的两个问题。% K2 H1 V" \% n6 @& { " ^4 E0 g% m1 g1 \4 e4 { 启动失败问题( N# E3 [1 w4 l) W2 i- k! I8 a# v 问题简述6 q, j0 N3 a, h g( q uboot代码中用到了一个库,考虑到库本身跟uboot版本没什么关系,就直接把旧的库文件拷贝过来使用。结果编译链接是没问题,启动却会卡住。 . c" m( B! j: Z& [# b - Q. D: a- e5 D5 ~ 消失的打印 为了明确卡住的位置,就去修改了库的源码,添加一些打印(此时还是在旧版本uboot下编译的),结果发现卡住的位置或随着添加打印的变化而变化,且有些打印语句,添加后未打印出来。 我决定先从这些神秘消失的打印入手。1 g6 F" p1 \/ ?! u/ o, l 分析下uboot中的printf实现,最底层就是写寄存器,是一个同步的函数,也没什么可疑的地方。 为了确认打印不出来的时候,到底有没有调用到printf,我决定给printf增加一个计数器,在gd结构体中,增加一个printf_count字段,初始化为0,每次打印时执行printf_count++并打印出值。9 r" S$ D" \3 d% d1 d8 U, ?+ s 设计这个试验,本意是确认未打印出来时是否确实也调用到了printf,但却有了别的发现,实验结果中printf_count值会异常变化,不是按打印顺序递增,而是会突变成很大的异常值。 printf_count是gd结构体的成员,那就是gd的问题了。进一步将uboot全局结构体gd的地址打印出来。确认了原因是gd结构体的指针变化了。+ M+ C$ a9 b/ J J, U: l 这也可以解释部分打印消失的现象,原因是我们在gd中有另一个字段,用于控制打印等级。当gd被改动了,printf就可能解析出错,误以为打印等级为0而提前返回。 9 S5 N8 n/ z3 Z4 e4 }. d" ?3 [ gd的实现$ Z) Z9 {# E2 |2 q! ] 那么好端端的,gd为什么会被改了呢?这就要先看看gd到底是怎么实现的了。uboot中维护了一个全局的结构体gd。在代码中加入DECLARE_GLOBAL_DATA_PTR;/ b6 y% m) D4 J, U( T 即可使用gd指针访问这个全局结构体,许多地方都会借助gd来保存传递信息。进一步看看这个宏的定义 旧版本uboot: #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")8 }/ Y2 _% G* ` 9 {/ g! |3 Z/ }+ f& [ 新版本uboot:! s r8 s- [! @) R3 r [! f9 k- t; r #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9") 居然不一样,一个是将gd的值放到r8寄存器,一个是放在r9寄存器。 那么就可以猜测到,库是在旧版本uboot中编译出来的,可能使用了r9,那么放到新版本uboot中去,就会破坏r9寄存器中保存的gd值,导致一系列依赖gd的代码不能正常工作。7 G+ [( n* ?0 F " x4 C# O& {- Q- w; A . g2 \4 p/ {, G! _: D3 o: _ 验证改动 为了求证,将库反汇编出来,发现确实避开了r8寄存器,但使用了r9寄存器。 说明uboot在指定gd寄存器的同时,还有某种方法让其他代码不使用这个寄存器。. \' \8 U {# C$ e3 V P) u 那是不是把旧uboot中的这个r8改成r9,重新编译库就可以了呢?试一下,还是不行。 那么禁止其他代码使用r8寄存器肯定就是通过别的方式实现的了。简单粗暴地在旧版本uboot下搜索r8,去掉.c .h等类型后,很容易发现了 ./arch/arm/cpu/armv7/config.mk:24 LATFORM_RELFLAGS += -fno-common -ffixed-r8 -msoft-floa将-ffixed-r8修改为-ffixed-r9,重新编译出库,这回就可以正常工作了,打印正常,启动正常。反汇编出来也可以看到,新编译出来的库用了r8没有用r9。' T. T* ]! u- v2 b T$ h/ z) _ O 当然更好的改法,是直接在新版本的uboot中编译,这是最可靠的。# F8 a* ^2 m, [* o1 Y* K! P1 o7 q $ u7 N5 i$ d' U# O9 D ) b' x- v# v* w, @8 p% t, V: Z7 @ 追本溯源 话说回来,为什么两个版本的uboot,会使用不同的寄存器呢?难道有什么坑?这就得去翻一下git记录了。 commit fe1378a961e508b31b1f29a2bb08ba1dac063155, Y ^) G# Z6 j" C+ J Author: Jeroen Hofstee <jeroen@myspectrum.nl> Date: Sat Sep 21 14:04:41 2013 +0200. V6 {8 C- D! _* z" l 8 |- P+ S" @! }( e" _+ e3 Q ARM: use r9 for gd; C9 i/ P b% a% P% ?0 e8 P; o! K & `. A# i0 I* |5 q To be more EABI compliant and as a preparation for building with clang, use the platform-specific r9 register for gd6 z2 F# R. `7 v3 \) x) E instead of r8.( x; @) F% T9 A; I3 w: F- V note: The FIQ is not updated since it is not used in u-boot,2 Q% Y" Y/ @* `( w: A and under discussion for the time being. 1 P) d: y+ {" X5 ~2 U The following checkpatch warning is ignored:' J6 V' z, w l b) R" q WARNING: Use of volatile is usually wrong: see Documentation/volatile-considered-harmful.txt 5 j; U# P- m5 I6 k8 G) t 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% a% _" u9 n4 ^ index 16c2e3d1e0..d0cf43ff41 1006440 m3 c& [8 j4 C( n. q" y --- a/arch/arm/config.mk, x: }1 R" Z# D +++ b/arch/arm/config.mk( s2 `% x/ u9 }0 Z* o: B1 H @@ -17,7 +17,7 @@ endif8 ?1 r* a: T7 |9 K+ A+ x LDFLAGS_FINAL += --gc-sections PLATFORM_RELFLAGS += -ffunction-sections -fdata-sections \+ ?* U( e9 g" i( g m - -fno-common -ffixed-r8 -msoft-float + -fno-common -ffixed-r9 -msoft-float $ G4 ~" E# r0 G # 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 1006440 c9 o! Q7 ]6 H --- a/arch/arm/cpu/armv7/lowlevel_init.S +++ b/arch/arm/cpu/armv7/lowlevel_init.S; H* Z" v8 e% e+ c4 {, n @@ -22,11 +22,11 @@ ENTRY(lowlevel_init) ldr sp, =CONFIG_SYS_INIT_SP_ADDR6 U- N5 s% G R/ J6 R2 W9 w% J bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ #ifdef CONFIG_SPL_BUILD: z! c( Z% `3 q! Q+ u - ldr r8, =gdata + ldr r9, =gdata. F% S- Q: s; \( k& o: P #else8 B$ F% _: s+ b$ e sub sp, #GD_SIZE, \) s9 k! o: \6 B bic sp, sp, #7' G3 {5 f1 b$ p! W/ @ - mov r8, sp + mov r9, sp+ P, y# y- |' N7 s" { #endif2 Z2 K# c2 z; A8 W9 {5 p* N /*" \ G, X3 }. h1 A K! H* e7 e2 l F * Save the old lr(passed in ip) and the current lr to stack+ Q) p6 O* u7 t6 G! Y1 \' N, F diff --git a/arch/arm/include/asm/global_data.h b/arch/arm/include/asm/global_data.h index 79a9597419..e126436093 100644* ^. `$ y" W& M( d2 Z --- a/arch/arm/include/asm/global_data.h +++ b/arch/arm/include/asm/global_data.h0 @/ ^- S4 d( j% Q @@ -47,6 +47,6 @@ struct arch_global_data {# p4 z( \8 f' k0 b0 V) Q" M9 T" R , d: f; W( v" T3 k9 I. E4 L #include <asm-generic/global_data.h>. g+ W- @8 G" j6 [/ M- e 9 _& M; q: e9 Z1 v -#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")1 P4 I' e& Q: W! T, Y% s6 q +#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9") T/ U( V/ I9 B& D; G #endif /* __ASM_GBL_DATA_H */' i7 J5 C; f; F' q2 g+ h5 O diff --git a/arch/arm/lib/crt0.S b/arch/arm/lib/crt0.S index 960d12e732..ac54b9359a 100644) L8 }: H+ }! q% D; ~1 [ --- a/arch/arm/lib/crt0.S* j% p3 I' F5 u& F' @. I; r2 W6 S4 S +++ b/arch/arm/lib/crt0.S# D4 C {& U2 |& M* u$ _ @@ -69,7 +69,7 @@ ENTRY(_main)+ t# R; @. @0 B& | bic sp, sp, #7 /* 8-byte alignment for ABI compliance */9 n, ^! f' A6 H7 z3 H 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 */6 E- ~0 L" r! F$ @9 v mov r0, #05 w& a/ `8 ?7 ]* _ bl board_init_f P! }3 c/ `. E; ~' V0 P4 { @@ -81,15 +81,15 @@ ENTRY(_main)- V9 R" z% J# ^! N0 ] * 'here' but relocated. */6 Y+ j' m$ s7 e4 \ - 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 */" @7 E. D' z v' s0 c/ L/ b + ldr r9, [r9, #GD_BD] /* r9 = gd->bd */- p5 {) h* P X5 `, _ + sub r9, r9, #GD_SIZE /* new GD is below bd */0 \& v+ v4 V0 _5 A7 I+ n* n adr lr, here- y( {2 s# m, d# W. X - ldr r0, [r8, #GD_RELOC_OFF] /* r0 = gd->reloc_off */ + ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */& g& M; @3 T, B add lr, lr, r00 ^# p7 D. C5 q) e8 J - ldr r0, [r8, #GD_RELOCADDR] /* r0 = gd->relocaddr */ + ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */, F# s& ?& S) c' y b relocate_code; |* f* T4 l7 x! y! }& b+ X: L here:4 g6 p1 Y: j U. R9 W# [ @@ -111,8 +111,8 @@ clbss_l:cmp r0, r1 /* while not at end of BSS */ bl red_led_on' o) G( N j" K0 b( q+ M) v; l . v3 k! `$ F# T. t9 k' r /* call board_init_r(gd_t *id, ulong dest_addr) */$ B6 @" v6 c( [# t3 ] - mov r0, r8 /* gd_t */ - ldr r1, [r8, #GD_RELOCADDR] /* dest_addr */% F) b/ F4 t- r6 @* L + mov r0, r9 /* gd_t */( ^8 o2 l+ w l) i: T, g* f + ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */6 U7 A$ X. @' e- `3 G3 F /* call board_init_r */: a" d# @4 C* }7 { ldr pc, =board_init_r /* this is auto-relocated! */ 启动慢问题 W' A6 v b! W, O# N: v: ` 问题简述7 l, u8 t, W {! k3 M 填了几个坑之后,新的uboot可以启动到内核了,但发现启动速度非常慢,内核启动速度慢了接近10倍!明明是同一个内核,为什么差异这么大。 L l8 V/ n2 J / \: \0 i6 `( L* c6 f4 o" C) j 排查寄存器* m- h! n0 E- X 初步排查了下设备树配置,以及uboot跳转内核前的一些关键寄存器,确实在两个版本的uboot中有所不同,但具体去看这些不同,发现都不会影响速度,将一些驱动对齐之后寄存器差异基本就消失了。 + O, t! ~. {* M0 o/ G 差异的分界0 {5 \: B. e/ d& |9 B 那再细看,kernel的速度有差异,uboot呢?在哪个时间点之后,速度开始产生差异?& ]" t2 [7 W/ ]: c 尝试在两个版本的uboot中插入一些操作,对比时间戳,发现两个uboot在某个节点之后的速度确实有区别。 进一步排查,原来是在打开cache操作之后,旧uboot的速度就会比新uboot快。尝试将旧uboot的cache关掉,则二者基本一致。尝试将旧uboot操作cache的代码,移植到新uboot,未发生改变。 此时可确认新uboot的开cache有问题。但觉得这个跟kernel启动慢没关系。因为uboot进入kernel之前都会关cache,由kernel自己去重新打开。 也就是不管是用哪份uboot,也不管uboot中是否开了cache,对kernel阶段都应该没有影响才对。) i% x5 Y5 s: m) L6 C! ] 于是记录下来uboot的这个问题,待后续修复。先继续找kernel启动慢的原因。(注:现在看来当时的做法是有问题的,这里的异常这么明显,应该设法追踪下去找出原因才对) ; Z2 p6 E- m; w 锁定uboot6 q: q% B* w. C, I4 J8 A8 w. v# q uboot的嫌疑非常大,但还不能完全确认,因为uboot之前还有一级spl。是否会是spl的问题呢?1 G$ u! s8 b) g, C6 P; |8 C% U 尝试改用新spl+旧uboot,启动速度正常。而新spl+新uboot的启动速度则很慢,其他因素都不变,说明问题确实出在uboot阶段。 8 A* u0 n% Z$ j3 | 多做or少做" h; M0 N+ k2 q5 t) t. I' t- u 当时到这一步就卡住了,直接比较两份uboot的代码不太现实,差异太大了。 后来我就给自己提了个问题,到底新uboot是多做了某件事情,还是少做了某件事情? 换个说法,目前已知0 p, I& @) D1 {" J( a spl --> 旧uboot --> kernel(速度快) spl --> 新uboot --> kernel(速度快)) \8 q/ q' e% F; V) K k 但到底是以下的情况A还是情况B呢?/ ^9 L- z& M) m: M2 w' p. J6 W A: spl(速度慢) --> 旧uboot(做了某个会提升速度的操作) --> kernel(速度快) spl(速度慢) --> 新uboot(少做了某个会提升速度的操作) --> kernel(速度慢)" n7 m) e: ?1 O8 W3 d" I B: spl(速度快) --> 旧uboot(没做特殊操作) --> kernel(速度快) spl(速度快) --> 新uboot(多做了某个会限制速度的操作) --> kernel(速度慢) 为了验证,我决定让spl直接启动内核,看看内核到底是快是慢。 支持过程碰到了一些小问题) z; J/ [. y! O" O' b9 M6 i% q 1.spl没有能力加载这么大的kernel 解决:此时不需要kernel能完全启动,只需要能加载启动一段,足以体现出启动速度是否正常即可,于是裁剪出一个非常小kernel来辅助实验。 2.kernel需要dtb 解决:内核有一个CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE选项。选上重新编译。编译后再用dd将kernel和dtb拼接到一起,作为新的kernel。这样,spl就只需要加载一个文件并跳转过去即可。3 Z0 T1 D/ n, b, e/ [& U 试验结果,spl启动的kernel和使用新uboot启动的kernel速度一致,均比旧uboot启动的kernel慢。说明,旧uboot中做了某个关键操作,而新uboot没做。 找出关键操作 那接下来的任务就是,找出旧uboot中的这个关键操作了。1 ^0 N0 |/ i( h0 t5 v% J9 r2 ? 怎么找呢?有了上一步的成果,我们可以使用以下方法来排查
#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, 19 Z, T+ j6 Y/ C* ]$ q #endif( d$ o* g: L5 y* N+ V 新uboot的start.S中没有这段代码,尝试在新uboot的start.S中添加此操作,速度立马恢复正常了。 再全局搜索下,原来这个新版本uboot中,套路是在board_init中进行此项设置的,而这个平台从旧版本移植过来,就没有设置 SMP bit, 补上即可。 , D- G$ C* |4 K2 z& I4 q6 P ; v m1 P) M9 l- u+ T" | SMP bit是什么2 t* T, T6 D; o) ~ SMP 是指对称多处理器,看起来这个 bit 会影响多核的 cache一致性,此处没有再深入研究。0 x- [4 X x7 t1 G, N3 | 但可以知道,对于单处理器的情况,也需要设置这个bit才能正常使用cache。贴下arm的图和描述:
[6] SMP Signals if the Cortex-A9 processor is taking part in coherency or not.( H9 R) ^* X: }0 D, i) T! @1 y ' Q% g" B) I$ d% j 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?: `, i9 F3 E* y+ Y orreq r0, r0, #(1 << 6) @ Enable SMP/nAMP mode- H6 a: c" E% d$ ^# U. u% i orreq r0, r0, r10 @ Enable CPU-specific SMP bits mcreq p15, 0, r0, c1, c0, 1 #endif 总结6 J: x4 \- Y! M9 \ 整理出来一方面是记录这两个bug,另一方面也是想记录下当时的一些操作。毕竟同样的bug可能以后都不会碰到了,但解bug的方法和思路却是可以积累复用的。 $ ]# [4 {( f1 b5 Z) [$ i |
微信公众号
手机版