uboot1.1.6是一个很老的版本的uboot了,但是正因为老,所以它的文档资料也很多,更适合深入研究uboot的源码。接下来将深入分析天嵌公司tq2440开发板提供的uboot的主要源码,并且通过在熟悉源码的基础上掌握uboot的移植技术,最后弄清楚uboot的内部架构与设计规范,便于以后分析与移植其他版本的uboot到其他的开发板。分析过程采用逐个子程序的方法进行,在弄懂了每一个子程序的基础上,最后通过粗读整个程序,很快就能理解整个程序的框架。
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
@在《ARM体系结构与编程》的204页有关于如何设置与cache和writebuffer相关的协处理器的内容。其指令的固定格式为MCR p15,0,
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
@在《ARM体系结构与编程》的189页有关于如何设置与TLB(就是页表的cache吧)相关的协处理器的内容。其指令的固定格式为MCR p15,0,
@在《ARM体系结构与编程》的177页有关于如何设置与MMU相关的协处理器的内容。主要是C1寄存器的设置。bic就是与上操作数的反码,为1的反码为0,与上后就清0了。orr直接或上去。先把c1读到r0,改下写回c1去。
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/在重定位之前,我们必须设置RAM的时序,因为内存时序是独立于具体的板子的,在你的板子的目录下有一个lowlevel_init.S做这件事情
mov ip, lr
bl lowlevel_init
mov lr, ip
mov pc, lr
@ip就是r12,把lr保存到ip中,转去执行子程序,因为lr肯定在子程序中会被用到,所以要先保存在ip中。最后恢复lr,并返回调用程序。
位于board/板子名/lowlevel_init.S中有
_TEXT_BASE:
.word TEXT_BASE
@在链接的时候TEXT_BASE = 0x33D00000 这个是在board/板子名/config.mk文件中定义的,在前面分析uboot编译的文章中介绍了,根目录下的config.mk文件主要用来配置编译用到的一些变量等,LDFLAGS就是在这个文件中定义的。LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)。在config.mk中有CPPFLAGS := $(DBGFLAGS) $(OPTFLAGS) $(RELFLAGS) -D__KERNEL__ -DTEXT_BASE=$(TEXT_BASE),其中的-D选项就相当于是一个在预处理期间定义到源码中去的一个宏定义,这样就让代码中的TEXT_BASE值为0x33D00000。
lowlevel_init:
/* 位于board/板子名/lowlevel_init.S*/
/* memory control configuration */
/* make r0 relative the current location so that it */
/* reads SMRDATA out of FLASH rather than memory ! */
ldr r0, =SMRDATA
ldr r1, _TEXT_BASE @r1中的值为_TEXT_BASE单元中存储的值为TEXT_BASE,就是0x33D00000,也就是最后代码段的运行时地址
sub r0, r0, r1 @位置无关!!!不管你怎么搬代码,相对位置是不变的,位置无关的原理就是相对位置不变。在我们编译链接好了我们的程序后,这些符号都是有运行时地址的,比如,SMRDATA这个标号就是一个运行时地址,TEXT_BASE也是为0x33D00000,也是一个运行时地址。但是,可以肯定的是,刚开始这段代码在 运行时一定是在开头的4kB以内,因此,他们一定会被copy到SteppingStone里面去,那么,这时候程序怎么知道自己去哪里找到这个SMRDATA指向的符号表的内容?也就是如何找到这些配置数据呢?只有使用位置无关的方法就是计算一个相对偏移量,不管代码在哪里运行,(地址可能在变化),但是他们的相对位置(在同一个存储器里面)是不变化的。
ldr r1, =BWSCON /* Bus Width Status Controller */
add r2, r0, #13*4
0:
ldr r3, [r0], #4
str r3, [r1], #4 @r3寄存器作为一个暂存的寄存器,采用事后的访问方式,先取寄存器中地址里保存的值,指令执行成功以后将偏移量加到r1中去,作为下一次的地址,实现了访问完成后地址的自动增加
cmp r2, r0
bne 0b
/* everything is fine now */
mov pc, lr
@SMRDATA就是存储控制器中寄存器的配置的列表,一共是13个,前9个是固定的,后4个则根据配置来,根据CONFIG_133MHZ_SDRAM(tq2440不是),选择下边那组。
.ltorg @在《ARM体系结构与编程》的120页,声明一个数据缓冲池的开始,一般放在代码的最后面(无条件跳转指令之后,或者子程序返回指令之后)
SMRDATA:
.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28)) /* Set up the stack */
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */ @ldr指令和ldr伪指令之间的区别,ldr指令取的是存储器的内容,第二个操作数只是一个存储器地址
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */@#define CFG_MALLOC_LEN (CFG_ENV_SIZE + 128*1024)------>256KB
@#define CFG_ENV_SIZE 0x20000 /* Total Size of Environment Sector */,0x20000就是2^17=128K
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
@#define CFG_GBL_DATA_SIZE 128 /* size in bytes reserved for initial data */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
@#define CONFIG_USE_IRQ 1 显然这里是定义了这个CONFIG_USE_IRQ
@#ifdef CONFIG_USE_IRQ
@#define CONFIG_STACKSIZE_IRQ (4*1024) /* IRQ stack */4KB的IRQ栈
@#define CONFIG_STACKSIZE_FIQ (4*1024) /* FIQ stack */4KB的FIQ栈
@#endif
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */12B的abort栈
@这里可以看到在逐步地形成整个uboot的内存空间,这里我们看一张图就明白了。其中我们的TEXT_BASE=0x33D0 0000,就是61MB,那么往下就是栈区了,栈首先是256KB的CFG_MALLOC_LEN,接下来就是128B的CFG_GBL_DATA_SIZE,再接着是各4KB的CONFIG_STACKSIZE_IRQ和CONFIG_STACKSIZE_FIQ,还有12B的abort-stack ,那么最后就是真正的用户栈了,也就是sp指针指向的栈,也就是以前写C语言程序中经常提到的那个栈。
图1 uboot的内存分布
bl clock_init
@接下来就是调用clock_init这个子程序,因为我们已经设置了用户栈了,可以直接使用伟大的C语言了,解放了,不过,我们仍然没有跳转,因此,可能是在SteppingStone上运行,我们说可能是因为它处在4KB以内而且没有搬运代码和跳转,但是也有例外就是如果我们从norflash启动的呢?根本不关nand的事!所以,总的来说,只要有可能还在SteppingStone上运行,我们就必须要用能够生成位置无关指令的C语言的代码去写程序,如不要用全局变量或者不要去访问那些静态变量或者存储在.RODATA段的数据,因为现在那些地方还是空的。
void clock_init(void) //初始化系统时钟设置为400MHZ
{
//这个函数是在board/板子名/boot_init.c中
S3C24X0_CLOCK_POWER *clk_power = (S3C24X0_CLOCK_POWER *)0x4C000000;
/****这种用C处理寄存器的风格需要分析下,看代码:
****typedef struct {
**** S3C24X0_REG32 LOCKTIME; 其中typedef volatile u32 S3C24X0_REG32; typedef unsigned int u32;S3C24X0_REG32定义的是 volatile unsigned int类型
**** S3C24X0_REG32 MPLLCON;按照datasheet上的寄存器的地址一字排开,然后定义一个这种结构体类型的指针变量clk_power,然后将某个具体的地址值强制转换为这种
**** S3C24X0_REG32 UPLLCON;类型的指针再赋值给clk_power这个指针变量,以后访问某个具体的寄存器直接使用这个变量去指向一个寄存器的名字(结构体的成员变量
**** S3C24X0_REG32 CLKCON;名)就可以了。
**** S3C24X0_REG32 CLKSLOW;
**** S3C24X0_REG32 CLKDIVN;
**** S3C24X0_REG32 CAMDIVN; /* for s3c2440, by www.embedsky.net */
****} S3C24X0_CLOCK_POWER;
****/
/* FCLK:HCLK:PCLK = ?:?:? */
#if CONFIG_133MHZ_SDRAM //#define CONFIG_133MHZ_SDRAM 0,因此执行else部分
clk_power->CLKDIVN = S3C2440_CLKDIV136; //HJ 1:3:6
#else
clk_power->CLKDIVN = S3C2440_CLKDIV; //HJ 1:4:8 #define S3C2440_CLKDIV 0x05 /* FCLK:HCLK:PCLK = 1:4:8, UCLK = UPLL */
#endif
/* change to asynchronous bus mod */
__asm__( "mrc p15, 0, r1, c1, c0, 0\n" /* read ctrl register */
"orr r1, r1, #0xc0000000\n" /* Asynchronous */
"mcr p15, 0, r1, c1, c0, 0\n" /* write ctrl register */
:::"r1"
);//这就是在datasheet的243页上说的那个note吧,CPU bus mode has to be changed from the fast bus mode to the asynchronous bus mode
/* to reduce PLL lock time, adjust the LOCKTIME register */
clk_power->LOCKTIME = 0xFFFFFF;//时序上的锁定时间
/* configure UPLL */
clk_power->UPLLCON = S3C2440_UPLL_48MHZ; //fin=12.000MHz
// clk_power->UPLLCON = S3C2440_UPLL_48MHZ_Fin16MHz; //fin=16.934MHz
/* some delay between MPLL and UPLL */
delay (4000);
/****static inline void delay (unsigned long loops)
****{
**** __asm__ volatile ("1:\n"
**** "subs %0, %1, #1\n"
**** "bne 1b":"=r" (loops):"0" (loops));
****}
*****/
/* configure MPLL */
clk_power->MPLLCON = S3C2440_MPLL_400MHZ; //fin=12.000MHz #define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01))
// clk_power->MPLLCON = S3C2440_MPLL_405MHZ; //HJ 405MHz
// clk_power->MPLLCON = S3C2440_MPLL_440MHZ; //HJ 440MHz
// clk_power->MPLLCON = S3C2440_MPLL_480MHZ; //HJ 480MHz
// clk_power->MPLLCON = S3C2440_MPLL_399MHz; //fin=16.934MHz
/* some delay between MPLL and UPLL */
delay (8000);
}
adr r0, _start /* r0 <- current position of code *///无论是从NOR Flash还是从NAND Flash启动,地址0处为U-Boot的第一条指令,因此,_start所表示的地址值0地址送r0;但如果是在调试时,uboot是下载到SDRAM中,第一条指令的地址就会是链接地址TEXT_BASE
ldr r1, _TEXT_BASE /* test if we run from flash or RAM *///TEXT_BASE就是0x33D0 0000
cmp r0, r1 /* don't reloc during debug */
beq clear_bss //如果是从SDRAM启动,在调试,那么就直接跳过去,直接去做后边的事情 ,去清除bss段
ldr r2, _armboot_start //r2中就是_start标号的值吧,是一个地址
/****
****.globl _armboot_start
****_armboot_start:
**** .word _start
****/
ldr r3, _bss_start //r3中就是__bss_start标号的内容,是一个地址,这些是在板子目录下的u-boot.lds文件中定义的,__bss_start = .;表示它的值为bss段的起始地址
/****
****_bss_start:
****.word __bss_start
****/
sub r2, r3, r2 /* r2 <- size of armboot *///r2中是要拷贝的代码的长度
#if 1
bl CopyCode2Ram /* r0: source, r1: dest, r2: size */ 这是一个C语言的函数,注意参数是怎么传递的,ATPCS吧,前面总结过,任何时候要拷贝都要三个东西,源地址、目标地址、拷贝的大小(用来判断何时拷贝结束)
#else
add r2, r0, r2 /* r2 <- source end address *///注意这个else的代码都没有执行
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] *///批量地将数据从r0指向的内存中拷贝到r3-r10中去,
stmia r1!, {r3-r10} /* copy to target address [r1] */ //批量地将数据从r3-r10拷贝到r1所指向的内存中
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop //r0<=r2则没有复制完继续循环复制
#endif
int CopyCode2Ram(unsigned long start_addr, unsigned char *buf, int size) //注意这里size的单位是Bytes
{//注意三个参数的意义:源地址,目的地址,长度;同时注意他们的类型
//board/板子名/boot_init.c文件中
unsigned int *pdwDest;
unsigned int *pdwSrc;
int i;
if (bBootFrmNORFlash()) //判断启动flash为nor(返回值为1)还是nand的函数
{//是norflash启动时运行的代码
pdwDest = (unsigned int *)buf;
pdwSrc = (unsigned int *)start_addr;
for (i = 0; i < size / 4; i++) //每一次拷贝都是4B
{
pdwDest[i] = pdwSrc[i];
}
return 0;
}
else
{//是nandflash启动时运行的代码
nand_init_ll(); //NandFlash初始化和复位
if (NF_ReadID() == 0x76 ) //K9F1208U0C的Device Code是0x76H,来自nandflash的datasheet的P33
nand_read_ll(buf, start_addr, (size + NAND_BLOCK_MASK)&~(NAND_BLOCK_MASK)); //参数的意义发生变化:目的地址,源地址,BLOCK对齐后的长度(不足一块的补足一块)
/******** #define NAND_SECTOR_SIZE 512
/******** #define NAND_BLOCK_MASK (NAND_SECTOR_SIZE - 1)
else
nand_read_ll_lp(buf, start_addr, (size + NAND_BLOCK_MASK_LP)&~(NAND_BLOCK_MASK_LP));
return 0;
}
}
int bBootFrmNORFlash(void)
{
//board/板子名/boot_init.c文件中
volatile unsigned int *pdw = (volatile unsigned int *)0;void nand_init_ll(void) //注意这里的nand初始化加了一个_ll的尾巴用来与后面真正的nand_init()进行区分
{//board/板子名/boot_init.c
S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;
/****typedef struct {
**** S3C24X0_REG32 NFCONF;
**** S3C24X0_REG32 NFCONT;
**** S3C24X0_REG32 NFCMD;
**** S3C24X0_REG32 NFADDR;
**** S3C24X0_REG32 NFDATA;
**** S3C24X0_REG32 NFMECCD0;
**** S3C24X0_REG32 NFMECCD1;
**** S3C24X0_REG32 NFSECCD;
**** S3C24X0_REG32 NFSTAT;
**** S3C24X0_REG32 NFESTAT0;
**** S3C24X0_REG32 NFESTAT1;
**** S3C24X0_REG32 NFMECC0;
**** S3C24X0_REG32 NFMECC1;
**** S3C24X0_REG32 NFSECC;
**** S3C24X0_REG32 NFSBLK;
**** S3C24X0_REG32 NFEBLK;
****} S3C2440_NAND;//这种寄存器的结构体定义前面已经分析过
****/
#define TACLS 0
#define TWRPH0 3
#define TWRPH1 0
s3c2440nand->NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
s3c2440nand->NFCONT = (1<<4)|(1<<1)|(1<<0); //[3]=0(默认),[1]=1指定为4个地址周期;[0]=1数据总线宽度为16位
nand_reset();
}
static void nand_reset(void)
{//board/板子名/boot_init.c
s3c2440_nand_reset();
}
static void s3c2440_nand_reset(void)
{//board/板子名/boot_init.c
//这个复位我们把握它的流程是4点:发出片选信号,发复位命令(0xff),等待就绪,取消片选信号
s3c2440_nand_select_chip();
s3c2440_write_cmd(0xff);
s3c2440_wait_idle();
s3c2440_nand_deselect_chip();
}
static void s3c2440_nand_select_chip(void) //发出片选信号
{
int i;
S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;
s3c2440nand->NFCONT &= ~(1<<1); //Force nFCE to low (Enable chip select)
for(i=0; i<10; i++); //延时下,延时写在这里很好,后面调用这个函数做发出片选操作时完全不用考虑还要加延时等
}
static void s3c2440_nand_deselect_chip(void) //取消片选信号
{
S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;
s3c2440nand->NFCONT |= (1<<1); //Force nFCE to high (Disable chip select)
}
static void s3c2440_write_cmd(int cmd) //写命令
{
S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;
volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFCMD;
//特别注意这里的p的是指向unsigned char型的指针,是因为这里的NFCMD寄存器只有低8位有效
*p = cmd;
}
static void s3c2440_wait_idle(void) //测试RnB管脚的状态,为1时nand Flash就绪,这里是一直程序查询等待直到RnB为1
{
int i;
S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;
volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFSTAT;
//NFSTAT寄存器也是只有低8位有效,*p就是读取NFSTAT寄存器的内容,其中的[0]位1: NAND Flash memory ready to operate
while(!(*p & BUSY)) //#define BUSY 1
for(i=0; i<10; i++);
}
int NF_ReadID(void)
{//board/板子名/boot_init.c
char pMID;
char pDID;
int nBuff;
char n4thcycle;
int i;
S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;
volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFADDR;
b128MB = 1;
n4thcycle = nBuff = 0;
nand_init_ll(); //复位下nand flash
nand_select_chip(); //使能片选信号
write_cmd(0x90); // read id command
*p=0x00 & 0xff; //这么写还是0x00,但是确保是一个字节的0x00
for ( i = 0; i < 100; i++ );
pMID = read_data(); //Marker Code 0xECH
pDID = read_data(); //Device Code 0x76H
nBuff = read_data(); //0x5AH
n4thcycle = read_data(); //0x3FH
nand_deselect_chip(); //禁止片选信号
if (pDID >= 0xA0)
{
b128MB = 0;
}
return (pDID);
}
static unsigned char read_data(void)
{
return s3c2440_read_data();
}
static unsigned char s3c2440_read_data(void)
{
S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;
volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFDATA;
return *p; //熟悉这种模式,定义一个指针指向这个寄存器的地址,然后用*去引用这个指针,就可以得到这个寄存器的内容,定义的是指向unsigned char的指针则获得低8位的数据。所以是分两步走,第一步p指针指向寄存器的地址,第二步用*号才是真正去读。
}
write_cmd(0x50); //read2命令
write_cmd(0); // nandflash的datasheet说The Read1 command(00h/01h) is needed to move the pointer back to the main area
}
static void write_addr(unsigned int addr)
{
s3c2440_write_addr(addr);
}
static void s3c2440_write_addr(unsigned int addr)
{
int i;
S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;
volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFADDR;
*p = addr & 0xff;
for(i=0; i<10; i++);
*p = (addr >> 9) & 0xff;
for(i=0; i<10; i++);
*p = (addr >> 17) & 0xff;
for(i=0; i<10; i++);
*p = (addr >> 25) & 0xff;
for(i=0; i<10; i++);
}
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:
str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l //就一个循环通过b的le条件执行(r0<=r1时执行)不断地把0x00000000送到__bss_start(注意是两根下划线__)
ldr pc, _start_armboot //ldr不是一个地址无关指令,但是这里已经没问题了,因为代码已经搬到了sdram对应的位置了
_start_armboot: .word start_armboot