一.明白bootloader的作用,和要实现的功能
- bootloader最终的目的就是去启动内核,将一些参数传递给内核,使内核可以在内存上运行。
- bootloader要实现的功能就是要将内核从FLASH上读出来给内存。
二.既然bootloader要从FLASH上读内核传给内存,那么bootloader就要完成一些准备工作
针对于JZ2440这款开发板,我们要做以下工作(选择NAND FLASH启动):
1.初始化硬件:关看门狗,初始化时钟,初始化SDRAM,初始化NAND FLASH(这里要知道NAND FLASH和NOR FLASH的区别)
2.如果boot loader比较大,我们要进行代码重定位,重定位到SDRAM(这里要明白什么是代码的重定位,代码重定位需要链接地址,链接地址和存储地址的区别)
3.清BSS段(什么是BSS段)
4.跳转到main函数,从NAND FLASH中将内核读到SDRAM中
5.设置参数(这里的参数设置是用一个TAG结构体实现的,参数的位置放在SDRAM中的一个地址处是和内核约定好的,在启动内核时将地址告诉内核)
5.跳转到SDSRAM内核处,执行内核
三.可以分别在start.S、init.c、boot.c、这几个文件中完成代码,当然不能缺少链接文件boot.lds、Makefile和头文件setup.h
1.start.S 是汇编文件,在这个文件中主要实现上述的一二三步骤。
我们要明白这几条指令的意思:
mov r0 ,#0 //将r0写0
ldr r1, = 0x123456 //r1的值为0x123456
str r0 ,[r1] //实现r1这个值对应的地址处的值为0
初始化SDRAM,将SDRAM的值对应的写入SDRAM的寄存器中
ldr r0, =MEM_CTL_BASE
adr r1, sdram_config
add r3, r0, #(13*4)
1:
ldr r2, [r1], #4
str r2, [r0], #4
cmp r3, r0
bne 1b //1b表示如果比较不相等,则返回前一个1。还有1f是返回下一个1的意思
2.代码重定位,将bootloader本身的代码拷到SDRAM中
bl copy_code_to_sdram() 因为这条指令在汇编中,跳转到.c文件中,所以在这条语句之前需要设置栈
ldr sp , =0x34000000 这个sp指向SDRAM的最大处。
起始地址为0x30000000,64M大小。
3.bl copy_code_to_sdram() 跳转到init.c文件中
在init.c中实现了copy_code_to_sdram(unsigned char *src,unsigned char*dst,unsigned int len)。
这三个参数是在start.S中设置。
第一个参数设置:无论是NAND启动还是NOR启动,他们的源地址都是0。NOR启动CPU直接指向NOR的o地址,NAND启动CPU指向片内内存的0地址。因为上电后,CPU自动将NAND Flash中4K的代码拷到片内内存中。
第二个参数设置:是目的地址,也就是CPU要将boot loader拷到的地方,这里就是第一条指令的标号_start,也就是链接地址
第三个参数设置:是长度。表示整个二进制文件(包括代码段,只读数据段,数据段)的大小,这里不包括BSS段,BSS段是单独的。需要看链接脚本。大小是从链接地址到__bss的起始地址。
在这个函数中首先要判断是NAND、还是NOR启动。判断的依据是根据Nand和Nor Flash的特性。Nand Flash可以直接写但不能读;Nor Flash可以直接读但不能写。我们可以往一个地址写如一个数,如果写成功则是NAND 启动,否则是Nor启动。
如果是Nor启动,则可以直接传递:
for(i=0;i<len ;i++)
{
dst[i] = src[i];
}
如果是NAND启动,则需要命令进行读,在读之前要进行Nand 初始化,这个可以在跳转到copy_code_to_sdram之前进行。
nand_init()需要完成对nand flash控制器的初始化,主要是时序和使能控制器。
nand_read()来完成读
void nand_read(unsigned int addr,unsigned char *buf,int len)
{
int i = 0;
int col = addr % 2048; /*因为2440的nand flash 每一页的大小为2048,所以这样可以确定列数*/
/* 1.片选选中 */
nand_select();
while(i<len)
{
/* 2.写入00H的命令 */
nand_cmd(0x00);
/* 3.写地址,需要五个周期,五部来完成 */
nand_addr(addr);
/* 4.写入命令30H */
nand_cmd(0X30);
/* 5.判断状态,是处于准备还是忙碌 */
nand_wait_ready();
/* 6.读数据 */
for(;(col<2048) && (i<len);col++)
{
buf[i] = nand_read_data();
i++;
addr++; /*这里如果不写addr++可不可以???????*/
}
col = 0;
}
/* 7.取消片选 */
nand_deselect();
}
4.清BSS段
void clear_bss(void) { extern int __bss_start, __bss_end; int *p = &__bss_start; for(;p <(int*) __bss_end;p++) { *p = 0; } }
5.跳转到main
首先要读内核,然后设置参数,跳转执行。
读内核用nand_read(0x60000+64,0x30008000;0x200000)
这里要解释的是0x60000+64的原因是因为uImage在Flash中存的地址是0x60000,而uImage是由64字节的头部加上zImage组成的
我们要读的是zImage,所以源地址应该是0x60000+64.
设置参数:是通过一个tag的结构体,包括设置NAND FLASH、还有命令行参数
最后是跳转,跳转涉及到一个函数指针 void (*theKernel)(int zero, int arch, unsigned int params); /*定义的函数指针*/
theKernel = (void (*)(int, int, unsigned int))0x30008000;/*0x30008000内核在SDRAM中存放的地址*/
theKernel(0 ,362, 0x30000100); /*0x30000100是内核和bootloader约定存放参数的地址*/
在这些之前要先进行串口初始化,因为kernel要在串口中打印一些东西,但是kernel不具有初始化串口的功能,所以boot loader要对
串口进行初始化。
这就是bootloader的主要部分。还需要完成的还有Makefile、boot.lds
启动改进:加入ICACHE 可以加快内核启动,在start.S中,初始化时钟后可以启动ICACHE:
/* 启动ICACHE */
mrc p15, 0 ,r0 ,c1 ,c0,0 /*协处理器到ARM寄存器,相当于从协处理器读数据到ARM*/
orr r0 , r0, #(1<<12)
mcr p15, 0 ,r0 ,c1, c0,0/*写入控制寄存器*/