自己编写JZ2440 bootloader分析

来源:互联网 发布:网络加速app 编辑:程序博客网 时间:2024/05/17 04:00

一.明白bootloader的作用,和要实现的功能

  1. bootloader最终的目的就是去启动内核,将一些参数传递给内核,使内核可以在内存上运行。
  2. 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/*写入控制寄存器*/

0 0