zoukankan      html  css  js  c++  java
  • (嵌入式开发)自己写bootloader之编写第一阶段


    最简单的bootloader的编写步骤:
    1. 初始化硬件:关看门狗、设置时钟、设置SDRAM、初始化NAND FLASH
    2. 如果bootloader比较大,要把它重定位到SDRAM
    3. 把内核从NAND FLASH读到SDRAM
    4. 设置"要传给内核的参数"
    5. 跳转执行内核


    改进:
    1. 提高CPU频率, 200MHZ ==> 400MHZ
    2. 启动ICACHE

    重定位
    分为nor启动和nand启动

    int isBootFromNorFlash(void)
    {
    volatile int *p = (volatile int *)0;
    int val;


    val = *p;
    *p = 0x12345678;
    if (*p == 0x12345678)
    {       
    /* 写成功, 是nand启动 */
    //nand启动时,0地址对应内存,内存是可以写的
    *p = val;//回复原来的值
    return 0;
    }
    else
    {
    /* NOR不能像内存一样写 */
    return 1;
    }
    }



    TACLS、TWRPH0 、TWRPH1的设置

    nand_read
    读页数据读到页寄存器
    nand结构







    bootloader的最终目的是启动内核,而在启动内核前要进行一系列的初始化:
    关闭看门狗、改变系统时钟、初始化存储控制器、重定位代码(将更多的代码复制到内存中去)等,
    然后将内核从nand flash读到SDRAM中,为内核传递启动参数,跳到相应的地址启动内核。
    <pre name="code" class="objc" style="widows: 1;">#define S3C2440_MPLL_200MHZ     ((0x5c<<12)|(0x01<<4)|(0x02))
    #define MEM_CTL_BASE    0x48000000
    
    
    <pre name="code" class="objc" style="widows: 1;">.text                 //指定了后续编译出来的内容放在代码段【可执行】;
    .global _start  //告诉编译器后续跟的是一个全局可见的名字【可能是变量,也可以是函数名】;
    _start:            /*_start是一个函数的起始地址,也是编译、链接后程序的起始地址。由于程序是通过加载器来加载的,
                           必须要找到 _start名字的函数,因此_start必须定义成全局的,以便存在于编译后的全局符合表中,
                           供其它程序【如加载器】寻找到。*/
    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
    " /* 读出控制寄存器 */ 
    "orr r1, r1, #0xc0000000
    " /* 设置为“asynchronous bus mode” */
    "mcr p15, 0, r1, c1, c0, 0
    " /* 写入控制寄存器 */
    );
    /*******************************************************************
     *                 时钟初始化函数
     * 对于MPLLCON寄存器,[19:12]为MDIV,[9:4]为PDIV[1:0]为SDIV
     * 计算公式如下:
     * S3C2410 : MPLL(FCLK)=(m*fin)/(p*2^s)
     * S3C2440 : MPLL(FCLK)=(2*m*Fin)/(p*2^s)
     * 其中:m=MDIV+8;    p=PDIV+2; s=SDIV
     * 设置CLKDIVN,令分频比为:FCLK:HCLK:PCLK=1:2:4
     * 由于开发板的输入时钟为12MHz,而且设置MDIV PDIV SDIV分别为
     * S3C2410 : MDIV=0x5C      PDIV=0x04    SDIV=0x00
     * S3C2440 :MDIV=0x12   PDIV=0x01    SDIV=0x02
     * 则有:FCLK=200MHz      HCLK=100MHz   PCLK=50MHz
    *******************************************************************/
    
    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//将r1地址中的内容存到r2中,同时r1=r1+4
    str r2, [r0], #4//将r2中的值存到r0所指定的地址中, 同时r0=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//从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:Mapreduce实例——WordCount
    暑期生活10
    暑期生活9
    暑期生活8
    暑期生活7
    暑期生活6
    暑期生活5
    暑期生活4
    暑期生活3
    暑期生活2
  • 原文地址:https://www.cnblogs.com/Zyf2016/p/6337830.html
Copyright © 2011-2022 走看看