自己写一个最简单的bootloader_jz2440

写在前面:

我的博客已迁移至自建服务器:博客传送门,CSDN博客暂时停止,如有机器学习方面的兴趣,欢迎来看一看。

此外目前我在gitHub上准备一些李航的《统计学习方法》的实现算法,目标将书内算法全部手打实现,欢迎参观并打星。GitHib传送门

正文

boot是为了启动内核,本质上也就是一个裸板程序,就是为了引导内核的启动。所以打算自己写一个boot,功能只有引导内核启动。

首先是汇编的代码段,是为了关闭看门狗,设置时钟以及代码的重定位,这些都是在main函数之前执行的。之前学习单片机的时候,我们只看到main函数,实际上是main之前的执行步骤都被包起来了。

整个汇编文件的开头要写上

.text				@这是为了表示这是一个代码段
.global _start
_start:

第一步:关闭看门狗,2440是默认关门狗打开的,如果不关闭看门狗,三秒钟后板子会自动重启

/* 关看门狗 */
	ldr 	r0, 	= 0x53000000		/* 看门狗的寄存器地址,通过芯片手册可以查看 */
	mov r1, 	#0				/* 把0放入r1*/
	str 	r1,	 [r0]				/* 把r1中的0赋给看门狗,即关闭看门狗 */

第二步:设置时钟,2440板子的晶振是12M(也可以使用16M或其他的),如果不设置时钟去倍频,板子是以12M的速度跑的,这里是设置分频系数以及设置板子的频率为400M
//经过实测,boot在200M和400M的情况下,都需要6秒才能启动内核,速度有点慢,所以使用了ICACHE提高速度,使用ICACHE以后,启动内核只需要2秒,可以接受

#define 	S3C2440_MPLL_400MHZ     ((0x5c<<12)|(0x01<<4)|(0x01))
/* 设置时钟 */
	ldr 	r0, 	= 0x4c000014
	mov r1,	#0x05			//FCLK:HCLK:PCLK=1:4:8
	str 	r1, 	[r0]

	ldr 	r0, 	=0x4c000004
	ldr 	r1, 	=S3C2440_MPLL_400MHZ
	str 	r1, 	[r0]

/******以下为启动ICACHE*******/
/* 启动ICACHE */
	mrc	 p15, 0, r0, c1, c0, 0
	orr	r0,	r0,	#(1<<12)
	mcr	 p15, 0, r0, c1, c0, 0
/******* 以上为启动ICACHE *****/

启动Icahe的原理是这样:每次CPU去RAM读代码,很费力,ICACHE就是把这段代码copy到片子里的一个区域,CPU直接去读这块区域就行了,而且这块区域的读速度比RAM快多了。就好像每天都有一份快递,自己每天去取费时费力,直接让快递员送门口,就省很多事。

第三步:初始化SDRAM,代码需要在SDRAM中执行,所以需要初始化

/* 初始化SDRAM */
	ldr 	r0,  	=MEM_CTL_BASE		//设置SDRAM寄存器的首地址
	adr 	r1, 	sdram_config			/*SDRAM每个寄存器的配置值 */
	add	r3, 	r0, #(13 * 4)			//SDRAM寄存器的尾地址

1:
	ldr	r2, 	[r1], #4				//把寄存器的配置值写入r2,然后r1地址加4字节,定位到sdram_config的下一个配置值
	str	r2, 	[r0], #4				//r2里的值写入到r0地址,也就是SDRAM的第一个寄存器,然后寄存器地址加四字节,指
//向下一个寄存器
	cmp r0, 	r3					//当前寄存器地址和寄存器尾地址比较,如果不一致说明还没配置完,跳转到1继续循环
	bne 	1b

/*放在文件末尾,这是关于SDRAM的每个寄存器的配置值*/
sdram_config:
	.long 0x22011110      //BWSCON
	.long 0x00000700      //BANKCON0
	.long 0x00000700      //BANKCON1
	.long 0x00000700      //BANKCON2
	.long 0x00000700      //BANKCON3  
	.long 0x00000700      //BANKCON4
	.long 0x00000700      //BANKCON5
	.long 0x00018005      //BANKCON6
	.long 0x00018005      //BANKCON7
	.long 0x008C04F4 	    //REFRESH
	.long 0x000000B1      //BANKSIZE
	.long 0x00000030      //MRSRB6
	.long 0x00000030      //MRSRB7

第四步:代码重定位,因为代码是存在NOR FLASH 或者 NAND FLASH里面的,CPU要将代码移入SDRAM中才可以使用

/* 重定位 */
	ldr	sp,	=0x34000000		//因为nand初始化比较复杂,使用C语言实现,如果要使用C语言,需要设置堆栈指针
	
	bl nand_init				//初始化nand,其实如果使用的是nor,完全可以不初始化nand,这里是考虑到不知道使用的是nor还是nand,所以把nand初始化了,之后会判断使用的是什么flash。之所以不用初始化nor,是因为CPU可以直接读nor

	mov r0, 	#0				//设置copy_code_to_sdram的参数,r0是该函数第一个参数,r1是第二个,r2是第三个,很好理解
	ldr 	r1,	=_start
	ldr 	r2,	= __bss_start
	sub 	r2,	r2,	r1
	
	bl copy_code_to_sdram		//调用C函数,实现将代码copy至SDRAM
	bl clear_bss				//清除BSS段,未初始化或初始化为0的变量都存在这里,所以需要清零

bss段挺好玩的,这样的,如果你程序里面设置了很多变量,初始值都为0,那么程序一开始一个一个去赋0效率太低了,所以所有初始值为0的变量,都保存在bss代码段里,程序启动前将整个代码段清零就行了,就很方便

第五步:执行main函数

/* 执行main */
	ldr	lr,	=halt		//设置main函数的返回地址,其实boot启动内核以后直接就死掉了,根本不会返回,这里还是写一下返回地址,main返回以后去执行下面的halt段,不断地死循环,防止出问题
	ldr	pc,	=main		//指针定位到main函数

halt:
	b	halt

/* 重定位过程中使用的C函数 挑了几个稍微重要点的,别的都不写了,没什么意思*/
/* nand初始化,没什么意思,就不多写了 */

void nand_init (void)
{
#define TACLS  	0
#define TWRPH0  	1
#define TWRPH1  	0

	/* 设置时序 */        
	NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);        

	/* 使能NAND Flash控制器, 初始化ECC, 禁止片选 */        
	NFCONT = (1<<4)|(1<<1)|(1<<0);
}

/* 复制代码段到SDRAM*/
//这里就是判断是NOR还是NAND,因为它们的特性不同,导致nand可读可写,NOR只能读,所以测试的时候就随便找个地址,往里写一个数,再往外读,如果值被修改了,说明是nand,反之是nor

void copy_code_to_sdram (unsigned char *src, unsigned char *dest, unsigned int len)
{
	int i = 0;
	/* 如果是NOR启动 */
	if (isBootFromNorFlash())
	{
		while(i < len)
		{
			dest[i] = src[i];
			i++;
		}
	}
	else
	{
		nand_read((unsigned int)src, dest, len);
	}
}
/* main函数 */
int main (void)
{
	void (*theKernel)(int zero, int arch, unsigned int params);		//申明内核的指针

	/*0:  设置串口,内核启动的开始部分会从串口打印一些信息,但是内核一开始并没有初始化串口,提前初始化好,免得内核没有串口用 */
	uart0_init();

	/* 1.从NAND FLASH 里把内核读入内存 */
	puts("Copy kernel from nand\n\r");
	nand_read(0x60000 + 64, (unsigned char *)0x30008000, 0x200000);  	/*读出地址是根据板子看的,mtd命令可以看板子的分区,找到kernel分区就行了*/
													/* 存入地址在内核启动的时候会显示, 大小也是,板子用的内核是1.8M ,这里给了2M 空间 */
	
	/* 2. 设置参数 */
//这里设置各种传给内核的参数,因为启动内核以后boot就死了,没法和内核面对面交流,boot将一些命令放在一块区域里,内核启动以后去这个地址读就行了
	puts("Set boot params\n\r");
	setup_start_tag();
	setup_memory_tag();
	setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0");
	setup_end_tag();

	/* 3. 跳转执行 */
	puts("Boot kernel\n\r");
	theKernel = ((void (*)(int, int, uint))0x30008000);
	theKernel(0, 362, 0x30000100);		//第一个参数写的就是0,我就直接写0了,第二个参数是ID,2440的ID是这个,第三个参数是参数存放的地址,内核直接去这个地址读就可以了

	/* 如果一切正常,不会执行到这里,也就是内核启动以后不会再回到boot */
	puts("Error!\n\r");

	return -1;
}

/* 给内核传入的各种命令 */
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_tag (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);
}
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;

	strcpy (params->u.cmdline.cmdline, cmdline);

	params = tag_next (params);
}

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

你可能感兴趣的:(2440学习路,boot相关)