我的博客已迁移至自建服务器:博客传送门,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;
}