uboot内存重定向详解--Apple的学习笔记

1.前言

对于uboot中的重定向,之前大概了解,因为不是我要学习的重点,所以没有去深度思考。那么本次移植uboot2016.11到TQ2440的过程中遇到了些问题,让我怀疑是重定位等导致的,让我绕了弯路,原因就是我对从定向了解不清楚导致的。所以我准备对TQ2440 uboot2016.11的重定位详细了解下。另外,所有的芯片的uboot的重定位思路类似。

2.为什么uboot c代码中要有重定向?

网上找了资料后,我自己理解了下。nandflash需要重定向我理解。首先从norflash下载是否没有重定向?查看了下debug log,依然在ram这块内容是有重定向的。由于log中显示的是ram重定向,code没有重定向。那么又是重定向到哪里呢?通过地址0x3xxxxxxx可以看出是重定向到外部sdram。让我一下子关注了ram的大小问题。因为单片机的ram很小也不用重定向,设置好bss和data的空间,再分配栈空间即可使用。我通过jlink下载uboot到norflash,不是和操作单片机一样了,为什么这块arm芯片要重定向到外部ram。难道是觉得内部ram比较小,不够用。按我的猜测带着问题看了s3c2440的spec,原来S3C2440只有片内4KB的RAM,哈哈,我猜测对了,norflash中ram相关内容在uboot c代码中需要重定向的原因是s3c2440芯片的内部ram不够用。
那么从norflash驱动,然后直接把uboot下载到sdram中运行,为什么uboot代码还要重定向?这个是在网上搜索的,我觉得说的有道理,uboot在sdram中依然要重定向的原因是优化内存空间,使地址连续。你想呀,代码运行在哪个地址,哪个地址是堆和栈的空间,一定要心理有数呀,那么统一排布就是最好的解决方法。我理解另一个目的就是保持内存分布的一致性。

3.TQ2440中重定向后的内存分布图

这个图我是怎么画出来的呢?这些地址数据都是我从debug log中找出来的,然后按地址大小绘制的。debug log数据可以参考我昨天的博客内容TQ2440成功移植uboot2016.11解决Using dm9000 device卡死问题--Apple的学习笔记这样的话,我首先对内存分布有了一个全局观。这属于我的个人分格,喜欢先了解大框架,再了解具体细节。画个图还蛮花费时间的哦,不过画出来还是很有成就感的,因为看图讲故事比较形象。

image.png

4.uboot2016.11的重定向代码分析

通过自己绘制了图片,已经可以直观的了解了内存分布,为什么我还要去分析代码吗?因为上图只是大框架,我通过看代码,还要看看有什么细节漏了。另外,通过就是熟悉下这部分的源码,了解下这些base地址是从哪里来的。
那么代码怎么去分析呢?很简单,通过log信息去搜索源码进行定位分析。
4.1) 搜索"TLB table from",然后我已经添加了注释,自己看吧。如下函数的作用就是将relocaddr - 16k并且对齐后的地址赋值给了TBL。也就是通过这一点点的源码,我已经分析出了内存分布图中的地址2和3之间的差距是16K。但是gd->relocaddr地址从源码中哪里来的呢?我觉得我应该从头开始分析也就是从地址1开始分析会比较好。

static int reserve_mmu(void)
{
    /* reserve TLB table */
    gd->arch.tlb_size = PGTABLE_SIZE;   // 16K
    gd->relocaddr -= gd->arch.tlb_size; // relocaddr = 当前的relocaddr - 16K

    /* round down to next 64 kB limit */
    gd->relocaddr &= ~(0x10000 - 1);    // 将地址的后4个值变为0000。可以理解为目的地址对齐

    gd->arch.tlb_addr = gd->relocaddr;  // 将relocaddr - 16k并且对齐后的地址赋值给了TBL
    debug("TLB table from %08lx to %08lx\n", gd->arch.tlb_addr,
          gd->arch.tlb_addr + gd->arch.tlb_size);

#ifdef CONFIG_SYS_MEM_RESERVE_SECURE
    /*
     * Record allocated tlb_addr in case gd->tlb_addr to be overwritten
     * with location within secure ram.
     */
    gd->arch.tlb_allocated = gd->arch.tlb_addr;
#endif

    return 0;
}

====
#if defined(CONFIG_ARMV7_LPAE) && !defined(PGTABLE_SIZE)
#define PGTABLE_SIZE        (4096 * 5)
#elif !defined(PGTABLE_SIZE)
#define PGTABLE_SIZE        (4096 * 4)  // 16K
#endif

4.2)搜索"Ram top"关键字,如下分析后,地址1的来历也弄清楚了。relocaddr内容一开始保存的是ram top的地址。那么接下来会调用哪个函数对relocaddr地址进行赋值呢?当然是要看intial call中的顺序啦~不要问我为什么,因为我一开始已经先了解了代码框架及函数调用顺序。在init_sequence_f数组中搜索setup_dest_addr,然后看它的下一个函数,依次分析

static int setup_dest_addr(void)
{
    ......
#ifdef CONFIG_SYS_SDRAM_BASE
    gd->ram_top = CONFIG_SYS_SDRAM_BASE;
#endif
    gd->ram_top += get_effective_memsize();             // 0x30000000+64M的值赋给ram_top
    gd->ram_top = board_get_usable_ram_top(gd->mon_len);// 对2440无作用,直接返回gd->ram_top
    gd->relocaddr = gd->ram_top;                        // 为gd->relocaddr赋值0x34000000
    debug("Ram top: %08lX\n", (ulong)gd->ram_top);
    ......
}

ram size的来历是board配置文件中的PHYS_SDRAM_1_SIZE,也就是64M。gd->ram_top就是配置文件中的CONFIG_SYS_SDRAM_BASE,也就是0x30000000。

int dram_init(void)
{
    /* dram_init must store complete ramsize in gd->ram_size */
    gd->ram_size = PHYS_SDRAM_1_SIZE;
    return 0;
}

4.3) 依次分析,至于哪些函数会被调用,哪些不被调用,不用查配置文件,因为通过log可以看出setup_dest_addr后的第一个函数reserve_uboot没有调用。reserve_uboot是在打印出了TLB table from后再调用的。TLB table from是在reserve_mmu函数中被打印的。后面的函数reserve_prom,reserve_logbuffer和reserve_pram在debug信息的log中也没有出现,pass。

TLB table from 33ff0000 to 33ff4000
initcall: 3200e7a0
initcall: 3200e82c
Reserving 810k for U-Boot at: 33f25000
    setup_dest_addr,
#if defined(CONFIG_BLACKFIN) || defined(CONFIG_XTENSA)
    /* Blackfin u-boot monitor should be on top of the ram */
    reserve_uboot,
#endif
#if defined(CONFIG_SPARC)
    reserve_prom,
#endif
#if defined(CONFIG_LOGBUFFER) && !defined(CONFIG_ALT_LB_ADDR)
    reserve_logbuffer,
#endif
#ifdef CONFIG_PRAM
    reserve_pram,
#endif
    reserve_round_4k,
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \
        defined(CONFIG_ARM)
    reserve_mmu,
#endif

4.4) 继续分析reserve_round_4k函数是无条件调用,将最后12个bit值设置为0。等于减4K。那么这4K用来干嘛呢?貌似看不出来,我就当做是保留区间吧!这个没有debug log信息所以我画的内存分布图中没有体现0x33FFF000这个地址。此时relocaddr变成了0x33FFF000

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

4.5)我在4.1中提出的问题,刚进入reserve_mmu时候的relocaddr值是多少呢?已经解决了,就是0x33FFF000。那么0x33FFF000-16K=就是0x33fff000-0x5800=0x33FF9800,然后还记得有一个对齐吗?对齐后后面的16bit清0,变成了0x33FF0000。0x33FF0000为大小16K的TBL的起始地址。等于先有了地址3(起始地址),再计算出它的结束地址是gd->arch.tlb_addr + gd->arch.tlb_size

4.6) 关于地址对齐
看到这里我觉得对齐以前我理解的都是按字节对齐,比如8个字节或者16个字节。没想到这里的对齐都是4K或者64K对齐。为什么呢?网上搜索了下,4K对齐可以理解为4096个扇区。4k对齐是一种先进的bai硬盘使用技术,使用特殊的方法将文件系统格式与硬盘物理层匹配,为提高硬盘寿命和有效利用硬盘空间提供了解决方案。那么我理解目的就是为了提高命中率,page页的大小估计也是4K的整数倍。

4.7) 继续分析后面的地址
结合打印的log之后会进入reserve_uboot函数。又是相同的套路,先减去mon_len长度,通过之前的log"Monitor len: 000CAB3C",mon_len长度为0xCAB3C,见setup_mon_len函数,其实就是lds链接文件中定义的代码段+数据段,如下bss_end是最后一个定义的段名了。继续看reserve_uboot,0x33FF0000-0xCAB3C=0x33F254C4,然后进行对齐,后面的12bit清0。relocaddr的值为0x33F25000,与分布图中4号地址一致。通过看源码我在uboot内存分布图中补充了几个橙色区域对齐保留空间。比较奇怪的是gd->start_addr_sp = gd->relocaddr;sp也开始赋值了呢。

static int setup_mon_len(void)
{
#if defined(__ARM__) || defined(__MICROBLAZE__)
    gd->mon_len = (ulong)&__bss_end - (ulong)_start;
    .....
#elif defined(CONFIG_SYS_MONITOR_BASE)
    /* TODO: use (ulong)&__bss_end - (ulong)&__text_start; ? */
    gd->mon_len = (ulong)&__bss_end - CONFIG_SYS_MONITOR_BASE;
#endif
    return 0;
}

bss是最后一个段名了

    ...
    .bss_end __bss_limit (OVERLAY) : {
        KEEP(*(.__bss_end));
    }

    .dynsym _image_binary_end : { *(.dynsym) }
    .dynbss : { *(.dynbss) }
    .dynstr : { *(.dynstr*) }
    .dynamic : { *(.dynamic*) }
    .plt : { *(.plt*) }
    .interp : { *(.interp*) }
    .gnu.hash : { *(.gnu.hash) }
    .gnu : { *(.gnu*) }
    .ARM.exidx : { *(.ARM.exidx*) }
    .gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
}
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);
#ifdef CONFIG_E500
    /* round down to next 64 kB limit so that IVPR stays aligned */
    gd->relocaddr &= ~(65536 - 1);
#endif

    debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10,
          gd->relocaddr);

    gd->start_addr_sp = gd->relocaddr;

    return 0;
}

4.8) 继续看源码,我发现了reserve打头的函数都是初始化重定向相关的函数,由于我没有定义SPL。所以会进入如下2个函数。CONFIG_SYS_MALLOC_LEN是4M,就是0x33F25000-0x400000=0x33B25000。与分布图中5号地址一致。然后看reserve_board函数。里面也是很容易理解的。就是将start_addr_sp减去bd结构体的大小。然后将此地址作为bd结构体的起始地址。此地址见分布图6号地址。继续看reserve_global_data函数,也是同样的思路,最后减去gd的size后得到gd的起始地址,此地址见分布图7号地址

#ifndef CONFIG_SPL_BUILD
    reserve_malloc,
    reserve_board,
#endif
    setup_machine,
    reserve_global_data,
#define CONFIG_SYS_MALLOC_LEN   (4 * 1024 * 1024)
static int reserve_malloc(void)
{
    gd->start_addr_sp = gd->start_addr_sp - TOTAL_MALLOC_LEN;
    debug("Reserving %dk for malloc() at: %08lx\n",
            TOTAL_MALLOC_LEN >> 10, gd->start_addr_sp);
    return 0;
}
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);
    }
    return 0;
}
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);
    return 0;
}

4.9) 接着看上去我还有1个地址要找到来与。根据log"New Stack Pointer is"找到函数如下。0x33B24EF0-0x10=0x33B24EE0,然后将后面4个bit清0。就是0x33B24EE0,那么分布图中位置8的地址也被计算出来了,验证正确。看到这里我又想明白一个问题,就是它为什么要从top开始减少,而不从bottom开始增加。原来就是用与的方式来清楚后面的位数,这样处理等于放宽了空间,代码运行起来速度也快。所以从top开始减的方式,在代码运行效率上是优选。

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

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

4.10)关于uboot重定向的分析到此结束了吗?还差最后一步。上面的分析都是赋值,可以理解为分布图已经在board_init_f函数运行后被设计师设计完成了。那么接下来需要进行从定位的搬迁执行,是哪个函数负责的呢?然后在汇编中找了下,应该是relocate_code函数负责搬运的。在relocate.S文件中ENTRY(relocate_code)为此函数的入口。今天主要是分析c代码部分,汇编相关的分析我就省略了。

/*
 * Set up initial C runtime environment and call board_init_f(0).
 */
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
    ldr x0, =(CONFIG_SPL_STACK)
#else
    ldr x0, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
    bic sp, x0, #0xf    /* 16-byte alignment for ABI compliance */
    mov x0, sp
    bl  board_init_f_alloc_reserve
    mov sp, x0
    /* set up gd here, outside any C code */
    mov x18, x0
    bl  board_init_f_init_reserve

    mov x0, #0
    bl  board_init_f

#if !defined(CONFIG_SPL_BUILD)
/*
 * Set up intermediate environment (new sp and gd) and call
 * relocate_code(addr_moni). Trick here is that we'll return
 * 'here' but relocated.
 */
    ldr x0, [x18, #GD_START_ADDR_SP]    /* x0 <- gd->start_addr_sp */
    bic sp, x0, #0xf    /* 16-byte alignment for ABI compliance */
    ldr x18, [x18, #GD_BD]      /* x18 <- gd->bd */
    sub x18, x18, #GD_SIZE      /* new GD is below bd */

    adr lr, relocation_return
    ldr x9, [x18, #GD_RELOC_OFF]    /* x9 <- gd->reloc_off */
    add lr, lr, x9  /* new return address after relocation */
    ldr x0, [x18, #GD_RELOCADDR]    /* x0 <- gd->relocaddr */
    b   relocate_code

5. 总结

今天主要是对uboot中c代码中重定向的分析。思考了为什么要有重定向。以及思考了从norflash启动时候为什么也要重定向。若没有重定向到外部sdram的话,s3c2440内部的ram只有4K(0x1000)不够用。所以一开始sp指针赋值确实是在4K内,包括了uboot的gd全局变量也保存在这4k中,证据就在smdk2410.h的配置文件中#define CONFIG_SYS_INIT_SP_ADDR (CONFIG_SYS_SDRAM_BASE + 0x1000 - GENERATED_GBL_DATA_SIZE)。然后就是通过源码分析出uboot的内存分配地址,绘制了比较详细的uboot内存分布图。也学到了一种地址对齐的优化设计方式,用减法和位屏蔽的方式来对齐地址。收工,早点睡觉,今天是20年一遇的寒冬,真冷!

你可能感兴趣的:(uboot内存重定向详解--Apple的学习笔记)