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内存分配进行了简单的分析。

posted @ 2020-01-03 23:16  liangliangge  阅读(2232)  评论(0编辑  收藏  举报