bootloader 要想啟動內核,可以直接跳到內核的第一個指令處,即內核的起始地址,這樣便可以完成內核的啟動工作了。但是要想啟動內核還需要滿足一些條件,如下所示:
1、cpu 寄存器設置
??? * R0 = 0
? ? * R1 = 機器類型 id
? ? * R2 = 啟動參數在內存中的起始地址
2、cpu 模式
? ? * 禁止所有中斷
? ??* 必須為SVC(超級用戶)模式
3、Cache、MMU
? ? * 關閉 MMU
? ? * 指令Cache可以開啟或者關閉
? ? * 數據Cache必須關閉
4、設備
? ? * DMA 設備應當停止工作
5、PC為內核的起始地址
? ? ?
? ? ? 這些需求都由 boot loader 實現,在常用的 uboot 中完成一系列的初始化后最后通過 bootm 命令加載 linux 內核。bootm 向將內核映像從各種媒介中讀出,存放在指定的位置;然后設置標記列表給內核傳遞參數;最后跳到內核的入口點去執行。
Uboot版本:u-boot-2013.01
一、bootm命令用法介紹如下:
? ? ? ?在 common/cmd_bootm.c 中可以看到bootm 的定義:
可以看到 bootm 命令使調用了do_bootm 函數。
do_bootm 函數
在cmd_bootm.c 第586行可以看到do_bootm函數的定義(為方便閱讀,對其中一些代碼進行了刪減,完整代碼請閱讀uboot源碼):
[cpp]?view plaincopy
?? ?? ?? ?? int?do_bootm(cmd_tbl_t?*cmdtp,?int?flag,?int?argc,?char?*?const?argv[])?? {?? ????ulong???????iflag;?? ????ulong???????load_end?=?0;?? ????int?????ret;?? ????boot_os_fn??*boot_fn;?? ?? ????if?(bootm_start(cmdtp,?flag,?argc,?argv))?? ????????return?1;?? ?? ????iflag?=?disable_interrupts();??? ?? ????usb_stop();?? ?? ????ret?=?bootm_load_os(images.os,?&load_end,?1);?? ?? ????lmb_reserve(&images.lmb,?images.os.load,?(load_end?-?images.os.load));?? ?? ????if?(images.os.type?==?IH_TYPE_STANDALONE)?{?? ????????if?(iflag)?? ????????????enable_interrupts();?? ?????????? ????????bootm_start_standalone(iflag,?argc,?argv);?? ????????return?0;?? ????}?? ?? ????boot_fn?=?boot_os[images.os.os];?? ?? ????arch_preboot_os();?? ?? ????boot_fn(0,?argc,?argv,?&images);?? ?? #ifdef?DEBUG?? ????puts("\n##?Control?returned?to?monitor?-?resetting...\n");?? #endif?? ????do_reset(cmdtp,?flag,?argc,?argv);?? ?? ????return?1;?? }??
該函數的實現分為 3 個部分:
a -- 首先通過 bootm_start 函數分析鏡像的信息;
b -- 如果滿足判定條件則進入 bootm_load_os 函數進行加載;
c -- 加載完成后就可以調用 boot_fn 開始啟動。
1、bootm_start
在cmd_bootm.c 第193行可以看到bootm_start函數的定義,?主要作用是填充內核相關信息
[cpp]?view plaincopy
static?int?bootm_start(cmd_tbl_t?*cmdtp,?int?flag,?int?argc,?char?*?const?argv[])?? {?? ????void????????*os_hdr;?? ????int?????ret;?? ?? ????memset((void?*)&images,?0,?sizeof(images));?? ????images.verify?=?getenv_yesno("verify");?? ?? ????boot_start_lmb(&images);?? ?? ????bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START,?"bootm_start");?? ?? ?????? ????os_hdr?=?boot_get_kernel(cmdtp,?flag,?argc,?argv,?? ????????????&images,?&images.os.image_start,?&images.os.image_len);?? ????if?(images.os.image_len?==?0)?{?? ????????puts("ERROR:?can't?get?kernel?image!\n");?? ????????return?1;?? ????}?? ?? ?????? ????switch?(genimg_get_format(os_hdr))?{?? ????case?IMAGE_FORMAT_LEGACY:?? ????????images.os.type?=?image_get_type(os_hdr);?? ????????images.os.comp?=?image_get_comp(os_hdr);?? ????????images.os.os?=?image_get_os(os_hdr);?? ?? ????????images.os.end?=?image_get_image_end(os_hdr);?? ????????images.os.load?=?image_get_load(os_hdr);/加載地址?? ????????break;?? ?? ?????? ????if?(images.legacy_hdr_valid)?{?? ????????images.ep?=?image_get_ep(&images.legacy_hdr_os_copy);?? ?? ????}?else?{?? ????????puts("Could?not?find?kernel?entry?point!\n");?? ????????return?1;?? ????}?? ?? ????if?(images.os.type?==?IH_TYPE_KERNEL_NOLOAD)?{?? ????????images.os.load?=?images.os.image_start;?? ????????images.ep?+=?images.os.load;?? ????}?? ?? ????if?(((images.os.type?==?IH_TYPE_KERNEL)?||?? ?????????(images.os.type?==?IH_TYPE_KERNEL_NOLOAD)?||?? ?????????(images.os.type?==?IH_TYPE_MULTI))?&&?? ????????(images.os.os?==?IH_OS_LINUX))?{?? ?????????? ????????ret?=?boot_get_ramdisk(argc,?argv,?&images,?IH_INITRD_ARCH,?? ????????????????&images.rd_start,?&images.rd_end);?? ????????if?(ret)?{?? ????????????puts("Ramdisk?image?is?corrupt?or?invalid\n");?? ????????????return?1;?? ????????}?? ?? #if?defined(CONFIG_OF_LIBFDT)?? ?????????? ????????ret?=?boot_get_fdt(flag,?argc,?argv,?&images,?? ???????????????????&images.ft_addr,?&images.ft_len);?? ????????if?(ret)?{?? ????????????puts("Could?not?find?a?valid?device?tree\n");?? ????????????return?1;?? ????????}?? ?? ????????set_working_fdt_addr(images.ft_addr);?? #endif?? ????}?? ?? ????images.os.start?=?(ulong)os_hdr;?? ????images.state?=?BOOTM_STATE_START;?? ?? ????return?0;?? }??
該函數主要進行
鏡像的有效性判定、校驗、計算入口地址
等操作,大部分工作通過?
boot_get_kernel -> image_get_kernel
?完成。
2、bootm_load_os
? ? ? 在cmd_bootm.c 第317行可以看到bootm_load_os函數的定義,?這個函數主要判斷鏡像是否需要解壓,并且將鏡像移動到加載地址:
[cpp]?view plaincopy
static?int?bootm_load_os(image_info_t?os,?ulong?*load_end,?int?boot_progress)???? {???? ????uint8_t?comp?=?os.comp;????????????? ????ulong?load?=?os.load;??????????????? ????ulong?blob_start?=?os.start;???????? ????ulong?blob_end?=?os.end;???????????? ????ulong?image_start?=?os.image_start;???????? ????ulong?image_len?=?os.image_len;???????????? ????uint?unc_len?=?CONFIG_SYS_BOOTM_LEN;??????? ???? ????const?char?*type_name?=?genimg_get_type_name?(os.type);?????? ???? ????switch?(comp)?{?????? ????case?IH_COMP_NONE:?????? ????????if?(load?==?blob_start)?{??????? ????????????printf?("???XIP?%s?...?",?type_name);???? ????????}?else?{???? ????????????printf?("???Loading?%s?...?",?type_name);???? ???? ????????????if?(load?!=?image_start)?{???? ????????????????memmove_wd?((void?*)load,?(void?*)image_start,?image_len,?CHUNKSZ);???? ????????????}???? ????????}???? ????????*load_end?=?load?+?image_len;???? ????????puts("OK\n");???? ????????break;???? ????case?IH_COMP_GZIP:?????? ????????printf?("???Uncompressing?%s?...?",?type_name);???? ????????if?(gunzip?((void?*)load,?unc_len,?(uchar?*)image_start,?&image_len)?!=?0)?{?????? ????????????puts?("GUNZIP:?uncompress,?out-of-mem?or?overwrite?error?"???? ????????????????"-?must?RESET?board?to?recover\n");???? ????????????return?BOOTM_ERR_RESET;???? ????????}???? ???? ????????*load_end?=?load?+?image_len;???? ????????break;???? ????...???? ????default:???? ????????printf?("Unimplemented?compression?type?%d\n",?comp);???? ????????return?BOOTM_ERR_UNIMPLEMENTED;???? ????}???? ????puts?("OK\n");???? ????debug?("???kernel?loaded?at?0x%08lx,?end?=?0x%08lx\n",?load,?*load_end);???? ???? ????if?((load?<?blob_end)?&&?(*load_end?>?blob_start))?{???? ????????debug?("images.os.start?=?0x%lX,?images.os.end?=?0x%lx\n",?blob_start,?blob_end);???? ????????debug?("images.os.load?=?0x%lx,?load_end?=?0x%lx\n",?load,?*load_end);???? ????????return?BOOTM_ERR_OVERLAP;???? ????}???? ???? ????return?0;???? }????
3、do_bootm_linux
在bootm_load_os 執行結束后,回到do_bootm 函數,調用boot_fn 運行linux 內核;
boot_os 為函數指針數組,在cmd_bootm.c 136行有定義
可以看出 boot_fn 函數指針指向的函數是位于?arch/arm/lib/bootm.c的 do_bootm_linux,這是內核啟動前最后的一個函數,該函數主要完成啟動參數的初始化,并將板子設定為滿足內核啟動的環境,代碼如下:
可以看到 do_bootm_linux 實際調用的是?boot_jump_linux 函數。
4、boot_jump_linux?
在arch/arm/lib/bootm.c 下第326行有定義
[cpp]?view plaincopy
?? static?void?boot_jump_linux(bootm_headers_t?*images)?? {?? ????unsigned?long?machid?=?gd->bd->bi_arch_number;?? ????char?*s;?? ????void?(*kernel_entry)(int?zero,?int?arch,?uint?params);?? ????unsigned?long?r2;?? ?? ????kernel_entry?=?(void?(*)(int,?int,?uint))images->ep;?? ?? ????s?=?getenv("machid");?? ????if?(s)?{?? ????????strict_strtoul(s,?16,?&machid);?? ????????printf("Using?machid?0x%lx?from?environment\n",?machid);?? ????}?? ?? ????debug("##?Transferring?control?to?Linux?(at?address?%08lx)"?\?? ????????"...\n",?(ulong)?kernel_entry);?? ????bootstage_mark(BOOTSTAGE_ID_RUN_OS);?? ????announce_and_cleanup();?? ?? #ifdef?CONFIG_OF_LIBFDT?? ????if?(images->ft_len)?? ????????r2?=?(unsigned?long)images->ft_addr;?? ????else?? #endif?? ????????r2?=?gd->bd->bi_boot_params;?? ?? ????kernel_entry(0,?machid,?r2);?? }??
kernel_entry(0, machid, r2)?
真正將控制權交給內核,?啟動內核;
滿足arm架構linux內核啟動時的寄存器設置條件:第一個參數為0 ;第二個參數為板子id需與內核中的id匹配,第三個參數為啟動參數地址 。
二、為內核設置啟動參數
? ? ? ? Uboot 也是通過標記列表向內核傳遞參數,標記在源代碼中定義為tag,是一個結構體,在arch/arm/include/asm/setup.h?中定義。
tag_header 結構體定義如下:
? ? ?在一些內存標記、命令行標記的示例代碼就是取自Uboot 中的?setup_memory_tags、setup_commandline_tag函數,他們都是在?arch/arm/lib/bootm.c中定義。
[cpp]?view plaincopy
#if?defined(CONFIG_SETUP_MEMORY_TAGS)?||?\?? ????defined(CONFIG_CMDLINE_TAG)?||?\?? ????defined(CONFIG_INITRD_TAG)?||?\?? ????defined(CONFIG_SERIAL_TAG)?||?\?? ????defined(CONFIG_REVISION_TAG)?? static?void?setup_start_tag?(bd_t?*bd)?? {?? ????params?=?(struct?tag?*)bd->bi_boot_params;?? ?? ????params->hdr.tag?=?ATAG_CORE;?? ????params->hdr.size?=?tag_size?(tag_core);?? ?? ????params->u.core.flags?=?0;?? ????params->u.core.pagesize?=?0;?? ????params->u.core.rootdev?=?0;?? ?? ????params?=?tag_next?(params);?? }?? #endif?? ?? #ifdef?CONFIG_SETUP_MEMORY_TAGS?? static?void?setup_memory_tags(bd_t?*bd)?? {?? ????int?i;?? ?? ????for?(i?=?0;?i?<?CONFIG_NR_DRAM_BANKS;?i++)?{?? ????????params->hdr.tag?=?ATAG_MEM;?? ????????params->hdr.size?=?tag_size?(tag_mem32);?? ?? ????????params->u.mem.start?=?bd->bi_dram[i].start;?? ????????params->u.mem.size?=?bd->bi_dram[i].size;?? ?? ????????params?=?tag_next?(params);?? ????}?? }?? #endif?? ?? #ifdef?CONFIG_CMDLINE_TAG?? static?void?setup_commandline_tag(bd_t?*bd,?char?*commandline)?? {?? ????char?*p;?? ?? ????if?(!commandline)?? ????????return;?? ?? ?????? ????for?(p?=?commandline;?*p?==?'?';?p++);?? ?? ????? ? ?? ????if?(*p?==?'\0')?? ????????return;?? ?? ????params->hdr.tag?=?ATAG_CMDLINE;?? ????params->hdr.size?=?? ????????(sizeof?(struct?tag_header)?+?strlen?(p)?+?1?+?4)?>>?2;?? ?? ????strcpy?(params->u.cmdline.cmdline,?p);?? ?? ????params?=?tag_next?(params);?? }?? #endif??
一般有 setup_memory_tags、setup_commandline_tag 這兩個標記就可以了,在配置文件Include/configs/fs4412.h中定義:
總結
以上是生活随笔為你收集整理的Exynos4412 Uboot 移植(四)—— Uboot引导内核过程分析的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。