日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

3-uboot-spl代码流程

發布時間:2025/3/21 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 3-uboot-spl代码流程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
[uboot] (第三章)uboot流程——uboot-spl代碼流程
2016年10月28日 16:24:14閱讀數:2077

以下例子都以project X項目tiny210(s5pv210平臺,armv7架構)為例。

[uboot] uboot流程系列:?
[project X] tiny210(s5pv210)上電啟動流程(BL0-BL2)?
[uboot] (第一章)uboot流程——概述?
[uboot] (第二章)uboot流程——uboot-spl編譯流程

建議參考文章?
[kernel 啟動流程] (第二章)第一階段之——設置SVC、關閉中斷?
[kernel 啟動流程] (第六章)第一階段之——打開MMU?
ARM的CP15協處理器的寄存器

建議先看《[project X] tiny210(s5pv210)上電啟動流程(BL0-BL2)》,根據例子了解一下上電之后的BL0\BL1\BL2階段,以及各個階段的運行位置,功能。

========================================================================================================

一、說明

1、uboot-spl入口說明

通過uboot-spl編譯腳本project-X/u-boot/arch/arm/cpu/u-boot-spl.lds

ENTRY(_start)
  • 1

所以uboot-spl的代碼入口函數是_start?
對應于路徑project-X/u-boot/arch/arm/lib/vector.S的_start,后續就是從這個函數開始分析。

2、CONFIG_SPL_BUILD說明

前面說過,在編譯SPL的時候,編譯參數會有如下語句:?
project-X/u-boot/scripts/Makefile.spl

KBUILD_CPPFLAGS += -DCONFIG_SPL_BUILD
  • 1

所以說在編譯SPL的代碼的過程中,CONFIG_SPL_BUILD這個宏是打開的。?
uboot-spl和uboot的代碼是通用的,其區別就是通過CONFIG_SPL_BUILD宏來進行區分的。

二、uboot-spl需要做的事情

CPU初始剛上電的狀態。需要小心的設置好很多狀態,包括cpu狀態、中斷狀態、MMU狀態等等。?
在armv7架構的uboot-spl,主要需要做如下事情

  • 關閉中斷,svc模式
  • 禁用MMU、TLB
  • 芯片級、板級的一些初始化操作?
    • IO初始化
    • 時鐘
    • 內存
    • 選項,串口初始化
    • 選項,nand flash初始化
    • 其他額外的操作
  • 加載BL2,跳轉到BL2

上述工作,也就是uboot-spl代碼流程的核心。

三、代碼流程

1、代碼整體流程

代碼整體流程如下,以下列出來的就是spl核心函數。?
_start———–>reset————–>關閉中斷?
………………………………|?
………………………………———->cpu_init_cp15———–>關閉MMU,TLB?
………………………………|?
………………………………———->cpu_init_crit————->lowlevel_init————->平臺級和板級的初始化?
………………………………|?
………………………………———->_main————–>board_init_f_alloc_reserve & board_init_f_init_reserve & board_init_f———->加載BL2,跳轉到BL2?
board_init_f執行時已經是C語言環境了。在這里需要結束掉SPL的工作,跳轉到BL2中。

2、_start

上述已經說明了_start是整個spl的入口,其代碼如下:?
arch/arm/lib/vector.S

_start: #ifdef CONFIG_SYS_DV_NOR_BOOT_CFG.word CONFIG_SYS_DV_NOR_BOOT_CFG #endifb reset
  • 1
  • 2
  • 3
  • 4
  • 5

會跳轉到reset中。?
注意,spl的流程在reset中就應該被結束,也就是說在reset中,就應該轉到到BL2,也就是uboot中了。?
后面看reset的實現。

3、reset

建議先參考[kernel 啟動流程] (第二章)第一階段之——設置SVC、關閉中斷,了解一下為什么要設置SVC、關閉中斷以及如何操作。

代碼如下:?
arch/arm/cpu/armv7/start.S

.globl reset.globl save_boot_params_retreset:/* Allow the board to save important registers */b save_boot_params save_boot_params_ret:/** disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,* except if in HYP mode already*/mrs r0, cpsrand r1, r0, #0x1f @ mask mode bitsteq r1, #0x1a @ test for HYP modebicne r0, r0, #0x1f @ clear all mode bitsorrne r0, r0, #0x13 @ set SVC modeorr r0, r0, #0xc0 @ disable FIQ and IRQmsr cpsr,r0 @@ 以上通過設置CPSR寄存器里設置CPU為SVC模式,禁止中斷 @@ 具體操作可以參考《[kernel 啟動流程] (第二章)第一階段之——設置SVC、關閉中斷》的分析/* the mask ROM code should have PLL and others stable */ #ifndef CONFIG_SKIP_LOWLEVEL_INITbl cpu_init_cp15 @@ 調用cpu_init_cp15,初始化協處理器CP15,從而禁用MMU和TLB。 @@ 后面會有一小節進行分析bl cpu_init_crit @@ 調用cpu_init_crit,進行一些關鍵的初始化動作,也就是平臺級和板級的初始化 @@ 后面會有一小節進行分析 #endifbl _main @@ 跳轉到主函數,也就是要加載BL2以及跳轉到BL2的主體部分
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

4、cpu_init_cp15

建議先參考[kernel 啟動流程] (第六章)第一階段之——打開MMU兩篇文章的分析。?
cpu_init_cp15主要用于對cp15協處理器進行初始化,其主要目的就是關閉其MMU和TLB。?
代碼如下(去掉無關部分的代碼):?
arch/arm/cpu/armv7/start.S

ENTRY(cpu_init_cp15)/** Invalidate L1 I/D*/mov r0, #0 @ set up for MCRmcr p15, 0, r0, c8, c7, 0 @ invalidate TLBsmcr p15, 0, r0, c7, c5, 0 @ invalidate icachemcr p15, 0, r0, c7, c5, 6 @ invalidate BP arraymcr p15, 0, r0, c7, c10, 4 @ DSBmcr p15, 0, r0, c7, c5, 4 @ ISB @@ 這里只需要知道是對CP15處理器的部分寄存器清零即可。 @@ 將協處理器的c7\c8清零等等,各個寄存器的含義請參考《ARM的CP15協處理器的寄存器》/** disable MMU stuff and caches*/mrc p15, 0, r0, c1, c0, 0bic r0, r0, #0x00002000 @ clear bits 13 (--V-)bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)orr r0, r0, #0x00000002 @ set bit 1 (--A-) Alignorr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB #ifdef CONFIG_SYS_ICACHE_OFFbic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache #elseorr r0, r0, #0x00001000 @ set bit 12 (I) I-cache #endifmcr p15, 0, r0, c1, c0, 0 @@ 通過上述的文章的介紹,我們可以知道cp15的c1寄存器就是MMU控制器 @@ 上述對MMU的一些位進行清零和置位,達到關閉MMU和cache的目的,具體的話去看一下上述文章吧。ENDPROC(cpu_init_cp15)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

5、cpu_init_crit

cpu_init_crit,進行一些關鍵的初始化動作,也就是平臺級和板級的初始化。其代碼核心就是lowlevel_init,如下?
arch/arm/cpu/armv7/start.S

ENTRY(cpu_init_crit)/** Jump to board specific initialization...* The Mask ROM will have already initialized* basic memory. Go here to bump up clock rate and handle* wake up conditions.*/b lowlevel_init @ go setup pll,mux,memory ENDPROC(cpu_init_crit)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

所以說lowlevel_init就是這個函數的核心。?
lowlevel_init一般是由板級代碼自己實現的。但是對于某些平臺來說,也可以使用通用的lowlevel_init,其定義在arch/arm/cpu/lowlevel_init.S中?
以tiny210為例,在移植tiny210的過程中,就需要在board/samsung/tiny210下,也就是板級目錄下面創建lowlevel_init.S,在內部實現lowlevel_init。(其實只要實現了lowlevel_init了就好,沒必要說在哪里是實現,但是通常規范都是創建了lowlevel_init.S來專門實現lowlevel_init函數)。

在lowlevel_init中,我們要實現如下:?
* 檢查一些復位狀態?
* 關閉看門狗?
* 系統時鐘的初始化?
* 內存、DDR的初始化?
* 串口初始化(可選)?
* Nand flash的初始化

下面以tiny210的lowlevel_init為例(這里說明一下,當時移植tiny210的時候,是直接把kangear的這個lowlevel_init.S文件拿過來用的)?
這部分代碼和平臺相關性很強,簡單介紹一下即可?
board/samsung/tiny210/lowlevel_init.S

lowlevel_init:push {lr}/* check reset status */ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)ldr r1, [r0]bic r1, r1, #0xfff6ffffcmp r1, #0x10000beq wakeup_reset_precmp r1, #0x80000beq wakeup_reset_from_didle @@ 讀取復位狀態寄存器0xE010_a000的值,判斷復位狀態。/* IO Retention release */ldr r0, =(ELFIN_CLOCK_POWER_BASE + OTHERS_OFFSET)ldr r1, [r0]ldr r2, =IO_RET_RELorr r1, r1, r2str r1, [r0] @@ 讀取混合狀態寄存器E010_e000的值,對其中的某些位進行置位,復位后需要對某些wakeup位置1,具體我也沒搞懂。/* Disable Watchdog */ldr r0, =ELFIN_WATCHDOG_BASE /* 0xE2700000 */mov r1, #0str r1, [r0] @@ 關閉看門狗@@ 這里忽略掉一部分對外部SROM操作的代碼/* when we already run in ram, we don't need to relocate U-Boot.* and actually, memory controller must be configured before U-Boot* is running in ram.*/ldr r0, =0x00ffffffbic r1, pc, r0 /* r0 <- current base addr of code */ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */bic r2, r2, r0 /* r0 <- current base addr of code */cmp r1, r2 /* compare r0, r1 */beq 1f /* r0 == r1 then skip sdram init */ @@ 判斷是否已經在SDRAM上運行了,如果是的話,就跳過以下兩個對ddr初始化的步驟 @@ 判斷方法如下: @@ 1、獲取當前pc指針的地址,屏蔽其低24bit,存放與r1中 @@ 2、獲取_TEXT_BASE(CONFIG_SYS_TEXT_BASE)地址,也就是uboot代碼段的鏈接地址,后續在uboot篇的時候會說明,并屏蔽其低24bit @@ 3、如果相等的話,就跳過DDR初始化的部分/* init system clock */bl system_clock_init @@ 初始化系統時鐘,后續有時間再研究一下具體怎么配置的/* Memory initialize */bl mem_ctrl_asm_init @@ 重點注意:在這里初始化DDR的!!!后續會寫一篇文章說明一下s5pv210平臺如何初始化DDR.1:/* for UART */bl uart_asm_init @@ 串口初始化,到這里串口會打印出一個'O'字符,后續通過寫字符到UTXH_OFFSET寄存器中,就可以在串口上輸出相應的字符。bl tzpc_init#if defined(CONFIG_NAND)/* simple init for NAND */bl nand_asm_init @@ 簡單地初始化一下NAND flash,有可能BL2的鏡像是在nand flash上面的。 #endif/* Print 'K' */ldr r0, =ELFIN_UART_CONSOLE_BASEldr r1, =0x4b4b4b4bstr r1, [r0, #UTXH_OFFSET] @@ 再串口上打印‘K’字符,表示lowlevel_init已經完成pop {pc} @@ 彈出PC指針,即返回。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

當串口中打印出‘OK’的字符的時候,說明lowlevel_init已經執行完成。?
system_clock_init是初始化時鐘的地方。 mem_ctrl_asm_init這個函數是初始化DDR的地方。后續應該有研究一下這兩個函數。這里先有個印象。

6、_main

spl的main的主要目標是調用board_init_f進行先前的板級初始化動作,在tiny210中,主要設計為,加載BL2到DDR上并且跳轉到BL2中。DDR在上述lowlevel_init中已經初始化好了。?
由于board_init_f是以C語言的方式實現,所以需要先構造C語言環境。?
注意:uboot-spl和uboot的代碼是通用的,其區別就是通過CONFIG_SPL_BUILD宏來進行區分的。?
所以以下代碼中,我們只列出spl相關的部分,也就是被CONFIG_SPL_BUILD包含的部分。?
arch/arm/lib/crt0.S

ENTRY(_main)/** Set up initial C runtime environment and call board_init_f(0).*/ @ 注意看這里的注釋,也說明了以下代碼的主要目的是,初始化C運行環境,調用board_init_f。ldr sp, =(CONFIG_SPL_STACK)bic sp, sp, #7 /* 8-byte alignment for ABI compliance */mov r0, spbl board_init_f_alloc_reservemov sp, r0/* set up gd here, outside any C code */mov r9, r0bl board_init_f_init_reservemov r0, #0bl board_init_fENDPROC(_main)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

代碼拆分如下:?
(1)因為后面是C語言環境,首先是設置堆棧

ldr sp, =(CONFIG_SPL_STACK) @@ 設置堆棧為CONFIG_SPL_STACKbic sp, sp, #7 /* 8-byte alignment for ABI compliance */ @@ 堆棧是8字節對齊,2^7bit=2^3byte=8bytemov r0, sp @@ 把堆棧地址存放到r0寄存器中
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

關于CONFIG_SPL_STACK,我們通過前面的文章《[project X] tiny210(s5pv210)上電啟動流程(BL0-BL2)》?
我們已經知道s5pv210的BL1(spl)是運行在IRAM的,并且IRAM的地址空間是0xD002_0000-0xD003_7FFF,IRAM前面的部分放的是BL1的代碼部分,所以把IRAM最后的空間用來當作堆棧。?
所以CONFIG_SPL_STACK定義如下:?
include/configs/tiny210.h

#define CONFIG_SPL_STACK 0xD0037FFF
  • 1

注意:上述還不是最終的堆棧地址,只是暫時的堆棧地址!!!

(2)為GD分配空間

bl board_init_f_alloc_reserve @@ 把堆棧的前面一部分空間分配給GD使用mov sp, r0 @@ 重新設置堆棧指針SP/* set up gd here, outside any C code */mov r9, r0 @@ 保存GD的地址到r9寄存器中
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注意:雖然sp的地址和GD的地址是一樣的,但是堆棧是向下增長的,而GD則是占用該地址后面的部分,所以不會有沖突的問題。?
關于GD,也就是struct global_data,可以簡單的理解為uboot的全局變量都放在了這里,比較重要,所以后續有會寫篇文章說明一下global_data。這里只需要知道在開始C語言環境的時候需要先為這個結構體分配空間。?
board_init_f_alloc_reserve實現如下?
common/init/board_init.c

ulong board_init_f_alloc_reserve(ulong top) {/* Reserve early malloc arena *//* LAST : reserve GD (rounded up to a multiple of 16 bytes) */top = rounddown(top-sizeof(struct global_data), 16); // 現將top(也就是r0寄存器,前面說過存放了暫時的指針地址),減去sizeof(struct global_data),也就是預留出一部分空間給sizeof(struct global_data)使用。 // rounddown表示向下16個字節對其return top; // 到這里,top就存放了GD的地址,也是SP的地址 //把top返回,注意,返回后,其實還是存放在了r0寄存器中。 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

還有一點,其實GD在spl中沒什么使用,主要是用在uboot中,但在uboot中的時候還需要另外分配空間,在講述uboot流程的時候會說明。

(3)初始化GD空間?
前面說了,此時r0寄存器存放了GD的地址。

bl board_init_f_init_reserve
  • 1

board_init_f_init_reserve實現如下?
common/init/board_init.c?
編譯SPL的時候_USE_MEMCPY宏沒有打開,所以我們去掉了_USE_MEMCPY的無關部分。

void board_init_f_init_reserve(ulong base) {struct global_data *gd_ptr;int *ptr;/** clear GD entirely and set it up.* Use gd_ptr, as gd may not be properly set yet.*/gd_ptr = (struct global_data *)base; // 從r0獲取GD的地址/* zero the area */for (ptr = (int *)gd_ptr; ptr < (int *)(gd_ptr + 1); )*ptr++ = 0; // 對GD的空間進行清零 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

(4)跳轉到板級前期的初始化函數中?
如下代碼

bl board_init_f
  • 1

board_init_f需要由板級代碼自己實現。?
在這個函數中,tiny210主要是實現了從SD卡上加載了BL2到ddr上,然后跳轉到BL2的相應位置上?
tiny210的實現如下:?
board/samsung/tiny210/board.c

#ifdef CONFIG_SPL_BUILD void board_init_f(ulong bootflag) {__attribute__((noreturn)) void (*uboot)(void);int val; #define DDR_TEST_ADDR 0x30000000 #define DDR_TEST_CODE 0xaatiny210_early_debug(0x1);writel(DDR_TEST_CODE, DDR_TEST_ADDR);val = readl(DDR_TEST_ADDR);if(val == DDR_TEST_CODE)tiny210_early_debug(0x3);else{tiny210_early_debug(0x2);while(1);} // 先測試DDR是否完成copy_bl2_to_ddr(); // 加載BL2的代碼到ddr上uboot = (void *)CONFIG_SYS_TEXT_BASE; // uboot函數設置為BL2的加載地址上(*uboot)(); // 調用uboot函數,也就跳轉到BL2的代碼中 } #endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

關于copy_bl2_to_ddr的實現,也就是如何從SD卡或者nand flash上加載BL2到DDR上的問題,請參考后續文章《[project X] tiny210(s5pv210)代碼加載說明》。

到此,SPL的任務就完成了,也已經跳到了BL2也就是uboot里面去了。

總結

以上是生活随笔為你收集整理的3-uboot-spl代码流程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。