Beagleboneblack在啟動linux之前還有三個啟動階段:
ROM code --> MLO --> u-boot --> kernel
先看看ROM code干了些什么
ROM code是TI固化在芯片內部的,處理器上電之后會先跑到這里執行一部分代碼,看看這部分代碼在哪:
Boot ROM一共有128K+48K,其中后48K具有可讀可執行屬性,那估計就應該在0x4002_0000的地方了.這48K的空間又被分成了這樣子:
不過程序不應該是在RAM里運行的嗎,怎么可以在ROM里運行呢.回想以前用STM32時,程序在靜態時肯定被燒寫到內部的Flash上了,上電之后也是直接從Flash里面執行代碼,之后也只是把部分代碼分散加載到RAM里,那這樣說來程序也不是在RAM里運行的.
這個問題讓我很困惑,我只能這么解釋:如果CPU能直接取到指令,那它就能譯碼,執行對於這些芯片內部的Flash也好,ROM也好,RAM也好,這些存儲器都是直接掛在CPU三總線上的,CPU可以直接從這些地方取到指令,然后執行,只是有的存儲器不能寫.而對於有些存儲器,如EEPROM,SD卡,它們一般並不直接掛在總線上,中間還要有控制器和相應的驅動時序,所以CPU不能直接取到指令,程序也就不能在那里面運行.如果我說的不對,網友們一定予以指正.
回到這個ROM code上來,看看它干了些什么:
它說上電之后CPU執行公共端的初始化和堆棧設置,然后配置WDT1為3分鍾,執行系統時鍾配置,最終跳到booting的處理程序中.
首先根據軟件配置或者SYSBOOY引腳生成啟動設備列表,如果不按卡旁邊的按鈕,啟動順序是MMC1(eMMC) -> MMC0 -> UART0 -> USB0,按下則為SPI0 -> MMC0(SD card) -> USB0 -> UART0,然后根據設備類型執行存儲器啟動或者是外設啟動初始化,假如我們從SD卡啟動,
初始化MMC/SD控制器 -> 檢測設備 -> 判斷啟動模式 -> 獲取啟動文件,即找到SD卡上的MLO文件,然后加載到SRAM去,同時跳到MLO程序中.至於它是怎么加載的,想必是讀取MLO文件頭信息,通過目的地址和文件大小復制過去的吧.
所以ROM code就做了這么些事情,那MLO程序從哪個地方開始執行呢,前面說過MLO文件的編譯過程,在編譯SPL程序時鏈接器的腳本是u-boot-spl.lds
MEMORY { .sram : ORIGIN = 0x402F0400, LENGTH = (0x4030B800 - 0x402F0400) }
其中給出了SRAM的起始地址和大小(109K),而且在makefile的配置文件autoconf.mk中,也給出了基地址:
CONFIG_SPL_TEXT_BASE=0x402F0400
所以MLO程序就從0x4020F0400的地方開始執行,再看看SRAM的內存分布:
109K的Image空間,6K的stack空間,上面還有xxxx,我的u-boot(2014.04)在編譯之后MLO有76.5K,還是裝得下的,u-boot.img有329K,自然就裝不下的,所以才要MLO來引導u-boot.
接下來看看MLO程序干了些什么:
從start.S開始,位於arch/arm/cpu/armv7下,首先跳到reset:
_start: b reset
跳到save_boot_params:
reset:
bl save_boot_params
save_boot_params具有weak屬性,在start.S中有定義,如果其他地方沒有定義的話,它就直接返回,由於這里是SPL程序,想必還是要save一下的,實際上在arch/arm/cpu/armv7/am33xx/lowlevel_init.S中有它的定義:
ENTRY(save_boot_params) ldr r1, =OMAP_SRAM_SCRATCH_BOOT_PARAMS str r0, [r1] bx lr ENDPROC(save_boot_params)
將R0的值保存在這個地方OMAP_SRAM_SCRATCH_BOOT_PARAMS,即0x4030B824處,位於6K的stack空間中,R0是什么東西呢:
R0保存了Booting Parameters Structure這樣一個結構體的地址,包括啟動設備描述符,當前啟動設備,重啟原因這些,這個結構體在哪,啟動設備描述符又是個什么東西我也不知道了.
回到start.S中,接着關中斷,切換值SVC模式:
mrs r0, cpsr and r1, r0, #0x1f @ mask mode bits teq r1, #0x1a @ test for HYP mode bicne r0, r0, #0x1f @ clear all mode bits orrne r0, r0, #0x13 @ set SVC mode orr r0, r0, #0xc0 @ disable FIQ and IRQ msr cpsr,r0
設置中斷向量表:
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD)) /* Set V=0 in CP15 SCTRL register - for VBAR to point to vector */ mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTRL Register bic r0, #CR_V @ V = 0 mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTRL Register /* Set vector address in CP15 VBAR register */ ldr r0, =_start mcr p15, 0, r0, c12, c0, 0 @Set VBAR #endif
底層初始化:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_cp15 bl cpu_init_crit #endif
在編譯SPL程序時,沒有定義CONFIG_SKIP_LOWLEVEL_INIT這個宏,所以下面兩個函數會執行,而編譯u-boot時,會跳過,這也比較合理,只需要初始化一次嘛.
cpu_init_cp15主要是操作CP15寄存器,關掉緩存,關掉MMU,然后跳到cpu_init_crit:
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)
跳到lowlevel_init中,這里是"b"指令跳轉,不會改變lr寄存器的值:
ENTRY(lowlevel_init) /* * Setup a temporary stack */ ldr sp, =CONFIG_SYS_INIT_SP_ADDR bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ #ifdef CONFIG_SPL_BUILD ldr r9, =gdata #else sub sp, sp, #GD_SIZE bic sp, sp, #7 mov r9, sp #endif /* * Save the old lr(passed in ip) and the current lr to stack */ push {ip, lr} /* * go setup pll, mux, memory */ bl s_init pop {ip, pc} ENDPROC(lowlevel_init)
設置sp=0x4030FF40,即將運行C代碼,讓R9指向gdata,gdata是寄存器變量,在arch/arm/include/asm/global_data.h中聲明,定義在arch/arm/lib/spl.c中,位於data段
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
然后講ip,lr寄存器入棧跳到s_init中,位於arch/arm/cpu/am33xx/board.c中,主要調用函數如下:
-> save_omap_boot_params();
-> watchdog_disable();
-> timer_init();
-> set_uart_mux_conf();
-> setup_clocks_for_console();
-> uart_soft_reset();
-> gd = &gdata;
-> preloader_console_init();
-> prcm_init();
-> set_mux_conf_regs();
-> rtc32k_enable();
-> sdram_init();
這些函數就不再一一展開了,因為展開看了一下發現很難看懂,C語言功底遠遠不夠(⊙﹏⊙)b
值得注意的是save_omap_boot_params函數,它將前面說的R0所保存的啟動信息保存到gdata結構體中,以加載u-boot鏡像文件.
preloader_console_init函數,位於common/spl/spl.c中,初始化了一個串口,輸出了第一行信息,即u-boot版本號,編譯時間
void preloader_console_init(void) { gd->bd = &bdata; gd->baudrate = CONFIG_BAUDRATE; serial_init(); /* serial communications setup */ gd->have_console = 1; puts("\nU-Boot SPL " PLAIN_VERSION " (" U_BOOT_DATE " - " \ U_BOOT_TIME ")\n");//wlg: now we print our first information #ifdef CONFIG_SPL_DISPLAY_PRINT spl_display_print(); #endif }
最后將DRAM初始化,便於加載u-boot到DRAM中.
之后返回到lowlevel_init.S中,
push {ip, lr}
pop {ip, pc}
將之前的lr彈給pc,也就是跳到前一個bl的下面,也就是start.S中:
bl _main
繼續跳到_main中,位於arch/arm/lib/crt0.S中:
ENTRY(_main) /* * Set up initial C runtime environment and call board_init_f(0). */ #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) ldr sp, =(CONFIG_SPL_STACK) #else ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) #endif bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ sub sp, sp, #GD_SIZE /* allocate one GD above SP */ bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ mov r9, sp /* GD is above SP */ mov r0, #0 bl board_init_f
再次賦值sp,仍然指向0x4030FF40,然后向下留出gd結構體的空間,並讓R9指向這里,跳到board_init_f,位於arch/arm/lib/spl.c中,具有weak屬性,在u-boot階段有不同的實現:
void __weak board_init_f(ulong dummy) { /* Clear the BSS. */ memset(__bss_start, 0, __bss_end - __bss_start); /* Set global data pointer. */ gd = &gdata; board_init_r(NULL, 0); }
清BSS段,再次讓gd,即R9指向gdata,跳到board_init_r,位於common/spl/spl.c中,主要執行函數如下:
-> spl_board_init();
-> boot_device = spl_boot_device();
根據boot_device的值在相應的地方加載鏡像文件,比如是MMC/SD的話,將執行spl_mmc_load_image();位於common/spl/spl_mmc.c中,主要執行函數如下:
-> mmc_initialize(gd->bd);
-> mmc = find_mmc_device(0);
-> err = mmc_init(mmc);
-> boot_mode = spl_boot_mode();
根據boot_mode的值使用不同的方式讀取鏡像,比如是FAT模式,將執行一下分支:
else if (boot_mode == MMCSD_MODE_FAT) { debug("boot mode - FAT\n"); #ifdef CONFIG_SPL_OS_BOOT if (spl_start_uboot() || spl_load_image_fat_os(&mmc->block_dev, CONFIG_SYS_MMC_SD_FAT_BOOT_PARTITION)) #endif err = spl_load_image_fat(&mmc->block_dev, CONFIG_SYS_MMC_SD_FAT_BOOT_PARTITION, CONFIG_SPL_FAT_LOAD_PAYLOAD_NAME); #endif
spl_load_image_fat這個函數將u-boot.img文件讀到它該去的地方,即DRAM中,具體是怎么實現的我也不太懂了,執行完畢之后回到spl.c中,根據鏡像文件的類型執跳到u-boot中或者linux中:
switch (spl_image.os) { case IH_OS_U_BOOT: debug("Jumping to U-Boot\n"); break; #ifdef CONFIG_SPL_OS_BOOT case IH_OS_LINUX: debug("Jumping to Linux\n"); spl_board_prepare_for_linux(); jump_to_image_linux((void *)CONFIG_SYS_SPL_ARGS_ADDR); #endif default: debug("Unsupported OS image.. Jumping nevertheless..\n"); } jump_to_image_no_args(&spl_image);
SPL程序自然是跳到u-boot中了,執行下面的jump_to_image_no_args(&spl_image),位於arch/arm/cpu/armv7/omap-common/boot-common中:
void __noreturn jump_to_image_no_args(struct spl_image_info *spl_image) { typedef void __noreturn (*image_entry_noargs_t)(u32 *); image_entry_noargs_t image_entry = (image_entry_noargs_t) spl_image->entry_point; debug("image entry point: 0x%X\n", spl_image->entry_point); /* Pass the saved boot_params from rom code */ image_entry((u32 *)&gd->arch.omap_boot_params); }
貌似是跳到了image_entry那個地方,具體是怎么來的我就不曉得了╮(╯▽╰)╭
直接用file命令看一下最后生成的u-boot.img鏡像文件:
u-boot.img:
u-boot legacy uImage, U-Boot 2014.04 for am335x board, Firmware/ARM, Firmware Image (Not compressed),
329036 bytes, Tue Feb 28 05:56:24 2017, Load Address: 0x80800000, Entry Point: 0x00000000, Header CRC: 0xBA1D5896, Data CRC: 0x697BF780
看到加載地址和入口點,加載地址貌似比DRAM的基址高出了8M.
到這里,MLO程序也就結束了,接着會進入u-boot的世界,這個u-boot,真是。。。