加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

自己动手写最简单的bootloader

发布时间:2020-12-15 18:31:06 所属栏目:百科 来源:网络整理
导读:首先我们必须要知道 , 一开始我们的开发板上电的时候 , 如果我们的板子是从 norflash 启动的 , 那么硬件会从 nandflash 拷贝其前 4k 的代码到内部 RAM 中 ,( 这也是为什么我们的 bootloader 第一阶段需要在 4k 内 ), 如果是 norflash 启动 , 那也是从 norfla

首先我们必须要知道,一开始我们的开发板上电的时候,如果我们的板子是从norflash启动的,那么硬件会从nandflash拷贝其前4k的代码到内部RAM,(这也是为什么我们的bootloader第一阶段需要在4k),如果是norflash启动,那也是从norflash把前4k代码复制到内部RAM

我们知道mini2440有个看门狗,如果说我们不能够定时去喂狗,那么它会在超时的时候自动重启(硬件决定的),所以我们的bootloader的第一步就是

???? 关闭看门狗

2440在启动的时候他是直接以外部晶振直接作为系统时钟,所以系统运行在12MHz,这个频率实在是太小了,所以我们为了让我们的板子运行速度加快,那么我们第二步就是

???? 修改板子的时钟频率

在接下来我们想想要做什么呢?我们是不是想要启动内核,那启动内核之前是不是需要先把内核从nandflash(内核是放在nandflash0x60000)拷贝到内存SDRAM,而这个SDRAM(64M)在进行读写前是需要你先去初始化他的一些存储单元才能够正常的进行读写的!!!!所以我们的第三部就是

???? 初始化SDRAM

我们把内核拷贝到SDRAM,得先初始化nandflash,才能够写nandflash,所以:

???? 初始化nandflash

接下来由于我们后面的操作是使用了c语言,故我们先要设置栈sp

???? 设置栈sp

那么我们接下来是不是就是去把内核拷贝到SDRAM中呢?别急,我们要知道我们的bootloader目前只有前4k的代码被拷贝到内部RAM,如果说我们的bootloader超过4k的话那么我们的bootloader就很有可能会出问题,? 所以我们接下来要做的就是拷贝bootloaderSDRAM,再跳过去运行,这叫做:

???? 重定位

重定位结束后,我们需要先清除bss,也就是赋值为0,具体什么是bss段这里就不解释了

???? bss

接下来进入bootloader第二阶段

接下来我们终于到了把内核从nandflash中拷贝到SDRAM,这就涉及到读nandflash,nandflash读的时候呢,需要我们先去

???? 为了调试方便我们在这里初始化串口,让串口能够为我们打印一下提示信息

???? 把内核从nandflash拷贝到SDRAM

???? 设置好传递给内核的参数? 调用内核

接下来讲解具体的代码,首先是 start.s 这个是第一阶段的文件.

.text
.global _start
_start:
/*①关看门狗*/
	ldr r0,= 0x53000000
	mov r1,#0
	str r1,[r0]
/* ②设置时钟,因为他本来的时钟频率为12mhz 太低*/
	ldr r0,= 0x4c000014  /*CLKDIVN*/
	mov r1,#0x5           /*#5     F:H:P = 1 : 4:8*/
	str r1,[r0]
	/*数据手册说的,设置异步模式*/
	
/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
		mrc p15,r1,c1,c0,0		/* 读出控制寄存器 */ 
		orr r1,#0xc0000000 		/* 设置为“asynchronous bus mode” */
		mcr p15,0		/* 写入控制寄存器 */
	
/* MPLLCON = S3C2440_MPLL_400MHZ */
	ldr r0,= 0x4c000004
	ldr r1,= ((0x7f<<12)|(0x2<<4)|0x1) /* 设置为400mhz*/
	str r1,[r0]

/* 启动ICACHE,此步骤可以没有,加上的原因是因为这样能够让启动速度加快 */
	mrc p15,r0,0	@ read control reg
	orr r0,#(1<<12)
	mcr	p15,0   @ write it back



/* ③初始化SDRAM*/
	ldr r0,= 0x48000000
	adr r1,sdram_config
	add r3,#(13*4)
0:
	ldr r2,[r1],#4
	str r2,[r0],#4
	cmp r0,r3
	bne 0b

/*  led  此步骤可以不要,只是一开始用于调试,点灯看看程序运行状况 */
	ldr r0,= 0x56000010
	ldr r1,= (0x55<<10)
	str r1,[r0]	
/* ④ 设置栈 */
	ldr sp,=0x34000000  /*0x34000000这是sdram的结束地址,栈的设置 很重要,设置错误 很容易导致系统重启*/
	bl uart0_init  /*初始化串口,之所以把这个放到这里,主要原因是为了方便调试*/
/*⑤初始化nandflash*/
	bl nand_init
/*⑥重定位*/
	mov r0,#0
	ldr r1,= 0x33f80000  @_start    @0x33f80000
	ldr r2,= __bss_start	
	@sub r2,r2,r1

	bl copy_code_to_sdram  /*上面的r0-r2是参数*/
	
	bl clear_bss /*清 bss 段*/	

	/*goto main*/
	ldr lr,= halt
	ldr pc,= Main
	bl led_on

halt:
	b halt
		

sdram_config:
	.long 0x22011110	 
	.long 0x00000700	 
	.long 0x00000700	 
	.long 0x00000700	 
	.long 0x00000700	 
	.long 0x00000700	 
	.long 0x00000700	 
	.long 0x00018005	 
	.long 0x00018005	 
	.long 0x008C04F4	 
	.long 0x000000B1	 
	.long 0x00000030	 
	.long 0x00000030	 

首先看我们的uart_init()初始化串口:

void uart0_init(void)
{
GPHCON = (GPHCON&(~(0xf<<4)))|(0xa<<4);// GPH2,GPH3用作TXD0,RXD0	
UFCON0 = 0x00;   //不使用FIFO
UMCON0 = 0x00;   //不使用自动流控制
ULCON0 = 0x03;   //不采用红外线传输模式,无奇偶校验位,1个停止位,8个数据位
UCON0  = 0x05;   //发送中断为电平方式,接收中断为边沿方式,禁止超时中断,允许产生错误状态中断,禁止回送模式,禁止中止信号			//信号,传输模式为中断请求模式,接收模式也为中断请求模式。
UBRDIV0=((int)(50000000/(UART_BAUD_RATE*16))-1); //根据波特率计算UBRDIV0的值  

}

串口设置好后,很明显我们需要写串口输出的函数:

/* 发送一个字符 */
void _putc(char c)
{
    /* 等待,直到发送缓冲区中的数据已经全部发送出去 */
    while (!(UTRSTAT0 & TXD0READY));
    
    /* 向UTXH0寄存器中写入数据,UART即自动将它发送出去 */
    UTXH0 = c;
}
/*发送字符串*/
void _puts(char *str)
{
?while(*str){
??_putc(*str++);
?}
}

先贴下宏定义:

#define uchar  unsigned char 
#define uint   unsigned int 
/* NAND FLASH控制器 */
#define NFCONF (*((volatile unsigned long *)0x4E000000))
#define NFCONT (*((volatile unsigned long *)0x4E000004))
#define NFCMMD (*((volatile unsigned char *)0x4E000008))
#define NFADDR (*((volatile unsigned char *)0x4E00000C))
#define NFDATA (*((volatile unsigned char *)0x4E000010))
#define NFSTAT (*((volatile unsigned char *)0x4E000020))

/* GPIO */
#define GPHCON              (*(volatile unsigned long *)0x56000070)
#define GPHUP               (*(volatile unsigned long *)0x56000078)

/* UART registers*/
#define ULCON0              (*(volatile unsigned long *)0x50000000)
#define UCON0               (*(volatile unsigned long *)0x50000004)
#define UFCON0              (*(volatile unsigned long *)0x50000008)
#define UMCON0              (*(volatile unsigned long *)0x5000000c)
#define UTRSTAT0            (*(volatile unsigned long *)0x50000010)
#define UTXH0               (*(volatile unsigned char *)0x50000020)
#define URXH0               (*(volatile unsigned char *)0x50000024)
#define UBRDIV0             (*(volatile unsigned long *)0x50000028)

#define PCLK            50000000    // init.c中的clock_init函数设置PCLK为50MHz
#define UART_CLK        PCLK        //  UART0的时钟源设为PCLK
#define UART_BAUD_RATE  115200      // 波特率
#define UART_BRD        ((UART_CLK  / (UART_BAUD_RATE * 16)) - 1)

#define u32 unsigned long 

#define TXD0READY   (1<<2)


接下来我们看看nandflash的初始化函数nand_init()

void nand_init(){
#define TACLS   0
#define TWRPH0  1    //  hclk * (twrph0+1)  = 0.1ns * (twrph0 + 1)>=12
#define TWRPH1  0
	/* 设置时序  上面的三个宏的设置请参考数据手册,具体说明:http://hi.baidu.com/kqalowgkkceqvys/item/f6406fdb731fdef593a97404*/
	NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4 );
	/* 使能nand flash 控制器,初始化ECC,禁止片选 */
	NFCONT = (1<<4)|(1<<1)|(1<<0);	
}

接下来到了copy_code_to_sdram(),也就是重定位

void copy_code_to_sdram(uchar* src_addr,uchar* dest,uint len){
	/* 判断从哪里启动  */
	uint i = 0;
	_puts("copy_code_to_sdramnr");
	if(isBootFromNorFlash()){// 如果是从norflash启动,直接拷贝
		_puts("NorFlashnr");
		while(i<len){
			*dest++ = *src_addr++;
			i++;
		}
	}else{	
		_puts("Nand Flashnr");
		nand_read((uint)src_addr,dest,len);//下面解释
	}
	_puts("copy_code_to_sdram end .................nr");
}

nandflash的读操作是要参考数据手册的,

  1. 片选
  2. 发送00h命令
  3. 发送地址5周期
  4. 发送读命令 30h
  5. 判断状态
  6. 读..
  7. 取消片选
void nand_read(uint src_addr,uint len){
	uint i = 0,j=0;
	int col = src_addr%2048;
	/*片选*/
	nand_select();
	
	while(i < len){
		/*发出读命令00h*/
		nand_cmd(0x0);
		/*发出地址(5个周期)*/
		nand_addr(src_addr);
		/*发出读命令30h*/
		nand_cmd(0x30);
		/*判断状态*/
		nand_wait_ready();
		/*读数据*/
	
		for(;(col<2048)&&(i<len);col++){			
			dest[i++] = nand_data();
		}
		if(!((j++)%20))
			_putc('#');// 打印提示
		col = 0;
	
	}
	/*取消片选*/
	nand_disselect();
	_puts("nr");
	_puts("nand_read endnr");
}

?

void nand_cmd(uchar cmd){/*发送nand命令*/
	volatile int i = 10;
	NFCMMD  = cmd;
	while(i--);
}

void nand_addr(uint addr){/*发送nand地址,5个周期,具体参考对应的数据手册*/
	uint col  = addr % 2048;
	uint page = addr / 2048;
	volatile int i;
	NFADDR = col&0xff;
	for(i = 10 ;i; i--);
	NFADDR = (col>>8)&0xff;
	for(i = 10 ;i; i--);
	NFADDR = page&0xff;
	for(i = 10 ;i; i--);
	NFADDR = (page>>8)&0xff;
	for(i = 10 ;i; i--);
	NFADDR = (page>>16)&0xff;
	for(i = 10 ;i; i--);
}

void nand_wait_ready(){
	while(!(NFSTAT&1));
}

uchar nand_data(){/*读取数据*/
	return NFDATA;
}

void nand_select(){/*片选*/
	NFCONT &= ~(1<<1);
}
void nand_disselect(){/*取消片选*/
	NFCONT |= (1<<1);
}


?接下来是清bss段

void clear_bss()
{
	extern int __bss_start,__bss_end;
	int *p = &__bss_start;
	_puts("clear_bssnr");
	while(p<&__bss_end){
		*p++ = 0;
	}
}

好啦,我们跳到Main函数,也就是我们的第二阶段啦:

void Main(){
	void	(*theKernel)(int zero,int arch,uint params);

	/*1. 从nand flash 里把内核读到内存*/
	nand_read(0x60000,(uchar *)0x30008000,0x500000);/*从0x600000拷贝0x500000 = 5M到0x30008000去*/	
	/*2. 设置参数*/
	_puts("set params nr");
	setup_start_tag ();
	setup_memory_tags ();
	setup_commandline_tag ("noinitrd root=/dev/mtdblock2 init=/linuxrc console=ttySAC0");
	setup_end_tag ();

	
	/*3. 跳转执行*/
	_puts("boot kernel nr");
	theKernel = (void (*)(int,int,uint))(0x30008000);
	theKernel (0,1999,0x30000100);

	_puts("error............. nr");
	
}


?

static struct tag *params;

static void setup_start_tag ()
{
	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);
}
static void setup_memory_tags ()
{

	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);
}

static uint _strlen(char *buf){
	uint i=0;
	while(*buf++){
		i++;
	}
	return i;
}

static void my_strcpy(char * src,char *dest){

	while ((*dest++ = *src++) != '');
}
static void setup_commandline_tag ( char *commandline)
{

	int len = _strlen(commandline)+1;
	_puts("setup_commandline_tag now nr");
	params->hdr.tag = ATAG_CMDLINE;
	params->hdr.size = (sizeof (struct tag_header) + len + 3) >> 2;/* 向4取整*/

	my_strcpy(params->u.cmdline.cmdline,commandline);
	params = tag_next (params);
}

static void setup_end_tag ()
{
	params->hdr.tag = ATAG_NONE;
	params->hdr.size = 0;
}


最后附上链接脚本:

SECTIONS {
    . = 0x0;
    .text : { *(.text) }
    
    . = ALIGN(4);/*四字节对齐*/
    .rodata : {*(.rodata)} 
    
    . = ALIGN(4);
    .data : { *(.data) }
    
    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss)  *(COMMON) }
    __bss_end = .;
}

Makefile

CC       := arm-linux-gcc
LD       := arm-linux-ld
AR		 := arm-linux-ar
OBJCOPY  := arm-linux-objcopy
OBJDUMP  := arm-linux-objdump
CFLAGS   := -Wall -O2
CPPFLAGS := -nostdinc


objs := start.o  init.o Main.o
boot.bin:$(objs)
	$(LD) -o boot.elf -Tboot.lds $^           # 链接
	$(OBJCOPY) -O binary -S boot.elf $@       # 转为2进制
	$(OBJDUMP) -D -m arm boot.elf > boot.dis  #  反汇编

%.o:%.c
	$(CC) -o $@ -c $< $(CPPFLAGS) $(CFLAGS) 

%.o:%.s
	$(CC) -o $@ -c $< $(CPPFLAGS) $(CFLAGS) 

clean:
	rm -r *.o *.elf *.bin *.dis

代码:? ?

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读