7. 自实现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 编译和测试

bianyi
烧写流程:先利用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的起始地址
mk

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
mk

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启动,结果如下:
结果
成功进入文件系统控制台,如下:
结果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值