U-BOOT详解2.从0编写uboot

一、硬件初始化和制作链接脚本lds

1.1、目标

第一阶段:

  • 关看门狗
  • 设置时钟
  • 初始化SDRAM(初始化寄存器以及清除bss段)
  • 重定位(将nand/nor中的代码COPY到链接地址上,需要初始化nandflash,读flash)
  • 执行main

进入第二阶段:

  • 写main函数,在main()中设置要传给内核的参数,然后跳转内核基地址处
  • 制作uboot.lds链接脚本

1.2、创建工程并编写start.S

1)创建个名为"my_bootloader"根目录,方便编写uboot
2)新建my_bootloader/si目录,创建source insight工程
3)新建my_bootloader/start.S (后缀名必须是大写的S,或者后面编译会报错)
  编写start.S (第一阶段初始硬件文件):

/*  看门狗寄存器            */
#define WTCON      0x53000000
 
/*  2.时钟寄存器            */
#define CLKDIVN    0x4C000014        //设置FCLK,HCLK,PCLK比例
#define MPLLCON   0x4C000004          //设置FCLK频率
#define S3C2440_MPLL_200MHZ     ((0x5c<<12)|(0x01<<4)|(0x02))  //设置FCLK=200MHZ
#define S3C2440_MPLL_400MHZ     ((0x5c<<12)|(0x01<<4)|(0x01))  //设置FCLK=400MHZ
 
/*  3.bank寄存器       */
#define BWSCON      0x48000000
 
.text                                /*设置代码段*/
.global   _start                  /*定义全局变量,要被连接脚本用到*/
_start:                           /*_start跳转到这里实现硬件初始化*/
 
    /*  1.关看门狗            */
       ldr r0,=WTCON
       mov r1,#0
       str r1,[r0]
        
    /*  2.设置时钟(必须设为异步总线模式)  */
        
       ldr r0,=CLKDIVN
       mov r1,#3                                  /*FCLK:HCLK:PCLK=1:2:4*/
       str r1,[r0]        
       mrc  p15, 0, r1, c1, c0                 /* 读出控制寄存器 */   
       orr   r1, r1, #0xc0000000         /* 设置为“asynchronous bus mode” */
       mcr    p15, 0, r1, c1, c0, 0             /* 写入控制寄存器 */      
       ldr r0,=MPLLCON
       ldr r1,=S3C2440_MPLL_200MHZ      //使用复杂的数不能用mov,需要用ldr
       str r1,[r0]
        
    /*  3.初始化SDRAM       */
       ldr r0,=BWSCON                   //r0=SDRAM寄存器基地址
       adr r1,SDRAM_CONFIG           //使用adr相对跳转,因为SDRAM未初始化
       add r2,r0,#(13*4)               //r2等于 SDRAM尾地址+4
 
0:
       ldr r3,[r1],#4                      //r3=r1里的    内容, &r1+=4;
       str r3,[r0],#4                      //*r0=r3,&r0+=4
       cmp r0,r2
       bne 0b                               //(ne:no equal   b:bank)   若r0!=r2,跳转到后面的0处
 
 /*  4.重定位              */
        ldr sp,=0x34000000                     //64Msdram,所以设置栈SP=0x34000000,避免堆栈溢出
        mov r0,#0                           //r0->src
    ldr r1,=_start                     //r1->dest
    ldr r2,=__bss_start            //r2->len->__bss_start-_start
    sub r2,r2,r1
    bl copy_code_to_sdram          //复制代码到SDRAM连接地址dest上
    bl clear_bss                         //清除bss段 
     
   /*  5.执行main              */
        ldr lr,=halt                         //执行一个子程序调用返回时,需要先将返回地址赋给lr链接寄存器
        ldr pc,=main                              //初始化SDRAM后,可以使用pc 绝对跳转到SDRAM地址上
        mov pc,lr                             //若main函数跳出后,使PC等于lr链接寄存器,避免程序跑飞 
 
halt:                                   //死循环,避免跑飞
     b halt               
 
SDRAM_CONFIG:
    .long 0x22011110;     //BWSCON
    .long 0x00000700;     //BANKCON0
    .long 0x00000700;     //BANKCON1
    .long 0x00000700;     //BANKCON2
    .long 0x00000700;     //BANKCON3  
    .long 0x00000700;     //BANKCON4
    .long 0x00000700;     //BANKCON5
    .long 0x00018005;     //BANKCON6
    .long 0x00018005;     //BANKCON7
    .long 0x008C04F4;     //REFRESH
    .long 0x000000B1;     //BANKSIZE 
    .long 0x00000030;     //MRSRB6
    .long 0x00000030;     //MRSRB7

1.3、编写my_bootloader/init.c

  新建my_bootloader/init.c,用于重定位,bss段清除,初始化NandFlash等
  在重定位函数中的nand驱动在(http://www.cnblogs.com/lifexy/p/7097695.html )这篇帖子有详细介绍,这里就不描述.
  1)编写copy_code_tosdram() 重定位函数

void copy_code_to_sdram(unsigned char *src,unsigned char *dest,unsigned int len)            //复制代码到SDRAM连接地址dest上
{
    unsigned int i;
    /*判断nor启动还是nand启动*/
    if(is_boot_from_norflash())                 //nor启动,直接复制
    {
        for(i=0;i<len;i++)
        {
            dest[i]=src[i];
        }
    }
    else
    {
        nand_init();
        nand_read((unsigned int)src,dest,len);
    }
}

  2)编写isBootFramNorFlash()函数,来判断nand启动还是nor启动

/*判断nor启动还是nand启动*/
unsigned char is_boot_from_norflash(void)
{
    volatile unsigned int *p=(volatile unsigned int *)0;
    unsigned int i;
    i=*p;
    *p=0x12345678;
    if(*p==0x12345678)    //nand
    {
        *p=i;
        return 0;
    }
    else                    //nor
    {
        *p=i;
        return 1;
    }
}

  3)编写clear_bss()函数

void clear_bss(void)  //清除BSS段
{
    extern int __bss_start,__bss_end;
    int  *p=&__bss_start;
    for( ;p<&__bss_end;p++)
    {
        *p=0;
    }
}

1.4、编写链接脚本uboot.lds

  参考硬件实验里的uart.lds和u-boot-1.1.6里的u-boot.lds

SECTIONS {
    . = 0x33f80000; //0地址里存放0X33F80000
    . = ALIGN(4);
 
    .text : { *(.text) }
    . = ALIGN(4);
 
    .rodata : { *(.rodata)  }
    . = ALIGN(4);
 
    .data : {   *(.data)    }
    . = ALIGN(4);
 
    __bss_start = .;
    .bss : { *(.bss) *(COMMON) }
    __bss_end = .;
}

  其中0x33f80000就是链接地址首地址,共512K空间存放bootloader
  定义__bss_start__bss_end符号,是用来程序开始之前将这些未定义的变量清0,节省内存且bss_start-0x33f80000就等于代码的大小(copy_code_tosdram函数中len值)

二、启动内核和制作Makefile

2.1、目标

1)添加头文件setup.h和serial.h
2)写main函数
  帮内核设置串口0, (内核启动会打印出启动信息)
  把内核读入到SDRAM
  设置参数(参考u-boot-1.1.6 /lib_arm/armlinux.C中do_bootm_linux()函数)
  跳转运行内核(参考u-boot-1.1.6/lib_arm/armlinux.C中do_bootm_linux()函数)
3)写tag参数函数
  setup_start_tag (void);
  setup_memory_tags(void);
  setup_commandline_tag (char *cmdline);
  setup_end_tag (void);
4)写makefile文件

2.2、添加头文件setup.h和serial.h

1)将lcd裸板程序中串口uart0初始化文件serial.c复制到my_bootloader目录中.并修改serial.c
2)因为TAG结构体定义是存在u-boot-1.1.6/include/asm-arm/setup.h中,所以设置TAG参数需要用到这个文件,将setup.h复制到my_bootloader目录中.
3)修改setup.h文件
删除以下不需要的代码

#define __tag __attribute__((unused, __section__(".taglist")))
#define __tagtable(tag, fn) \
static struct tagtable __tagtable_##fn __tag = { tag, fn }
#define tag_member_present(tag,member)                      \
         ((unsigned long)(&((struct tag *)0L)->member + 1)  \
                   <= (tag)->hdr.size * 4)

添加以下代码

#define  u32  unsigned  long
#define  u16  unsigned  int
#define  u8   unsigned  char

2.3、编辑my_bootloader/boot.c(boor.c?)

  新建my_bootloader/boor.c,用于存放main函数(main:由start.S跳转过来的).
  main函数代码如下:

void main(void)
{
    void (*theKernel)(int zero, int arch, unsigned int params); 
    /*定义一个函数指针theKernel,其中第一个参数zero:0           */
    /* arch:机器ID ,由于芯片类型很多,内核为了辨别芯片而定义的机器ID,其中2440芯片的ID号是362,*/
    /* params :tag参数位置,这里我们的tag起始地址=0x30000100*/

    /*1 初 始 化 串 口 0 , 使 内 核 能 打 印 信 息  */
    uart0_init();                                             //调用serial.h头文件里的uart0_init()
    puts(“uart0 init OK\r\n”);                                //打印uart0初始化  

    /*2从  nand   flash  里 把 内 核 复 制 到  SDRAM  中 */
    puts(“copy  kernel  from  nand\r\n”);                  //打印内核复制
    nand_read((0x60000+64),0X30008000,0X200000);              //烧写2MB,多烧写点避免出错       
/*
0x60000+64:表示内核在nand(存储)地址上位置,
0X30008000:内核在sdram(运行)地址上位置
0X200000:内核长度2MB
因为Flash上存的内核格式是:uImage(64B头部(header)  + 真正的内核 )
在uboot界面中输入mtd命令可以看到:
              kernel分区位于 nand的0X00060000~0x00260000
所以在nand中真正的内核地址=0x60000+64,
在uboot界面中输入boot命令可以看到:
            Data Size:    1848656 Bytes =1.8 MB
            Load Address: 30008000
所以内核目的地址=0X30008000
长度=1.8MB
*/

    /*3 设  置  T  A  G  参  数        */
    puts(“set  boot  params\r\n”);                  //打印设置参数信息
    setup_start_tag (void);                      //在0X30000100地址保存start_tag数据,
    setup_memory_tags (void);                      //保存memory_tag数据,让内核知道内存多大
    setup_commandline_tag (“boottargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0”);  
    /*保存命令行bootargs参数,让内核知道根文件系统位置在/dev/mtdblock3,指定开机运行第一个脚本/linuxrc,指定打印串口0*/
    setup_end_tag (void);                            //初始化tag结构体结束
 
    /*  4 跳 转 执 行          */
    puts(“boot   kernel\r\n”);                      //打印启动内核
    theKernel = (void (*)(int, int, unsigend int))0x30008000;
    // 设置theKernel地址=0x30008000,用于后面启动内核
    theKernel(0,362,0x300000100);        //362:机器ID,  0x300000100: params(tag)地址
    /*传递参数跳转执行到0x30008000启动内核,           */
    /*相当于: mov r0,#0                             */
    /*ldr r1,=362                                  */
    /*ldr r2,= 0x300000100                         */
    /*mov pc,#0x30008000                           */
    puts(“kernel  ERROR\r\n”);                  //打印内核启动出错
}

2.4、创建TAG参数函数

设置tag参数函数代码如下

#include “setup.h”
static struct tag *params;               //定义个tag结构体变量params指针
setup_start_tag (void)                    //开始tag
{
    params = (struct tag *) 0x30000100;      //tag起始地址等于0X30000100
    params->hdr.tag = ATAG_CORE;        //头部常量tag=0x54410001
    params->hdr.size = tag_size (tag_core);    //size=5,

    params->u.core.flags = 0;
    params->u.core.pagesize = 0;
    params->u.core.rootdev = 0;
  
    params = tag_next (params);     //parmas=( struct tag *)((u32 *)parmas+ params->hdr.size)  
}
// setup_start_tag (bd)保存tag参数如下:
setup_memory_tags (void)                //内存tag
{
    int i;
    params->hdr.tag = ATAG_MEM;               //头部常量tag=0x54410002
    params->hdr.size = tag_size (tag_mem32);       //size=4
    params->u.mem.start = 0x30000000;            //SDRAM起始地址
    params->u.mem.size = 0x4000000;             //SDRAM内存大小64M
    params = tag_next (params);                  //指向下个tag
}
// setup_memory_tag s(bd)保存tag参数如下:
int strlen(char  *str)          //uboot不依赖任何库,所以需要自己写strlen函数
{
    int  i=0;
    while(str[i])
    {
        i++;
    }
    return i;
}
void strcpy(char  *dest, char  *src)
{  
    while((*dest++=*src++)!=’\0’&&*dest!=’\0’);  
}

setup_commandline_tag (char  *cmdline)     //命令行tag
/**cmdline :指向命令行参数*/
/*一般为:“boottargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0”     */
{
    int len=strlen(cmdline)+1;                       //计算cmdline长度,并加上结束符
    params->hdr.tag = ATAG_CMDLINE;             //头部常量tag=0x54410009
    params->hdr.size =(sizeof (struct tag_header) +len+3) >> 2;    /*size=(字符串长度+头部长度) >>2 */
/*“+3”表示:按4字节对齐,比如当总长度=(1,2,3,4)时,size=(总长度+3)>>2=1,实现4字节对齐         */ 
    strcpy (params->u.cmdline.cmdline, cmdline);      //复制形参字符串到params->u.cmdline.cmdline
    params = tag_next (params);                    //执行下个tag
} 
setup_end_tag (void)                            //结束tag
{
    params->hdr.tag = 0;                   
    params->hdr.size = 0;
}

2.5、写makefile文件

首先将lcd裸板程序里的makefile复制到my_bootloader目录中,并修改.

备注:在makefile中‘=’与‘:=’的区别:
  ‘=’:无关位置的等于(比如:”x=a  y=$(x)  x=b”,那么y的值永远等于最后的值,等于 b ,而不是a)
  ‘:=’:有关位置的等于(比如:”x:=a  y:=$(x)  x:=b”,那么y的值取决于当时位置的值,等于 a ,而不是b)
CC      = arm-linux-gcc               //定义CC变量=arm-linux-gcc,简化书写,编译命令,(*.C,*.S)文件生成*.O文件
LD      = arm-linux-ld                 //连接命令,将多个*.O文件生成 boot.elf 
AR      = arm-linux-ar                 //库管理命令,这里没有用到 
OBJCOPY = arm-linux-objcopy            //复制/格式转换命令, boot.elf生成boot.dis
OBJDUMP = arm-linux-objdump           //反汇编命令,boot.bin生成boot.dis
CFLAGS           := -Wall -O2         //GCC编译参数,-Wall:显示所有错误和警告, -O2:采用2级编译优化
CPPFLAGS     := -nostdinc -fno-builtin       
//添加头文件参数,-nostdinc忽略缺省目录, -fno-builtin不连接系统标准启动文件和标准库文件(表示不用自带的strlen()等库函数)
objs := start.o init.o boot.o         //定义objs变量,包含生成boot.bin目标文件需要的依赖文件
boot.bin: $(objs)                      //执行生成目标文件,首先是先满足objs所有依赖文件都拥有,才执行
         ${LD} -Tuboot.lds -o boot_elf $^
         ${OBJCOPY} -O binary -S boot_elf $@
         ${OBJDUMP} -D -m arm boot_elf > boot.dis
%.o:%.c                                //%通配符。生成xxx.o文件先要找到xxx.c文件
         ${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<   //-c编译不连接。$@表示目标文件   $<表示第一个依赖文件
%.o:%.S
         ${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
clean:
         rm -f *.bin *.elf *.dis *.o

三、编译测试以及改进

3.1、编译测试

1)将写好的uboot复制到linux下面
2)make编译,然后将错误的地方修改,生成boot.bin(编译出错的解决方案:http://www.cnblogs.com/lifexy/p/7326172.html)
3)通过make生成的反汇编来查看代码是否正确
4)通过oflash烧写到板子nand flash上
5)查看串口是否数据打印
  发现串口无数据,发现两处错误:
  5.1)在init.C中define定义 没有加大括号,没有定义volatile型

例如:
#define NFCONF       *((unsigned long *)0x4E000000)  
需要改为: #define NFCONF      (*((volatile unsigned long *)0x4E000000) )

  5.2)在init.C中nand_read_addr()函数出错,代码如下:

void nand_read_addr(unsigned int addr)
{
   volatile int i;
   NFADDR=( addr>>0)&0xff;         //A7~A0        
   for(i=0;i<10;i++);

   NFADDR=( addr>>8)&0x0f;         //A11~A8
   for(i=0;i<10;i++);

   NFADDR=( addr>>12)&0xff;         //A19~A12
   for(i=0;i<10;i++);

   NFADDR=( addr>>20)&0xff;         //A27~A20
   for(i=0;i<10;i++);

   NFADDR=( addr>>28)&0xff;         //A28
   for(i=0;i<10;i++);  
}

  上面之所以错是因为nand flash是2048B一页,这里的写的一页是A11~A0,其值=4096,已经属于两页大小了
  应该改为:

void nand_read_addr(unsigned int addr)
{
   unsigned int col  = addr % 2048;
   unsigned int page = addr / 2048;
   volatile int i;

   NFADDR=(col>>0)&0xff;         //A7~A0
   for(i=0;i<10;i++);

   NFADDR=(col>>8)&0x0f;         //A10~A8
   for(i=0;i<10;i++);

   NFADDR=(page>>0)&0xff;         //A18~A11
   for(i=0;i<10;i++); 

   NFADDR=(page>>8)&0xff;         //A26~A19
   for(i=0;i<10;i++);

   NFADDR=(page>>16)&0xff;         //A27
   for(i=0;i<10;i++);   
}

3.2、优化改进(加快内核启动时间)

3.2.1、提高CPU频率

  将CPU频率设为最大值400MHZ(内核启动时间7S变为6S,因为HCLK和PCLK频率没有改变)
  然后分频系数FCLK:HCLK:PCLK需要设置为1:4:8
  因为HCLK最高133MHZ,这里需要设置为100MHZ
  PCLK最高50MHZ,这里需要设置为50HZ
  如下图所示,得出 CLKDIVN寄存器需要等于0X5即可
  通过下图看出,提高CPU频率需要设置MPLLCON中MDIV、PDIV、SDIV
  通过下图得出,取400MHZ时,设置MDIV为0X5C,PDIV为0x1,SDIV为0x1
所以改进代码如下:

#define S3C2440_MPLL_400MHZ     ((0x5c<<12)|(0x01<<4)|(0x01))  //设置FCLK=400MHZ
/*    2.设置时钟(必须设为异步总线模式)  */
   ldr r0,=CLKDIVN
   mov r1,#5                                  /*FCLK:HCLK:PCLK=1:4:8*/
   str r1,[r0]
   mrc p15, 0, r1, c1, c0                 /* 读出控制寄存器 */
   orr  r1, r1, #0xc0000000               /* 设置为“asynchronous bus mode” */
   mcr   p15, 0, r1, c1, c0, 0               /* 写入控制寄存器 */

   ldr r0,=MPLLCON
   ldr r1,= S3C2440_MPLL_400MHZ      //使用复杂的数不能用mov,需要用ldr
   str r1,[r0]

3.2.2、开启ICAHE(内核启动时间6S变为1.5S)

CAHE介绍:
  通过高速缓存存储器来加快对内存的数据访问,在CAHE中有ICAHE(指令缓存)和DCAHE(数据缓存)
  ICAHE: 指令缓存,用来存放执行这些数据的指令
  DCAHE:用来存放数据,需要开启MMU才能开启DCAHE
  在没开启ICAHE之前,CPU读取SDRAM地址数据时,每次都需要先访问一次地址值,在读数据.
  当开了ICAHE后,第一次读取SDRAM地址数据时,ICAHE发现缓存里没有这个地址数据,然后将SDRAM中需要读取的那部分一大块内存数据都复制在缓存中,后面陆续读取数据就不会再访问SDRAM了,直到CPU没有找到地址数据后ICAHE再从SDRAM中重新复制

通过CP15协处理器来开启ICAHE
   (CP15协处理器操作用法: http://www.cnblogs.com/lifexy/p/7203786.html)
  从上面链接中可以找到ICAHE控制位在CP15的寄存器C1中位12(如下图), 然后通过MRS和MSR向该位12置1,开启ICAHE
  所以代码如下(放在SDRAM初始化之前)

   mrc p15, 0, r0, c1, c0, 0                     //将 CP15 的寄存器 C1 的值读到 r0 中
   orr r0, r0, #(1<<12)                          //将r0中位12置1     
   mcr p15,0, r0,c1,c0,0                         //开启ICAHE

参考链接:
https://www.cnblogs.com/lifexy/p/7337475.html

转载于:https://www.cnblogs.com/princepeng/p/11572616.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值