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的学习笔记这样的话,我首先对内存分布有了一个全局观。这属于我的个人分格,喜欢先了解大框架,再了解具体细节。画个图还蛮花费时间的哦,不过画出来还是很有成就感的,因为看图讲故事比较形象。
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年一遇的寒冬,真冷!
网友评论