在前面的章節關於u-boot的源碼,以及u-boot的移植這一塊我們介紹完了。接下來,我們應該開始進入第二個階段,linux內核移植,以及驅動開發。
但是在這之前,我們遺漏了u-boot中的一個重要環節沒有介紹,就是u-boot如何執行bootm命令,如何實現linux內核啟動。
我們在Mini440之uboot移植之源碼分析命令解析(五) 介紹過如果配置了CONFIG_BOOTCOMMAND宏:
#define CONFIG_BOOTCOMMAND "nand read 0x30000000 kernel; bootm 0x30000000" //bootcmd
那么在執行autoboot_command函數的時候,將會執行bootcmd中保存的命令。
- nand read 0x30000000 kernel:這里將Nand Flash kernel分區的內核鏡像加載到地址0x30000000;
- bootm 0x3000000:啟動linux內核;bootm這個命令用於啟動一個內核鏡像,這個鏡像就是uImage文件,它會從uImage鏡像文件的頭部取得一些信息,這些信息包括:CPU架構、操作系統類型、文件類型、壓縮方式、加載地址、運行的入口地址等;
一、內核鏡像
前面我們說了u-boot啟動linux內核,從Nand Flash內核分區中加載uImage鏡像,那么什么是uImage鏡像呢?說到這個我們就不得不聊一聊內核鏡像的幾種文件格式了。
1.1 介紹
linux內核編譯之后一般會生成一下兩個文件,一個是Image,一個是zImage,其中Image為內核鏡像文件(可以直接在芯片上運行原生二進制文件),而zImage為內核的鏡像壓縮文件(但它不僅愛是一個壓縮文件,在文件的開頭部分內嵌有gzip解壓縮代碼)。
但是這兩種鏡像的格式並沒有辦法提供給u-boot的足夠的信息來進行load、jump或者驗證操作等等。因此,u-boot提供了mkimage工具,來將zImage制作為u-boot可以識別的格式,將生成的文件稱之uImage。
需要注意的是:這里並不是說u-boot一定不支持zImage鏡像文件的啟動,一般可以通過配置CONFIG_ZIMAGE_BOOT使u-boot支持zImage啟動。
1.1.1 Legacy uImage
Legacy uImage它是在zImage之前加上了一個長度為64字節的頭,說明這個內核的CPU架構、操作系統類型、文件類型、壓縮方式、加載地址、運行的入口地址等。
1.1.2 FIT uImage
FIT uImage是在Legacy uImage的基礎上,為了滿足Linux Flattened Device Tree(FDT)的標准,而重新改進和定義出來的一種鏡像文件格式;它一般將kernel、fdt、ramdisk等等鏡像打包到一個itb鏡像文件中;u-boot只要獲得了這個鏡像文件,就可以得到kernel、fdt、ramdisk等等鏡像的具體信息和內容。
1.1.3 Image、zImage、uImage發展歷程
為什么會出現 Legacy uImage ,然后又出現 FIT uImage ?
最開始出現的是Image,就一個普通的內核鏡像。然后為了節省空間,有了 zImage,進行了壓縮可以節省空間。
u-boot啟動一個Image或者 zImage,還必須要給它傳遞一些參數;
- 鏡像文件的類型,如kernel image、dtb文件、ramdisk image等等?
- 鏡像文件需要放在內存的的哪個位置(加載地址)?
- 鏡像文件需要從內存哪個位置開始執行(入口地址)?
- 鏡像文件是否有壓縮?
- 鏡像文件是否有一些完整性校驗的信息(如CRC)?
這種方式的不足就在於,鏡像本身沒有帶有這些參數的,用工具制作完鏡像后,還需要另外再向u-boot提供這些參數,才能正常啟動(就是比較麻煩)。
如果可以把這些參數,在制作鏡像的時候就一起弄到鏡像里面,然后u-boot一讀取鏡像,就馬上可以知道這些參數了。不需要在制作好鏡像之后再另外告訴u-boot這些參數。這種帶有以上參數的鏡像格式就是 Legacy uImage。
最后一個 FIT uImage ,其主要目的是為了支持基於device tree的unify kernel。
1.2 Legacy uImage
1.2.1 配置
使能需要打開的宏:
CONFIG_IMAGE_FORMAT_LEGACY=y
注意,這個宏在自動生成的autoconf.mk中會自動配置,不需要額外配置。
1.2.2 制作
編譯完u-boot之后,使用u-boot目錄下tools/mkimage工具來制作uImage。命令如下:
mkimage -A arm -O linux -C none -T kernel -a 0x20008000 -e 0x20008040 -n Linux_Image -d zImage uImage 各個參數意義如下 Usage: mkimage -l image -l ==> list image header information mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image -A ==> set architecture to 'arch' // 架構 -O ==> set operating system to 'os' // 操作系統 -T ==> set image type to 'type' // 鏡像類型 -C ==> set compression type 'comp' // 壓縮類型 -a ==> set load address to 'addr' (hex) // 加載地址 -e ==> set entry point to 'ep' (hex) // 入口地址 -n ==> set image name to 'name' // 鏡像名稱,注意不能超過32B -d ==> use image data from 'datafile' // 輸入文件 -x ==> set XIP (execute in place)
1.2.3 使用
將生成的Legacy uImage下載到內存中,使用bootm <download addr>命令啟動kernel中。
但是注意,如果使用Legacy uImage后面還需要根據實際情況決定是否需要傳入ramdisk以及dtb的在內存中的地址,否則可能會導致bootm失敗。
格式如下:
bootm <Legacy uImage addr> <ramdisk addr> <dtb addr>
1.3 FIT uImage
1.3.1 FIT介紹
FIT是flattened image tree的簡稱,它采用了device tree source filse(DTS)的語法,生成的image文件也和dtb文件類似(稱做itb)。
其通過一定語法和格式將一些需要使用到的鏡像(例如kernel、dtb以及文件系統)組合到一起生成一個image file。其生成步驟如下圖所示:

其中image source file(.its)和device tree source file(.dts)類似,負責描述要生成的image file的信息。mkimage和dtc工具,可以將.its文件以及對應的image data file,打包成一個image file。
這里我們有必要對涉及到的這幾類文件進行一個總結:
- its文件 :image source file,類似於dts文件,負責描述要聲稱的image的的信息。需要自行進行構造;
- itb文件 :最終得到的image文件,類似於dtb文件,也就是uboot可以直接對其進行識別和解析的FIT uImage;
- mkimage :mkimage則負責dtc的角色,用於通過解析its文件、獲取對應的鏡像,最終生成一個u-boot可以直接進行識別和解析的itb文件;
- image data file :實際使用到的鏡像文件;
mkimage將its文件以及對應的image data file,打包成一個itb文件,也就是u-boot可以識別的image file(FIT uImage)。我們將這個文件下載到么內存中,使用bootm命令就可以執行了。
1.3.2 配置
使能需要打開的宏:
CONFIG_FIT=y
1.3.3 創建its文件
因為mkimage是根據its文件中的描述來打包鏡像生成itb文件(FIT uImage),所以首先需要制作一個its文件,在its文件中描述需要被打包的鏡像,主要是kernel鏡像,dtb文件,ramdisk鏡像。
關於its文件的語法,可以參考這篇博客FIT介紹。簡單的例子如下:
/* * U-Boot uImage source file for "X project" */ /dts-v1/; / { description = "U-Boot uImage source file for X project"; #address-cells = <1>; images { kernel@tiny210 { description = "Unify(TODO) Linux kernel for project-x"; data = /incbin/("/home/hlos/code/xys/temp/project-x/build/out/linux/arch/arm/boot/zImage"); type = "kernel"; arch = "arm"; os = "linux"; compression = "none"; load = <0x20008000>; entry = <0x20008000>; }; fdt@tiny210 { description = "Flattened Device Tree blob for project-x"; data = /incbin/("/home/hlos/code/xys/temp/project-x/build/out/linux/arch/arm/boot/dts/s5pv210-tiny210.dtb"); type = "flat_dt"; arch = "arm"; compression = "none"; }; ramdisk@tiny210 { description = "Ramdisk for project-x"; data = /incbin/("/home/hlos/code/xys/temp/project-x/build/out/rootfs/initramfs.gz"); type = "ramdisk"; arch = "arm"; os = "linux"; compression = "gzip"; }; }; configurations { default = "conf@tiny210"; conf@tiny210 { description = "Boot Linux kernel with FDT blob"; kernel = "kernel@tiny210"; fdt = "fdt@tiny210"; ramdisk = "ramdisk@tiny210"; }; }; };
注意,可以有多個kernel節點或者fdt節點等等,兼容性更強。同時,可以有多種configurations,來對kernel、fdt、ramdisk來進行組合,使FIT-uImage可以兼容於多種板子,而無需重新進行編譯燒寫。
1.3.4 生成FIT uImage
生成的命令相對Legacy uImage較為簡單,因為信息都在its文件里面描述了:
${UBOOT_OUT_DIR}/tools/mkimage -f ${UIMAGE_ITS_FILE} ${UIMAGE_ITB_FILE}
其中 -f 指定需要編譯的source文件,並在后面指定需要生成的image文件(一般以.itb為后綴,例如u-boot.itb)。
1.3.5 使用
將生成的FIT uImage下載到內存某個地址,使用bootm <FIT uImage addr>命令啟動。
u-boot會自動解析出FIT uImage中,kernel、ramdisk、dtb的信息,使用起來相當方便。
二、linux內核啟動
2.1 autoboot_command
autoboot_command函數定義在common/autoboot.c文件中:
void autoboot_command(const char *s) { debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>"); if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) { run_command_list(s, -1, 0); } }
如果在u-boot啟動倒計時結束之前,沒有按下任何鍵,將會執行那么將執行run_command_list,此函數會執行參數s指定的一系列命令,也就是bootcmd中配置中的命令,bootcmd中保存着默認的啟動命令。
在默認環境變量default_environment中定義有:
#ifdef CONFIG_BOOTCOMMAND "bootcmd=" CONFIG_BOOTCOMMAND "\0" #endif
2.2 do_bootm
由於要執行bootm命令,所以我們需要打開與bootm命令相關的文件進行分析,bootm命令定義在cmc/bootm.c文件中:
U_BOOT_CMD( bootm, CONFIG_SYS_MAXARGS, 1, do_bootm, "boot application image from memory", bootm_help_text );
找到對應的do_bootm函數,去除無用的代碼:
/*******************************************************************/ /* bootm - boot application image from image in memory */ /*******************************************************************/ int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { /* determine if we have a sub command */ argc--; argv++; if (argc > 0) { char *endp; simple_strtoul(argv[0], &endp, 16); /* endp pointing to NULL means that argv[0] was just a * valid number, pass it along to the normal bootm processing * * If endp is ':' or '#' assume a FIT identifier so pass * along for normal processing. * * Right now we assume the first arg should never be '-' */ if ((*endp != 0) && (*endp != ':') && (*endp != '#')) return do_bootm_subcommand(cmdtp, flag, argc, argv); }
// 到這里參數中的bootm參數會被去掉 return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START | BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER | BOOTM_STATE_LOADOS | BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO, &images, 1); }
當執行bootm 0x30000000,函數入參:第一個參數是bootm命令結構體,flag是命令標志位,argv[0]='"bootm"、argv[1]="0x3000000",argc=2。
這里進入函數之后argc=1,argv[0]=0x30000000。
bootm的核心是do_bootm_states,以全局變量bootm_headers_t images作為do_bootm_states的參數,該變量在cmd/bootm.c文件中聲明。
bootm_headers_t images; /* pointers to os/initrd/fdt images */
bootm會根據參數以及參數指向的鏡像來填充這個結構體里面的成員。 最終再使用這個結構體里面的信息來填充kernel啟動信息並且到跳轉到kernel中,下面詳細說明這個函數。
三、do_bootm_states函數
3.1 函數聲明
我們先來看一下這個函數的聲明:
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[], int states, bootm_headers_t *images, int boot_progress);
3.2 相關結構體
3.2.1 bootm_headers_t
參數bootm_headers_t *images是一個復雜的數據結構,bootm_headers_t定義在include/image.h文件中:
/* * Legacy and FIT format headers used by do_bootm() and do_bootm_<os>() * routines. */ typedef struct bootm_headers { /* * Legacy os image header, if it is a multi component image * then boot_get_ramdisk() and get_fdt() will attempt to get * data from second and third component accordingly. */ image_header_t *legacy_hdr_os; /* image header pointer */ image_header_t legacy_hdr_os_copy; /* header copy */ ulong legacy_hdr_valid; #if IMAGE_ENABLE_FIT const char *fit_uname_cfg; /* configuration node unit name */ void *fit_hdr_os; /* os FIT image header */ const char *fit_uname_os; /* os subimage node unit name */ int fit_noffset_os; /* os subimage node offset */ void *fit_hdr_rd; /* init ramdisk FIT image header */ const char *fit_uname_rd; /* init ramdisk subimage node unit name */ int fit_noffset_rd; /* init ramdisk subimage node offset */ void *fit_hdr_fdt; /* FDT blob FIT image header */ const char *fit_uname_fdt; /* FDT blob subimage node unit name */ int fit_noffset_fdt;/* FDT blob subimage node offset */ void *fit_hdr_setup; /* x86 setup FIT image header */ const char *fit_uname_setup; /* x86 setup subimage node name */ int fit_noffset_setup;/* x86 setup subimage node offset */ #endif #ifndef USE_HOSTCC image_info_t os; /* os image info */ ulong ep; /* entry point of OS */ ulong rd_start, rd_end;/* ramdisk start/end */ char *ft_addr; /* flat dev tree address */ ulong ft_len; /* length of flat device tree */ ulong initrd_start; ulong initrd_end; ulong cmdline_start; ulong cmdline_end; bd_t *kbd; #endif int verify; /* getenv("verify")[0] != 'n' */ #define BOOTM_STATE_START (0x00000001) #define BOOTM_STATE_FINDOS (0x00000002) #define BOOTM_STATE_FINDOTHER (0x00000004) #define BOOTM_STATE_LOADOS (0x00000008) #define BOOTM_STATE_RAMDISK (0x00000010) #define BOOTM_STATE_FDT (0x00000020) #define BOOTM_STATE_OS_CMDLINE (0x00000040) #define BOOTM_STATE_OS_BD_T (0x00000080) #define BOOTM_STATE_OS_PREP (0x00000100) #define BOOTM_STATE_OS_FAKE_GO (0x00000200) /* 'Almost' run the OS */ #define BOOTM_STATE_OS_GO (0x00000400) int state; #ifdef CONFIG_LMB struct lmb lmb; /* for memory mgmt */ #endif } bootm_headers_t;
bootm_headers_t用於Legacy或設備樹(FDT)方式鏡像的啟動,其中包括了os/initrd/fdt images的信息。我們這里大概介紹一下和這個結構體的成員變量:
- legacy_hdr_os:Legacy uImage的鏡像頭;對應就是Legacy uImage加載到內存的起始地址;
- legacy_hdr_os_copy:Legacy uImage的鏡像頭備份;
- legacy_hdr_valid:用於表示Legacy uImage的頭部指針是否可用,也就是這是否是一個Legacy uImage;
- fit_uname_cfg:配置節點名;
- fit_hdr_os:FIT uImage中kernel鏡像頭;對應就是FIT uImage加載到內存的的起始地址;
- fit_uname_os:FIT uImage中kernel的節點名稱;比如我們之前案例中的kernel@tiny210;
- fit_noffset_os:FIT uImage中kernel的節點偏移,直接代表了kernel節點;也就是說通過fit_hdr_os、fit_noffset_os就可以獲取到itb文件中描述的kernel節點的信息;
- fit_hdr_rd:FIT uImage中ramdisk的鏡像頭;
- fit_uname_rd:FIT uImage中ramdisk的節點名;
- fit_noffset_rd:FIT uImage中ramdisk的節點偏移;
- fit_hdr_fdt:FIT uImage中FDT的鏡像頭;
- fit_uname_fdt:FIT uImage中FDT的節點名;
- fit_noffset_fdt:FIT uImage中FDT的節點偏移;
- os:操作系統信息的結構體,包含os.load、os.type、os.os等字段信息;
- ep:操作系統的入口地址;
- rd_start:ramdisk在內存上的起始地址;
- rd_end:ramdisk在內存上的結束地址
- ft_addr:fdt在內存上的地址;
- ft_len:fdt在內存上的長度;
- verify:是否需要驗證;
- state:狀態標識,用於標識對應的bootm需要做什么操作;
其中 legacy_hdr_os是image_header_t類型。這個結構尤為重要,下面來介紹。
3.2.2 image_header_t
u-boot使用image_header_t來表示Legacy uImage的頭部信息,定義在include/image.h文件:
/* * Legacy format image header, * all data in network byte order (aka natural aka bigendian). */ typedef struct image_header { __be32 ih_magic; /* Image Header Magic Number */ __be32 ih_hcrc; /* Image Header CRC Checksum */ __be32 ih_time; /* Image Creation Timestamp */ __be32 ih_size; /* Image Data Size */ __be32 ih_load; /* Data Load Address */ __be32 ih_ep; /* Entry Point Address */ __be32 ih_dcrc; /* Image Data CRC Checksum */ uint8_t ih_os; /* Operating System */ uint8_t ih_arch; /* CPU architecture */ uint8_t ih_type; /* Image Type */ uint8_t ih_comp; /* Compression Type */ uint8_t ih_name[IH_NMLEN]; /* Image Name */ } image_header_t;
比較重要的成員有:
- ih_magic:鏡像的魔數,用來校驗是不是一個Legacy uImage;為0x27051956則表示是一個Legacy uImage;
- ih_size:數據鏡像長度,和zImage長度一樣,不包含頭部信息;
- ih_load:鏡像的加載地址;和mkimage使用的參數一致;zImage解壓之后得到Image會加載到這個地址上;
- ih_ep:鏡像的入口地址;和mkimage使用的參數一致;內核啟動的入口地址;
- ih_os:鏡像的系統;
- ih_type:鏡像的類型;
3.2.3 狀態說明
do_bootm_states的state參數是一大堆的標志宏,這些標志宏就是u-boot啟動時需要的階段,每個階段都有一個宏來表示。
#define BOOTM_STATE_START (0x00000001) #define BOOTM_STATE_FINDOS (0x00000002) #define BOOTM_STATE_FINDOTHER (0x00000004) #define BOOTM_STATE_LOADOS (0x00000008) #define BOOTM_STATE_RAMDISK (0x00000010) #define BOOTM_STATE_FDT (0x00000020) #define BOOTM_STATE_OS_CMDLINE (0x00000040) #define BOOTM_STATE_OS_BD_T (0x00000080) #define BOOTM_STATE_OS_PREP (0x00000100) #define BOOTM_STATE_OS_FAKE_GO (0x00000200) /* 'Almost' run the OS */ #define BOOTM_STATE_OS_GO (0x00000400)
- BOOTM_STATE_START :開始執行bootm的一些准備動作;
- BOOTM_STATE_FINDOS :查找內核鏡像,需要注意的是內核鏡像有多種,比如Image、zImage、uImage等,像Image內核鏡像加載到內存,就可以直接運行,而像zImage這種是壓縮之后的內核鏡像文件,它在頭部包含了解壓縮代碼,而uImage在zImage之前又加了0x40的頭信息,而u-boot引導的內核鏡像文件一般為zImage(bootz命令)和uImage(bootm命令);
- BOOTM_STATE_FINDOTHER :查找內核鏡像外的其它鏡像,比如FDT、ramdisk等;
- BOOTM_STATE_LOADOS :查找到內核鏡像后,解析加載的內核鏡像的頭信息,根據內核鏡像的格式是zImage、uImage還是設備樹,有不同的處理邏輯,比uImage需要解壓,最終會將vmlinux加載到images.os.load指向的內存空間;
- BOOTM_STATE_RAMDISK :操作ramdisk;
- BOOTM_STATE_FDT :操作FDT;
- BOOTM_STATE_OS_CMDLINE :操作commandline;
- BOOTM_STATE_OS_BD_T :跳轉到內核前的准備動作;
- BOOTM_STATE_OS_PREP :執行跳轉前的准備動作 ;
- BOOTM_STATE_OS_FAKE_GO :偽跳轉,一般都能直接跳轉到kernel中去
- BOOTM_STATE_OS_GO :設置啟動參數,跳轉到kernel所在的地址上 ;
上面划掉的可以先忽略掉。do_bootm_states根據states來判斷要執行的操作。在這些流程中,起傳遞作用的是bootm_headers_t images這個數據結構,有些流程是解析內核鏡像,往這個結構體里寫數據。 而跳轉的時候,則需要使用到這個結構體里面的數據。
3.3 do_bootm_states函數
do_bootm_states函數定義在cmd/bootm.c文件:
/** * Execute selected states of the bootm command. * * Note the arguments to this state must be the first argument, Any 'bootm' * or sub-command arguments must have already been taken. * * Note that if states contains more than one flag it MUST contain * BOOTM_STATE_START, since this handles and consumes the command line args. * * Also note that aside from boot_os_fn functions and bootm_load_os no other * functions we store the return value of in 'ret' may use a negative return * value, without special handling. * * @param cmdtp Pointer to bootm command table entry * @param flag Command flags (CMD_FLAG_...) * @param argc Number of subcommand arguments (0 = no arguments) * @param argv Arguments * @param states Mask containing states to run (BOOTM_STATE_...) * @param images Image header information * @param boot_progress 1 to show boot progress, 0 to not do this * @return 0 if ok, something else on error. Some errors will cause this * function to perform a reboot! If states contains BOOTM_STATE_OS_GO * then the intent is to boot an OS, so this function will not return * unless the image type is standalone. */ int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[], int states, bootm_headers_t *images, int boot_progress) { boot_os_fn *boot_fn; ulong iflag = 0; int ret = 0, need_boot_fn; images->state |= states; /* * Work through the states and see how far we get. We stop on * any error. */ if (states & BOOTM_STATE_START) ret = bootm_start(cmdtp, flag, argc, argv); if (!ret && (states & BOOTM_STATE_FINDOS)) ret = bootm_find_os(cmdtp, flag, argc, argv); if (!ret && (states & BOOTM_STATE_FINDOTHER)) { ret = bootm_find_other(cmdtp, flag, argc, argv); argc = 0; /* consume the args */ } /* Load the OS */ if (!ret && (states & BOOTM_STATE_LOADOS)) { ulong load_end; iflag = bootm_disable_interrupts(); ret = bootm_load_os(images, &load_end, 0); if (ret == 0) lmb_reserve(&images->lmb, images->os.load, (load_end - images->os.load)); else if (ret && ret != BOOTM_ERR_OVERLAP) goto err; else if (ret == BOOTM_ERR_OVERLAP) ret = 0; #if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY) if (images->os.os == IH_OS_LINUX) fixup_silent_linux(); #endif } /* Relocate the ramdisk */ #ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH // 不執行 if (!ret && (states & BOOTM_STATE_RAMDISK)) { ulong rd_len = images->rd_end - images->rd_start; ret = boot_ramdisk_high(&images->lmb, images->rd_start, rd_len, &images->initrd_start, &images->initrd_end); if (!ret) { setenv_hex("initrd_start", images->initrd_start); setenv_hex("initrd_end", images->initrd_end); } } #endif #if IMAGE_ENABLE_OF_LIBFDT && defined(CONFIG_LMB) // 不執行 if (!ret && (states & BOOTM_STATE_FDT)) { boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr); ret = boot_relocate_fdt(&images->lmb, &images->ft_addr, &images->ft_len); } #endif /* From now on, we need the OS boot function */ if (ret) return ret; boot_fn = bootm_os_get_boot_func(images->os.os); need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE | BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO); if (boot_fn == NULL && need_boot_fn) { if (iflag) enable_interrupts(); printf("ERROR: booting os '%s' (%d) is not supported\n", genimg_get_os_name(images->os.os), images->os.os); bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS); return 1; } /* Call various other states that are not generally used */ if (!ret && (states & BOOTM_STATE_OS_CMDLINE)) ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images); if (!ret && (states & BOOTM_STATE_OS_BD_T)) ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images); if (!ret && (states & BOOTM_STATE_OS_PREP)) ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images); #ifdef CONFIG_TRACE // 不執行 /* Pretend to run the OS, then run a user command */ if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) { char *cmd_list = getenv("fakegocmd"); ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO, images, boot_fn); if (!ret && cmd_list) ret = run_command_list(cmd_list, -1, flag); } #endif /* Check for unsupported subcommand. */ if (ret) { puts("subcommand not supported\n"); return ret; } /* Now run the OS! We hope this doesn't return */ if (!ret && (states & BOOTM_STATE_OS_GO)) ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO, images, boot_fn); /* Deal with any fallout */ err: if (iflag) enable_interrupts(); if (ret == BOOTM_ERR_UNIMPLEMENTED) bootstage_error(BOOTSTAGE_ID_DECOMP_UNIMPL); else if (ret == BOOTM_ERR_RESET) do_reset(cmdtp, flag, argc, argv); return ret; }
代碼具體執行流程:
- 初始化images->state |= states;
- states跟宏BOOTM_STATE_START進行與操作,通過執行bootm_start;
- states跟宏BOOTM_STATE_FINDOS進行與操作,通過執行bootm_find_os;
- states跟宏BOOTM_STATE_FINDOTHER進行與操作,通過執行bootm_find_other;
- states跟宏BOOTM_STATE_LOADOS進行與操作,通過關閉中斷,執行bootm_load_os;
- states跟宏BOOTM_STATE_OS_PREP進行與操作,通過執行boot_fn;
- states跟宏BOOTM_STATE_OS_GO進行與操作,通過執行boot_selected_os;
boot_selected_os,這函數里面就執行do_bootm_linux跳轉到我們的內核去運行了,如無意外,到了這里一般情況下就不返回了。
我們根據狀態參數,繪制出這個函數的執行流程:

3.3.1 bootm_start
boot_start函數定義在common/bootm.c文件:
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { memset((void *)&images, 0, sizeof(images)); images.verify = getenv_yesno("verify"); boot_start_lmb(&images); bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start"); images.state = BOOTM_STATE_START; return 0; }
代碼具體執行流程:
- 清空images結構體;
- 獲取環境遍歷verify,並賦值給images.verify;
- 執行boot_start_lmb()初始化images.lmb;
- 執行bootstage_mark_name,記錄啟動階段的名字;
- 設置images.state = BOOTM_STATE_START;
3.3.2 bootm_find_os
bootm_find_os函數用於獲取操作系統相關的一些信息,例如類型、壓縮方式,加載地址、入口地址等。
定義在common/bootm.c:
static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { const void *os_hdr; bool ep_found = false; int ret; /* get kernel image header, start address and length */ os_hdr = boot_get_kernel(cmdtp, flag, argc, argv, // 1. 獲取到Legacy uImage的頭部,並將其指針返回給os_hdr &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; } /* get image parameters */ switch (genimg_get_format(os_hdr)) { // 2. 根據不同的內核鏡像格式,如Android、Legacy uImage、FIT uImage,獲取操作系統的信息 #if defined(CONFIG_IMAGE_FORMAT_LEGACY) case IMAGE_FORMAT_LEGACY: // Legacy uImage images.os.type = image_get_type(os_hdr); // 從頭部image_header中獲得鏡像類型並存儲到images.os.type中 images.os.comp = image_get_comp(os_hdr); // 從頭部image_header中獲得壓縮類型類型並存儲到images.os.comp中 images.os.os = image_get_os(os_hdr); // 從頭部image_header中獲得操作系統類型並存儲到images.os.os中 images.os.end = image_get_image_end(os_hdr); // 當前鏡像的尾地址 images.os.load = image_get_load(os_hdr); // 從頭部image_header中獲得加載地址並存儲到images.os.load中 images.os.arch = image_get_arch(os_hdr); // 從頭部image_header中獲得cpu體系結構類型並存儲到images.os.arch中 break; #endif #if IMAGE_ENABLE_FIT case IMAGE_FORMAT_FIT: // FIT uImage if (fit_image_get_type(images.fit_hdr_os, // 獲取itb文件中kernel節點中的"type"屬性 images.fit_noffset_os, &images.os.type)) { puts("Can't get image type!\n"); bootstage_error(BOOTSTAGE_ID_FIT_TYPE); return 1; } if (fit_image_get_comp(images.fit_hdr_os, // 獲取itb文件中kernel節點中的"comp"屬性 images.fit_noffset_os, &images.os.comp)) { puts("Can't get image compression!\n"); bootstage_error(BOOTSTAGE_ID_FIT_COMPRESSION); return 1; } if (fit_image_get_os(images.fit_hdr_os, images.fit_noffset_os, // 獲取itb文件中kernel節點中的"os"屬性 &images.os.os)) { puts("Can't get image OS!\n"); bootstage_error(BOOTSTAGE_ID_FIT_OS); return 1; } if (fit_image_get_arch(images.fit_hdr_os, // 獲取itb文件中kernel節點中的"arch"屬性 images.fit_noffset_os, &images.os.arch)) { puts("Can't get image ARCH!\n"); return 1; } images.os.end = fit_get_end(images.fit_hdr_os); if (fit_image_get_load(images.fit_hdr_os, images.fit_noffset_os, // 獲取itb文件中kernel節點中的"load"屬性 &images.os.load)) { puts("Can't get image load address!\n"); bootstage_error(BOOTSTAGE_ID_FIT_LOADADDR); return 1; } break; #endif #ifdef CONFIG_ANDROID_BOOT_IMAGE case IMAGE_FORMAT_ANDROID: // Android images.os.type = IH_TYPE_KERNEL; images.os.comp = IH_COMP_NONE; images.os.os = IH_OS_LINUX; images.os.end = android_image_get_end(os_hdr); images.os.load = android_image_get_kload(os_hdr); images.ep = images.os.load; ep_found = true; break; #endif default: puts("ERROR: unknown image format type!\n"); return 1; } /* If we have a valid setup.bin, we will use that for entry (x86) */ if (images.os.arch == IH_ARCH_I386 || images.os.arch == IH_ARCH_X86_64) { ulong len; ret = boot_get_setup(&images, IH_ARCH_I386, &images.ep, &len); if (ret < 0 && ret != -ENOENT) { puts("Could not find a valid setup.bin for x86\n"); return 1; } /* Kernel entry point is the setup.bin */ } else if (images.legacy_hdr_valid) { // Legacy uImage類型 images.ep = image_get_ep(&images.legacy_hdr_os_copy); // 從頭部image_header中獲得鏡像的入口地址並存儲到images.ep中 #if IMAGE_ENABLE_FIT } else if (images.fit_uname_os) { // FIT uImage類型 int ret; ret = fit_image_get_entry(images.fit_hdr_os, // 獲取itb文件中kernel節點中的"entry"屬性 images.fit_noffset_os, &images.ep); if (ret) { puts("Can't get entry point property!\n"); return 1; } #endif } else if (!ep_found) { 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; } images.os.start = map_to_sysmem(os_hdr); return 0; }
假設我們使用的內核鏡像格式為Legacy uImage,那么代碼具體執行流程:
(1) boot_get_kernel函數獲取內核鏡像在內存地址和大小:
- 調用genimg_get_kernel_addr_fit獲取內核鏡像uImage加載到內存的起始地址,也就是我們傳入的0x30000000參數;
- 調用genimg_get_image解析0x30000000這個地址,如果這個地址位於dataflash storage,將會將內核鏡像加載到RAM中;
- 調用genimg_get_format獲取內核鏡像格式:除了我們前面介紹的Legacy uImage、FIT uImage、還有安卓等格式;
- 然后初始化images.os.image_start,對於Legacy uImage,其值為內核鏡像加載到內存的起始地址 + 鏡像頭64字節,即zImage加載到內存的起始地址;也就是0x30000040;對於FIT uImage,其值為itb文件中kernel節點data屬性指向的內核鏡像文件在內存中的地址,一般也是zImage加載到內存的起始地址;
- 初始化images.os.image_len,內核鏡像數據長度,即zImage的大小;
- 初始化images.legacy_hdr_os:指向0x30000000地址;
- 將images.legacy_hdr_os指向的前64個字節拷貝到images.legacy_hdr_os_copy;即位uImage的頭部做了一個備份;
- 設置images.legacy_hdr_valid = 1; 說明uImage的頭部指針指向的頭部是可用的,這是一個Legacy uImage類型
- 如果是FIT格式,還會初始化images中部分與fit相關的字段;
(2) 根據內核鏡像文件的類型,去獲取到內核信息,並初始化images.os的各個成員,包括:
- 內核鏡像類型images.os.type;即images.legacy_hdr_os->ih_type;
- 內核壓縮方式images.os.comp; 即images.legacy_hdr_os->ih_comp;
- 內核操作系統類型images.os.os;即images.legacy_hdr_os->ih_os;
- 內核鏡像在內存的結束地址image.os.end;
- 內核要裝載到內存的地址images.os.load;即images.legacy_hdr_os->ih_load;
- 內核的CPU體系架構images.os.arch;即images.legacy_hdr_os->ih_arch;
(3) 最后從頭部image_header中獲得鏡像入口地址並存儲到images.ep,即images.legacy_hdr_os->ih_ep;其實就是內核的啟動地址了;
這里要說明一下,現在Legacy uImage內核鏡像文件所在的內存地址是0x30000000,而內核啟動的內存地址是在images.ep成員所指向的地址;
3.3.3 bootm_find_other
static int bootm_find_other(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { 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 || images.os.os == IH_OS_VXWORKS)) return bootm_find_images(flag, argc, argv); return 0; }
查找內核鏡像外的其它鏡像,比如FDT、ramdisk等。
3.3.4 bootm_load_os
static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end, int boot_progress) { image_info_t os = images->os; 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; bool no_overlap; void *load_buf, *image_buf; int err; load_buf = map_sysmem(load, 0); // 獲取zImage解壓之后,得到的Image的存放地址 image_buf = map_sysmem(os.image_start, image_len); // 獲取zImage鏡像在內存中的地址 err = bootm_decomp_image(os.comp, load, os.image_start, os.type, // 重點,解壓縮,重定位 load_buf, image_buf, image_len, CONFIG_SYS_BOOTM_LEN, load_end); if (err) { bootstage_error(BOOTSTAGE_ID_DECOMP_IMAGE); return err; } flush_cache(load, *load_end - load); debug(" kernel loaded at 0x%08lx, end = 0x%08lx\n", load, *load_end); bootstage_mark(BOOTSTAGE_ID_KERNEL_LOADED); no_overlap = (os.comp == IH_COMP_NONE && load == image_start); if (!no_overlap && (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); /* Check what type of image this is. */ if (images->legacy_hdr_valid) { if (image_get_type(&images->legacy_hdr_os_copy) == IH_TYPE_MULTI) puts("WARNING: legacy format multi component image overwritten\n"); return BOOTM_ERR_OVERLAP; } else { puts("ERROR: new format image overwritten - must RESET the board to recover\n"); bootstage_error(BOOTSTAGE_ID_OVERWRITTEN); return BOOTM_ERR_RESET; } } return 0; }
3.3.5 boot_fn
boot_fn的定義為:
boot_os_fn *boot_fn;
可以看出它是一個boot_os_fn類型的函數指針。它的定義為
/* * Continue booting an OS image; caller already has: * - copied image header to global variable `header' * - checked header magic number, checksums (both header & image), * - verified image architecture (PPC) and type (KERNEL or MULTI), * - loaded (first part of) image to header load address, * - disabled interrupts. * * @flag: Flags indicating what to do (BOOTM_STATE_...) * @argc: Number of arguments. Note that the arguments are shifted down * so that 0 is the first argument not processed by U-Boot, and * argc is adjusted accordingly. This avoids confusion as to how * many arguments are available for the OS. * @images: Pointers to os/initrd/fdt * @return 1 on error. On success the OS boots so this function does * not return. */ typedef int boot_os_fn(int flag, int argc, char * const argv[], bootm_headers_t *images); extern boot_os_fn do_bootm_linux;
然后boot_fn在do_bootm函數中被賦值為:
boot_fn = bootm_os_get_boot_func(images->os.os);
bootm_os_get_boot_func函數返回的是boot_os[os],boot_os是一個函數指針數組,定義在common/bootm_os.c文件中:
static boot_os_fn *boot_os[] = { [IH_OS_U_BOOT] = do_bootm_standalone, #ifdef CONFIG_BOOTM_LINUX [IH_OS_LINUX] = do_bootm_linux, #endif #ifdef CONFIG_BOOTM_NETBSD [IH_OS_NETBSD] = do_bootm_netbsd, #endif #ifdef CONFIG_LYNXKDI [IH_OS_LYNXOS] = do_bootm_lynxkdi, #endif #ifdef CONFIG_BOOTM_RTEMS [IH_OS_RTEMS] = do_bootm_rtems, #endif #if defined(CONFIG_BOOTM_OSE) [IH_OS_OSE] = do_bootm_ose, #endif #if defined(CONFIG_BOOTM_PLAN9) [IH_OS_PLAN9] = do_bootm_plan9, #endif #if defined(CONFIG_BOOTM_VXWORKS) && \ (defined(CONFIG_PPC) || defined(CONFIG_ARM)) [IH_OS_VXWORKS] = do_bootm_vxworks, #endif #if defined(CONFIG_CMD_ELF) [IH_OS_QNX] = do_bootm_qnxelf, #endif #ifdef CONFIG_INTEGRITY [IH_OS_INTEGRITY] = do_bootm_integrity, #endif #ifdef CONFIG_BOOTM_OPENRTOS [IH_OS_OPENRTOS] = do_bootm_openrtos, #endif };
u-boot支持的操作操作系統類型宏在include/config_defaults.h文件中定義:
/* Support bootm-ing different OSes */ #define CONFIG_BOOTM_LINUX 1 #define CONFIG_BOOTM_NETBSD 1 #define CONFIG_BOOTM_PLAN9 1 #define CONFIG_BOOTM_RTEMS 1 #define CONFIG_BOOTM_VXWORKS 1
我們的內核是linux操作系統,因此boot_fn指向的是do_bootm_linux。
3.3.6 boot_selected_os
int boot_selected_os(int argc, char * const argv[], int state, bootm_headers_t *images, boot_os_fn *boot_fn) { arch_preboot_os(); boot_fn(state, argc, argv, images); /* Stand-alone may return when 'autostart' is 'no' */ if (images->os.type == IH_TYPE_STANDALONE || state == BOOTM_STATE_OS_FAKE_GO) /* We expect to return */ return 0; bootstage_error(BOOTSTAGE_ID_BOOT_OS_RETURNED); #ifdef DEBUG puts("\n## Control returned to monitor - resetting...\n"); #endif return BOOTM_ERR_RESET; }
函數最后一個參數就是我們傳入的do_bootm_linux函數,這里將會執行該函數,進行linux內核的啟動。
四、do_bootm_linux函數
do_bootm_linux函數在不同的硬件平台有不同的實現,我們這里查看的ARM架構的實現代碼,代碼位於arch/arm/lib/bootm.c文件:
/* Main Entry point for arm bootm implementation * * Modeled after the powerpc implementation * DIFFERENCE: Instead of calling prep and go at the end * they are called if subcommand is equal 0. */ int do_bootm_linux(int flag, int argc, char * const argv[], bootm_headers_t *images) { /* No need for those on ARM */ if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE) // 不執行 return -1; if (flag & BOOTM_STATE_OS_PREP) { // 執行 boot_prep_linux(images); return 0; } if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) { // 執行 boot_jump_linux(images, flag); return 0; } boot_prep_linux(images); boot_jump_linux(images, flag); return 0; }
我們一點一點分析這里函數的執行流程,其流程大致如下:

4.1 boot_prep_linux
首先先執行BOOTM_STATE_OS_PREP的代碼,這里調用的是boot_prep_linux,函數位於arch/arm/lib/bootm.c,這個函數跟內核傳遞參數有關系,為內核設置啟動參數,u-boot向內核傳遞參數就是在這里做的准備:
/* Subcommand: PREP */ static void boot_prep_linux(bootm_headers_t *images) { char *commandline = getenv("bootargs"); if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) { // 不執行 #ifdef CONFIG_OF_LIBFDT debug("using: FDT\n"); if (image_setup_linux(images)) { printf("FDT creation failed! hanging..."); hang(); } #endif } else if (BOOTM_ENABLE_TAGS) { debug("using: ATAGS\n"); setup_start_tag(gd->bd); if (BOOTM_ENABLE_SERIAL_TAG) // 不執行 setup_serial_tag(¶ms); if (BOOTM_ENABLE_CMDLINE_TAG) // 執行 setup_commandline_tag(gd->bd, commandline); if (BOOTM_ENABLE_REVISION_TAG) // 不執行 setup_revision_tag(¶ms); if (BOOTM_ENABLE_MEMORY_TAGS) // 執行 setup_memory_tags(gd->bd); if (BOOTM_ENABLE_INITRD_TAG) { //執行 /* * In boot_ramdisk_high(), it may relocate ramdisk to * a specified location. And set images->initrd_start & * images->initrd_end to relocated ramdisk's start/end * addresses. So use them instead of images->rd_start & * images->rd_end when possible. */ if (images->initrd_start && images->initrd_end) { setup_initrd_tag(gd->bd, images->initrd_start, images->initrd_end); } else if (images->rd_start && images->rd_end) { setup_initrd_tag(gd->bd, images->rd_start, images->rd_end); } } setup_board_tags(¶ms); setup_end_tag(gd->bd); } else { printf("FDT and ATAGS support not compiled in - hanging\n"); hang(); } }
這里先調用char *commandline = getenv("bootargs");從u-boot的環境變量中獲取到我們傳入的啟動參數,比如上一節我們提到的啟動參數:
noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0
如果使用了設備樹的話,這里將會調用image_setup_linux函數,該函數會在設備樹的choosen節點下添加bootargs屬性,其屬性的內容就是bootargs環境變量的值。
不過這里我們沒有使用設備樹,因此走的是設置各種tag的邏輯,關於這一塊具體內容可以看Mini2440之BootLoader簡單實現第3.3節內容,下面是一個tag在內存的分布示意圖,圖中地址只供參考,實際上:
- u-boot中gd->bd:指向地址空間為CONFIG_SYS_SDRAM_BASE + PHYS_SDRAM_1_SIZE - uboot大小 - 64kb - 4MB - sizeof(bd_t):因此第一個tag地址位於gd->bd->bi_boot_params,而不是圖中的0x30000100;
- kernel地址位於0x30000000,而不是圖中的0x30008000;

4.1.1 struct tag
struct tag的結構如下:
struct tag { struct tag_header hdr; union { struct tag_core core; struct tag_mem32 mem; struct tag_videotext videotext; struct tag_ramdisk ramdisk; struct tag_initrd initrd; struct tag_serialnr serialnr; struct tag_revision revision; struct tag_videolfb videolfb; struct tag_cmdline cmdline; /* * Acorn specific */ struct tag_acorn acorn; /* * DC21285 specific */ struct tag_memclk memclk; } u; };
/* The list ends with an ATAG_NONE node. */ #define ATAG_NONE 0x00000000 struct tag_header { u32 size; u32 tag; }; /* The list must start with an ATAG_CORE node */ #define ATAG_CORE 0x54410001 struct tag_core { u32 flags; /* bit 0 = read-only */ u32 pagesize; u32 rootdev; }; /* it is allowed to have multiple ATAG_MEM nodes */ #define ATAG_MEM 0x54410002 struct tag_mem32 { u32 size; u32 start; /* physical start address */ }; ...
tag結構包括tag頭hdr和各種類型的tag_* ;hdr來標志當前的tag是哪種類型。
4.1.2 setup_start_tag
調用setup_start_tag設置啟動要用到的 tag,在這里有一個全局靜態變量static struct tag *params;bd->bi_boot_params的值賦給它:
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); }
setup_start_tag是初始化了第一個tag,類型為tag_core。 最后調用tag_next跳到第一個tag末尾,為下一個tag賦值做准備。
4.1.3 setup_commandline_tag
在頭文件smdk2410.h配置了:
#define CONFIG_CMDLINE_TAG /* enable passing of ATAGs */ #define CONFIG_SETUP_MEMORY_TAGS #define CONFIG_INITRD_TAG
因此,會執行setup_commandline_tag、setup_memory_tags、setup_initrd_tag(與ramdisk相關,這里我們沒有使用)。
setup_commandline_tag代碼如下:
static void setup_commandline_tag(bd_t *bd, char *commandline) { char *p; if (!commandline) return; /* eat leading white space */ for (p = commandline; *p == ' '; p++); /* skip non-existent command lines so the kernel will still * use its default command line. */ 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); }
可以看出,這里調用了strcpy來賦值字符串,賦值的字符串正是,函數開頭使用getenv獲取的啟動參數bootargs字符串。
4.1.4 setup_memory_tags
static void setup_memory_tags(bd_t *bd) { int i; for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { // 1 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); } }
如果有多片內存ram,會循環為每一片的ram設置一個tag。
4.1.5 setup_board_tags
setup_board_tags,這個是板級實現,如果沒有實現則跳過:
__weak void setup_board_tags(struct tag **in_params) {}
static void setup_end_tag(bd_t *bd) { params->hdr.tag = ATAG_NONE; params->hdr.size = 0; }
由此可知我們的啟動參數params是一片連續的內存,這片內存有很多個tag,我們通過調用不同的程序來設置這些tag。
4.2 boot_jump_linux
然后執行BOOTM_STATE_OS_GO里的代碼,調用boot_jump_linux函數,位於arch/arm/lib/bootm.c.
該函數會將tag的首地址也就是gd->bd->bi_boot_params傳給內核,並跳轉到內核地址、啟動內核,讓內核解析這些tag。
/* Subcommand: GO */ static void boot_jump_linux(bootm_headers_t *images, int flag) { unsigned long machid = gd->bd->bi_arch_number; // 獲取機器碼 char *s; void (*kernel_entry)(int zero, int arch, uint params); // 內核入口函數 unsigned long r2; int fake = (flag & BOOTM_STATE_OS_FAKE_GO); kernel_entry = (void (*)(int, int, uint))images->ep; // 指定為內核入口地址 s = getenv("machid"); if (s) { if (strict_strtoul(s, 16, &machid) < 0) { debug("strict_strtoul failed!\n"); return; } 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(fake); if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) // 使能設備樹 r2 = (unsigned long)images->ft_addr; else r2 = gd->bd->bi_boot_params; if (!fake) { kernel_entry(0, machid, r2); } }
主要流程:
- 獲取gd->bd->bi_arch_number為machid,如果有env則用env的machid;
- 設置內核入口地址kernel_entry,將images->ep賦值給它作為跳轉到內核執行的入口;
- r2設置為為gd->bd->bi_boot_params,也就是tag啟動參數的起始地址;
- kernel_entry傳入其余相關參數並執行kernel_entry啟動;
關於machid的值可以參考內核解析U-boot傳入的machid,S3C2440默認傳入的是362。
參考文章
[2][uboot] uboot啟動kernel篇(二)——bootm跳轉到kernel的流程
[4]S5PV210-uboot解析(五)-do_bootm函數分析
[5]Linux內核鏡像格式
