1. 程式人生 > >Bootm(cmd_bootm.c)詳解

Bootm(cmd_bootm.c)詳解

一、在開始之前先說明一下bootm相關的東西。

1、首先說明一下,S3C2410架構下的bootm只對sdram中的核心映象檔案進行操作(好像AT91架構提供了一段從flash複製核心映象的程式碼,不過針對s3c2410架構就沒有這段程式碼,雖然可以在u-boot下新增這段程式碼,不過好像這個用處不大),所以請確保你的核心映象下載到sdram中,或者在bootcmd下把flash中的核心映象複製到sdram中。

2、-a引數後是核心的執行地址,-e引數後是入口地址。

3、

1)如果我們沒用mkimage對核心進行處理的話,那直接把核心下載到0x30008000再執行就行,核心會自解壓執行(不過核心執行需要一個tag來傳遞引數,而這個tag建議是由bootloader提供的,在u-boot下預設是由bootm命令建立的)。

2)如果使用mkimage生成核心映象檔案的話,會在核心的前頭加上了64byte的資訊,供建立tag之用。bootm命令會首先判斷bootm xxxx 這個指定的地址xxxx是否與-a指定的載入地址相同。

(1)如果不同的話會從這個地址開始提取出這個64byte的頭部,對其進行分析,然後把去掉頭部的核心複製到-a指定的load地址中去執行之

(2)如果相同的話那就讓其原封不同的放在那,但-e指定的入口地址會推後64byte,以跳過這64byte的頭部。

bootm 用於載入並啟動 U-Boot 能辨識的作業系統映像,即 bootm 載入的映像必須是用mkimage工具打過包的映像,bootm 不能啟動直接的核心映像,因為bootm必須從映像的頭獲取映像的一些資訊,比如作業系統的型別,映像是否壓縮,映像的載入地址和壓縮地址等。更詳細的映像頭資訊可以檢視mkimage工具的說明。而bootm的詳細用法可通過help bootm

獲得。

=> help bootm

bootm [addr [arg ...]]

    - boot application image stored in memory passing arguments 'arg ...'; when booting a Linux

kernel, 'arg' can be the address of an initrd image

Bootm 用於將核心映像載入到指定的地址,如果需要還要進行解壓映像。然後根據作業系統和體系結構的不同給核心傳遞不同的核心引數,最後啟動核心。

bootm 可以有兩個引數,第一個引數為核心映像的地址,它可以是 RAM 地址或者 Flash 地址。第二個引數是可選引數,即initrd映像的地址,當採用Ramdisk 作為根檔案系統時需要使用 bootm 的第二個引數。當需要載入 initrd 映像時,首先 U-Boot 把核心映像載入到指定地址,然後再把Ramdisk 映像載入到指定地址,同時把 Ramdisk 映像的大小和地址告知核心。

Bootm命令後U-Boot會將控制權交給Kernel。

common/cmd_bootm.c是bootm的命令實現程式碼,下面結合U-Boob引導核心啟動資訊來分析bootm程式碼,下面是我的開發板的啟動資訊:

U-Boot 2009.11 (Jun 25 2010 - 08:28:06)

DRAM:  64 MB

Flash:  2 MB

NAND:  64 MiB

In:    serial

Out:   serial

Err:   serial

Net:   dm9000

Hit any key to stop autoboot:  0

NAND read: device 0 offset 0x80000, size 0x500000

5242880 bytes read: OK

## Booting kernel from Legacy Image at 30008000 ...

   Image Name:   mark

   Created:      2010-07-02  15:37:07 UTC

   Image Type:   ARM Linux Kernel Image (uncompressed)

   Data Size:    1800624 Bytes =  1.7 MB

  Load Address: 30008000

   Entry Point:  30008040

   Verifying Checksum ... OK

   XIP Kernel Image ... OK

OK

Starting kernel ...

Uncompressing Linux.................................................................................................................. done, booting the kernel.

Linux version 2.6.29.1 ([email protected]) (gcc version 4.3.2 (Sourcery G++ Lite 2008q3-72) ) #64 Fri Jul 2 08:30:03 PDT 2010

CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c0007177

CPU: VIVT data cache, VIVT instruction cache

Machine: Study-S3C2440

Memory policy: ECC disabled, Data cache writeback

cmd_bootm.c

先分析bootm命令在U-Boot中是如何新增的:

U_BOOT_CMD(

       bootm,    CONFIG_SYS_MAXARGS,  1,    do_bootm,

       "boot application image from memory",

       "[addr [arg ...]]\n    - boot application image stored in memory\n"

       "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"

       "\t'arg' can be the address of an initrd image\n"

#if defined(CONFIG_OF_LIBFDT)

       "\tWhen booting a Linux kernel which requires a flat device-tree\n"

       "\ta third argument is required which is the address of the\n"

       "\tdevice-tree blob. To boot that kernel without an initrd image,\n"

       "\tuse a '-' for the second argument. If you do not pass a third\n"

       "\ta bd_info struct will be passed instead\n"

#endif

#if defined(CONFIG_FIT)

       "\t\nFor the new multi component uImage format (FIT) addresses\n"

       "\tmust be extened to include component or configuration unit name:\n"

       "\taddr:<subimg_uname> - direct component image specification\n"

       "\taddr#<conf_uname>   - configuration specification\n"

       "\tUse iminfo command to get the list of existing component\n"

       "\timages and configurations.\n"

#endif

       "\nSub-commands to do part of the bootm sequence.  The sub-commands "

       "must be\n"

       "issued in the order below (it's ok to not issue all sub-commands):\n"

       "\tstart [addr [arg ...]]\n"

       "\tloados  - load OS image\n"

#if defined(CONFIG_PPC) || defined(CONFIG_M68K) || defined(CONFIG_SPARC)

       "\tramdisk - relocate initrd, set env initrd_start/initrd_end\n"

#endif

#if defined(CONFIG_OF_LIBFDT)

       "\tfdt     - relocate flat device tree\n"

#endif

       "\tcmdline - OS specific command line processing/setup\n"

       "\tbdt     - OS specific bd_t processing\n"

       "\tprep    - OS specific prep before relocation or go\n"

       "\tgo      - start OS"

);

巨集U_BOOT_CMD用於定義U-Boot命令,它的定義如下:

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \

cmd_btl_t __u_boot_cmd ##name Struct_Section={#name,maxargs,rep,cmd,usage,help|

#define Struct Section __attribute__ ((unused,section (".u_boot_cmd")))

巨集U_BOOT_CMD展開後如下所示:

cmd_tlb_t __u_boot_cmd_bootm __a

見—嵌入式liinux應用開發完全手冊

do_bootm函式分析

static boot_os_fn *boot_os[] = {

#ifdef CONFIG_BOOTM_LINUX

       [IH_OS_LINUX] = do_bootm_linux,  //由於我使用的是linux核心,編譯時將會選擇該函式

#endif

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) //這裡的argc和argv就是bootm命令列引數

{

   boot_os_fn     *boot_fn;  //定義跳轉到作業系統執行的指標

if (bootm_start(cmdtp, flag, argc, argv))   //呼叫bootm_start函式

              return 1;

iflag = disable_interrupts();   //禁止中斷

ret = bootm_load_os(images.os, &load_end, 1);   //呼叫bootm_load_os函式

boot_fn = boot_os[images.os.os]; //根據OS型別去選擇啟動作業系統的函式

boot_fn(0, argc, argv, &images);  //執行do_bootm_linux函式

}

do_bootm函式中呼叫的幾個重要的函式是bootm_start、bootm_load_os、do_bootm_linux,下面一一分析。

bootm_start

static bootm_headers_t images;            /* pointers to os/initrd/fdt images */

static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

    void        *os_hdr;

    …

/* get kernel image header, start address and length */

       os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,

                     &images, &images.os.image_start, &images.os.image_len);

    ….

    /* get image parameters */

       switch (genimg_get_format (os_hdr)) {  //獲取image頭格式型別

       case IMAGE_FORMAT_LEGACY:   //老的image頭格式

              images.os.type = image_get_type (os_hdr);   //獲取image的型別

              images.os.comp = image_get_comp (os_hdr);  //獲取image的壓縮型別

              images.os.os = image_get_os (os_hdr);    //獲取image的OS型別

              images.os.end = image_get_image_end (os_hdr); //獲取image開始地址

              images.os.load = image_get_load (os_hdr);   //獲取image結束地址

              break;

/* find kernel entry point */

       if (images.legacy_hdr_valid) {

              images.ep = image_get_ep (&images.legacy_hdr_os_copy); //獲取image入口指標

}

ulong load_addr = CONFIG_SYS_LOAD_ADDR;      /* Default Load Address */

//尋找核心映像、校驗它的完整性和定位核心資料位置

static void *boot_get_kernel (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],

              bootm_headers_t *images, ulong *os_data, ulong *os_len)

{

    image_header_t      *hdr;

/* find out kernel image address */

       if (argc < 2) {  //bootm命令後面不帶引數時

              img_addr = load_addr; //使用預設載入地址

              debug ("*  kernel: default image load address = 0x%08lx\n",

                            load_addr);

switch (genimg_get_format ((void *)img_addr)) {  //獲取image頭型別

       case IMAGE_FORMAT_LEGACY:  //早期的image頭型別

              printf ("## Booting kernel from Legacy Image at %08lx ...\n", img_addr); //該列印資訊出現在U-Boot啟動資訊裡

              hdr = image_get_kernel (img_addr, images->verify);  //獲取image頭

}

//校驗早期格式核心映像

static image_header_t *image_get_kernel (ulong img_addr, int verify)

{

image_header_t *hdr = (image_header_t *)img_addr;

       if (!image_check_magic(hdr)) { //校驗MAGIC number

              puts ("Bad Magic Number\n");

              show_boot_progress (-1);

              return NULL;

       }

       show_boot_progress (2);

       if (!image_check_hcrc (hdr)) {  //image頭校驗

              puts ("Bad Header Checksum\n");

              show_boot_progress (-2);

              return NULL;

       }

       show_boot_progress (3);

       image_print_contents (hdr);  //該函式內將列印imag頭資訊“

Image Name:   mark

   Created:      2010-07-02  15:37:07 UTC

   Image Type:   ARM Linux Kernel Image (uncompressed)

   Data Size:    1800624 Bytes =  1.7 MB

   Load Address: 30008000

   Entry Point:  30008040

       if (verify) {

              puts ("   Verifying Checksum ... ");  //出現在U-Boot啟動資訊中

              if (!image_check_dcrc (hdr)) { //校驗image資料

                     printf ("Bad Data CRC\n");

                     show_boot_progress (-3);

                     return NULL;

              }

              puts ("OK\n");  //出現在U-Boot啟動資訊中

       }

       show_boot_progress (4);

       if (!image_check_target_arch (hdr)) {  //ARM、i386、MIPS等架構檢查

              printf ("Unsupported Architecture 0x%x\n", image_get_arch (hdr));

              show_boot_progress (-4);

              return NULL;

       }

       return hdr;

}

//列印image頭資訊

void image_print_contents (const void *ptr)

{

       const image_header_t *hdr = (const image_header_t *)ptr;

       const char *p;

#ifdef USE_HOSTCC

       p = "";

#else

       p = "   ";  //列印空格

#endif

    //列印image名字

       printf ("%sImage Name:   %.*s\n", p, IH_NMLEN, image_get_name (hdr));

#if defined(CONFIG_TIMESTAMP) || defined(CONFIG_CMD_DATE) || defined(USE_HOSTCC)

       printf ("%sCreated:      ", p);  //列印image建立日期

       genimg_print_time ((time_t)image_get_time (hdr));

#endif

       printf ("%sImage Type:   ", p);  //列印image型別

       image_print_type (hdr);

       printf ("%sData Size:    ", p);  //列印image大小

       genimg_print_size (image_get_data_size (hdr));

       printf ("%sLoad Address: %08x\n", p, image_get_load (hdr)); //列印image載入地址

       printf ("%sEntry Point: %08x\n", p, image_get_ep (hdr));  //列印image入口指標

       …

}

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;

#ifndef USE_HOSTCC

       image_info_t   os;          /* os image info */

       ulong             ep;          /* entry point of OS */

       ulong             rd_start, rd_end;/* ramdisk start/end */

       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_LOADOS   (0x00000002)

#define    BOOTM_STATE_RAMDISK (0x00000004)

#define    BOOTM_STATE_FDT          (0x00000008)

#define    BOOTM_STATE_OS_CMDLINE  (0x00000010)

#define    BOOTM_STATE_OS_BD_T  (0x00000020)

#define    BOOTM_STATE_OS_PREP  (0x00000040)

#define    BOOTM_STATE_OS_GO     (0x00000080)

       int           state;

#ifndef USE_HOSTCC

       struct lmb       lmb;        /* for memory mgmt */

#endif

} bootm_headers_t;

bootm_load_os

static int bootm_load_os(image_info_t os, ulong *load_end, int boot_progress)

{

uint8_t comp = os.comp;  //獲取image壓縮型別

ulong load = os.load;

ulong blob_start = os.start;  //os.start為bootm後面的地址,如果bootm後面沒有引數,則預設為u-boot中載入地址

ulong blob_end = os.end;

ulong image_start = os.image_start; //os.image_start為os.start+u-boot頭,即核心所處記憶體的起始地址

ulong image_len = os.image_len; //os.image_len為核心長度

uint unc_len = CONFIG_SYS_BOOTM_LEN;

switch (comp) {

       case IH_COMP_NONE:  //image未壓縮

              if (load == blob_start) {

                     printf ("   XIP %s ... ", type_name);  //如果U-Boot頭中的載入地址與bootm後面的地址相同,則列印包含“XIP”的字串,分析u-boot程式碼發現load就是u-boot頭中的載入地址,blob_start是bootm後面的地址。這種情況對應的是使用mkimage工具生成包含u-boot頭時載入地址為0x30008000,入口指標為0x30008040,tftp 30008000。此種情況下load並不等於ep,即u-boot頭中的載入地址和入口指標不相同。

              } else {

                     printf ("   Loading %s ... ", type_name);

    //如果U-Boot頭中的載入地址與bootm後面的地址不相同。此種情況對應的是使用mkimage工具生成的u-boot頭時載入地址和入口指標都為0x30008000,tftp。此種情況下load等於ep,即u-boot頭中的載入地址等於入口指標。

                     if (load != image_start) {  //因為u-boot頭中的載入地址等於入口指標,這裡等於是判斷u-boot頭中的載入地址或入口指標是否等於記憶體中核心映像所處的位置,因為如果入口指標等於核心所處的記憶體地址,那麼就不用下面的將記憶體中核心所處的位置的值複製到載入地址或入口指標處,然後從入口指標處執行核心程式。

                            memmove_wd ((void *)load,

                                          (void *)image_start, image_len, CHUNKSZ);

                     }

              }

              *load_end = load + image_len;

              puts("OK\n");

              break;

       case IH_COMP_GZIP:  //image採用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");

                     if (boot_progress)

                            show_boot_progress (-6);

                     return BOOTM_ERR_RESET;

              }

              *load_end = load + image_len;

              break;

#ifdef CONFIG_BZIP2

       case IH_COMP_BZIP2:  //image採用bzip2壓縮

              printf ("   Uncompressing %s ... ", type_name);

              /*

              * If we've got less than 4 MB of malloc() space,

              * use slower decompression algorithm which requires

              * at most 2300 KB of memory.

              */

              int i = BZ2_bzBuffToBuffDecompress ((char*)load,  //解壓壓縮過的映像

                                   &unc_len, (char *)image_start, image_len,

                                   CONFIG_SYS_MALLOC_LEN < (4096 * 1024), 0);

              if (i != BZ_OK) {

                     printf ("BUNZIP2: uncompress or overwrite error %d "

                            "- must RESET board to recover\n", i);

                     if (boot_progress)

                            show_boot_progress (-6);

                     return BOOTM_ERR_RESET;

              }

              *load_end = load + unc_len;

              break;

#endif /* CONFIG_BZIP2 */

#ifdef CONFIG_LZMA

       case IH_COMP_LZMA:  //image採用LZMA壓縮

              printf ("  Uncompressing %s ... ", type_name);

              int ret = lzmaBuffToBuffDecompress(  //解壓壓縮過的映像

                     (unsigned char *)load, &unc_len,

                     (unsigned char *)image_start, image_len);

              if (ret != SZ_OK) {

                     printf ("LZMA: uncompress or overwrite error %d "

                            "- must RESET board to recover\n", ret);

                     show_boot_progress (-6);

                     return BOOTM_ERR_RESET;

              }

              *load_end = load + unc_len;

              break;

#endif /* CONFIG_LZMA */

       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 (boot_progress)

              show_boot_progress (7);

       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;

}

}

關於bootm_load_os函式,個人的理解是之所以叫它是load os,是因為一些image的起始地址和載入地址相同的話就直接執行,但是一些image的起始地址和載入地址不相同的話,要把image搬運到載入地址開始處,然後執行。

do_bootm_linux

int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)

{

    bd_t *bd = gd->bd;

       char *s;

       int    machid = bd->bi_arch_number;

       void (*theKernel)(int zero, int arch, uint params);

#ifdef CONFIG_CMDLINE_TAG

       char *commandline = getenv ("bootargs"); //啟動引數標記,如果在include/configs/mini2440.h中定義CONFIG_CMDLINE_TAG巨集,則boottargs將會傳遞給核心

#endif

       if ((flag != 0) && (flag != BOOTM_STATE_OS_GO))

              return 1;

       theKernel = (void (*)(int, int, uint))images->ep;  // image入口地址賦值給theKernel指標

       s = getenv ("machid");

       if (s) {

              machid = simple_strtoul (s, NULL, 16);  //獲取機器ID

              printf ("Using machid 0x%x from environment\n", machid);

       }

       show_boot_progress (15);

       debug ("## Transferring control to Linux (at address %08lx) ...\n",

              (ulong) theKernel);

//以下巨集可以在include/configs/mini2440.h中定義,然後相應的引數便可傳遞給核心

#if defined (CONFIG_SETUP_MEMORY_TAGS) || \

    defined (CONFIG_CMDLINE_TAG) || \

    defined (CONFIG_INITRD_TAG) || \

    defined (CONFIG_SERIAL_TAG) || \

    defined (CONFIG_REVISION_TAG) || \

    defined (CONFIG_LCD) || \

    defined (CONFIG_VFD)

       setup_start_tag (bd);

#ifdef CONFIG_SERIAL_TAG

       setup_serial_tag (&params);

#endif

#ifdef CONFIG_REVISION_TAG

       setup_revision_tag (&params);

#endif

#ifdef CONFIG_SETUP_MEMORY_TAGS

       setup_memory_tags (bd);

#endif

#ifdef CONFIG_CMDLINE_TAG

       setup_commandline_tag (bd, commandline);

#endif

#ifdef CONFIG_INITRD_TAG

       if (images->rd_start && images->rd_end)

              setup_initrd_tag (bd, images->rd_start, images->rd_end);

#endif

#if defined (CONFIG_VFD) || defined (CONFIG_LCD)

       setup_videolfb_tag ((gd_t *) gd);

#endif

       setup_end_tag (bd);

#endif

       /* we assume that the kernel is in place */

       printf ("\nStarting kernel ...\n\n");  //在啟動核心之前的列印語句,將會出現在U-BOOT啟動資訊中。

#ifdef CONFIG_USB_DEVICE

       {

              extern void udc_disconnect (void);

              udc_disconnect ();

       }

#endif

       cleanup_before_linux ();

       theKernel (0, machid, bd->bi_boot_params);   //執行核心程式碼

       /* does not return */

       return 1;

}