从0开始写最简版bootloader

bootloader的目标: 启动内核

为了启动内核,分两步进行。
一、从flash上把内核读入内存
a. bootloader能读flash
b. 初始化内存、时钟、其他硬件
二、启动内核
a. 设置启动参数
b. 跳转执行

最简单的bootloader的编写步骤:
1.初始化硬件:关看门狗、设置时钟、设置SDRAM、初始化NAND FLASH
2.重定位到SDRAM
3.把内核从NAND FLASH读到SDRAM
4.设置‘要传给内核的参数’
5.跳转执行内核

接下来按步骤编写代码
1.初始化硬件:关看门狗、设置时钟、设置SDRAM、初始化NAND FLASH
2.重定位到SDRAM

在start.S中用汇编实现关看门狗、设置时钟、设置栈,
然后调用C函数来初始化SDRAM、初始化NAND flash,重定位到SDRAM,再清bss段。

start.S的具体代码为:

.text
.global _start

_start:

	/* 关看门狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]
	
	/* 设置时钟 */
	/* 设置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, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]

	/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
	 * 然后CPU工作于新的频率FCLK
	 */

	/* 启动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

	/* 设置栈 */
	ldr sp, =0x34000000

	/* 初始化SDRAM */
	bl sdram_init
	
	/* 初始化NAND */
	bl nand_init
	
	/* 重定位到SDRAM */
	bl copy2sdram
	
	bl clean_bss				/* 清零bss段 */

	/* 跳到main 执行 */
	ldr lr, =halt
	ldr pc, =main

halt:
	b halt

在init.c中实现C函数——sdram_init、 copy2sdram、 clean_bss
init.c具体代码如下

#include "s3c2440_soc.h"
void sdram_init(void)
{
	BWSCON    = 0x22011110;
	BANKCON0 = 0x00000700;
	BANKCON1 = 0x00000700;
	BANKCON2 = 0x00000700;
	BANKCON3 = 0x00000700;
	BANKCON4 = 0x00000700;
	BANKCON5 = 0x00000700;
	BANKCON6 = 0x00018005;
	BANKCON7 = 0x00018005;

	REFRESH = 0x008C04F4;
	BANKSIZE = 0x000000B1;
	MRSRB6 = 0x00000030;
	MRSRB7 = 0x00000030;
}

int isBootFromNorFlash(void)
{
	volatile unsigned int *p = (volatile unsigned int *)0;
	unsigned int val = *p;

	*p = 0x12345678;
	if (*p == 0x12345678)
	{
		/* 写成功, 对应nand启动 */
		*p = val;
		return 0;
	}
	else
	{
		/* nor boot */
		return 1;
	}
}


/* 把全部代码copy到SDRAM */
void copy2sdram(void)
{
	/* 把nor flash的代码读出,复制到SDRAM */
	
	/* 声明lds文件中的__code_start, __bss_start
	 * 从0地址把数据复制到 __code_start 处,一直复制到 __bss_start 为止。
	 */
	extern int __code_start, __bss_start;
	
	volatile unsigned int *dest = (volatile unsigned int *)&__code_start;
	volatile unsigned int *end = (volatile unsigned int *)&__bss_start;
	volatile unsigned int *src = (volatile unsigned int *)0;
	int len;

	len = ((int)&__bss_start) - ((int)&__code_start);
	if (isBootFromNorFlash())
	{
		while (dest < end)
		{
			*dest++ = *src++;
		}
	}
	else
	{
		nand_read((unsigned int)src, dest, len);
	}
	
}


/* 把bss段的数据全部清0 */
/* bss段不在 bin文件中 */
void clean_bss(void)
{
	/* 声明lds文件中的 __bss_start, _end
	 * __bss_start是bss段的起始地址, _end是bss段的终止地址
	 */
	extern int __bss_start, _end;
	
	volatile unsigned int *start = (volatile unsigned int *)&__bss_start;
	volatile unsigned int *end = (volatile unsigned int *)&_end;
	
	while (start <= end)
	{
		*start++ = 0;
	}
}

在nand.c中实现nand_init()初始化nand flash,nand_read()nand读函数。

#include "nand.h"

/* 宏定义时序 */
#define  TACLS   0
#define  TWRPH0  1
#define  TWRPH1  0

void nand_init(void)
{
	/* 初始化NAND FLASH,主要是配置 芯片引脚、NFCONF和NFCONT寄存器 */

	/* GPA17 ~ GPA22 */
	GPACON |= (0x3f<<17);
	
	/*设置NAND FLASH的时序*/	
	NFCONF = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4) | (0<<0);	
	/*使能NAND FLASH控制器,初始化ECC,禁止片选*/	
	NFCONT = (1<<4) | (1<<1) | (1<<0);	
}

static void chip_enable(void)
{
	NFCONT &= ~(1<<1);
}

static void chip_disable(void)
{
	NFCONT |= (1<<1);
}

static void nand_cmmd(unsigned char data)
{
	NFCMD = data;
}

static void nand_addr(unsigned char addr)
{
	NFADDR = addr;
}

static void nand_write_io(unsigned char data)
{
	NFDATA = data;
}

static unsigned char nand_read_io(void)
{
	return NFDATA;
}

static void wait_ready(void)
{
	while (!(NFSTAT & 0x01));
}


void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
{
	unsigned int i = 0;
	unsigned int page, col;
	page = addr / 2048;
	col = addr & (2048 - 1);

	chip_enable();

	while (i < len)
	{
		nand_cmmd(0x00);
		
		nand_addr((unsigned char)(col & 0xff));
		nand_addr((unsigned char)((col>>8)& 0xff));
		nand_addr((unsigned char)(page & 0xff));
		nand_addr((unsigned char)((page>>8)& 0xff));
		nand_addr((unsigned char)((page>>16)& 0xff));

		nand_cmmd(0x30);

		wait_ready();

		for (; (col < 2048) && (i < len); col++)
		{
			buf[i++] = nand_read_io();
		}
		if (i == len)
			break;
		col = 0;
		page++;

	}
	
	chip_disable();
}

3.把内核从NAND FLASH读到SDRAM
4.设置‘要传给内核的参数’
5.跳转执行内核

把main()函数中要把内核从nand flash读到SDRAM,要用到nand_read()函数。
设置启动参数,就是在一个约定好的内存中存入一些tag。用函数在0x30000100处存入params。
跳转到内核执行就是到内核的首地址去执行代码,本bootloader中把内核读到了0x30008000。所以直接跳转到内存中的0x30008000处执行。

main函数在boot.c文件中,头文件setup.h包含一些结构体的定义和一些参数的宏定义。
boot.c的具体代码为

#include "s3c2440_soc.h"
#include "setup.h"
#include "nand.h"
#include "uart.h"

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 Mystrcpy(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;	
	
	Mystrcpy (params->u.cmdline.cmdline, cmdline);	
	
	params = tag_next (params);
}
void setup_end_tag(void)
{
	params->hdr.tag = ATAG_NONE;	
	params->hdr.size = 0;
}

int main(void)
{
	void (*theKernel)(int zero, int arch, unsigned int params);

	/* 0.初始化串口 
	 * 内核启动的开始部分会从串口打印一些信息,但是内核一开始没有初始化串口
	 */
	uart0_init();
	
	/* 1.把内核从NAND FLASH 读到SDRAM */
	puts("Copy kernel from nand\n\r");
	nand_read(0x60000+64, (unsigned char *)0x30008000, 0x200000);


	
	/* 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");
}

为了串口显示还需初始化串口。

**

注:涉及到的一些地址

0x30000000————SDRAM首地址
0x30000100————params的地址
0x30008000————内核读到SDRAM的地址
0x33f80000————代码的链接地址
0x34000000————栈sp

0x60000+64————0x60000是内核在nand的地址,前64字节是uImage的头部,后面的才是真正的内核。所以从0x60000+64开始读,读到SDRAM的0x30008000.

**

链接文本link.lds为

SECTIONS
{
	. = 0x33f80000;

	__code_start = .; 

	. = ALIGN(4);
	.text      :		
	{
	  *(.text)
	}

	. = ALIGN(4);
	.rodata : { *(.rodata) }		

	. = ALIGN(4);
	.data : { *(.data) }			

	. = ALIGN(4);
	__bss_start = .;
	.bss : { *(.bss) *(.COMMON) }	
	_end = .;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值