CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2017/04/13/自己写bootloader/#more
参考写了个bootloader。
0.编写bootloader步骤
- 初始化硬件:关看门狗、设置时钟、设置SDRAM、初始化NAND FLASH;
- 因为bootloader比较大(大于4K),需要重定位到SDRAM;
- 把内核从NAND FLASH读到SDRAM;
- 设置“要传给内核的参数”;
- 跳转启动内核;
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.其它
相关源码
参考:韦东山老师毕业班视频;