说一下u-boot,它是一个在嵌入式设备中相当于电脑bootloader的一个东西,能干啥:1.初始化硬件 2.启动内核
只有内核启动了才能让一个系统在硬件上跑起来,这样才能扔给程序员编写应用程序app。这是链接脚本(u-boot.lds),内存是这么放的。Makefile在编译时根据u-boot.lds去放的各个段,下面的代码我都写了相关的注释,有问题请指点,看不懂的话就只能对不起你自己了,更对不起我,好,话不多说
SECTIONS {
. = 0x33f80000;
.text : { *(.text) } //在链接脚本里 .text表示代码内存的代码段。代码段放在0x33f80000,也就是0x33f80000开始执行程序
. = ALIGN(4);
.rodata : {*(.rodata*)} //只读数据段 代码段的结束地址按4个对齐接下来就放只读数据段
. = ALIGN(4);
.data : { *(.data) } //数据段
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(COMMON) } //bss段,是未初始化的全局变量先放这里,这些数据没用,程序会清掉的
__bss_end = .;
}
为什么需要这样的一个链接脚本:如果不这么放没有这些代码段数据段的环境的话程序是无法运行的,要知道我们正处于0阶段,c程序还不能运行,这个环境还只够汇编程序运行,代码的第一个入口程序文件一定得用汇编写,这个问价是设备一开机就会运行的那种,现在的所有的嵌入式设备也好PC机也好入口程序肯定是汇编这种比较直接的机器语言。
首先需要编写汇编程序start.S (底层初始化) start.S必要干的事情有以下(百问科技的JZ2440平台为例):
1. 关闭看门狗 2.设置始终分频比例,频率 3.内存控制器初始化 45.设置栈(想调用c函数先设置栈) 5.nand初始化并重定位(nand_init是c函数) 6.进入main
start.S源代码如下:
#define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01))
#define MEM_CTL_BASE 0x48000000
.text
.global _start //_start标识符=0x33f80000 第一个指令挂载到0x33f80000
_start:
/* 1. 关看门狗 */
ldr r0, =0x53000000
mov r1, #0
str r1, [r0]
/* 2. 设置时钟 */
ldr r0, =0x4c000014
mov r1, #0x05; // PCLK:HCLK:FCLK=1:2:8
str r1, [r0]
/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
mrc p15, 0, r1, c1, c0, 0 /* 读出控制寄存器 */
orr r1, r1, #0xc0000000 /* 设置为“asynchronous bus mode” */
mcr p15, 0, r1, c1, c0, 0 /* 写入控制寄存器 */
ldr r0, =0x4c000004
ldr r1, =S3C2440_MPLL_400MHZ //MPLL=400MHZ
str r1, [r0]
/* 启动ICACHE和DCACHE 会更快 */
mrc p15, 0, r0, c1, c0, 0 @ read control reg //启动I/D CACHE内存,这样很快。 可以去掉这段代码看看,然后发现设备启动起来比毛驴车还慢
orr r0, r0, #(1<<12) //cache技术可以查资料自己看,也就是一个内部的很快很高贵的高速内存。
mcr p15, 0, r0, c1, c0, 0 @ write it back
/* 3. 初始化内存控制器,就可以读写sdram了 */
ldr r0, =MEM_CTL_BASE
adr r1, sdram_config /* sdram_config的当前地址 */
add r3, r0, #(13*4) /*r3是结束地址*/
1:
ldr r2, [r1], #4
str r2, [r0], #4
cmp r0, r3
bne 1b
/* 4. 重定位 : 把bootloader本身的代码从flash复制到它的链接地址去 */
ldr sp, =0x34000000 //设置栈 内存的最顶 //从0x33f80000(在.lds链接脚本里的链接地址)到0x34000000总共是512kB空间
bl nand_init //nand初始化
mov r0, #0
ldr r1, =_start
ldr r2, =__bss_start
sub r2, r2, r1
bl copy_code_to_sdram //重定位函数 参数r0=0,r1=_start(0x33f80000), r2=u-boot_size
bl clear_bss
/* 5. 执行main */
ldr lr, =halt
ldr pc, =main
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
设置好栈以后可以运行C程序,nand flash要初始化才能在往后的程序里读写,这样才能重定位。有一个问题一直困扰我,机器一运行就从nand flash拷贝代码到内部sram,我觉得应该是芯片内部有固化的代码,拷贝到内部sram后就得重新编写nand flash的读写程序,固化的copy程序已经不能用了,不在代码区内。我猜的,芯片手册上nand flash那个页面有一个框图,根据这个去猜的,也挺合逻辑的,不过理解上要不能这么虚,还请各位路过朋友们指点。那,继续说,第一个C程序就是nand flash的读写,因为要为重定位做准备。
初始化nand flash 设置栈后第一个程序是nand_init函数
void nand_init(void)
{
#define TACLS 0 /* 设置时序 */ //看2440芯片手册根据读写flash接口的时序配置
#define TWRPH0 1
#define TWRPH1 0
NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
NFCONT = (1<<4)|(1<<1)|(1<<0); /* 使能NAND Flash控制器, 初始化ECC, 禁止片选 */
}
TACLS,TWRPH0,TWRPH1这三个时序参数设置好了就可以完成nand芯片初始化工作,能读写了。根据时序图和K9F2G08U0M(nand存储器)的时序要求去配置,下面是2440和nand芯片datasheet里的截图,根据这个去配置:
可以发现 TWRPH1对应talh/tclh>=5n TWRPH0对应twp>=15ns
TWRPH0: twp>=15ns(第二个图查表)=此处duration的值 HCLK x (TWRPH0 + 1) -> HCLK=100MHZ(10ns) -> 15ns<=10ns * (TWRPH0 +1 ) =0.5->Duaration=1 ->[10:8]=001
TWRPH1: talh/tclh>=5ns(查表) 同理 HCLK x (TWRPH1 + 1) 5ns<=HCLK x (TWRPH1 + 1) -> Duration=0时成立 [6:4]=000
TACLS: =tcls-twp tcls>=15ns twp>=15 TACLS=15-15=0 Duration=HCLK x TACLS = 0 [13:12]=00
其他的读写功能函数,在init.c文件里放着,已上传工程文件
flash的事情已经搞定。最后一部从flash里读出内核加载启动。这都在main函数里实现。调用c程序前,汇编程序中设置栈。
int main(void)
{
void (*theKernel)(int zero, int arch, unsigned int params); //设定函数指针
volatile unsigned int *p = (volatile unsigned int *)0x30008000; //0x30008000是读出内核后加载到sdram内存的地址
uart0_init(); /* 帮内核设置串口: 内核启动的开始部分会从串口打印一些信息,但是内核一开始没有初始化串口 */
puts("Copy kernel from nand\n\r"); /* 从NAND FLASH里把内核读入内存 */
nand_read(0x60000+64, (unsigned char *)0x30008000, 0x200000); //0x60000+64是nand flash里内核存放地址,0x30008000目标目标加载地址,0x200000=size
/* 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; //设置0x30008000=theKernel函数的入口地址。
theKernel(0, 362, 0x30000100); //调用theKernel启动内核命令。362是机器ID,0x30000100是有参数的地方
/*
* mov r0, #0
* ldr r1, =362
* ldr r2, =0x30000100
* mov pc, #0x30008000
*/
puts("Error!\n\r");
/* 如果一切正常, 不会执行到这里 */
return -1;
}
完结,这里摆出来的代码不全,只强调了一些重要的代码过程。
w...t...f 有点情况,这里附不了工程代码文件
请看我在CSDN上面的页面,这里能下工程文件:
http://bbs.elecfans.com/jishu_1896945_1_1.html
有问题或解释不恰当在下面点拨一下,我去改,感谢看完。
原文:https://www.cnblogs.com/maiti-123/p/12198809.html