一、硬件初始化和制作链接脚本lds
1.1、目标
第一阶段:
- 关看门狗
- 设置时钟
- 初始化SDRAM(初始化寄存器以及清除bss段)
- 重定位(将nand/nor中的代码COPY到链接地址上,需要初始化nandflash,读flash)
- 执行main
进入第二阶段:
- 写main函数,在main()中设置要传给内核的参数,然后跳转内核基地址处
- 制作uboot.lds链接脚本
1.2、创建工程并编写start.S
1)创建个名为"my_bootloader"根目录,方便编写uboot
2)新建my_bootloader/si目录,创建source insight工程
3)新建my_bootloader/start.S (后缀名必须是大写的S,或者后面编译会报错)
编写start.S (第一阶段初始硬件文件):
/* 看门狗寄存器 */
#define WTCON 0x53000000
/* 2.时钟寄存器 */
#define CLKDIVN 0x4C000014 //设置FCLK,HCLK,PCLK比例
#define MPLLCON 0x4C000004 //设置FCLK频率
#define S3C2440_MPLL_200MHZ ((0x5c<<12)|(0x01<<4)|(0x02)) //设置FCLK=200MHZ
#define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01)) //设置FCLK=400MHZ
/* 3.bank寄存器 */
#define BWSCON 0x48000000
.text /*设置代码段*/
.global _start /*定义全局变量,要被连接脚本用到*/
_start: /*_start跳转到这里实现硬件初始化*/
/* 1.关看门狗 */
ldr r0,=WTCON
mov r1,#0
str r1,[r0]
/* 2.设置时钟(必须设为异步总线模式) */
ldr r0,=CLKDIVN
mov r1,#3 /*FCLK:HCLK:PCLK=1:2:4*/
str r1,[r0]
mrc p15, 0, r1, c1, c0 /* 读出控制寄存器 */
orr r1, r1, #0xc0000000 /* 设置为“asynchronous bus mode” */
mcr p15, 0, r1, c1, c0, 0 /* 写入控制寄存器 */
ldr r0,=MPLLCON
ldr r1,=S3C2440_MPLL_200MHZ //使用复杂的数不能用mov,需要用ldr
str r1,[r0]
/* 3.初始化SDRAM */
ldr r0,=BWSCON //r0=SDRAM寄存器基地址
adr r1,SDRAM_CONFIG //使用adr相对跳转,因为SDRAM未初始化
add r2,r0,#(13*4) //r2等于 SDRAM尾地址+4
0:
ldr r3,[r1],#4 //r3=r1里的 内容, &r1+=4;
str r3,[r0],#4 //*r0=r3,&r0+=4
cmp r0,r2
bne 0b //(ne:no equal b:bank) 若r0!=r2,跳转到后面的0处
/* 4.重定位 */
ldr sp,=0x34000000 //64Msdram,所以设置栈SP=0x34000000,避免堆栈溢出
mov r0,#0 //r0->src
ldr r1,=_start //r1->dest
ldr r2,=__bss_start //r2->len->__bss_start-_start
sub r2,r2,r1
bl copy_code_to_sdram //复制代码到SDRAM连接地址dest上
bl clear_bss //清除bss段
/* 5.执行main */
ldr lr,=halt //执行一个子程序调用返回时,需要先将返回地址赋给lr链接寄存器
ldr pc,=main //初始化SDRAM后,可以使用pc 绝对跳转到SDRAM地址上
mov pc,lr //若main函数跳出后,使PC等于lr链接寄存器,避免程序跑飞
halt: //死循环,避免跑飞
b halt
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
1.3、编写my_bootloader/init.c
新建my_bootloader/init.c,用于重定位,bss段清除,初始化NandFlash等
在重定位函数中的nand驱动在(http://www.cnblogs.com/lifexy/p/7097695.html )这篇帖子有详细介绍,这里就不描述.
1)编写copy_code_tosdram() 重定位函数
void copy_code_to_sdram(unsigned char *src,unsigned char *dest,unsigned int len) //复制代码到SDRAM连接地址dest上
{
unsigned int i;
/*判断nor启动还是nand启动*/
if(is_boot_from_norflash()) //nor启动,直接复制
{
for(i=0;i<len;i++)
{
dest[i]=src[i];
}
}
else
{
nand_init();
nand_read((unsigned int)src,dest,len);
}
}
2)编写isBootFramNorFlash()函数,来判断nand启动还是nor启动
/*判断nor启动还是nand启动*/
unsigned char is_boot_from_norflash(void)
{
volatile unsigned int *p=(volatile unsigned int *)0;
unsigned int i;
i=*p;
*p=0x12345678;
if(*p==0x12345678) //nand
{
*p=i;
return 0;
}
else //nor
{
*p=i;
return 1;
}
}
3)编写clear_bss()函数
void clear_bss(void) //清除BSS段
{
extern int __bss_start,__bss_end;
int *p=&__bss_start;
for( ;p<&__bss_end;p++)
{
*p=0;
}
}
1.4、编写链接脚本uboot.lds
参考硬件实验里的uart.lds和u-boot-1.1.6里的u-boot.lds
SECTIONS {
. = 0x33f80000; //0地址里存放0X33F80000
. = ALIGN(4);
.text : { *(.text) }
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(COMMON) }
__bss_end = .;
}
其中0x33f80000就是链接地址首地址,共512K空间存放bootloader
定义__bss_start
和__bss_end
符号,是用来程序开始之前将这些未定义的变量清0,节省内存且bss_start-0x33f80000就等于代码的大小(copy_code_tosdram函数中len值)
二、启动内核和制作Makefile
2.1、目标
1)添加头文件setup.h和serial.h
2)写main函数
帮内核设置串口0, (内核启动会打印出启动信息)
把内核读入到SDRAM
设置参数(参考u-boot-1.1.6 /lib_arm/armlinux.C中do_bootm_linux()函数)
跳转运行内核(参考u-boot-1.1.6/lib_arm/armlinux.C中do_bootm_linux()函数)
3)写tag参数函数
setup_start_tag (void);
setup_memory_tags(void);
setup_commandline_tag (char *cmdline);
setup_end_tag (void);
4)写makefile文件
2.2、添加头文件setup.h和serial.h
1)将lcd裸板程序中串口uart0初始化文件serial.c复制到my_bootloader目录中.并修改serial.c
2)因为TAG结构体定义是存在u-boot-1.1.6/include/asm-arm/setup.h中,所以设置TAG参数需要用到这个文件,将setup.h复制到my_bootloader目录中.
3)修改setup.h文件
删除以下不需要的代码
#define __tag __attribute__((unused, __section__(".taglist")))
#define __tagtable(tag, fn) \
static struct tagtable __tagtable_##fn __tag = { tag, fn }
#define tag_member_present(tag,member) \
((unsigned long)(&((struct tag *)0L)->member + 1) \
<= (tag)->hdr.size * 4)
添加以下代码
#define u32 unsigned long
#define u16 unsigned int
#define u8 unsigned char
2.3、编辑my_bootloader/boot.c(boor.c?)
新建my_bootloader/boor.c,用于存放main函数(main:由start.S跳转过来的).
main函数代码如下:
void main(void)
{
void (*theKernel)(int zero, int arch, unsigned int params);
/*定义一个函数指针theKernel,其中第一个参数zero:0 */
/* arch:机器ID ,由于芯片类型很多,内核为了辨别芯片而定义的机器ID,其中2440芯片的ID号是362,*/
/* params :tag参数位置,这里我们的tag起始地址=0x30000100*/
/*1 初 始 化 串 口 0 , 使 内 核 能 打 印 信 息 */
uart0_init(); //调用serial.h头文件里的uart0_init()
puts(“uart0 init OK\r\n”); //打印uart0初始化
/*2从 nand flash 里 把 内 核 复 制 到 SDRAM 中 */
puts(“copy kernel from nand\r\n”); //打印内核复制
nand_read((0x60000+64),0X30008000,0X200000); //烧写2MB,多烧写点避免出错
/*
0x60000+64:表示内核在nand(存储)地址上位置,
0X30008000:内核在sdram(运行)地址上位置
0X200000:内核长度2MB
因为Flash上存的内核格式是:uImage(64B头部(header) + 真正的内核 )
在uboot界面中输入mtd命令可以看到:
kernel分区位于 nand的0X00060000~0x00260000
所以在nand中真正的内核地址=0x60000+64,
在uboot界面中输入boot命令可以看到:
Data Size: 1848656 Bytes =1.8 MB
Load Address: 30008000
所以内核目的地址=0X30008000
长度=1.8MB
*/
/*3 设 置 T A G 参 数 */
puts(“set boot params\r\n”); //打印设置参数信息
setup_start_tag (void); //在0X30000100地址保存start_tag数据,
setup_memory_tags (void); //保存memory_tag数据,让内核知道内存多大
setup_commandline_tag (“boottargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0”);
/*保存命令行bootargs参数,让内核知道根文件系统位置在/dev/mtdblock3,指定开机运行第一个脚本/linuxrc,指定打印串口0*/
setup_end_tag (void); //初始化tag结构体结束
/* 4 跳 转 执 行 */
puts(“boot kernel\r\n”); //打印启动内核
theKernel = (void (*)(int, int, unsigend int))0x30008000;
// 设置theKernel地址=0x30008000,用于后面启动内核
theKernel(0,362,0x300000100); //362:机器ID, 0x300000100: params(tag)地址
/*传递参数跳转执行到0x30008000启动内核, */
/*相当于: mov r0,#0 */
/*ldr r1,=362 */
/*ldr r2,= 0x300000100 */
/*mov pc,#0x30008000 */
puts(“kernel ERROR\r\n”); //打印内核启动出错
}
2.4、创建TAG参数函数
设置tag参数函数代码如下
#include “setup.h”
static struct tag *params; //定义个tag结构体变量params指针
setup_start_tag (void) //开始tag
{
params = (struct tag *) 0x30000100; //tag起始地址等于0X30000100
params->hdr.tag = ATAG_CORE; //头部常量tag=0x54410001
params->hdr.size = tag_size (tag_core); //size=5,
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next (params); //parmas=( struct tag *)((u32 *)parmas+ params->hdr.size)
}
// setup_start_tag (bd)保存tag参数如下:
setup_memory_tags (void) //内存tag
{
int i;
params->hdr.tag = ATAG_MEM; //头部常量tag=0x54410002
params->hdr.size = tag_size (tag_mem32); //size=4
params->u.mem.start = 0x30000000; //SDRAM起始地址
params->u.mem.size = 0x4000000; //SDRAM内存大小64M
params = tag_next (params); //指向下个tag
}
// setup_memory_tag s(bd)保存tag参数如下:
int strlen(char *str) //uboot不依赖任何库,所以需要自己写strlen函数
{
int i=0;
while(str[i])
{
i++;
}
return i;
}
void strcpy(char *dest, char *src)
{
while((*dest++=*src++)!=’\0’&&*dest!=’\0’);
}
setup_commandline_tag (char *cmdline) //命令行tag
/**cmdline :指向命令行参数*/
/*一般为:“boottargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0” */
{
int len=strlen(cmdline)+1; //计算cmdline长度,并加上结束符
params->hdr.tag = ATAG_CMDLINE; //头部常量tag=0x54410009
params->hdr.size =(sizeof (struct tag_header) +len+3) >> 2; /*size=(字符串长度+头部长度) >>2 */
/*“+3”表示:按4字节对齐,比如当总长度=(1,2,3,4)时,size=(总长度+3)>>2=1,实现4字节对齐 */
strcpy (params->u.cmdline.cmdline, cmdline); //复制形参字符串到params->u.cmdline.cmdline
params = tag_next (params); //执行下个tag
}
setup_end_tag (void) //结束tag
{
params->hdr.tag = 0;
params->hdr.size = 0;
}
2.5、写makefile文件
首先将lcd裸板程序里的makefile复制到my_bootloader目录中,并修改.
备注:在makefile中‘=’与‘:=’的区别:
‘=’:无关位置的等于(比如:”x=a y=$(x) x=b”,那么y的值永远等于最后的值,等于 b ,而不是a)
‘:=’:有关位置的等于(比如:”x:=a y:=$(x) x:=b”,那么y的值取决于当时位置的值,等于 a ,而不是b)
CC = arm-linux-gcc //定义CC变量=arm-linux-gcc,简化书写,编译命令,(*.C,*.S)文件生成*.O文件
LD = arm-linux-ld //连接命令,将多个*.O文件生成 boot.elf
AR = arm-linux-ar //库管理命令,这里没有用到
OBJCOPY = arm-linux-objcopy //复制/格式转换命令, boot.elf生成boot.dis
OBJDUMP = arm-linux-objdump //反汇编命令,boot.bin生成boot.dis
CFLAGS := -Wall -O2 //GCC编译参数,-Wall:显示所有错误和警告, -O2:采用2级编译优化
CPPFLAGS := -nostdinc -fno-builtin
//添加头文件参数,-nostdinc忽略缺省目录, -fno-builtin不连接系统标准启动文件和标准库文件(表示不用自带的strlen()等库函数)
objs := start.o init.o boot.o //定义objs变量,包含生成boot.bin目标文件需要的依赖文件
boot.bin: $(objs) //执行生成目标文件,首先是先满足objs所有依赖文件都拥有,才执行
${LD} -Tuboot.lds -o boot_elf $^
${OBJCOPY} -O binary -S boot_elf $@
${OBJDUMP} -D -m arm boot_elf > boot.dis
%.o:%.c //%通配符。生成xxx.o文件先要找到xxx.c文件
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $< //-c编译不连接。$@表示目标文件 $<表示第一个依赖文件
%.o:%.S
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
clean:
rm -f *.bin *.elf *.dis *.o
三、编译测试以及改进
3.1、编译测试
1)将写好的uboot复制到linux下面
2)make编译,然后将错误的地方修改,生成boot.bin(编译出错的解决方案:http://www.cnblogs.com/lifexy/p/7326172.html)
3)通过make生成的反汇编来查看代码是否正确
4)通过oflash烧写到板子nand flash上
5)查看串口是否数据打印
发现串口无数据,发现两处错误:
5.1)在init.C中define定义 没有加大括号,没有定义volatile型
例如:
#define NFCONF *((unsigned long *)0x4E000000)
需要改为: #define NFCONF (*((volatile unsigned long *)0x4E000000) )
5.2)在init.C中nand_read_addr()函数出错,代码如下:
void nand_read_addr(unsigned int addr)
{
volatile int i;
NFADDR=( addr>>0)&0xff; //A7~A0
for(i=0;i<10;i++);
NFADDR=( addr>>8)&0x0f; //A11~A8
for(i=0;i<10;i++);
NFADDR=( addr>>12)&0xff; //A19~A12
for(i=0;i<10;i++);
NFADDR=( addr>>20)&0xff; //A27~A20
for(i=0;i<10;i++);
NFADDR=( addr>>28)&0xff; //A28
for(i=0;i<10;i++);
}
上面之所以错是因为nand flash是2048B一页,这里的写的一页是A11~A0,其值=4096,已经属于两页大小了
应该改为:
void nand_read_addr(unsigned int addr)
{
unsigned int col = addr % 2048;
unsigned int page = addr / 2048;
volatile int i;
NFADDR=(col>>0)&0xff; //A7~A0
for(i=0;i<10;i++);
NFADDR=(col>>8)&0x0f; //A10~A8
for(i=0;i<10;i++);
NFADDR=(page>>0)&0xff; //A18~A11
for(i=0;i<10;i++);
NFADDR=(page>>8)&0xff; //A26~A19
for(i=0;i<10;i++);
NFADDR=(page>>16)&0xff; //A27
for(i=0;i<10;i++);
}
3.2、优化改进(加快内核启动时间)
3.2.1、提高CPU频率
将CPU频率设为最大值400MHZ(内核启动时间7S变为6S,因为HCLK和PCLK频率没有改变)
然后分频系数FCLK:HCLK:PCLK需要设置为1:4:8
因为HCLK最高133MHZ,这里需要设置为100MHZ
PCLK最高50MHZ,这里需要设置为50HZ
如下图所示,得出 CLKDIVN寄存器需要等于0X5即可
通过下图看出,提高CPU频率需要设置MPLLCON中MDIV、PDIV、SDIV
通过下图得出,取400MHZ时,设置MDIV为0X5C,PDIV为0x1,SDIV为0x1
所以改进代码如下:
#define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01)) //设置FCLK=400MHZ
/* 2.设置时钟(必须设为异步总线模式) */
ldr r0,=CLKDIVN
mov r1,#5 /*FCLK:HCLK:PCLK=1:4:8*/
str r1,[r0]
mrc p15, 0, r1, c1, c0 /* 读出控制寄存器 */
orr r1, r1, #0xc0000000 /* 设置为“asynchronous bus mode” */
mcr p15, 0, r1, c1, c0, 0 /* 写入控制寄存器 */
ldr r0,=MPLLCON
ldr r1,= S3C2440_MPLL_400MHZ //使用复杂的数不能用mov,需要用ldr
str r1,[r0]
3.2.2、开启ICAHE(内核启动时间6S变为1.5S)
CAHE介绍:
通过高速缓存存储器来加快对内存的数据访问,在CAHE中有ICAHE(指令缓存)和DCAHE(数据缓存)
ICAHE: 指令缓存,用来存放执行这些数据的指令
DCAHE:用来存放数据,需要开启MMU才能开启DCAHE
在没开启ICAHE之前,CPU读取SDRAM地址数据时,每次都需要先访问一次地址值,在读数据.
当开了ICAHE后,第一次读取SDRAM地址数据时,ICAHE发现缓存里没有这个地址数据,然后将SDRAM中需要读取的那部分一大块内存数据都复制在缓存中,后面陆续读取数据就不会再访问SDRAM了,直到CPU没有找到地址数据后ICAHE再从SDRAM中重新复制
通过CP15协处理器来开启ICAHE
(CP15协处理器操作用法: http://www.cnblogs.com/lifexy/p/7203786.html)
从上面链接中可以找到ICAHE控制位在CP15的寄存器C1中位12(如下图), 然后通过MRS和MSR向该位12置1,开启ICAHE
所以代码如下(放在SDRAM初始化之前)
mrc p15, 0, r0, c1, c0, 0 //将 CP15 的寄存器 C1 的值读到 r0 中
orr r0, r0, #(1<<12) //将r0中位12置1
mcr p15,0, r0,c1,c0,0 //开启ICAHE
参考链接:
https://www.cnblogs.com/lifexy/p/7337475.html