1. 程式人生 > >記一次uboot升級過程的兩個坑

記一次uboot升級過程的兩個坑

## 背景 之前做過一次`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<<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`的圖和描述: ![](https://img2020.cnblogs.com/blog/908492/202006/908492-20200621151532734-1773204893.png) ``` [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`。 ```c #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://www.cnblogs.com/zqb-all/p/13172546.html) 公眾號:[https://sourl.cn/shT3kz](https://sourl.cn