自实现Bootloader
Bootloader作用
1. 启动流程
三星210开发板Bootloader分为BL0、BL1,BL0由三星公司提供,BL1一般由uboot中前一部分实现,也可以自己实现
NFC:NandFlash控制器
首先是iROM中的BL0做的事情
初始化硬件
1.关闭看门狗定时器,因为它是默认开启的,每隔一段时间会重启SOC
2.初始化指令Cache,加快指令执行速度
3.初始化栈区,初始化堆区,将iRAM一部分设为栈区,一部分设为堆区
4.初始化块设备的拷贝功能,因为要拷贝外部设备的数据
5.初始化系统时钟,默认800MHz
判断OM
6.将bl1拷贝到内部的iRAM中去
7.验证bl1的正确性,若正确则跳转到bl1中执行
BL1做的事情
1.初始化硬件
init 时钟
init DDR
init NandFlash
2.将OS拷贝到DDR中
3.设置好参数,start kernel
2. 自制Bootloader
2.1 在BL中添加蜂鸣器提示
当bl1执行时,蜂鸣器嘀嗒提醒
start.s
@要求bl1保留16个字节
.word 0x2000 @bl1 size
.word 0 @ 0
.word 0 @checksum
.word 0 @ 0
_start:
@有效的第一条指令
bl buzzer_init
loop:
bl buzzer_on
bl delay
bl buzzer_off
bl delay
b loop
buzzer_init:
@GPD0CON
ldr r0, =0xE02000A0
ldr r1, [r0]
bic r1, r1, #(0xf<<4) @4~7=0000
orr r1, r1, #(0x1<<4) @4~7=0001
str r1, [r0]
mov pc, lr
buzzer_on:
@GPD0CON
ldr r0, =0xE02000A4
ldr r1, [r0]
orr r1, r1, #(0x1<<1) @1=1
str r1, [r0]
mov pc, lr
buzzer_off:
@GPD0CON
ldr r0, =0xE02000A4
ldr r1, [r0]
bic r1, r1, #(0x1<<1) @1=0
str r1, [r0]
mov pc, lr
delay:
mov r0, #0x100000
d:
subs r0, r0, #1
bne d
mov pc, lr
2.2 编译和测试
烧写流程:先利用uboot将uboot.bin传递到DDR中,后用命令将DDR中的boot.bin存到NandFlash中的0x0地址,最后将拨码开关拨成NandFlash启动。
重启开发板后蜂鸣器滴滴的响
2.3 在BL中添加初始化时钟驱动和串口驱动
clock.s:
.global clock_init
clock_init:
@先关闭所有PLL
@CLK_SRC0
ldr r0, =0xE0100200
ldr r1, =0x0
str r1, [r0]
@设置PLL
@APLL_LOCK
ldr r0, =0xE0100000
ldr r1, =30*24
str r1, [r0]
@MPLL_LOCK
ldr r0, =0xE0100008
ldr r1, =200*24
str r1, [r0]
@EPLL_LOCK
ldr r0, =0xE0100010
ldr r1, =375*24
str r1, [r0]
@VPLL_LOCK
ldr r0, =0xE0100020
ldr r1, =100*24
str r1, [r0]
@设置倍频因子
@APLL_CON0
ldr r0, =0xE0100100
ldr r1, =(1<<31)|(125<<16)|(3<<8)|(1<<0)
str r1, [r0]
@MPLL_CON
ldr r0, =0xE0100108
ldr r1, =(1<<31)|(667<<16)|(12<<8)|(1<<0)
str r1, [r0]
@配置分频器
@CLK_DIV0
ldr r0, =0xE0100300
ldr r1, =(1<<28)|(4<<20)|(1<<20)|(3<<16)|(1<<12)|(4<<8)|(4<<4)|0
str r1, [r0]
@选择使用PLL
@CLK_SRC0
ldr r0, =0xE0100200
ldr r1, =0x1111
str r1, [r0]
修改start.s
@要求bl1保留16个字节
.word 0x2000 @bl1 size
.word 0 @ 0
.word 0 @checksum
.word 0 @ 0
_start:
@开机蜂鸣器提示
@有效的第一条指令
bl buzzer_init
bl buzzer_on
bl delay
bl buzzer_off
@初始化时钟
bl clock_init
@初始化sp
ldr sp, =0xd0022000 @这个地址做栈指针
@初始化串口驱动
bl uart_init
bl main
buzzer_init:
@GPD0CON
ldr r0, =0xE02000A0
ldr r1, [r0]
bic r1, r1, #(0xf<<4) @4~7=0000
orr r1, r1, #(0x1<<4) @4~7=0001
str r1, [r0]
mov pc, lr
buzzer_on:
@GPD0CON
ldr r0, =0xE02000A4
ldr r1, [r0]
orr r1, r1, #(0x1<<1) @1=1
str r1, [r0]
mov pc, lr
buzzer_off:
@GPD0CON
ldr r0, =0xE02000A4
ldr r1, [r0]
bic r1, r1, #(0x1<<1) @1=0
str r1, [r0]
mov pc, lr
delay:
mov r0, #0x100000
d:
subs r0, r0, #1
bne d
mov pc, lr
uart.c
#define GPA0CON (*(volatile unsigned int *)0xE0200000)
#define ULCON0 (*(volatile unsigned int *)0xE2900000)
#define ULON0 (*(volatile unsigned int *)0xE2900004)
#define UTRSTAT0 (*(volatile unsigned int *)0xE2900010)
#define UTXH0 (*(volatile unsigned int *)0xE2900020)
#define URXH0 (*(volatile unsigned int *)0xE2900024)
#define UBRDIV0 (*(volatile unsigned int *)0xE2900028)
#define UDIVSLOT0 (*(volatile unsigned int *)0xE290002C)
void uart_init()
{
//GPIO配置
GPA0CON &= ~0xff;
GPA0CON |= 0x22;
//UART配置传输格式
//8位长,一个停止位,没有校验位
ULCON0 = 0x3;
//读写模式: 轮询模式
UCON0 = 0x5;
//波特率
//DIV_VAL=(PCLK/(bps x 16))-1
//34.8= 66*1000*1000/(115200*16)-1
UBRDIV0 = 34;
UDIVSLOT0=0xDFDD;
}
char _getc()
{
char ch;
//查询是否有有效数据
while((UTRSTAT0 & 0x1)==0)
;
ch = URXH0;
return ch;
}
void _putc(char ch)
{
//查询是否可以发送数据 UTRSTAT0的第二位
while((UTRSTAT0 & (0x1<<2)) == 0)
;
UTXH0 = ch;
}
void _puts(char *str)
{
while(*str != '\0')
{
_putc(*str);
if(*str == '\n')
_putc('\r');
str++;
}
}
main.c
int main()
{
while(1)
{
_puts("hello bootloader\n");
}
return 0;
]
修改makefile
编译器编译的C语言代码可能与链接地址有关,所以要修改链接地址为iRAM的起始地址
2.4 编译和测试
烧写流程:先利用uboot将uboot.bin传递到DDR中,后用命令将DDR中的boot.bin存到NandFlash中的0x0地址,最后将拨码开关拨成NandFlash启动。
重启开发板后蜂鸣器只响了一声,然后串口输出如下
2.5 在BL中添加初始化DDR驱动
添加 原厂mem_setup.S驱动到之前的工程中
修改start.s
@要求bl1保留16个字节
.word 0x2000 @bl1 size
.word 0 @ 0
.word 0 @checksum
.word 0 @ 0
_start:
@开机蜂鸣器提示
@有效的第一条指令
bl buzzer_init
bl buzzer_on
bl delay
bl buzzer_off
@初始化时钟
bl clock_init
@初始化ddr
bl mem_ctrl_asm_init
@初始化sp
ldr sp, =0xd0022000 @这个地址做栈指针
@初始化串口驱动
bl uart_init
buzzer_init:
@GPD0CON
ldr r0, =0xE02000A0
ldr r1, [r0]
bic r1, r1, #(0xf<<4) @4~7=0000
orr r1, r1, #(0x1<<4) @4~7=0001
str r1, [r0]
mov pc, lr
buzzer_on:
@GPD0CON
ldr r0, =0xE02000A4
ldr r1, [r0]
orr r1, r1, #(0x1<<1) @1=1
str r1, [r0]
mov pc, lr
buzzer_off:
@GPD0CON
ldr r0, =0xE02000A4
ldr r1, [r0]
bic r1, r1, #(0x1<<1) @1=0
str r1, [r0]
mov pc, lr
delay:
mov r0, #0x100000
d:
subs r0, r0, #1
bne d
mov pc, lr
修改makefile
2.6 在BL中添加初始化NandFlash驱动
nand.c
#define NFCONF (*(volatile unsigned int *)0xB0E00000)
#define NFCONT (*(volatile unsigned int *)0xB0E00004)
#define NFCMMD (*(volatile unsigned int *)0xB0E00008)
#define NFADDR (*(volatile unsigned int *)0xB0E0000C)
#define NFDATA (*(volatile unsigned int *)0xB0E00010)
#define NFSTAT (*(volatile unsigned int *)0xB0E00028)
void nand_init()
{
//配置寄存器:时序
#define TACLS 0
#define TWRPH0 2
#define TWRPH1 0
NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4)|(1<<1);
//开启flash控制器
NFCONT |= 0x1; //enable nand flash control
//选住NandFlash
NFCONT &= ~(0x1<<1) //enable flash cs
}
void nand_send_addr(unsigned int ddr)
{
unsigned int page_in_addr = addr % 2048;//页内地址 实际用不到
unsigned int page_addr = add / 2048;
NFADDR = page_in_addr & 0xff;
NFADDR = (page_in_addr >> 8) & 0xff;
NFADDR = page_addr & 0xff;
NFADDR = (page_addr >> 8) & 0xff;
NFADDR = (page_addr >> 16) & 0xff;
}
void nand_ready()
{
while((NFSTAT & 0x1) == 0)
;
}
void nand_read(unsigned int ddr,unsigned int nand,unsigned int len)
{
int i = 0;
unsigned int addr = nand;
for(addr = nand; addr < nand + len;addr += 2048)//判断读几页
{
NFCMMD = 0x00;
nand_send_addr(addr);
NFCMMD = 0x30;
nand_ready();
//读2k数据
for(i = 0;i < 512; i++,ddr +=4)
{
*(unsigned int *)ddr = NFDATA;//每次取四个字节
}
}
}
修改start.s
@要求bl1保留16个字节
.word 0x2000 @bl1 size
.word 0 @ 0
.word 0 @checksum
.word 0 @ 0
_start:
@开机蜂鸣器提示
@有效的第一条指令
bl buzzer_init
bl buzzer_on
bl delay
bl buzzer_off
@初始化时钟
bl clock_init
@初始化ddr
bl mem_ctrl_asm_init
@初始化sp
@ldr sp, =0xd0022000 @iRAM这个地址做栈指针
ldr sp, =0x41000000
@初始化nandflash
bl nand_init
@拷贝BL1到ddr中,实现重定向
ldr r0, =0x40008000
ldr r1, =0x0
ldr r2, =0x10000000
bl nand_read
@初始化串口驱动 以下代码是绝对跳转,在ddr中执行
mov lr, pc
ldr pc, =uart_init
mov lr, pc
ldr pc, =main
buzzer_init:
@GPD0CON
ldr r0, =0xE02000A0
ldr r1, [r0]
bic r1, r1, #(0xf<<4) @4~7=0000
orr r1, r1, #(0x1<<4) @4~7=0001
str r1, [r0]
mov pc, lr
buzzer_on:
@GPD0CON
ldr r0, =0xE02000A4
ldr r1, [r0]
orr r1, r1, #(0x1<<1) @1=1
str r1, [r0]
mov pc, lr
buzzer_off:
@GPD0CON
ldr r0, =0xE02000A4
ldr r1, [r0]
bic r1, r1, #(0x1<<1) @1=0
str r1, [r0]
mov pc, lr
delay:
mov r0, #0x100000
d:
subs r0, r0, #1
bne d
mov pc, lr
修改makefile
2.7 编译和测试
烧写流程:先利用uboot将uboot.bin传递到DDR中,后用命令将DDR中的boot.bin存到NandFlash中的0x0地址,最后将拨码开关拨成NandFlash启动。
重启开发板后蜂鸣器只响了一声,然后串口输出如下
说明程序是在DDR中运行的
2.8 添加引导启动Linux内核及文件系统
2.8.1 准备
提前通过linux-3.0.8编译生成系统镜像zImage,还准备好了一个根文件系统rootfs.cramfs
先通过uboot的tftp功能将上面两个烧到内存DDR中,再通过uboot的写nandflash功能按分区表写到对应地址
分区表
boot 0x0 - 0x100000
os 0x100000 - 0x400000
root 0x400000 - 0x1400000
2.8.2 修改文件启动Linux内核
因为操作系统拷贝时,经过0x40008000,可能会覆盖bootloader
修改start.s
@要求bl1保留16个字节
.word 0x2000 @bl1 size
.word 0 @ 0
.word 0 @checksum
.word 0 @ 0
_start:
@开机蜂鸣器提示
@有效的第一条指令
bl buzzer_init
bl buzzer_on
bl delay
bl buzzer_off
@初始化时钟
bl clock_init
@初始化ddr
bl mem_ctrl_asm_init
@初始化sp
@ldr sp, =0xd0022000 @iRAM这个地址做栈指针
ldr sp, =0x42000000
@初始化nandflash
bl nand_init
@拷贝BL1到ddr中,实现重定向
ldr r0, =0x41000000
ldr r1, =0x0
ldr r2, =0x100000
bl nand_read
@初始化串口驱动 以下代码是绝对跳转,在ddr中执行
mov lr, pc
ldr pc, =uart_init
mov lr, pc
ldr pc, =main
buzzer_init:
@GPD0CON
ldr r0, =0xE02000A0
ldr r1, [r0]
bic r1, r1, #(0xf<<4) @4~7=0000
orr r1, r1, #(0x1<<4) @4~7=0001
str r1, [r0]
mov pc, lr
buzzer_on:
@GPD0CON
ldr r0, =0xE02000A4
ldr r1, [r0]
orr r1, r1, #(0x1<<1) @1=1
str r1, [r0]
mov pc, lr
buzzer_off:
@GPD0CON
ldr r0, =0xE02000A4
ldr r1, [r0]
bic r1, r1, #(0x1<<1) @1=0
str r1, [r0]
mov pc, lr
delay:
mov r0, #0x100000
d:
subs r0, r0, #1
bne d
mov pc, lr
修改main.c
void __start_kernel()
{
//函数指针 内核启动需要三个参数,zero一般为0,mach为机器码,每个SOC有其独一无二的,查头文件可知,cmd为命令地址
void (*start_kernel)(int zero,int mach,unsigned int cmd);
_puts("nand read kernel..");
nand_read(0x40008000,0x100000,0x300000);
start_kernel=(void *)0x40008000;
start_kernel(0,2456,0x20000100);
}
int main()
{
_puts("bootloader start...\n");
__start_kernel();
return 0;
]
2.8.3 编译和测试
编译后先烧到ddr,再烧到nandflash
拨到Nandflash启动后重启开发板
说明内核启动成功,只是参数未设置
2.8.4 添加启动参数头文件并设置参数
将内核源码中的arch/arm/include/asm/setup.h到我们的工程目录中
参数结构体如下图:
修改main.c
#include "setup.h"
struct tag *params = (void *)0x20000100;
void setup_start_tag()
{
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);
}
//因为bootloader已经初始化了内存,所以这里只需要告诉内核内存的参数
void setup_mem_tag()
{
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size(tag_mem32);
params->u.mem.size= 1024*1024*1024;//1G
params->u.mem.start= 0x20000000;
params = tag_next(params);
}
int strlen(const char *s)
{
const char *sc;
for(sc = s; *sc !='\0'; ++sc)
;
return sc-s;
}
char *strcpy(char *dest,const char *src)
{
char *tmp = dest;
while((*dest++ = *src++) != '\0')
;
return tmp;
}
//告诉操作系统根文件系统在哪里
void setup_cmd_tag()
{
//告诉内核console调试信息从什么地方输出和根文件系统的类型
char *cmd = "console=ttySAC0,115200 root=1f02 rootfstype=cramfs rw init=/linuxrc";
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size = (sizeof(struct tag_header)+ strlen(cmd) + 3 >> 2;
strcpy(params->u.cmdline.cmdline,cmd)
}
void setup_end_tap()
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
void setup_parms()
{
setup_start_tag();
setup_mem_tag();
setup_cmd_tag();
setup_end_tag();
}
void __start_kernel()
{
//函数指针 内核启动需要三个参数,zero一般为0,mach为机器码,每个SOC有其独一无二的,查头文件可知,cmd为命令地址
void (*start_kernel)(int zero,int mach,unsigned int cmd);
//1.把kernel督导ddr中
_puts("nand read kernel..");
nand_read(0x40008000,0x100000,0x300000);
start_kernel=(void *)0x40008000;
//2.设置参数
//3.启动内核
start_kernel(0,2456,0x20000100);
}
int main()
{
_puts("bootloader start...\n");
__start_kernel();
return 0;
]
2.8.5 编译和测试
烧入板子
拨码开关拨到nandflash启动,结果如下:
成功进入文件系统控制台,如下: