u-boot v2018.01 啓動流程分析

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. 通過SCTLRVBAR設置異常向量表的地址到_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. 設置sp0x2f000000
  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 = 0gd->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,
       或字符串S5Ps5p_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_topgd->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_sizegd->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_spr9(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->relocaddrc_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_lengd->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_READYGD_FLG_ENV_DEFAULT;插入或設置環
     境變量fdtcontroladdrgd->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_INPUTDEV_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 yyyyyy爲對應地址);
   (2) boot_get_fdt():獲取和解析設備樹鏡像內容,設備樹鏡像的起始地址需在bootm命令第四個參數指明,如
    bootm xxx yyy zzzzzz爲對應地址;
  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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章