编写最简单的u-boot附工程源码 jz2440平台

说一下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

有问题或解释不恰当在下面点拨一下,我去改,感谢看完。 

猜你喜欢

转载自www.cnblogs.com/maiti-123/p/12198809.html