一、为什么要用Bootloader?
1、对于不同的CPU体系结构都有不同的Bootloader,例如ARM、MIPS都有自己的Bootloader。除了依赖于CPU的体系结构外,Bootloader还依赖于具体的嵌入式板级设备的配置,比如ARM架构CPU会有不同的板卡,而板卡的硬件地址分配,外设芯片类型等又大不相同。因此针对不同的板卡,尽管他们的CPU架构一样,但是还是需要针对硬件资源的配置去写不同的Bootloader。
2、看到这里,有人不禁会问,竟然Bootloader这么复杂,它是不是必要的?答案当然不是了,因为完全可以上电后直接运行操作系统,当然这个操作系统的开头必须要包含上述bootloader的功能。
3、如果不用Bootloader会有存在一些问题,假设你将产品已经卖到世界各地去了,但是有一天你发现产品程序存在一些问题,在解决这个问题之后想要升级软件就需要用下载器去重新下载程序,你不可能拿着一堆东西(电脑、下载器等)去找人家升级程序吧?但是有Bootloader之后,我们只需要将升级后的操作系统放到“硬盘”或Nand Flash中的某个位置,然后断电重启一下,bootloader就能在引导系统的同时,完成了对操作系统的升级。
二、Bootloader主要做什么事?
1、Bootloader就是系统上电之后运行的一段小程序,通过该程序能够实现关闭看门狗、配置时钟、初始化存储器等硬件设备,准备好软件环境,让软硬件环境达到一个最佳状态,最后启动操作系统内核。
2、大多数 Bootloader 都分为 stage1 和 stage2 两大部分。
(1)、对于 stage1 ,它通常都用汇编语言来实现,以达到短小精悍的目的,它的主要包括:
- 硬件设备初始化
- 为加载Bootlodader的stage2准备RAM空间
- 拷贝Bootloader的stage2到RAM空间
- 设置好堆栈段为stager2的C语言环境做准备
- 跳转到 stage2 的 C 入口点。
(2)、对于stage2, 则通常用C语言来实现,这样可以实现比较复杂的功能,且代码会有更好的可读性和可移植性,它主要包括:
- 初始化本阶段要使用到的硬件设备。
- 检测系统内存映射(memory map)。
- 将 内核映像和根文件系统映像从 flash 上读到 RAM 空间中。
- 为内核设置启动参数。
- 调用内核
三、基于ARM从0写一个Bootloader
这里使用JZ2440开发板,SOC为s3c2440,外部有64MSDRAM,一个Nand Flash、一个Nor Flash。
1、stage1
(1)、关闭看门狗
ldr r0, =0x53000000 /* 0x53000000是看门狗寄存器地址 */
ldr r1, =0
str r1, [r0]
(2)、配置时钟
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(0x5c<<12)|(0x01<<4)|(0x01) /* 400MHz */
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
(3)、初始化SDRAM
bl sdram_init /* 跳转执行,用C语言实现 */
(4)、代码重定位
ldr sp, =0x34000000 /* 设置栈 */
bl nand_init /* 初始化Nand Flash */
bl copy2sdram /* 重定位text, rodata, data段整个程序 */
bl clean_bss /* 清除BSS段 */
(5)、跳转执行main
ldr lr, =halt
ldr pc, =main
halt:
b halt
2、stage2
int main(void)
{
void (*theKernel)(int zero, int arch, unsigned int params);
volatile unsigned int *p = (volatile unsigned int *)0x30008000;
/* 0. 帮内核设置串口: 内核启动的开始部分会从串口打印一些信息,但是内核一开始没有初始化串口 */
uart0_init();
/* 1. 从NAND FLASH里把内核读入内存 */
puts("Copy kernel from nand\n\r");
nand_read(0x60000+64, (unsigned char *)0x30008000, 0x200000);
printHex(0x1234ABCD);
puts("\n\r");
printHex(*p);
puts("\n\r");
/* 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();
/* 3. 跳转执行 */
puts("Boot kernel\n\r");
theKernel = (void (*)(int, int, unsigned int))0x30008000;
theKernel(0, 362, 0x30000100);
puts("Error!\n\r");
/* 如果一切正常, 不会执行到这里 */
}
3、启动内核成功
附录代码
1、sdram初始化
/**************************************************************
函数名称:sdram_init
函数功能:sdram初始化
输入参数:无
返 回 值:无
备 注:无
**************************************************************/
void sdram_init(void)
{
/* 根据SDRAM和s3c2440芯片手册配置各寄存器值 */
BWSCON = 0x22000000; /* 总线宽度和等待控制寄存器 */
BANKCON6 = 0x18001; /* BANK控制寄存器 */
REFRESH = 0x8404f5; /* 刷新控制寄存器 */
BANKSIZE = 0xb1; /* BANKSIZE寄存器 */
MRSRB6 = 0x20; /* SDRAM模式寄存器设置寄存器 */
}
2、设置参数
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;
}
参考文献
韦东山:《嵌入式系统 Boot Loader 技术内幕》
猪哥-嵌入式:为什么需要bootloader