自己写bootloader

版权声明:本文采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可,欢迎转载,但转载请注明来自hceng blog(www.hceng.cn),并保持转载后文章内容的完整。本人保留所有版权相关权利。 https://blog.csdn.net/hceng_linux/article/details/89838895

CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2017/04/13/自己写bootloader/#more
参考写了个bootloader。


0.编写bootloader步骤

  1. 初始化硬件:关看门狗、设置时钟、设置SDRAM、初始化NAND FLASH;
  2. 因为bootloader比较大(大于4K),需要重定位到SDRAM;
  3. 把内核从NAND FLASH读到SDRAM;
  4. 设置“要传给内核的参数”;
  5. 跳转启动内核;

1.初始化硬件

1.1 关看门狗

{% codeblock lang:asm [start.S] %}
.text
.global _start
_start:
/* 1. 关看门狗 :向寄存器WTCON(0x53000000)写0即可;P462;*/
ldr r0, =0x53000000 //将0x53000000放入r0;
mov r1, #0 //将0放入r1中;
str r1, [r0] //将r1中的0放入r0所指的存储器中;
{% endcodeblock %}

1.2 设置时钟

{% codeblock lang:asm [start.S] %}
/* 2. 设置时钟:配置寄存器CLKDIVN(0x4C000014)设置分频;P260; /
ldr r0, =0x4c000014
mov r1, #0x05; //FCLK:HCLK:PCLK=1:4:8
str r1, [r0]
/
如果HDIVN非0,CPU的总线模式应设置为“asynchronous bus mode”;P244 /
mrc p15, 0, r1, c1, c0, 0 //读出控制寄存器;
orr r1, r1, #0xc0000000 //设置为“asynchronous bus mode”;
mcr p15, 0, r1, c1, c0, 0 //写入控制寄存器;
/
设置MPLLCON = S3C2440_MPLL_400MHZ; P256 */
ldr r0, =0x4c000004
ldr r1, =(0x5c<<12)|(0x01<<4)|(0x01)
str r1, [r0]

/*优化速度: 启动ICACHE */
mrc p15, 0, r0, c1, c0, 0    @ read control reg
orr r0, r0, #(1<<12)
mcr    p15, 0, r0, c1, c0, 0 @ write it back	

{% endcodeblock %}

1.3 初始化SDRAM

{% codeblock lang:asm [start.S] %}
/初始化SDRAM:依次向SDRAM寄存器 写入sdram_config中的值/
ldr r0, = 0x48000000 //将SDRAM寄存器基地址写入r0;
adr r1, sdram_config //将sdram_config的当前地址写入r1;
add r3, r0, #(134) //r3=r0+134,即为SDRAM寄存器结尾地址;
1:
ldr r2, [r1], #4 //将r1寄存器的值放入r2,并将r1的地址+4;
str r2, [r0], #4 //将r2的值写入r0,并将r0的地址+4;
cmp r0, r3 //比较r0和r3,判断是否设置到了最后一个寄存器;
bne 1b //不相等即没有设置完,跳到前面(b)的1处;
… …
… …
sdram_config: //把要设置的值,存在这里;P56;
.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
{% endcodeblock %}


2.重定位

{% codeblock lang:asm [start.S] %}
/把bootloader本身的代码从flash复制到它的链接地址去/
ldr sp, =0x34000000 //在调用C语言前,需要先设置栈,即SP指向一个地址;1

bl nand_init             //初始化nand flash;[2]

mov r0, #0               //设置copy_code_to_sdram传入参数;参数1:r0=0;
ldr r1, =_start          //参数2:r1=_start;
ldr r2, =__bss_start     //r2=__bss_start
sub r2, r2, r1           //参数3:r2=r2-r1 即除去_bss段的部分;
bl copy_code_to_sdram    //执行复制代码到SDRAM的函数;[3]

bl clear_bss             //执行清理_bss段函数;[4]

{% endcodeblock %}

栈是由上向下增加的,只要SP指向SDRAM最高处就行。

[2]:为何在这里初始化Nand Flash?
  不管后面的copy_code_to_sdram是Nand启动还是Nor启动,最后都会拷贝Nand上面的内核到SDRAM,所以早晚都得初始化。

{% codeblock lang:c [init.c] %}
void nand_init(void)
{
#define TACLS 0
#define TWRPH0 1
#define TWRPH1 0
NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4); //设置时序;P225;
NFCONT = (1<<4)|(1<<1)|(1<<0); //使能NAND Flash控制器, 初始化ECC, 禁止片选;P226;
}
{% endcodeblock %}

start.S中的所有的C函数都在init.c中(除了最后的main),copy_code_to_sdram()先判断是Nor启动还是Nand启动。

  • 如果是Nor启动:由于Nor Flash特性(可以像内存一样读,但不能像内存一样写),直接复制上面的代码到SDRAM中的链接地址即可;

  • 如果是Nand启动:2440上电后,硬件上将Nand上前4k拷贝到内部RAM中运行,这4k代码再将后续的整个代码拷贝到SDRAM中的链接地址。
    {% codeblock lang:c [init.c] %}
    //传入参数:*src源地址;*dest目的地地址;len数据长度;
    void copy_code_to_sdram(unsigned char *src, unsigned char *dest, unsigned int len)
    {
    int i = 0;

    if (isBootFromNorFlash()) //如果是NOR启动;
    {
    while (i < len) //将len长度的数据,从源地址复制到目标地址上去;
    {
    dest[i] = src[i];
    i++;
    }
    }
    else
    {
    nand_read((unsigned int)src, dest, len); //nand_read较麻烦,再后面介绍;
    }
    }
    {% endcodeblock %}

判断是Nand还是Nor:
(利用Nor Flash可以像内存一样读,但不能像内存一样写的特性)
{% codeblock lang:c [init.c] %}
int isBootFromNorFlash(void)
{
volatile int *p = (volatile int *)0;
int val;

val = *p;               //备份原来0地址的值;
*p = 0x12345678;        //向0地址写值;
if (*p == 0x12345678)   //判断是否写入成功;
{
    *p = val;    	    //写成功, 是nand启动,恢复原来的值;
    return 0;
}
else
{
     return 1;          //NOR不能像内存一样写;
}

}
{% endcodeblock %}
Nand Flash结构:


Nand Flash读操作:

{% codeblock lang:c [init.c] %}
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();
}

/* nand_read 涉及的子函数*/
/* 1.选中Nand;P226*/
void nand_select(void)
{
NFCONT &= ~(1<<1);
}
/* 2.取消选中Nand /
void nand_deselect(void)
{
NFCONT |= (1<<1);
}
/
3.nand命令 /
void nand_cmd(unsigned char cmd)
{
volatile int i;
NFCMMD = cmd;
for (i = 0; i < 10; i++);
}
/
4.nand地址 */
void nand_addr(unsigned int addr)
{
unsigned int col = addr % 2048; //对2k取余,得到列;
unsigned int page = addr / 2048; //对2k取整,得到行;
volatile int i;

NFADDR = col & 0xff; 			//列的7-0位;
for (i = 0; i < 10; i++);
NFADDR = (col >> 8) & 0xff;        //列的15-8位;
for (i = 0; i < 10; i++);

NFADDR = page & 0xff;			//行的7-0位;
for (i = 0; i < 10; i++);
NFADDR = (page >> 8) & 0xff;      //行的15-8位;
for (i = 0; i < 10; i++);
NFADDR = (page >> 16) & 0xff;     //行的23-16位;
for (i = 0; i < 10; i++);    

}
/* 5.等待读结束 /
void nand_wait_ready(void)
{
while (!(NFSTAT & 1));
}
/
6.nand数据 */
unsigned char nand_data(void)
{
return NFDATA;
}
{% endcodeblock %}

清理_bss段:
  在start.S跳转到main前,还需要清理_bss段。_bss段存放的是未初始化的全局变量或者初始化为0的全局变量,用于处理可能过多0占用过多空间的情况。现在清0,后续要使用时,再赋0使用(勘误:清理bss段的目的,并不是节约空间,而是为未初始化的全局变量赋值为0。在操作系中写应用程序或者使用集成开发环境写裸机代码,都能保证未定义的全局变量为0,因为操作系统、集成开发环境做了类似清理bss段的操作。而我们自己从头写裸机,需要自己清理bss段。参考文章:Why do we have to clear bss in assembly?)。
{% codeblock lang:c [init.c] %}
void clear_bss(void)
{
extern int __bss_start, __bss_end; //声明链接脚本的链接地址;
int *p = &__bss_start;

for (; p < &__bss_end; p++)         //以_bss_start开始,清理到_bss_end;
    *p = 0;

}
{% endcodeblock %}

链接脚本:
{% codeblock lang:asm [boot.lds] %}
SECTIONS {
. = 0x33f80000;
.text : { *(.text) } //代码段在0x33f80000,即链接地址,重定位后程序运行的地址;

    . = ALIGN(4);            //四字节对齐(32位);
    .rodata : {*(.rodata*)}  //只读数据段
    
    . = ALIGN(4);
    .data : { *(.data) }     //数据段
    
    . = ALIGN(4);
    __bss_start = .;         //_bss开始
    .bss : { *(.bss) *(COMMON) }
    __bss_end = .;           //_bss结束

}
{% endcodeblock %}


3.把内核读到SDRAM

前面start.S中调用init.c中的nand_init初始化了Nand Flahsh和写好了nand_read()函数。start.S最后跳到main函数中,而mian函数boot.c中。
{% codeblock lang:c [boot.c] %}
/* 0. 帮内核设置串口: 内核启动的开始部分会从串口打印一些信息,但是内核一开始没有初始化串口 /
uart0_init(); //略,看相应章节设置;
{% endcodeblock %}
把内核读到SDRAM:
{% codeblock lang:c [boot.c] %}
/
1. 从NAND FLASH里把内核读入内存 */
nand_read(0x60000+64, (unsigned char *)0x30008000, 0x400000);
{% endcodeblock %}

  • 第一个参数:源地址:0x60000+64
      Nand Flash一般分区情况如图,kernel设计的位置在0x60000,这里加64是因为内核格式为uImage,uImage结构是64字节头+zImage。
  • 第二个参数:目的地址:0x3000 8000
      即为Nand Flash的起始地址。
  • 第三个参数:长度:0x200000
      内核大小,后面内核大于2M,这里为0x4000 0000。

4.设置要传给内核的参数

{% codeblock lang:c [boot.c] %}
/* 2. 设置参数 */
setup_start_tag();
setup_memory_tags();
setup_commandline_tag(“noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 rootfstype=yaffs2”);
setup_end_tag();
{% endcodeblock %}
  主要分四部分:开始、内存信息、命令信息、结束。


5.跳转启动内核#

{% codeblock lang:c [boot.c] %}
/* 3. 跳转执行 /
theKernel = (void (
)(int, int, unsigned int))0x30008000;
theKernel(0, 362, 0x30000100);
{% endcodeblock %}
  函数指针指向0x3000 8000,即指向内核位置,跳转到内核。
theKernel()参数:

  • 第一个参数:固定的0;
  • 第二个参数:机器ID;
  • 第三个参数:要传入给内核的参数位置;

6.其它

相关源码
参考:韦东山老师毕业班视频

猜你喜欢

转载自blog.csdn.net/hceng_linux/article/details/89838895