make smdkc100_defconfig
以被默認支持的smdkc100單板爲背景分析u-boot v2018.01
- 參考圖1可知uboot code鏈接順序:
圖1 u-boot.lds
一、sections.c (arch\arm\lib)
第24行:
char __image_copy_start[0] __attribute__((section(".__image_copy_start")));
不佔內存空間,可在u-boot鏡像開始位置生成標籤__image_copy_start。
二、vectors.S (arch\arm\lib)
圖2 .vectors段頭部
_start:
建立異常向量表。
某些SOC要求Bootloader頭部有hook數據用來指導BL0(固化在iROM)將Nand flash中的BootLoader加載到iRAM中。此時需定義
CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK
,及定義asm/arch/boot0.h 文件,參考arch\arm\include\asm\arch-bcm281xx\boot0.h
三、start.S (arch\arm\cpu\armv7)
reset:
1. 設置CPSR:CPU爲SVC模式,禁止IRQ/FIQ;
2. 通過SCTLR和VBAR設置異常向量表的地址到_start
;
3. cpu_init_cp15
: 失效TLB、L1 icache、BP數組;關MMU、dcache,開icache和分支預測;將CPU的
variant + revision存於R2;
4. cpu_init_crit
: 調用lowlevel_init
(board\samsung\smdkc100\lowlevel_init.S):
① 關閉看門狗;
② 設置SRAM;
③ 禁止所有中斷線,並設爲IRQ及清除標誌位;
④ 初始化uart的引腳;
⑤ 初始化tzpc爲關閉;
5. 跳到_main
。
四、crt0.S (arch\arm\lib)
_main:
1. 設置sp
爲0x2f000000
;
2. 調用board_init_f_alloc_reserve (top=0x2f000000)
(common\init\board_init.c):返回top
=
(0x2f000000 - 1KB(malloc)
- sizeof(struct global_data)
) & ~0xF ;
3. r9(gd)
= sp
= top
;
4. 調用board_init_f_init_reserve (base=top)
:struct global_data
清0,gd->malloc_base
設在
struct global_data
之上;如下圖所示爲建立C語言運行環境的內存分佈:
圖3 C運行環境建立
5. 調用board_init_f (boot_flags=0)
(common\board_f.c):gd->flags = 0
,gd->have_console = 0
,執行
init_sequence_f[]
中的函數:
(1) setup_mon_len()
:設gd->mon_len
爲__bss_end
-_start
;
(2) fdtdec_setup()
:gd->fdt_blob
設在_end
(CONFIG_OF_SEPARATE
=> u-boot.bin = uboot+dtb),或用
default_environment
的"fdtcontroladdr"
覆蓋其值;檢查設備樹的header;
(3) initf_malloc()
:設gd->malloc_limit
爲(1KB),gd->malloc_ptr = 0
;
(4) log_init()
,initf_bootstage()
, initf_console_record()
:空;
(5) arch_cpu_init()
:讀PRO_ID寄存器內容解析出CPU id到全局變量s5p_cpu_id
;
(6) mach_cpu_init()
:空;
(7) initf_dm()
:初始化dm資源,綁定dm驅動到gd
中,掃描設備樹中dm設備內容;
(8) arch_cpu_init_dm()
:空;
(9) timer_init()
:初始化定時器4和gd->arch
中的定時器成員;
(10) env_init()
:通過默認env_driver
初始化env或者gd->env_addr = (ulong)&default_environment[0];
,
gd->env_valid = ENV_VALID;
(11) init_baud_rate()
:gd->baudrate
設爲env中"baudrate"
的值;
(12) serial_init()
(drivers\serial\serial-uclass.c):在設備樹中找"stdout-path"
的節點,用節點找
UCLASS_SERIAL
類設備probe
起來,gd->cur_serial_dev = dev;
,gd->flags |= GD_FLG_SERIAL_READY
;
(13) console_init_f()
:gd->have_console = 1
,用CONFIG_SILENT_CONSOLE
可讓控制檯“沉默”;
(14) display_options()
:打印u-boot版本信息;
(15) display_text_info()
:開debug時,打印u-boot code的內存地址;
(16) print_cpuinfo()
(arch\arm\cpu\armv7\s5p-common\cpu_info.c):打印設備樹"cpu-model"
標籤的data,
或字符串S5P
和s5p_cpu_id
變量值;打印CPU主頻;
(17) show_board_info()
:打印設備樹"model"
的data和單板名;
(18) announce_dram_init()
,dram_init()
:初始化gd->ram_size
爲通過寫讀SDRAM校驗後得到的實際大小;
Now that we have DRAM mapped and working, we can relocate the code and continue running from DRAM.
(19) setup_dest_addr()
:gd->ram_top
,gd->relocaddr
設爲SDRAM末尾:
CONFIG_SYS_SDRAM_BASE + gd->ram_size
;
(20) reserve_round_4k()
:gd->relocaddr
調整爲4KB對齊;
(21) reserve_mmu()
:gd->arch.tlb_size
設爲16KB,SDRAM爲TLB預留空間,設置gd->arch.tlb_addr
;
(22) reserve_video()
:依賴CONFIG_LCD
(未定義),爲顯存預留內存,初始化gd->fb_base
;
(23) reserve_trace()
:依賴CONFIG_TRACE
(未定義),初始化gd->trace_buff
;
(24) reserve_uboot()
:預留gd->mon_len
個字節給u-boot code,地址存於gd->relocaddr
;
(25) reserve_malloc()
:預留malloc和env區;
(26) reserve_board()
:預留struct bd_info
的空間並清零,地址存於gd->bd
;
(27) setup_machine()
:依賴CONFIG_MACH_TYPE
(未定義),設置gd->bd->bi_arch_number
;
(28) reserve_global_data()
:預留struct global_data
的空間,地址存於gd->new_gd
;
(29) reserve_fdt()
:預留存放設備樹的內存,設置gd->fdt_size
和gd->new_fdt
;
(30) reserve_bootstage()
:依賴CONFIG_BOOTSTAGE
(未定義),預留存放struct bootstage_data
的內存,設置
gd->new_bootstage
;
(31) reserve_arch()
:空;
(32) reserve_stacks()
:設置gd->irq_sp
(需16B對齊),預留爲4個word的地址記到gd->start_addr_sp
;
函數(19)到(32)進行的內存劃分結果如圖4所示:
圖4 重定位前內存劃分
(33) dram_init_banksize()
:初始化gd->bd->bi_dram
;
(34) show_dram_config()
:打印DRAM的大小;
(35) display_new_sp()
:打印gd->start_addr_sp
的值;
(36) reloc_fdt()
:將gd->fdt_blob
地址的設備樹重定位到gd->new_fdt
地址上,更新gd->fdt_blob
;
(37) reloc_bootstage()
:依賴CONFIG_BOOTSTAGE
(未定義),重定位gd->bootstage
內容到
gd->new_bootstage
,更新gd->bootstage
;
(38) setup_reloc()
:初始化gd->reloc_off
爲重定位目標地址與鏈接地址之差,重定位gd_t
內容到
gd->new_gd
;
6. 執行sp = gd->start_addr_sp
,r9(gd) = gd->new_gd
,記錄重定位代碼後的here
地址到lr
,執行
relocate_code (gd->relocaddr)
(arch\arm\lib\relocate.S),如圖5所示:
① 將地址__image_copy_start
至__image_copy_end
的u-boot code 重定位到地址gd->relocaddr
;
② 通過.rel.dyn
段確定u-boot code中所有符號索引的內存地址,用重定位偏移校正符號索引的值[1];
圖5 動態重定位
跳轉到重定位後的u-boot code執行以下代碼:
here:
7. 調用relocate_vectors()
設置VBAR重定位異常向量表地址到gd->relocaddr
;c_runtime_cpu_setup()
(arch\arm\cpu\armv7\start.S)失效icache內容,數據同步內存屏障(DSB),指令同步內存屏障(ISB);執行
memset(__bss_start,__bss_end,__bss_end-__bss_start)
清零BSS段;
8. coloured_LED_init()
,red_led_on()
,空;
9. 執行board_init_r (gd, gd->relocaddr)
;正式進入bootloader第二階段。
五、board_init_r(gd, gd->relocaddr) (common/board_r.c)
1. gd->flags &= ~GD_FLG_LOG_READY;
:指示log系統未初始化;
2. 調用init_sequence_r[]
中函數,打印函數指針鏈接地址和重定位地址(需開DEBUG
):
(1) initr_trace()
:依賴CONFIG_TRACE
(未定義),trace system
函數未實現;
(2) initr_reloc()
:gd->flags |= GD_FLG_RELOC | GD_FLG_FULL_MALLOC_INIT;
標誌重定位完成;
(3) initr_caches()
:調用arch/arm/mach-s5pc1xx/cache.c
函數,開dcache (undef CONFIG_SYS_DCACHE_OFF
);
(4) initr_reloc_global_data()
:重定位全局變量:monitor_flash_len
,gd->fdt_blob
(CONFIG_OF_EMBED
),
EFI的擴展固件(CONFIG_EFI_LOADER
);
(5) initr_barrier()
:空;
(6) initr_malloc()
:初始化malloc功能和清零malloc區;
(7) log_init()
:依賴CONFIG_LOG
(未定義),初始化log驅動;
(8) initr_bootstage()
:設進度爲BOOTSTAGE_ID_START_UBOOT_R
,並記到bootstage(依賴CONFIG_BOOTSTAGE
-未定義),show_boot_progress()
(未實現)提示進度,枚舉bootstage_id
羅列了進度id;
(9) initr_console_record()
:依賴CONFIG_CONSOLE_RECORD
(未定義),給console record功能分配內存;
(10) bootstage_relocate()
:依賴CONFIG_BOOTSTAGE
(未定義),重定位gd->bootstage
的內容;
(11) initr_of_live()
:依賴CONFIG_OF_LIVE
(未定義),用gd->fdt_blob
在堆上建立設備樹;
(12) initr_dm()
:依賴CONFIG_DM
,初始化驅動模型,綁定所有設備(使用U_BOOT_DEVICE
或設備樹中聲明)和
驅動(U_BOOT_DRIVER
聲明)並probe;
(13) board_init()
:smc9115連到SOC接口和對應SROMC的初始化,保存機器ID到gd->bd->bi_arch_number
,
設置gd->bd->bi_boot_params
保存引導操作系統的啓動參數;
(14) efi_memory_init()
:依賴CONFIG_EFI_LOADER
,初始化EFI功能及分配內存;
(15) stdio_init_tables()
:初始化標準輸入輸出設備鏈表;
(16) initr_serial()
:調用drivers/serial/serial-uclass.c
(依賴CONFIG_DM_SERIAL
),在設備樹alias
節點
獲得屬性"stdout-path"
或"console"
,從而得到作爲標準輸入輸出的設備節點,生成UCLASS_SERIAL
類的
udevice
來匹配兼容的驅動及probe;該串行設備記錄到gd->cur_serial_dev
;標誌GD_FLG_SERIAL_READY;
;
(17) initr_announce()
:打印u-boot重定位後起始地址(需開DEBUG);
(18) power_init_board()
:空;
(19) initr_nand()
:依賴CONFIG_CMD_NAND
,調用nand硬件驅動之board_nand_init()
填充nand_chip
的成員,
架構層通過nand驅動掃描外部nand設備,從而完善MTD原始設備mtd_info
並註冊到MTD子系統;打印nand
的容量;
(20) initr_env()
:通用env層(env/env.c)調用env硬件驅動層(若定義CONFIG_ENV_IS_IN_NAND
則在env/nand.c),
加載nand中CONFIG_ENV_OFFSET
開始的env數據到棧中,檢查crc成功則將其(失敗則使用default_environment
)複製到堆中,內存地址記錄進env_htab
,標誌置位GD_FLG_ENV_READY
或GD_FLG_ENV_DEFAULT
;插入或設置環
境變量fdtcontroladdr
爲gd->fdt_blob
;
(21) initr_secondary_cpu()
:空;
(22) stdio_add_devices()
:調用drv_xxx_init()
(需開CONFIG_XXX
),如drv_lcd_init()
(需定義CONFIG_LCD
),
drv_system_init()
則關於串口;驅動通用層填充stdio_dev
並註冊添加到標準輸入輸出鏈表上,在硬件驅動
層做硬件初始化;
(23) initr_jumptable()
:爲函數跳轉表(struct jt_funcs
定義)分配內存並記錄內存地址到gd->jt
;
(24) console_init_r()
:定義CONFIG_SILENT_CONSOLE
和環境變量"silent"
可標誌GD_FLG_SILENT
,在標準輸入輸
出設備表(stdio_add_devices()
生成,common/console.c)將首個標誌爲DEV_FLAGS_INPUT
或DEV_FLAGS_OUTPUT
作
爲控制檯io設備,設置環境變量”stdxxx”爲設備名,標誌GD_FLG_DEVINIT
;
(25) interrupt_init(),initr_enable_interrupts
:關於irq棧的設置;
(26) initr_ethaddr()
:設置gd->bd->bi_enetaddr
爲環境變量"ethaddr"
的值;
(27) initr_net()
:通過網絡設備驅動通用層(net/eth_legacy.c)調用硬件驅動層smc911x_initialize()
初始化網
路設備,檢測環境變量"ethaddr"
值有效性,爲空則生成隨機MAC地址(需開CONFIG_NET_RANDOM_ETHADDR
),
網絡設備名記錄到環境變量"ethact"
;
(28) run_main_loop()
:初始化hush解析器(CONFIG_HUSH_PARSER
),用環境變量"bootdelay"
或設備樹節點config
的屬性bootdelay
作爲啓動延遲時間,通過hush解析控制檯輸入的內容打斷倒計時並進入命令行;倒計時期間
控制檯無輸入則執行環境變量或設備樹/config
節點的bootcmd
,最後執行命令bootm 0x30007FC0
;
六、bootm 0x30007FC0
(cmd/bootm.c)
1. do_bootm(...)
執行該命令,作命令的解析;
2. do_bootm_states(...)
,如下內容:
3. bootm_start()
:環境變量verify
決定後續是否對kernel鏡像進行校驗和檢查,lmb(logical memory blocks)相關內
容的初始化;
4. bootm_find_os()
:
(1) boot_get_kernel()
:獲取kernel鏡像格式爲IMAGE_FORMAT_LEGACY
,驗證鏡像hcrc,打印鏡像的名字、類型、數
據大小、加載地址和入口地址,驗證dcrc(依賴env的verify
),判斷arch是否支持;
(2) 解析鏡像的結果填充images.os
的成員,kernel入口地址記到images.ep
,鏡像頭部地址記到images.os.start
;
5. bootm_find_other()
:
(1) boot_get_ramdisk()
:解析ramdisk鏡像,bootm
第三個參數爲其地址(如bootm xxx yyy
,yyy
爲對應地址);
(2) boot_get_fdt()
:獲取和解析設備樹鏡像內容,設備樹鏡像的起始地址需在bootm
命令第四個參數指明,如
bootm xxx yyy zzz
,zzz
爲對應地址;
6. bootm_load_os()
:解壓os數據或移動到images->os.load
地址,所以kernel應有Load Address
=Entry Point
;
7. boot_ramdisk_high()
:重新定位並初始化ramdisk,需定義CONFIG_SYS_BOOT_RAMDISK_HIGH
;
8. bootm_os_get_boot_func(images->os.os)
根據os類型獲得啓動函數boot_fn = do_bootm_linux
;
9. do_bootm_linux(BOOTM_STATE_OS_PREP, argc, argv, images)
: boot_prep_linux()
:若未指定傳遞給kernel的設
備樹地址,則建立各種tag到地址gd->bd->bi_boot_params
;
10. boot_selected_os()
:通過函數指針boot_fn
調用do_bootm_linux(BOOTM_STATE_OS_GO, ...)
,進而調用
boot_jump_linux(images, BOOTM_STATE_OS_GO)
:
(1) 通過gd->bd->bi_arch_number
或者環境變量machid
獲得機器碼;
(2) announce_and_cleanup()
:打印提示開始啓動內核,註銷驅動模型下設備驅動;調用cleanup_before_linux()
:
關L1/2 D-cache和MMU,沖刷掉dcache內數據;關I-cache,失效I-cache內條目,失效整個分支預測器陣列;
執行數據和指令內存屏障,確保前面的操作完成;
(3) kernel_entry(0, machid, r2)
:參數r2傳遞啓動參數(tag或設備樹)的內存地址,正式跳轉到kernel。
參考文獻
[1] fireaxe. PIC(與位置無關代碼)在u-boot上的實現[EB/OL]. ChinaUnix,2014