Uboot啟動流程分析(四)


1、前言

在前面的文章《Uboot啟動流程分析(三)》中,鏈接如下:

https://www.cnblogs.com/Cqlismy/p/12006287.html

已經對init_sequence_f前半部分函數進行了簡單分析,前半部分主要是對調試串口終端進行了初始化,以及輸出了一些必要的字符串,接下來,本篇文章將對init_sequence_f后半部分函數進行分析,后半部分主要是對DRAM的內存進行分配,並對gd的相關結構體成員進行初始化,在init_sequence_f函數初始化列表中,已經執行到了setup_dest_addr()函數,后半部分初始化列表如下:

/* 初始化序列函數數組 */
static init_fnc_t init_sequence_f[] = {

  ...
  ...
/* * Now that we have DRAM mapped and working, we can * relocate the code and continue running from DRAM. * * Reserve memory at end of RAM for (top down in that order): * - area that won't get touched by U-Boot and Linux (optional) * - kernel log buffer * - protected RAM * - LCD framebuffer * - monitor code * - board info struct */ setup_dest_addr,/* 設置目的地址(gd->ram_size,gd->ram_top,gd->relocaddr) */ reserve_round_4k, /* 對gd->relocaddr做4K對齊 */ #if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \ defined(CONFIG_ARM) reserve_mmu, /* 留出mmu的TLB表位置 */ #endif reserve_trace, /* 留出ddr調試追蹤的內存 */ #if !defined(CONFIG_BLACKFIN) reserve_uboot, /* 留出重定位uboot占用的位置 */ #endif #ifndef CONFIG_SPL_BUILD reserve_malloc, /* 留出malloc的內存位置和ENV的內存大小 */ reserve_board, /* 留出bd所占用的內存大小(80字節) */ #endif setup_machine, /* 對於i.mx6ul該函數無效 */ reserve_global_data, /* 留出gd_t結構的內存大小(248字節) */ reserve_fdt, /* 留出設備樹的內存大小(i.mx6ul沒有用) */ reserve_arch, /* 空函數 */ reserve_stacks, /* 留出棧空間(16字節)並做16字節對齊 */ setup_dram_config, /* 設置DRAM的信息 */ show_dram_config, /* 顯示DRAM的位置 */ display_new_sp, /* 顯示新的sp位置 */ INIT_FUNC_WATCHDOG_RESET reloc_fdt, /* 重定位fdt(沒有用) */ setup_reloc, /* 設置gd結構體的一些其他成員 */ NULL, };

 

2、board_init_f函數

程序將調用setup_dest_addr()函數,該函數的定義如下:

static int setup_dest_addr(void)
{
    /*
     * Subtract specified amount of memory to hide so that it won't
     * get "touched" at all by U-Boot. By fixing up gd->ram_size
     * the Linux kernel should now get passed the now "corrected"
     * memory size and won't touch it either. This has been used
     * by arch/powerpc exclusively. Now ARMv8 takes advantage of
     * thie mechanism. If memory is split into banks, addresses
     * need to be calculated.
     */
    gd->ram_size = board_reserve_ram_top(gd->ram_size);

#ifdef CONFIG_SYS_SDRAM_BASE
    gd->ram_top = CONFIG_SYS_SDRAM_BASE;
#endif
    gd->ram_top += get_effective_memsize();
    gd->ram_top = board_get_usable_ram_top(gd->mon_len);
    gd->relocaddr = gd->ram_top;

    /* 將gd->ram_size、gd->ram_top、gd->relocaddr的值打印 */
    printf("\ngd->ram_size = %#lx\n"
            "gd->ram_top = %#lx\n"
            "gd->relocaddr = %#lx\n",
            gd->ram_size, gd->ram_top, gd->relocaddr);

    return 0;
}

函數setup_dest_addr()用來設置目的地址,設置gd結構體中的ram_size成員、ram_top成員和relocaddr成員,我們可以使用printf()函數打印輸出查看這些值,其中ram_size表示DRAM的大小,ram_top表示DRAM的最終地址,relocaddr表示uboot重定位的地址,添加打印輸出后,重新燒寫uboot,將板子上電,該函數調用后,輸出的值如下:

從圖中可以看到,DRAM的大小為256MB,DRAM的最終地址為0x90000000,而此時uboot重定位的地址被設置為在DRAM的最終地址0x90000000。

程序繼續往下運行,調用函數reserve_round_4k(),該函數的定義如下:

/* Round memory pointer down to next 4 kB limit */
static int reserve_round_4k(void)
{
    gd->relocaddr &= ~(4096 - 1);
    return 0;
}

該函數是gd結構體中的relocaddr地址做4KB字節對齊,在setup_dest_addr()函數調用后,relocaddr的值被設置為0x90000000,已經是4KB字節對齊了,因此,該函數調用后,值不變,還是0x90000000。

接下來,調用函數reserve_mmu(),該函數的定義如下:

#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \
        defined(CONFIG_ARM)
static int reserve_mmu(void)
{
    /* reserve TLB table */
    gd->arch.tlb_size = PGTABLE_SIZE;
    gd->relocaddr -= gd->arch.tlb_size;

    /* round down to next 64 kB limit */
    gd->relocaddr &= ~(0x10000 - 1); /* 對gd->relocaddr做64KB字節對齊 */

    gd->arch.tlb_addr = gd->relocaddr;

    printf("gd->arch.tlb_size = %#lx\n"
            "gd->arch.tlb_addr = %#lx\n"
            "gd->relocaddr = %#lx\n",
            gd->arch.tlb_size, gd->arch.tlb_addr, gd->relocaddr);
    
    return 0;
}
#endif

該函數用於留出MMU的TLB表位置,留出該塊內存后,對relocaddr地址做64KB字節對齊,調試輸出如下所示:

從圖中可以看到,MMU的TLB大小為0x4000,TLB的首地址為0x8fff0000,此時的relocaddr的值和TLB的首地址一樣,也是0x8fff0000。

接下來,調用函數reserve_trace(),該函數用於留出uboot跟蹤內存調試的地方,由於i.mx6ul的相關配置文件中沒有定義相關宏,因此並沒有分配出此塊內存。

程序繼續往下運行,將調用函數reserve_uboot(),該函數的定義如下所示:

static int reserve_uboot(void)
{
    /*
     * reserve memory for U-Boot code, data & bss
     * round down to next 4 kB limit
     */
    gd->relocaddr -= gd->mon_len;
    gd->relocaddr &= ~(4096 - 1);

    gd->start_addr_sp = gd->relocaddr;

    printf("gd->mon_len = %#lx\n"
            "gd->relocaddr = %#lx\n"
            "gd->start_addr_sp = %#lx\n",
            gd->mon_len, gd->relocaddr, gd->start_addr_sp);

    return 0;
}

函數reserve_uboot()用於留出重定位后uboot代碼所占用的內存位置,uboot所占用的內存位置空間由gd結構體中mon_len成員變量指定,調試輸出如下所示:

從上圖中可以看出,uboot重定位后代碼所占用的內存大小為0xb4bb4,分配后uboot重定位內存位置后,對relocaddr的值做4KB字節對齊,此時relocaddr的值為0x8ff3b000,同時重新將gd結構體中的start_addr_sp成員變量設置和此時的relocaddr的值一樣,也就是0x8ff3b000。

由於i.mx6ul中的配置文件中沒有定義宏CONFIG_SPL_BUILD,因此,接下來將會調用函數reserve_malloc(),該函數的定義如下:

/* reserve memory for malloc() area */
static int reserve_malloc(void)
{
    gd->start_addr_sp = gd->start_addr_sp - TOTAL_MALLOC_LEN;

    printf("TOTAL_MALLOC_LEN = %#lx\n"
            "gd->start_addr_sp = %#lx\n",
            TOTAL_MALLOC_LEN, gd->start_addr_sp);
    
    return 0;
}

對於TOTAL_MALLOC_LEN宏的定義如下:

/* 對於imx6ul從nand flash啟動 */

#define CONFIG_ENV_SECT_SIZE        (128 << 10)    /* 128KB */
#define CONFIG_ENV_SIZE            CONFIG_ENV_SECT_SIZE

/* Size of malloc() pool */
#define CONFIG_SYS_MALLOC_LEN        (16 * SZ_1M)  /* 16MB */
#define TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE)

從宏定義可以計算出TOTAL_MALLOC_LEN = 16MB + 128KB = 0x1020000,調試輸出如下:

從上面的分析和輸出可以知道,函數reserve_malloc()分配出了malloc的內存區域,分配的大小為0x1020000,包含了16MB的malloc內存池以及128KB的環境變量內存區域,malloc內存分配完成后,重新賦值gd結構體中的start_addr_sp成員變量,此時的start_addr_sp值為0x8ef1b000。

接下來,繼續調用函數reserve_board(),該函數的定義如下:

/* (permanently) allocate a Board Info struct */
static int reserve_board(void)
{
    if (!gd->bd) {
        gd->start_addr_sp -= sizeof(bd_t);
        gd->bd = (bd_t *)map_sysmem(gd->start_addr_sp, sizeof(bd_t));
        memset(gd->bd, '\0', sizeof(bd_t));
        debug("Reserving %zu Bytes for Board Info at: %08lx\n",
              sizeof(bd_t), gd->start_addr_sp);
    }

    printf("gd->bd = %#lx\n"
            "gd->start_addr_sp = %#lx\n",
            gd->bd, gd->start_addr_sp);
    
    return 0;
}

函數reserve_board()用於留出bd_t結構體的內存區域,該內存區域的大小為80Bytes,bd_t結構體用於描述Board的一些信息,對gd結構體中的bd成員重新賦值后,並對bd_t結構體的內存進行清零操作,調試輸出如下所示:

從上圖可以知道,此時gd結構體中的bd和start_addr_sp成員變量的值都為0x8ef1afb0。

接下來,調用函數setup_machine()設置機器ID,在以前的Linux版本,啟動的時候會和這個機器ID進行匹配,如果匹配Linux就會正常啟動,但是,目前基於imx6ul的BSP都是基於設備樹的了,因此該函數並不會有效,直接返回0。

程序繼續往下運行,將會調用reserve_global_data()函數,該函數的定義如下:

static int reserve_global_data(void)
{
    gd->start_addr_sp -= sizeof(gd_t);
    gd->new_gd = (gd_t *)map_sysmem(gd->start_addr_sp, sizeof(gd_t));
    debug("Reserving %zu Bytes for Global Data at: %08lx\n",
            sizeof(gd_t), gd->start_addr_sp);

    printf("gd->new_gd = %#lx\n"
            "gd->start_addr_sp = %#lx\n",
            gd->new_gd, gd->start_addr_sp);
    
    return 0;
}

函數reserve_global_data()用於留出gd_t結構體的內存區域,分配的內存大小為248Bytes,分配完成后,並對gd結構體中的new_gd成員變量重新賦值,表示新的gd_t結構體的內存地址,調試輸出如下:

從上圖可以看到,將新的gd_t結構體內存分配后,此時的gd結構體的new_gd成員變量和start_addr_sp成員變量的值都為0x8ef1aeb8。

接下來,程序將會調用reserve_fdt()函數,留出設備樹的內存區域,但是imx6ul的uboot中並沒有用到設備樹,因此該函數無效,並沒有內存分配。

繼續往下運行,將會調用reserve_arch()函數,對於imx6ul,該函數為空函數,沒有內存分配,直接返回0。

程序繼續運行,接下來調用reserve_stacks()函數,該函數的定義如下:

static int reserve_stacks(void)
{
    /* make stack pointer 16-byte aligned */
    gd->start_addr_sp -= 16;
    gd->start_addr_sp &= ~0xf;

    printf("gd->start_addr_sp = %#lx\n", gd->start_addr_sp);

    /*
     * let the architecture-specific code tailor gd->start_addr_sp and
     * gd->irq_sp
     */    
    return arch_reserve_stacks();
}

函數reserve_stacks()用於留出棧區域的內存大小,大小為16Bytes,內存分配后,對gd結構體中的start_addr_sp成員變量做16B字節對齊,arch_reserve_stacks()函數是irq相關的東西,對於imx6ul並沒有分配內存空間,此函數無效,棧分配和16B字節對齊后,調試輸出如下:

從上圖可以看到,此時gd結構體中的start_addr_sp成員變量值為0x8ef1ae90。

完成了內存棧的分配的后,接下來調用setup_dram_config()設置DRAM的一些配置信息,主要是設置DRAM的起始地址和大小,該函數的定義如下:

static int setup_dram_config(void)
{
    /* Ram is board specific, so move it to board code ... */
    dram_init_banksize();

    printf("gd->bd->bi_dram[0].start = %#lx\n"
            "gd->bd->bi_dram[0].size = %#lx\n",
            gd->bd->bi_dram[0].start, gd->bd->bi_dram[0].size);

    return 0;
}

函數調用后,輸出如下:

從上圖中可以看到,DRAM的起始地址為0x80000000,大小為0x10000000,也就是256MB。

接下來,調用show_dram_config()函數顯示DRAM的配置信息,該函數調用后,將會將的DRAM的大小以字符串的形式輸出,也就是輸出"256MB"。

程序繼續往下運行,調用display_new_sp()函數顯示新的sp指針的值,該函數的定義如下:

static int display_new_sp(void)
{
    printf("New Stack Pointer is: %#lx\n", gd->start_addr_sp);

    return 0;
}

其實,就是直接輸出gd結構體中start_addr_sp成員變量的值,輸出如下:

New Stack Pointer is: 0x8ef1ae90

該值,和前面棧分配后的值一樣,該值就是新的sp指針值,為0x8ef1ae90。

接下來,調用函數INIT_FUNC_WATCHDOG_RESET和reloc_fdt(),作用是復位看門狗和重定位設備樹,對於imx6ul這兩個都是空函數,沒有用到設備樹。

程序繼續往下運行,將調用函數setup_reloc()設置gd結構體中一些成員變量,是和重定位相關的,該函數定義如下:

static int setup_reloc(void)
{
    if (gd->flags & GD_FLG_SKIP_RELOC) {
        debug("Skipping relocation due to flag\n");
        return 0;
    }

#ifdef CONFIG_SYS_TEXT_BASE
    gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;
#endif
    memcpy(gd->new_gd, (char *)gd, sizeof(gd_t));

    printf("Relocation Offset is: %#lx\n", gd->reloc_off);
    printf("Relocating to %#lx, new gd at %#lx, sp at %#lx\n",
            gd->relocaddr, (ulong)map_to_sysmem(gd->new_gd),
            gd->start_addr_sp);

    return 0;
}

對於CONFIG_SYS_TEXT_BASE宏的定義在include/configs/mx6_common.h配置文件中,該值為:

#define CONFIG_SYS_TEXT_BASE    0x87800000

該函數的輸出如下所示:

從上圖可以看到,uboot重定位的偏移為0x873b000,重定位的新地址為gd結構中的成員變量relocaddr,也就是0x8ff3b000,此外,新的gd_t結構體首地址為0x8ef1aeb8,新的sp指針在0x8ef1ae90,和上面棧分配時候的內存地址一樣。

到這里,board_init_f()函數就執行完成了,DRAM的最后內存配示意圖如下所示:

DRAM內存分配完成后,接下來就是開始uboot的重定位了。

 

3、小結

本篇文章主要是對board_init_f()函數的后半部分進行分析,並對uboot重定位之前的DRAM內存分配進行了簡單的分析。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM