自己写一个最简单的bootloader_jz2440

boot是为了启动内核,本质上也就是一个裸板程序,就是为了引导内核的启动。所以打算自己写一个boot,功能只有引导内核启动。

首先是汇编的代码段,是为了关闭看门狗,设置时钟以及代码的重定位,这些都是在main函数之前执行的。之前学习单片机的时候,我们只看到main函数,实际上是main之前的执行步骤都被包起来了。

整个汇编文件的开头要写上

.text               @这是为了表示这是一个代码段
.global _start
_start:

第一步:关闭看门狗,2440是默认关门狗打开的,如果不关闭看门狗,三秒钟后板子会自动重启

/* 关看门狗 */
    ldr     r0,     = 0x53000000        /* 看门狗的寄存器地址,通过芯片手册可以查看 */
    mov r1,     #0              /* 把0放入r1*/
    str     r1,  [r0]               /* 把r1中的0赋给看门狗,即关闭看门狗 */

第二步:设置时钟,2440板子的晶振是12M(也可以使用16M或其他的),如果不设置时钟去倍频,板子是以12M的速度跑的,这里是设置分频系数以及设置板子的频率为400M
//经过实测,boot在200M和400M的情况下,都需要6秒才能启动内核,速度有点慢,所以使用了ICACHE提高速度,使用ICACHE以后,启动内核只需要2秒,可以接受

#define     S3C2440_MPLL_400MHZ     ((0x5c<<12)|(0x01<<4)|(0x01))
/* 设置时钟 */
    ldr     r0,     = 0x4c000014
    mov r1, #0x05           //FCLK:HCLK:PCLK=1:4:8
    str     r1,     [r0]

    ldr     r0,     =0x4c000004
    ldr     r1,     =S3C2440_MPLL_400MHZ
    str     r1,     [r0]

/******以下为启动ICACHE*******/
/* 启动ICACHE */
    mrc  p15, 0, r0, c1, c0, 0
    orr r0, r0, #(1<<12)
    mcr  p15, 0, r0, c1, c0, 0
/******* 以上为启动ICACHE *****/

启动Icahe的原理是这样:每次CPU去RAM读代码,很费力,ICACHE就是把这段代码copy到片子里的一个区域,CPU直接去读这块区域就行了,而且这块区域的读速度比RAM快多了。就好像每天都有一份快递,自己每天去取费时费力,直接让快递员送门口,就省很多事。

第三步:初始化SDRAM,代码需要在SDRAM中执行,所以需要初始化

/* 初始化SDRAM */
    ldr     r0,     =MEM_CTL_BASE       //设置SDRAM寄存器的首地址
    adr     r1,     sdram_config            /*SDRAM每个寄存器的配置值 */
    add r3,     r0, #(13 * 4)           //SDRAM寄存器的尾地址

1:
    ldr r2,     [r1], #4                //把寄存器的配置值写入r2,然后r1地址加4字节,定位到sdram_config的下一个配置值
    str r2,     [r0], #4                //r2里的值写入到r0地址,也就是SDRAM的第一个寄存器,然后寄存器地址加四字节,指
//向下一个寄存器
    cmp r0,     r3                  //当前寄存器地址和寄存器尾地址比较,如果不一致说明还没配置完,跳转到1继续循环
    bne     1b

/*放在文件末尾,这是关于SDRAM的每个寄存器的配置值*/
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

第四步:代码重定位,因为代码是存在NOR FLASH 或者 NAND FLASH里面的,CPU要将代码移入SDRAM中才可以使用

/* 重定位 */
    ldr sp, =0x34000000     //因为nand初始化比较复杂,使用C语言实现,如果要使用C语言,需要设置堆栈指针

    bl nand_init                //初始化nand,其实如果使用的是nor,完全可以不初始化nand,这里是考虑到不知道使用的是nor还是nand,所以把nand初始化了,之后会判断使用的是什么flash。之所以不用初始化nor,是因为CPU可以直接读nor

    mov r0,     #0              //设置copy_code_to_sdram的参数,r0是该函数第一个参数,r1是第二个,r2是第三个,很好理解
    ldr     r1, =_start
    ldr     r2, = __bss_start
    sub     r2, r2, r1

    bl copy_code_to_sdram       //调用C函数,实现将代码copy至SDRAM
    bl clear_bss                //清除BSS段,未初始化或初始化为0的变量都存在这里,所以需要清零

bss段挺好玩的,这样的,如果你程序里面设置了很多变量,初始值都为0,那么程序一开始一个一个去赋0效率太低了,所以所有初始值为0的变量,都保存在bss代码段里,程序启动前将整个代码段清零就行了,就很方便

第五步:执行main函数

/* 执行main */
    ldr lr, =halt       //设置main函数的返回地址,其实boot启动内核以后直接就死掉了,根本不会返回,这里还是写一下返回地址,main返回以后去执行下面的halt段,不断地死循环,防止出问题
    ldr pc, =main       //指针定位到main函数

halt:
    b   halt

/* 重定位过程中使用的C函数 挑了几个稍微重要点的,别的都不写了,没什么意思*/
/* nand初始化,没什么意思,就不多写了 */

void nand_init (void)
{
#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);
}

/* 复制代码段到SDRAM*/
//这里就是判断是NOR还是NAND,因为它们的特性不同,导致nand可读可写,NOR只能读,所以测试的时候就随便找个地址,往里写一个数,再往外读,如果值被修改了,说明是nand,反之是nor

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_read((unsigned int)src, dest, len);
    }
}
/* main函数 */
int main (void)
{
    void (*theKernel)(int zero, int arch, unsigned int params);     //申明内核的指针

    /*0:  设置串口,内核启动的开始部分会从串口打印一些信息,但是内核一开始并没有初始化串口,提前初始化好,免得内核没有串口用 */
    uart0_init();

    /* 1.从NAND FLASH 里把内核读入内存 */
    puts("Copy kernel from nand\n\r");
    nand_read(0x60000 + 64, (unsigned char *)0x30008000, 0x200000);     /*读出地址是根据板子看的,mtd命令可以看板子的分区,找到kernel分区就行了*/
                                                    /* 存入地址在内核启动的时候会显示, 大小也是,板子用的内核是1.8M ,这里给了2M 空间 */

    /* 2. 设置参数 */
//这里设置各种传给内核的参数,因为启动内核以后boot就死了,没法和内核面对面交流,boot将一些命令放在一块区域里,内核启动以后去这个地址读就行了
    puts("Set boot params\n\r");
    setup_start_tag();
    setup_memory_tag();
    setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0");
    setup_end_tag();

    /* 3. 跳转执行 */
    puts("Boot kernel\n\r");
    theKernel = ((void (*)(int, int, uint))0x30008000);
    theKernel(0, 362, 0x30000100);      //第一个参数写的就是0,我就直接写0了,第二个参数是ID,2440的ID是这个,第三个参数是参数存放的地址,内核直接去这个地址读就可以了

    /* 如果一切正常,不会执行到这里,也就是内核启动以后不会再回到boot */
    puts("Error!\n\r");

    return -1;
}

/* 给内核传入的各种命令 */
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_tag (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);
}
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;
}

猜你喜欢

转载自blog.csdn.net/ltc844139730/article/details/52171575