U-Boot啓動引導內核分析

  

U-Boot啓動引導內核分析(一)

分類: ARM Linux移植 2010-09-13 20:49 57人閱讀 評論(0) 收藏 舉報

標籤:ARM   bootloader   U-Boot   代碼分析 抵岸科技

U-Boot啓動流程

大多數bootloader都分爲stage1和stage2兩大部分,u-boot也不例外。依賴於CPU體系結構的代碼(如設備初始化代碼等) 通常都放在stage1,且可以用彙編語言來實現,而stage2則通常用C語言來實現,這樣可以實現複雜的功能,而且有更好的可讀性和移植性。

u-boot啓動大致流程如圖1所示

                                                                                        圖 1

> Stage1

在flash中執行的引導代碼,也就是bootloader中的stage1,負責初始化硬件環境,把u-boot從flash加載到RAM中去,然後跳到lib_arm/board.c中的start_armboot中去執行。

u-boot的stage1代碼通常放在start.s文件中,它用彙編語言寫成,其主要代碼部分如下:

1)  定義入口由於一個可執行的Image必須有一個入口點,並且只能有一個全局入口,通常這個入口放在ROM(Flash) 的0x0地址,因此,必須通知編譯器以使其知道這個入口,該工作可通過修改連接器腳本來完成。

2)  設置異常向量(Exception Vector) 。

3)  設置CPU的速度、時鐘頻率及中斷控制寄存器。

4)  初始化內存控制器

5)  將ROM中的程序複製到RAM中。

6)  初始化堆棧

7)  轉到RAM中執行,該工作可使用指令ldr pc, _start_armboot來完成。

> Stage2

lib_arm/board.c中的start_armboot是C語言開始的函數,也是整個啓動代碼中C語言的主函數,同時還是整個u-boot(armboot) 的主函數,該函數主要流程分析如下:

void start_armboot (void)
{
    init_fnc_t **init_fnc_ptr;
    char *s;
#if !defined(CFG_NO_FLASH) || defined (CONFIG_VFD) || defined(CONFIG_LCD)
    ulong size;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
    unsigned long addr;
#endif

    /* Pointer is writable since we allocated a register for it */
    /* 給全局數據變量gd安排空間 */
    gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
   
    /* compiler optimization barrier needed for GCC >= 3.4 */
    __asm__ __volatile__("": : :"memory");

    /* 給板子數據變量gd->bd安排空間 */
    memset ((void*)gd, 0, sizeof (gd_t));
    gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
    memset (gd->bd, 0, sizeof (bd_t));

    monitor_flash_len = _bss_start - _armboot_start;
   
    /* 順序執行init_sequence數組中的初始化函數 */
    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0) {
            hang ();
        }
    }
    /*初始化函數列表:
    init_fnc_t *init_sequence[] = {
    cpu_init,                 /* basic cpu dependent setup */
#if defined(CONFIG_SKIP_RELOCATE_UBOOT)
    reloc_init,            /* Set the relocation done flag, must
                           do this AFTER cpu_init(), but as soon
                           as possible */
#endif
    board_init,            /* basic board dependent setup */
    interrupt_init,        /* set up exceptions */
    env_init,              /* initialize environment */
    init_baudrate,         /* initialze baudrate settings */
    serial_init,           /* serial communications setup */
    console_init_f,        /* stage 1 init of console */
    display_banner,        /* say that we are here */
#if defined(CONFIG_HW_WATCHDOG)
    hw_watchdog_init,      /* watchdog setup */
#endif
#if defined(CONFIG_DISPLAY_CPUINFO)
    print_cpuinfo,         /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
    checkboard,            /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
    init_func_i2c,
#endif
    dram_init,                /* configure available RAM banks */
    display_dram_config,
    NULL,
};
    */

    /* armboot_start is defined in the board-specific linker script */
    mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);

#if defined(CONFIG_CMD_NAND)
    puts ("NAND: ");
    /* NAND FLASH初始化 */
    nand_init();        /* go init the NAND */
#endif

    /* 重新定位環境變量 */
    env_relocate ();

#ifdef CONFIG_VFD
    /* must do this after the framebuffer is allocated */
    drv_vfd_init();
#endif /* CONFIG_VFD */

#ifdef CONFIG_SERIAL_MULTI
    /* 串口初始化 */
    serial_initialize();
#endif

    /* 從環境變量中獲取IP地址和MAC地址 */
    gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
    /* MAC Address */
    {
        int i;
        ulong reg;
        char *s, *e;
        char tmp[64];

        i = getenv_r ("ethaddr", tmp, sizeof (tmp));
        s = (i > 0) ? tmp : NULL;

        for (reg = 0; reg < 6; ++reg) {
            gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
            if (s)
                s = (*e) ? e + 1 : e;
        }

#ifdef CONFIG_HAS_ETH1
        i = getenv_r ("eth1addr", tmp, sizeof (tmp));
        s = (i > 0) ? tmp : NULL;

        for (reg = 0; reg < 6; ++reg) {
            gd->bd->bi_enet1addr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
            if (s)
                s = (*e) ? e + 1 : e;
        }
#endif
    }

    devices_init ();    /* get the devices list going. */

#ifdef CONFIG_CMC_PU2
    load_sernum_ethaddr ();
#endif /* CONFIG_CMC_PU2 */
   
    /* 跳轉表的初始化*/
    jumptable_init ();

    /* 控制檯的初始化 */
    console_init_r ();    /* fully init console as a device */

    /* IRQ中斷使能 */
    enable_interrupts ();

    /* 各種型號網絡設備的初始化 */
#ifdef CONFIG_DRIVER_TI_EMAC
extern void dm644x_eth_set_mac_addr (const u_int8_t *addr);
    if (getenv ("ethaddr")) {
        dm644x_eth_set_mac_addr(gd->bd->bi_enetaddr);
    }
#endif

#ifdef CONFIG_DRIVER_CS8900
    cs8900_get_enetaddr (gd->bd->bi_enetaddr);
#endif

    /* 通過環境變量初始化load_addr
默認定義ulong load_addr = CFG_LOAD_ADDR; */
    if ((s = getenv ("loadaddr")) != NULL) {
        load_addr = simple_strtoul (s, NULL, 16);
    }
    /* */
#if defined(CONFIG_CMD_NET)
    if ((s = getenv ("bootfile")) != NULL) {
        copy_filename (BootFile, s, sizeof (BootFile));
    }
#endif

#ifdef BOARD_LATE_INIT
    board_late_init ();
#endif
#if defined(CONFIG_CMD_NET)
#if defined(CONFIG_NET_MULTI)
    puts ("Net:   ");
#endif
    eth_initialize(gd->bd);
#if defined(CONFIG_RESET_PHY_R)
    debug ("Reset Ethernet PHY/n");
    reset_phy();
#endif
#endif
    /* 循環不斷地執行main_loop ()函數
main_loop ()主要處理用戶命令 */
    for (;;) {
        main_loop ();
    }
}

整個u-boot的執行就進入等待用戶輸入命令,解析並執行命令的死循環中。

 

U-Boot啓動引導內核分析(二)

分類: ARM Linux移植 2010-09-13 20:50 92人閱讀 評論(0) 收藏 舉報

標籤:ARM   bootloader   U-Boot   代碼分析 抵岸科技

 

 

也許細心的你會問:我在用UBoot的時候並沒有直接進入用戶命令界面呀,而是在倒計時結束後自動引導kernel。這是怎麼回事呢?

在 main_loop()函數當中有如下一段代碼:

#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
      •
      •
      •
s = getenv ("bootcmd");
        /*獲取bootcmd 的內容*/
        /*bootcmd=nand read 0x22000000 0xB0000 0x200000; bootm */
      •
      •
# ifndef CFG_HUSH_PARSER
        run_command (s, 0);
        /*運行s包含的命令*/
        /*運行nand read 0x22000000 0xB0000 0x200000表示將NANDFLASH
      0xB0000處數據讀取放於0x22000000處,讀取長度爲0x200000
        */
        /*運行bootm命令,引導內核啓動*/
# else
        parse_string_outer(s, FLAG_PARSE_SEMICOLON |
                    FLAG_EXIT_FROM_LOOP);
# endif
      •
      •
      •
#endif    /* CONFIG_BOOTDELAY */

bootm命令是什麼?它是怎樣引導內核的?

要知道想解決這個問題,就要分析common/cmd_bootm.c中的函數do_bootm,因爲引導kernel就是bootm這條命令的工作,do_bootm是命令bootm的執行函數。

現在我們來分析一下common/cmd_bootm.c中的函數do_bootm,這是bootm命令的處理函數。

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    ulong        iflag;
    const char    *type_name;
    uint        unc_len = CFG_BOOTM_LEN;
    uint8_t        comp, type, os;

    void        *os_hdr;
    ulong        os_data, os_len;
    ulong        image_start, image_end;
    ulong        load_start, load_end;
    ulong        mem_start;
    phys_size_t    mem_size;

    struct lmb lmb;

    memset ((void *)&images, 0, sizeof (images));
    images.verify = getenv_yesno ("verify");
    images.lmb = &lmb;

    lmb_init(&lmb);

    mem_start = getenv_bootm_low();
    mem_size = getenv_bootm_size();

    lmb_add(&lmb, (phys_addr_t)mem_start, mem_size);

    board_lmb_reserve(&lmb);

    /* get kernel image header, start address and length */
    /* 獲取內核鏡像頭信息 */
    /* 打印 “## Booting kernel from Legacy Image at 22000000 ...
           Image Name:   Linux-2.6.30
           Image Type:   ARM Linux Kernel Image (uncompressed)
           Data Size:    1507760 Bytes = 1.4 MB
           Load Address: 20008000
           Entry Point: 20008000
           Verifying Checksum ... OK”*/
    os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,
            &images, &os_data, &os_len);
    if (os_len == 0) {
        puts ("ERROR: can't get kernel image!/n");
        return 1;
    }

    /* get image parameters */
    /* 獲取內核鏡像格式 */
    switch (genimg_get_format (os_hdr)) {
    case IMAGE_FORMAT_LEGACY:
        /* 獲取內核鏡像參數 */
        type = image_get_type (os_hdr);
        comp = image_get_comp (os_hdr);
        os = image_get_os (os_hdr);

        image_end = image_get_image_end (os_hdr);
        load_start = image_get_load (os_hdr);
        break;
    }

    image_start = (ulong)os_hdr;
    load_end = 0;
    type_name = genimg_get_type_name (type);

    /* 禁止所有中斷 */
    iflag = disable_interrupts();

#ifdef CONFIG_AMIGAONEG3SE
    /*
     * We've possible left the caches enabled during
     * bios emulation, so turn them off again
     */
    icache_disable();
    invalidate_l1_instruction_cache();
    flush_data_cache();
    dcache_disable();
#endif

    switch (comp) {
    case IH_COMP_NONE:
        /* 加載內核鏡像 */
        /* 打印“Loading Kernel Image ... OK” */
        if (load_start == (ulong)os_hdr) {
            printf ("   XIP %s ... ", type_name);
        } else {
            printf ("   Loading %s ... ", type_name);

            memmove_wd ((void *)load_start,
                   (void *)os_data, os_len, CHUNKSZ);
        }
        load_end = load_start + os_len;
        puts("OK/n");
        break;
    }
    puts ("OK/n");
    debug ("   kernel loaded at 0x%08lx, end = 0x%08lx/n", load_start, load_end);
    show_boot_progress (7);
   
    /* 加載錯誤 */
    if ((load_start < image_end) && (load_end > image_start)) {
        debug ("image_start = 0x%lX, image_end = 0x%lx/n", image_start, image_end);
        debug ("load_start = 0x%lx, load_end = 0x%lx/n", load_start, load_end);

        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");
        } else {
            puts ("ERROR: new format image overwritten - "
                "must RESET the board to recover/n");
            show_boot_progress (-113);
            do_reset (cmdtp, flag, argc, argv);
        }
    }

    show_boot_progress (8);

    lmb_reserve(&lmb, load_start, (load_end - load_start));

    switch (os) {
    default:            /* handled by (original) Linux case */
    case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
        fixup_silent_linux();
#endif
        /* 引導內核啓動函數 */
        do_bootm_linux (cmdtp, flag, argc, argv, &images);
        break;
    }

    show_boot_progress (-9);
#ifdef DEBUG
    puts ("/n## Control returned to monitor - resetting.../n");
    do_reset (cmdtp, flag, argc, argv);
#endif
    if (iflag)
        enable_interrupts();

    return 1;
}

至此do_bootm函數完成引導內核前的準備任務了。引導內核啓動函數將由do_bootm_linux()函數執行。

 

U-Boot啓動引導內核分析(三)

分類: ARM Linux移植 2010-09-13 20:53 47人閱讀 評論(0) 收藏 舉報

標籤:ARM   bootloader   U-Boot   代碼分析 抵岸科技

do_bootm_linux()函數位於lib_arm/Bootm.c文件中,主要流程分析如下:

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
             bootm_headers_t *images)
{
    ulong    initrd_start, initrd_end;
    ulong    ep = 0;
    bd_t    *bd = gd->bd;
    char    *s;
    int    machid = bd->bi_arch_number;
    void    (*theKernel)(int zero, int arch, uint params);
    int    ret;

#ifdef CONFIG_CMDLINE_TAG
    char *commandline = getenv ("bootargs");
#endif

    /* find kernel entry point */
    if (images->legacy_hdr_valid) {
        ep = image_get_ep (&images->legacy_hdr_os_copy);
#if defined(CONFIG_FIT)
    } else if (images->fit_uname_os) {
        ret = fit_image_get_entry (images->fit_hdr_os,
                    images->fit_noffset_os, &ep);
        if (ret) {
            puts ("Can't get entry point property!/n");
            goto error;
        }
#endif
    } else {
        puts ("Could not find kernel entry point!/n");
        goto error;
    }
    theKernel = (void (*)(int, int, uint))ep;

    s = getenv ("machid");
    if (s) {
        machid = simple_strtoul (s, NULL, 16);
        printf ("Using machid 0x%x from environment/n", machid);
    }

    ret = boot_get_ramdisk (argc, argv, images, IH_ARCH_ARM,
            &initrd_start, &initrd_end);
    if (ret)
        goto error;

    show_boot_progress (15);

    debug ("## Transferring control to Linux (at address %08lx) .../n",
           (ulong) theKernel);

#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)
    /* 初始化TAG結構體開始 */
    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
    /* 設置RAM參數 */
    setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
    setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
    if (initrd_start && initrd_end)
        setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
    setup_videolfb_tag ((gd_t *) gd);
#endif
    /* 初始化TAG結構體結束 */
    setup_end_tag (bd);
#endif

    /* we assume that the kernel is in place */
    printf ("/nStarting kernel .../n/n");

#ifdef CONFIG_USB_DEVICE
    {
        extern void udc_disconnect (void);
        udc_disconnect ();
    }
#endif

    cleanup_before_linux ();

    /* 將控制權交給內核,讓內核自解壓啓動 */
    /* 傳給KERNEL的參數:(struct tag *)bd->bi_boot_params */
    /* 在board/atmel/at91sam9g20ek/At91sam9g20ek.c文件中有如下定義:
    gd->bd->bi_boot_params = PHYS_SDRAM + 0x100;(PHYS_SDRAM = 0x20000000)
    */
    theKernel (0, machid, bd->bi_boot_params);
    /* does not return */
    return;

error:
    do_reset (cmdtp, flag, argc, argv);
    return;
}

 

文件歷史記錄

版本            編制                    日期                        更改內容

-----------------------------------------------------------------------------------

V1.0           抵岸科技              2010-7-20              首發

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