zoukankan      html  css  js  c++  java
  • 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("
    gd->ram_size = %#lx
    "
                "gd->ram_top = %#lx
    "
                "gd->relocaddr = %#lx
    ",
                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
    "
                "gd->arch.tlb_addr = %#lx
    "
                "gd->relocaddr = %#lx
    ",
                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
    "
                "gd->relocaddr = %#lx
    "
                "gd->start_addr_sp = %#lx
    ",
                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
    "
                "gd->start_addr_sp = %#lx
    ",
                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, '', sizeof(bd_t));
            debug("Reserving %zu Bytes for Board Info at: %08lx
    ",
                  sizeof(bd_t), gd->start_addr_sp);
        }
    
        printf("gd->bd = %#lx
    "
                "gd->start_addr_sp = %#lx
    ",
                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
    ",
                sizeof(gd_t), gd->start_addr_sp);
    
        printf("gd->new_gd = %#lx
    "
                "gd->start_addr_sp = %#lx
    ",
                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
    ", 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
    "
                "gd->bd->bi_dram[0].size = %#lx
    ",
                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
    ", 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
    ");
            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
    ", gd->reloc_off);
        printf("Relocating to %#lx, new gd at %#lx, sp at %#lx
    ",
                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内存分配进行了简单的分析。

  • 相关阅读:
    第二阶段冲刺站立会议报告
    09软件工程读后感之三
    08软件工程读后感之二
    07软件工程读后感之一
    一个整数数组中最大字数组二
    返回一个二维数组最大联通子数组的和
    项目阶段总结
    大道至简阅读笔记之三
    大道至简阅读笔记二
    课堂设计
  • 原文地址:https://www.cnblogs.com/Cqlismy/p/12147411.html
Copyright © 2011-2022 走看看