UBOOT 啟動流程

2022-12-10 12:00:20

一、概述

uboot 的啟動流程在網上有很多大佬記錄,但是了對於像我這樣的新手就有些困難了,而我也不做 uboot 相關的工作,所以沒必去研究程式碼,這裡我特意整理了一下,以流程圖的形式展現程式碼執行的流程,方便快速瞭解 uboot 是怎麼啟動的,此筆記就不進行程式碼分析了,主要記錄 uboot 啟動流程中所執行的函數已經函數所在的檔案,需要了解函數中的程式碼實現,可以結合 uboot 原始碼和正點原子的開發手冊或者其他部落格。

注意: uboot 執行過程中都是以單執行緒執行的,所以分析啟動流程的時候相對多執行緒好理解。流程中有些函數名和檔案位置可能不一樣,但是不要慌,就這樣慢慢的找下去就可以快速瞭解到自己的工程是怎麼啟動的了

二、SOC 啟動流程

uboot 只是一個啟動引導向,最終的目的是啟動 linux 那麼即使不使用 uboot 也可以用其他的引導向,但是目前主流都是使用的 uboot,所以這裡對uboot的執行函數進行了整理,方便大家好閱讀 uboot 的工程原始碼,在瞭解uboot之前,需要了解一下晶片的都做啥了。

看到這個筆記的小夥伴們,應該都知道,系統的啟動方式有很多種,比如 SD、mmc、norflash、nandflash等,那麼我們 uboot 就可以存在其中一個硬體裝置中,晶片是怎麼知道 uboot 在那裡又是怎麼去執行 uboot 程式碼的?

半導體廠商在製作晶片的時候,會在晶片內部的 ROM 中植入一小段程式,上電後晶片會先執行內部的程式碼,然後判斷我們是以什麼方式啟動,並在對應的裝置中找到 uboot 程式,最終啟動 linux 系統,當然晶片內部的這段程式碼還是比較麻煩的,並且廠家也不會公佈這段程式碼,這裡我就不做過多介紹了,需要的小夥伴可以去了解一下。

晶片內部的 SRAM 是比較小的,不足以跑複雜的程式,所以當晶片找到 uboot 程式後,會執行 uboot 的一小段程式,這小段程式叫做uboot SPL,他的主要目的就是初始化晶片使用的外部 RAM 然後將剩餘的 uboot 放到外部的記憶體中執行,提高晶片的執行能力,具體可以瞭解這位大佬的部落格:u-boot (3) —— spl

到這裡差不多了,接下來可以瞭解 uboot 的啟動流程了。

三、uboot 入口

在分析程式之前,都會從入口函數開始,從上圖可知uboot 的入口是 u-boot.lds 連結指令碼開始的。可能會有小夥變怎有疑問,我是怎麼知道最先執行的 u-boot.lds 連結指令碼,其實在瞭解一個工程之前,會先從 makefile 開始,只是我 uboot 中的makefile 比較複雜,我還有些不了明白,這裡就不獻醜了,有需要的小夥伴可以先看大佬的分析,所以從makefile檔案中知道,最先執行的是 u-boot.lds 連結指令碼。

  1. u-boot.lds
    分析 uboot 頂層 Makefile 時,得知 uboot 的啟動是從連結指令碼 u-boot.lds 檔案開始的,所以我們需要找到 u-boot.lds 的檔案位置,如果沒有編譯的話,最初的連結指令碼在 arch/arm/cpu/ 路徑下,但是這個不是最終使用的連結指令碼,在編譯時會在 uboot 的根目錄下生成 u-boot.lds 檔案,所以在編譯過程中使用的是根目錄下的連線指令碼。

    連結指令碼中描述了 uboot 的段的記憶體使用地址,以及中斷向量表的地址,可以結合 uboot 根目錄下的 u-boot.map 檔案進行分析,這裡就不詳細介紹了。

  2. _start
    開啟連結指令碼後,會看到 ENTRY(_start) 宣告的入口函數 _start ,而函數 _start 在 arch/arm/lib/vectors.S 檔案中,此函數的作用是宣告一些中斷函數,當上電啟動時會跳轉到 reset 復位函數。

  3. reset
    reset 函數在檔案 arch/arm/cpu/armv7/start.S 檔案中,不同的晶片檔案位置不同,我使用的晶片是armv7架構的,在 reset 函數中有 save_boot_params 、cpu_init_cp15 、 cpu_init_crit 、 _main 函數

    • save_boot_params 也在 start.S 檔案中,主要是設定 CPU 的為SVC模式。
    • cpu_init_cp15 也在檔案 start.S 中,主要作用是設定 CP15 相關的內容,比如關閉 MMU 啥的。
    • cpu_init_crit 也在檔案 start.S 中,cpu_init_crit 內部僅僅是呼叫了函數 lowlevel_init。
  4. lowlevel_init
    lowlevel_init函數在檔案 arch/arm/cpu/armv7/lowlevel_init.S 中,主要用於設定堆疊以呼叫C函數執行進一步的初始化,lowlevel_init 函數中呼叫了 s_init 函數。

  5. s_init
    s_init 函數在 arch/arm/cpu/armv7/xxx/soc.c 檔案中,有的晶片型號中沒有 soc.c 檔案,而 s_init 函數沒有什麼作用,就可以不用瞭解了 。

三、uboot 外設初始化

此流程主要是完成 uboot 工作的基本條件,並初始一些外設,程式碼很多,初次學習最好不要直接對函數進行具體的分析,先了解框架。

  1. _main
    _main 函數定義在檔案 arch/arm/lib/crt0.S 中,在 _main 函數主要有 board_init_f 、 relocate_code 、relocate_vectors 、 c_runtime_cpu_setup 、 board_init_r 函數

  2. board_init_f
    board_init_f 函數在檔案 common/board_f.c 中,如下圖所示:

    board_init_f 函數中會執行 init_sequence_f 表中的函數,主要有兩個工作

    • 初始化一系列外設,比如串列埠、定時器,或者列印一些訊息等。
    • 初始化 gd 的各個成員變數,uboot 會將自己重定位到 DRAM 最後面的地址區域,也就是將自己拷貝到 DRAM 最後面的記憶體區域中。

    其中 serial_init 函數初始串列埠後,我們就可以使用 printf 函數列印紀錄檔,列印後便會在控制檯中看到相應的資訊,和C語言中的用法一樣,

    display_options 函數中會列印 uboot 的版本資訊等,具體的函數實現只能後後面需要的時候自行了解了。

  3. relocate_code
    relocate_code 函數在檔案 arch/arm/lib/relocate.S 中,主要作用是用於程式碼拷貝。

  4. relocate_vectors
    relocate_vectors 函數在檔案 arch/arm/lib/relocate.S 中,主要作用是用於重定位向量表。

  5. c_runtime_cpu_setup
    c_runtime_cpu_setup 函數在檔案 arch/arm/cpu/armv7/start.S 中

  6. board_init_r
    board_init_r 函數在檔案 common/board_r.c 中,主要作用是完成 board_init_f 沒有初始化的外設,以及一些後續工作。也會執行 init_sequence_r 表中的函數,在函數最後會呼叫 run_main_loop 函數。

  7. run_main_loop
    函數 run_main_loop 也在檔案 common/board_r.c 中,此函數主要是在死迴圈中呼叫 main_loop() 函數

四、uboot 命令執行

  1. main_loop()
    main_loop 函數在檔案 common/main.c 中,在函數中主要執行 autoboot_command 和 cli_loop 函數。

  2. autoboot_command
    autoboot_command 函數在 common/autoboot.c 中,其中會通過 Abortboot 函數判斷在控制檯列印的倒計時結束之前是否有按鍵按下,如果存在按鍵按下時,會執行 run_command_list 函數進入 uboot 系統。反之會返回到 main_loop 函數中執行 cli_loop 函數
    注意:run_command_list 函數也在 cli.c 檔案中,只是流程圖不好直觀的表示出來。

  3. cli_loop
    cli_loop 在檔案 common/cli.c 中,主要作用是執行相應的命令操作,在 cli_simple_loop 函數存在一個死迴圈,用於接收控制檯的命,並處理相應的命令工作。

  4. cli_simple_run_command
    cli_simple_run_command 函數在 common/cli_simple.c 檔案中,主要作用是執行相應的命令操作,從圖中可以看出,不論是正常啟動 linux 或 進入uboot系統,最終都會執行此函數,在函數中會呼叫 find_cmd 查詢命令,呼叫 cmd_call 執行命令操作。

  5. find_cmd
    find_cmd 函數在 common/command.c 檔案中,主要作用是在對映表中查詢相應的命令是否存在,命令通過宏 U_BOOT_CMD 進行定義的。

  6. find_call
    find_call 函數在 common/command.c 檔案中,主要作用是呼叫 find_cmd 中查詢到的 do_xxx 函數,最終執行相應的命令操作。

  7. do_xxx
    do_xxx 函數在 cmd 目錄下,作用就是命令操作的實現函數,比如啟動函數 bootz 或 bootm ,所以從圖中可知,不論是正常啟動 linux 還是在 uboot 中通過命令啟動 linux 原理都是一樣的,最終也是執行 bootz 或 bootm 命令。

五、bootm 啟動 Linux 核心


注意:這裡我就沒有畫對應的流程圖了,因為在正點原子的教材中有相應的流程圖,所以我這裡就直接參照了。關於啟動linux 的流程我也沒有仔細分析,只是大體看了一下,此筆記的主要原因是我好奇 uboot 都做了些什麼,學習驅動開發是否有必要去學習 uboot 中的驅動開發。

通過對 uboot 流程的啟動分析,發現 uboot 中的驅動主要根據自己在啟動階段的去求是實現驅動即可,因為在啟動 linux 的時候,會在對外設驅動進行實現,達到同一管理,並且在 linux 啟動後 uboot 就沒有作用了,想在再次進入uboot,執行重新啟動。

  1. bootm
    bootm 命令的執行函數為 do_bootm,在檔案 cmd/bootm.c 中,do_bootm 最後呼叫的就是函數 do_bootm_states

  2. do_bootm_states
    do_bootm_states 函數定義在檔案 common/bootm.c 中,函數會根據不同的 BOOT 狀態執行不同的程式碼段。

  3. bootm_start
    bootm_start 函數在 common/bootm.c 檔案中,作用是清空 images 結構體,獲取 uboot 的環境變數 verify 的值

  4. bootm_find_os
    bootm_find_os 函數,函數在 common/bootm.c 檔案中,在函數中會呼叫 boot_get_kernel,
    boot_get_kernel 會根據 bootm 傳過來的引數去獲取 uImage(映象)的儲存地址,如果 bootm 沒有引數就使用全域性變數 load_addr,最後會呼叫 image_get_kernel 函數進行 kernel 格式校驗。

  5. bootm_find_other
    bootm_find_other 函數common/bootm.c 檔案中,主要作用是獲取 ramdisk 或者裝置樹資訊。

  6. bootm_disable_interrupts
    bootm_disable_interrupts 的作用是函數禁用中斷。

  7. do_bootm_linux
    do_bootm_linux 函數在 arch/arm/lib/bootm.c 檔案中,次函數就是最終啟動 Linux 核心的函數。

到此 uboot 的啟動流程也算完成,有什麼不對的地方望大佬指出,我會積極學習。

參考連結

u-boot (3) —— spl:https://blog.csdn.net/zhoutaopower/article/details/123133291