歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> ppc-booting-sequence

ppc-booting-sequence

日期:2017/3/1 11:17:35   编辑:Linux編程

Powerpc啟動順序分析。

摘要:本文致力於研究Powerpc的引導技術,其中包括U-boot啟動代碼分析,kernel for Powerpc 啟動代碼分析,以及U-boot加載kernel代碼分析。以上三個部分屬於體系結構相關的內容。

由於時間有限,只是對代碼進行粗讀。

一、Kernel 啟動代碼分析
如果由u-boot解壓縮內核,則內核的入口點是arch/ppc/head.s,注意,如果使用新的bsp,則arch使用powerpc而不是ppc。Ppc主要針對32位系統,powerpc把32位系統與64位系統整合在一起。

Powerpc有四種子體系:PMAC——PowerMacintosh主要是對mac的支持;PReP主要支持IBM,motorola,我們的ppc 440,460,mpc8548應該屬於這個體系;CHRP支持IBM RS/6000,Genesi;APUS Amiga Power-UP Systems (APUS)。每種架構的啟動是不一樣的。

head.s

1、找到_start,首先根據ABI規范保存r3~r7(保存到r27~r31)。主要是一些內核參數。此時mmu是打開的,當然物理內核和虛擬內存相同。然後跳到early_init中把bss清零,然後判斷CPU類型,根據類型判斷物理地址(在關掉mmu時需要定位代碼所在的物理地址),最後通過r3返回物理地址。

2、Bl mmu_off 關mmu,默認啟動mmu是開啟的,使用cpu內部的tlb進行內存映射。

3、Bl clear_bats 清除block address table

4、BL Flush_tlbs 應該是刷新tlb//其實都是寫寄存器的操作。

5、Bl initial_bats 將前16M內存映射給內核使用。

6、Bl reloc_offset 計算連接地址與實際地址的偏移

7、Call_setup_cpu 設置cpu的ctl寄存器

8、Reloc_kernel 如果有必要則reloc

9、B turn_on_mmu 這個函數通過rfi返回,將SPRN_SRR0設置成start_here地址,rfi中斷返回時將跳到這個地址執行,並切換上下文。

10、 Start_here 真正的打開mmu,通過rfi跳轉到start_kernel運行。

11、 Start_kernel在main.c中,後面的內容和所有架構下無異。

二、 U-boot啟動代碼分析
U-Boot啟動代碼(for ppc)主要關注如下幾個文件:1、u-boot.lds,2、start.s,3、board.c。u-boot.lds是鏈接文件,一般在對應開發板目錄下;

Start.s 是啟動代碼文件,一般在對應得cpu目錄下;

Board.c 是板子初始化文件,一般在對應體系結構的lib下;

首先來看u-boot.lds,從這個文件可以找到整個程序的入口位置。開頭代碼如下:

OUTPUT_ARCH(powerpc) /*代碼是for ppc的*/

/* Do we need any of these for elf?

__DYNAMIC = 0; */

SECTIONS

{

.resetvec 0xFFFFFFFC : /*定義resetvec段在0xFFFFFFFC位置,這個位置是ppc上電後IP指向的位置*/

{

*(.resetvec) /*段內容*/

} = 0xffff /*用0xffff填充空余部分*/

.bootpg 0xFFFFF000 : /*定義.bootpg段在0xFFFFF000位置*/

{

cpu/ppc4xx/start.o (.bootpg)

} = 0xffff /*用0xffff填充空余部分*/

…………

現在找到resetvec.s 文件。

#include <config.h>

.section .resetvec,"ax"

#if defined(CONFIG_440)

b _start_440 /*CPU上電後執行的第一條命令:b _start_440*/

#else

#if defined(CONFIG_BOOT_PCI) && defined(CONFIG_MIP405)

b _start_pci

#else

b _start

#endif

#endif

Start.S 代碼分析。_start_440在start.s中定義。Ppc440啟動時,只有最高位的4k地址被TLB映射,此時控制器將最高4k的內容拷貝到cache中運行(我猜想在啟動時440將自己的cache作為ram來使用,這樣在不初始化外部ram的時候也能跑代碼)。Start.s正是位於這4k當中,因此start.s擔負著初始化整個正常環境的重任。接下來:

1) 設置中斷控制器寄存器

2) 設置調試寄存器

3) 設置cpu控制器寄存器

4) 安裝中斷向量

5) 配置cache

6) 初始化mmu控制器,在啟動時使用I/D cache控制器中的tlb實現地址映射。

7) 刪除所有的tlb entries

8) 建立新的tlb。包括16M啟動flash,內存,PCI存儲空間,BCSR空間等等。

9) B _start 跳轉到_start。

10) 此時需要建立新的工作環境,因此首先需要disable異常和中斷。

11) 設置intenal ram

12) 設置電源管理

13) 設置堆棧 (到這裡為止代碼應該是在cache中)

14) bl cpu_init_f(board.c) 跳到Flash中執行cpu初始化。此時內存並沒有完全初始化,堆棧空間很小,BSS也沒有初始化。這段代碼主要是打開一個console以便於調試,以及初始化RAM。以便把代碼全部搬到RAM中運行。還有一個重要工作就是獲取全局數據——gd。

Cpu_init_f:

(1) gd = (gd_t *) (CFG_INIT_RAM_ADDR + CFG_GBL_DATA_OFFSET);全局gd指針。__asm__ __volatile__("": : :"memory"); 內存壁壘,防止編譯器優化。清空gd。

(2) for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

if ((*init_fnc_ptr) () != 0) {

hang ();

}

}/*cpu/board /console… 初始化*/

(3) 設置gd

(4) 初始化sram

(5) 計算需要保護的內存區域(board infor,kernel log buf ,monitor code等等),addr即為除去保護內存後用於存放relocate code的地址,addr_sp為堆棧可以使用的基地址

(6) 調用relocate_code (start.s文件中) ,r3 = addr_sp , r4 = gd, r5= addr

Relocate_code:

(1) 使能I/D cache

(2) 使用新的stack:r1=addr_sp。將gd(global data)保存在 r9中,將relocate code地址保存在r10中

mr r1, r3 /* Set new stack pointer */

mr r9, r4 /* Save copy of Init Data pointer */

mr r10, r5 /* Save copy of Destination Address */

(3) 重定位GOT

(4) Relocate code

(5) Flush D cache

(6) 跳轉到in_ram代碼中,r10中保存著RAM中代碼起始位置,加上in_ram的相對位置即為in_ram在ram中的位置:

addi r0, r10, in_ram - _start + _START_OFFSET

mtlr r0

blr /* NEVER RETURNS! */

in_ram:

(1) 重定位GOT2

(2) Clear bss

(3) 將gd以及代碼在內存中的起始位置作為參數,調用board_init_r

mr r3, r9 /* Init Data pointer */

mr r4, r10 /* Destination Address */

bl board_init_r

下面再次進入board.c 文件

board_init_r:

這部分代碼初始化一些列外設,這裡只提幾個感興趣的地方。

(1) 建立Command table。U-boot支持N多命令,通過一個cmdtbl來管理,這個cmdtbl是在運行時被負值的

for (cmdtp = &__u_boot_cmd_start; cmdtp != &__u_boot_cmd_end; cmdtp++) {……}

u-boot的命令都被定義在.u_boot.cmd段,並在鏈接時確定期位置:在__u_boot_cmd_start和__u_boot_cmd_end之間。

(2) Trap的安裝是通過調用start.s中的代碼完成的。其實就是將原有的trap重定位到dest_addr,也就是RAM中的起始位置。

(3) 茫茫多設備的初始化 …………

(4) 進入main_loop()等待用戶輸入命令,如bootm命令。

三、 Bootm代碼分析
目前大多數kernel的booting都由bootm來完成,可以說這個函數是連接U-boot和kernel的橋梁,bootm命令主要通過do_bootm()(/common/cmd_bootm.c)函數和do_bootm_linux()(/lib_ppc/bootm.c)函數來實現。後者是與架構相關的。Powerpc kernel booting有兩種方式:1、使用of——open firmware;2、使用dt——device tree。現在高版本的u-boot基本都支持dt啟動方式。在制作uImage時會在Image head中加入一段信息,告訴u-boot使用的啟動方式。這裡只討論dt啟動方式。

其實整個啟動過程很簡單,do_bootm命令接收命令行參數,根據命令行參數對內核的資源進行有效性校驗,然後將所有信息存入image結構。並把這個結構傳遞給bootm.c。bootm.c 從image中找到內核、ramdisk、cmdline以及FDT,將cmdline保存在固定的位置,然後跳轉到kernel所在的地址,並把FDT和kernel本身的地址作為參數傳遞過去,kernel會根據cmdline中的信息選擇啟動方式。

1、當用戶敲入bootm kernel_addr – fdt_addr時,u-boot調用do_bootm函數。

2、Local memory block initiate。初始化本地存儲塊(可能是用戶kernel內存管理)

3、boot_get_kernel(),這個函數找到kernel image,驗證其完整性,並定位內核數據。將信息存放在images中。

fit_parse_conf;取出load_addr,這個地址在uImage編譯時確定。

genimg_get_image;如果image在flash上,則拷貝到load_addr內存中。

image_get_type;判斷image head的類型,顯然kernel應該是IH_TYPE_KERNEL類型的。

image_get_kernel;根據image head中的信息對image進行校驗。

image_get_data;取得kernel數據指針。

image_get_data_size;取得kernel大小。

返回image地址

4、判斷壓縮格式,並解壓縮內核

5、調用do_bootm_linux()函數啟動內核。

下面分析do_bootm_linux()函數。這個函數比較重要,我們將詳細分析之。

(1) 首先為內核命令行參數分配空間,這個空間必須在內核可以訪問的高地址,並且不會被其他數據干擾。取出sp值:int sp; asm( "mr %0,1": "=r"(sp) : );// sp=r1。即棧。

(2) sp -= 1024; 留出1k空間,保證訪問安全

(3) lmb_reserve(lmb, sp, (CFG_SDRAM_BASE + get_effective_memsize() - sp)); //把這sp以上的空間加入到lmb保護起來。

(4) boot_get_fdt :獲取fdt,這段代碼較長,我沒有細看。主要就是從image中獲取tdt的地址和大小。

(5) boot_get_cmdline:獲取命令行參數,在lmb中申請一段空間,然後把uboot中bootargs的內容復制到裡面。

(6) boot_get_kbd:獲取board info,也就是從gd(global data)中將bd復制到lmb中。

(7) 找到內核起始位置

(8) 找ramdisk。如果有,首先判斷是否有效,是否是initrd,然後拷貝到RAM中的rd_load地址,這個地址在image 結構中有定義。

(9) boot_relocate_fdt :將fdt定位到lmb中

(10) 啟動內核,將fdt參數和內核本身參數傳遞給內核。

Copyright © Linux教程網 All Rights Reserved