自己动手写bootloader
为了写一个bootloader让板子跑起来,首先我们要知道bootloader是个什么东东才行。简单的说,bootloader就是一个引导内核启动的一段小代码,也就是说当启动完内核之后,它的使命就已经结束。
bootloader生命周期:初始化硬件==》设置启动参数==》跳到Linux内核的首地址==》消亡。那么它是怎么启动内核的呢或者说在启动内核之前它究竟完成了哪些工作呢?
写代码之前还是先了解下内核启动的硬性条件:
1.CPU寄存器的设置: r0=0,r1=机器类型ID(linux/arch/arm/tools/mach-types),r2=启动参数标记列表在RAM中的基地址。
2.CPU工作模式:设置为管理模式(因为SUV模式的权限最大),且必须禁止中断IRQ、FIQ。
3.cache和MMU的设置:MMU必须关闭,数据cache必须关闭,指令cache可以开启可以关闭,不过建议开启,因为可以加快bootloader的运行速度
bootloader的工作分为两个阶段,由汇编语言和C语言各自掌管:
第一阶段完成功能(汇编语言):包括模式设置,关闭看门狗(防止系统定时重启),关闭中断,关cache,设置时钟频率,初始化硬件(nand flash和sdram),初始化堆栈(因为要进入到C语言中执行),跳转到第二阶段代码的C入口点。
第二阶段完成功能(C语言):重定位从flash里把程序本身复制到sdram里去,清bss段,为内核设置启动参数,调用内核。
注释:bootloader的代码分为前4K和4K后,bootloader运行时前4K代码会由硬件自动的拷贝到sram中,然后前4K代码需要将bootloader拷贝到sdram中运行(拷贝4K后的代码还是全部分具体情况而定),因为nand flash是不能直接运行代码的。
完成一个简单的具有基本功能的bootloader:
start.S(bootloader入口)
.globl _start
_start:
/*1.1 设置CPU的工作模式为SVC*/
mrs r0 , cpsr
bic r0 , r0 , #0x1f
orr r0 , r0 , #0xd3
msr cpsr , r0
/*1.2 关闭看门狗*/
ldr r0, =0x53000000
ldr r1, = 0
str r1, [r0]
/*1.3 关闭中断*/
ldr r0 , =0x4a000008
ldr r1 , =0xffffffff
str r1 , [r0]
ldr r0 , =0x4a00001c
ldr r1 , =0x7fff
str r1 , [r0]
/*1.4使能icache 关闭data cache*/
mrc p15, 0, r2, c1, c0, 0
orr r2, r2, #( 1 << 12)
mcr p15, 0, r2, c1, c0, 0
/*1.5禁用MMU和缓存*/
mrc p15,0,r0,c1,c0,0
bic r0,r0,#0x00002300 @clear bits 13,9:8(--V- --RS)
bic r0,r0,#0x00000087 @clear bits 7,2:0(B--- -CAM)
orr r0,r0,#0x00000002 @set bit 1(A)Align
orr r0,r0,#0x00001000 @set bit 12(I)I-Cache
mcr p15,0,r0,c1,c0,0
/*1.6 设置堆栈.判断是nor启动还是nand启动初始化sp指针*/
/*判断原理:
* 无论是从NOR Flash还是从NAND Flash启动,
* 地址0处为指令"b Reset", 机器码为0xEA00000B,
* 对于从NAND Flash启动的情况,其开始4KB的代码会复制到CPU内部4K内存中,即SRAM
* 因此在NAND启动时往里写数据的时候实际上就是写SRAM,而SRAM是直接可读可写的
* 对于从NOR Flash启动的情况,NOR Flash的开始地址即为0。
* 对于NOR Flash,必须通过一定的命令序列才能写数据,
* 所以可以根据这点差别来分辨是从NAND Flash还是NOR Flash启动:
* 向地址0写入一个数据,然后读出来,如果没有改变的话就是Nand Flash
*/
/* NAND启动时,片内内存从0开始, sp=4096 */
/* NOR启动时,片内内存从0x40000000开始, sp=0x40000000+4096 */
ldr r1 , = 0
str r1, [r1] /* 在0地址写入0 */
ldr r1, [r1] /* 从0地址读出数据 */
cmp r1, # 0
ldreq sp, = 4096
ldrne sp, =(0x40000000 + 4096)
/***************************详见附件************************************************/
*1.7 时钟初始化,主要是MPLLCON、CLKDIVN寄存器的设置,特别注意将总线模式改为异步总线模式*/
bl clock_init
/*1.8. 初始化SDRAM,对于13个寄存器的设置 */
bl sdram_init
/*1.9. 初始化nand*/
bl nand_init
/***************************详见附件************************************************/
/* 2.1 重定位: 从flash里把程序本身复制到sdram里去 */
/*重定位(Relocate)的概念:调试u-boot时,开始一般是让它在RAM中运行,当RAM运行通过后才将其固化到FLASH中;这样如果我们当前的代码是通过BDI2000等Load到内存直接运行的话,
u-boot就不需要去将自己从FLASH搬移到内存了;而如果u-boot是固化在FLASH中在CPU复位后由第一个片选信号指向开始执行的话,则又一个从FLASH搬移到内存的过程。*/
/*拷贝函数*/
bl CopyCode2Ram
/* 2.2. 清bss段(初始值为0、无初始值的全局变量、静态变量放在BSS段 ),意味着给这些变量开辟空间,因为bootloader是不给bss段分配空间的*/
ldr r0, = __bss_start
ldr r1, =__bss_end
cmp r0, r1
beq setup_stack
mov r2, # 0
clear_loop :
str r2, [r0], # 4
cmp r0, r1
bne clear_loop
/* 2.3调用c函数 */
relocate : /* relocate U-Boot to RAM */
mov r0, # 0
ldr r1, =__start
ldr r2, =__bss_start
sub r2, r2, r1
setup_stack:
/* 前面已经设置sp, 这里不用再设置 */
ldr pc, =main /* 跳到sdram里去 */
main.c
int main(
void)
{
void ( *theKernel)(
int zero,
int machine_id,
int params_addr);
/*初始化串口: 内核启动时会用到串口输出一些信息 */
uart_init();
puts( "============BOOT for S3C2440=============\n\rCopy kernel for flash to sdram ...\n\r");
/*从NAND FLASH里把内核读到SDRAM里去 */
nand_read(0x30108000, 0x60000, 0x300000);
puts( "OK\n\r");
/*设置参数 */
puts( "Set parametes ...");
set_params();
puts( "OK\n\r");
/*启动内核 */
puts( "Boot kernel ...\n\r");
theKernel = 0x30108040;
theKernel( 0, 362, 0x30000100 );/*带三个参数的指向内核首地址的指针*/
puts( "\nerror!\n\r");
return 0;
}