自己编写简单的bootloader笔记

3720阅读 0评论2013-03-02 rabbit2013
分类:嵌入式

bootloader的最终目的是启动内核,而在启动内核前要进行一系列的初始化:
关闭看门狗、改变系统时钟、初始化存储控制器、重定位代码(将更多的代码复制到内存中去)等,
然后将内核从nand flash读到SDRAM中,为内核传递启动参数,跳到相应的地址启动内核。


1. 关闭看门狗
   向WTCON寄存器WTCON中写入零
   汇编代码:
    ldr r0, =0x53000000
 mov r1, #0
 str r1, [r0]
   C代码:(调用C代码之前必须先设置栈,即sp指针,指令mov sp, #4096)
    #define WTCON   (*(volatile unsigned long *)0x53000000)

    void disable_watch_dog(void)
 {
     WTCON = 0;  // 关闭WATCHDOG很简单,往这个寄存器写0即可
 }

2. 设置系统时钟
    汇编代码:
     #define S3C2440_MPLL_400MHZ     ((0x5c<<12)|(0x01<<4)|(0x01))
   
     ldr r0, =0x4c000014
 // mov r1, #0x03;     // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1
 mov r1, #0x05;     // FCLK:HCLK:PCLK=1:4:8
 str r1, [r0]

 /* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
 mrc p15, 0, r1, c1, c0, 0  /* 读出控制寄存器 */
 orr r1, r1, #0xc0000000   /* 设置为“asynchronous bus mode” */
 mcr p15, 0, r1, c1, c0, 0  /* 写入控制寄存器 */

 /* MPLLCON = S3C2440_MPLL_200MHZ */
 ldr r0, =0x4c000004
 ldr r1, =S3C2440_MPLL_400MHZ
 str r1, [r0]
  
   C代码:
    void clock_init(void)
 {
   // LOCKTIME = 0x00ffffff;   // 使用默认值即可
  CLKDIVN  = 0x03;            // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1
 // 潜入汇编的写法,语法上的要求。
     /* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
 __asm__(
   "mrc    p15, 0, r1, c1, c0, 0\n"        /* 读出控制寄存器 */
   "orr    r1, r1, #0xc0000000\n"          /* 设置为“asynchronous bus mode” */
   "mcr    p15, 0, r1, c1, c0, 0\n"        /* 写入控制寄存器 */
   );
 
 
  MPLLCON = S3C2440_MPLL_200MHZ;  /* 现在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */
    
 }

3. 初始化SDRAM
   汇编代码:
    ldr r0, =MEM_CTL_BASE
 adr r1, sdram_config     /* sdram_config的当前地址 */
 add r3, r0, #(13*4)
   1:
 ldr r2, [r1], #4
 str r2, [r0], #4
 cmp r0, r3                 // 比较r0和r1的值
 bne 1b                     // bne  表示如果不相同跳转的标号为1的地方,后面跟一个b表示跳转到前面的1标号,如果跳转到后面去将b改为f即可
 
   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

   C代码:
    
 void memsetup(void)
 {
     volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;
 
     /* 这个函数之所以这样赋值,而不是像前面的实验(比如mmu实验)那样将配置值
      * 写在数组中,是因为要生成”位置无关的代码”,使得这个函数可以在被复制到
      * SDRAM之前就可以在steppingstone中运行
      */
     /* 存储控制器13个寄存器的值 */
     p[0] = 0x22011110;     //BWSCON
     p[1] = 0x00000700;     //BANKCON0
     p[2] = 0x00000700;     //BANKCON1
     p[3] = 0x00000700;     //BANKCON2
     p[4] = 0x00000700;     //BANKCON3 
     p[5] = 0x00000700;     //BANKCON4
     p[6] = 0x00000700;     //BANKCON5
     p[7] = 0x00018005;     //BANKCON6
     p[8] = 0x00018005;     //BANKCON7
    
                                     /* REFRESH,
                                      * HCLK=12MHz:  0x008C07A3,
                                      * HCLK=100MHz: 0x008C04F4
                                      */
     p[9]  = 0x008C04F4;
     p[10] = 0x000000B1;     //BANKSIZE
     p[11] = 0x00000030;     //MRSRB6
     p[12] = 0x00000030;     //MRSRB7
 }

 /*
  * 初始化SDRAM后,必须重新设置栈,且将sp指针内存的指向最高,因为栈是重高地址向低地址向下增长的,
  * 即使用命令ldr sp, =0x34000000 (将0x34000000赋值给sp指针,ldr是一条伪指令,当0x34000000数字很大的时候不能转换为一个立即数的时候,会通过几条汇编指令来完成)
  */
4. 初始化nand控制器
 bl nand_init   // 汇编调用C函数
 
 /* 初始化NAND Flash */
 void nand_init(void)
 {
 // 这三个值结合S3C2440手册和nand flash手册设置时序
 #define TACLS     0  
 #define TWRPH0  1
 #define TWRPH1  0
  /* 设置时序 */
         NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
         /* 使能NAND Flash控制器, 初始化ECC, 禁止片选 */
         NFCONT = (1<<4)|(1<<1)|(1<<0);
 }
 
5. 重定位代码
 // 汇编中调用C函数时,r1传递函数的第一个参数,r2传递函数的第二个参数,r3传递函数的第三个参数
 mov r0, #0
 ldr r1, =_start        // 来自汇编代码的第一行
          // .text
          // .global _start
          // _start:
 ldr r2, =__bss_start   // __bss_start 来自链接脚本
 sub r2, r2, r1
 
 
 bl copy_code_to_sdram
 
 void copy_code_to_sdram(unsigned char *src, unsigned char *dest, unsigned int len)
 { 
  int i = 0;
  
  /* 如果是NOR启动 */
  if (isBootFromNorFlash())
  {
   while (i < len)
   {
    dest[i] = src[i];
    i++;
   }
  }
  else
  {
   //nand_init();
   nand_read((unsigned int)src, dest, len);
  }
 }
 
 
 void nand_select(void)
 {
  NFCONT &= ~(1<<1); 
 }
 
 void nand_deselect(void)
 {
  NFCONT |= (1<<1); 
 }
 
 void nand_cmd(unsigned char cmd)
 {
  volatile int i;
  NFCMMD = cmd;
  for (i = 0; i < 10; i++);
 }
 
 void nand_addr(unsigned int addr)
 {
  unsigned int col  = addr % 2048;
  unsigned int page = addr / 2048;
  volatile int i;
 
  NFADDR = col & 0xff;
  for (i = 0; i < 10; i++);
  NFADDR = (col >> 8) & 0xff;
  for (i = 0; i < 10; i++);
  
  NFADDR  = page & 0xff;
  for (i = 0; i < 10; i++);
  NFADDR  = (page >> 8) & 0xff;
  for (i = 0; i < 10; i++);
  NFADDR  = (page >> 16) & 0xff;
  for (i = 0; i < 10; i++); 
 }
 
 void nand_wait_ready(void)
 {
  while (!(NFSTAT & 1));
 }
 
 unsigned char nand_data(void)
 {
  return NFDATA;
 }
 
 void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
 {
  int col = addr % 2048;
  int i = 0;
   
  /* 1. 选中 */
  nand_select();
 
  while (i < len)
  {
   /* 2. 发出读命令00h */
   nand_cmd(0x00);
 
   /* 3. 发出地址(分5步发出) */
   nand_addr(addr);
 
   /* 4. 发出读命令30h */
   nand_cmd(0x30);
 
   /* 5. 判断状态 */
   nand_wait_ready();
 
   /* 6. 读数据 */
   for (; (col < 2048) && (i < len); col++)
   {
    buf[i] = nand_data();
    i++;
    addr++;
   }
   
   col = 0;
  }
 
  /* 7. 取消选中 */  
  nand_deselect();
 }


   链接脚本为:
   SECTIONS {

     . = 0x33f80000;           // 起始链接地址
     .text : { *(.text) }      // 代码段
     . = ALIGN(4);             // 四字节对齐
    
     .rodata :  {*(.rodata*)}  // 只读数据段
     . = ALIGN(4);
    
     .data :  { *(.data) }     // 数据段
     . = ALIGN(4);
    
     __bss_start = .;          //bss段开始地址
     .bss ALIGN(4)  : { *(.bss)  *(COMMON) }
     __bss_end = .;            //bss段结束地址
 }


6. 清bss段(bss段是没有初始化的全局变量和初始化为零的全局变量)

 void clear_bss(void)
 {
  extern int __bss_start, __bss_end;  // __bss_start, __bss_end变量的值在链接脚本中设置的
  int *p = &__bss_start;
  
  for (; p < &__bss_end; p++)
   *p = 0;
 }
    
7. 传递启动参数
   a. 初始化串口
    void uart0_init(void)
 {
     GPHCON  |= 0xa0;    // GPH2,GPH3用作TXD0,RXD0
     GPHUP   = 0x0c;     // GPH2,GPH3内部上拉
 
     ULCON0  = 0x03;     // 8N1(8个数据位,无较验,1个停止位)
     UCON0   = 0x05;     // 查询方式,UART时钟源为PCLK
     UFCON0  = 0x00;     // 不使用FIFO
     UMCON0  = 0x00;     // 不使用流控
     UBRDIV0 = UART_BRD; // 波特率为115200
 }

   b. 将内核从nand flash中读到SDRAM中
    nand_read(0x60000+64, (unsigned char *)0x30008000, 0x200000);
    读入的内核是uImage,它包含64字节的头部和内核文件。
    
   c. 设置启动参数
    /* 2. 设置参数 */
 puts("Set boot params\n\r");
 setup_start_tag();
 setup_memory_tags();
 setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0");
 setup_end_tag();

 
 static struct tag *params;
 
 void setup_start_tag(void)
 {
  params = (struct tag *)0x30000100;   // 设置传入参数的首地址
 
  params->hdr.tag = ATAG_CORE;        
  params->hdr.size = tag_size (tag_core);
 
  params->u.core.flags = 0;
  params->u.core.pagesize = 0;
  params->u.core.rootdev = 0;
 
  params = tag_next (params);
 }
 
 void setup_memory_tags(void)
 {
  params->hdr.tag = ATAG_MEM; 
  params->hdr.size = tag_size (tag_mem32);
  
  params->u.mem.start = 0x30000000;     // 设置内存的首地址
  params->u.mem.size  = 64*1024*1024;   // 设置内存的大小
  
  params = tag_next (params);
 }
 
 int strlen(char *str)
 {
  int i = 0;
  while (str[i])
  {
   i++;
  }
  return i;
 }
 
 void strcpy(char *dest, char *src)
 {
  while ((*dest++ = *src++) != '\0');
 }
 
 void setup_commandline_tag(char *cmdline)   // 设置参数
 {
  int len = strlen(cmdline) + 1;
  
  params->hdr.tag  = ATAG_CMDLINE;
  params->hdr.size = (sizeof (struct tag_header) + len + 3) >> 2;
 
  strcpy (params->u.cmdline.cmdline, cmdline);
 
  params = tag_next (params);
 }
 
 void setup_end_tag(void)
 {
  params->hdr.tag = ATAG_NONE;
  params->hdr.size = 0;
  
8. 启动内核

 void (*theKernel)(int zero, int arch, unsigned int params);   // 定义一个函数指针
 theKernel = (void (*)(int, int, unsigned int))0x30008000;     // 设置函数指针的值
 theKernel(0, 1999, 0x30000100);                               // 跳到该地址去执行,启动内核

 

上一篇:rm 误操作恢复
下一篇:解决 Agent admitted failure to sign using the key 问题 with ssh