背景
之前做過一次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
來儲存傳遞資訊。
進一步看看這個巨集的定義
舊版本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
快。嘗試將舊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
中的這個關鍵操作了。
怎麼找呢?有了上一步的成果,我們可以使用以下方法來排查
-
spl
載入kernel
和舊uboot
-
spl
跳轉到舊uboot
,此時kernel
其實已經在dram
中準備好了,隨時可以啟動 -
在舊
uboot
的啟動流程各個階段,嘗試直接跳轉到kernel
,觀察啟動速度 -
如果在舊
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<<6)
mcr p15, 0, r0, c1, c0, 1
#endif
新uboot
的start.S
中沒有這段程式碼,嘗試在新uboot
的start.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: https://www.cnblogs.com/zqb-all/p/13172546.html
公眾號:https://sourl.cn/shT3kz