这一节,我主要要讲解一下我们的bootload 是怎样做的。我们的产品启动代码放置在nor Flash 中,产品上电后,首先执行nor Flash 中的代码. 另外我们的启动代码
是在ADS 环境下开发的,不是运用了类似如uboot和Vivi这类bootload. 我个人认为这类启动代码用在实际产品中还需要做很多的改动,用在开发板上还不错,但做产品就还远
不够。比如产品在使用过程中,需要更新固件,如果要用UART把代码下载到sdram,然后又执行烧写命令等,我想普通用户是做不好的。
我们是自己写了一个bootload,我在网上看到一个文档 read.pudn.com/downloads68/ebook/245410/ARM嵌入式软件开发.ppt 。 发现和我们的bootload 的流程很相似.
先看一下我们的 scatter 文件吧.
ROM_LOAD 0xA0000000
{
ROM +0
{
init.o (Init, +First)
anon$$obj.o
__main.o(!!!)
;* (+RO)
}
RAM 0x0
{
vectors.o(Vect, +First)
}
RAM2 0x1000000
{
* (+RW, +ZI) ; put them about 16M, in case crashed with Linux kernel
}
HEAP 0x1800000 UNINIT
{
heap.o (+ZI)
}
STACK 0x1900000 UNINIT
{
stack.o (+ZI)
}
}
解释一下, 0xA0000000 是 nor Flash 启动之后,经过地址重新映射之后的起始地址. SDRAM 经过地址重新映射之后,地址为 0.
程序启动后,首先执行Nor Flash 中的程序,这是因为IC 有 pin 脚设置的,这是Nor Flash 的开始地址被映射在0地址,在程序的开始
一段时间内,禁止中断,然后计算好程序跳到映射后的地址,再设置好映射,使SDRAM 映射到 0x00, Nor Flash 映射为0xa0000000.
程序如下:
ENTRY
EXPORT Reset_Go
Reset_Go
; Disable Interrupt, This is for safe ...
LDR r0, =AICMDCR
LDR r1, =0xFFFFFFFF
STR r1, [r0]
MRS r0, CPSR
ORR r0, r0, #0xC0
MSR CPSR_c, r0
;上面是禁止中断,因为程序刚启动,在0x0000000 地址处,是没有中断向量表的,在没有进入main 函数之前,bootload 是一直禁止中断的.
MRS r0, cpsr
BIC r0, r0, #0x1F
ORR r0, r0, #0xD3
MSR cpsr_fc, r0
;设置为SVC模式,禁止中断,这里其实是安全起见而已
LDR r2, =remap_temp
MOV r1, pc
LDR r3, =remap_EndSysMapJump
remap_temp
MOV lr, #0
CMP r2, r1
LDRGT lr, =ROM_Start ;ROM_Start EQU 0xA0000000
SUB r3, r3, r2
ADD r1, r1, r3
ADD lr, lr, r1
;上面做的这些加减运算,其实是在做一件事情,就是计算地址重新映射后,PC应该设置为多少.
; Load in the target values into the control registers
ADRL r0, remap_SystemInitData ;
LDMIA r0, {r1-r8}
LDR r0, =EBICON
; Now run critical jump code
STMIA r0, {r1-r8} ;这里是在设置映射地址
MOV pc, lr ;lr 在前面已经计算好了,这里只不过是跳转到映射后的地址
remap_EndSysMapJump
;下面程序执行的地址是在0xa0000000 之后. 也就是映射之后的地址
B Reset_Handler
remap_SystemInitData
; Default sdram configuration
DCD 0x001529E1 ; EBICON
DCD 0x40015550 ; ROMCON - mapping to 0xA0000000
DCD 0xFFC00000 ; ROMCON1
DCD 0xFFE00000 ; ROMCON2
DCD 0x000088CD ; SDCONF0 - 32MB SDRAM
DCD 0x00000800 ; SDCONF1
DCD 0x0000015B ; SDTIME0
DCD 0x0000015B ; SDTIME1
ALIGN 0
Reset_Handler
; --- Initialise stack pointer registers
MSR CPSR_c, #Mode_UNDEF:OR:I_Bit:OR:F_Bit
LDR SP, =UND_Stack
MSR CPSR_c, #Mode_ABT:OR:I_Bit:OR:F_Bit
LDR SP, =Abort_Stack
MSR CPSR_c, #Mode_IRQ:OR:I_Bit:OR:F_Bit
LDR SP, =IRQ_Stack
MSR CPSR_c, #Mode_FIQ:OR:I_Bit:OR:F_Bit
LDR SP, =FIQ_Stack
MSR CPSR_c, #Mode_SYS:OR:I_Bit:OR:F_Bit
LDR SP, =USR_Stack
MSR CPSR_c, #Mode_SVC:OR:I_Bit:OR:F_Bit
LDR SP, =SVC_Stack
IMPORT __main, weak
; --- Now enter the C code
LDR r0, =__main
CMP r0, #0
BNE __main
99 B %B99
上面的代码中,我们看到没有拷贝RW 区域,把ZI区域清0 等设置。我们经常在uboot,vivi 等bootload 中看到,当时我也不是很理解.
其实这是在ADS 中的C library 帮我们做了,在真正进入我们的C语言main函数前,ADS library做了这些事情,我上面推荐的那份资料
里面,讲的很清楚.
经过上面的一番折腾,程序就进入了 main函数. 进入main 函数后,程序的功能主要分为三个部分:
1) 进入测试模式。 我认为这是做产品和做开发板的一个区别,其实测试模式是很重要的一个功能,在产品的生产过程中,在流水线作业过程中,最后一个
环节就是检测产品的好与坏,工人很简单,只要他们按一个组合键,就会进入测试模式,可以检测按键的好坏,LCD的好坏, NAND flash 的好坏等. 虽然
这个很简单,但我认为这是做产品与做开发板的一个很大的区别. 另外一个功能是在在产品有问题时,可以进入测试模式,查看固件版本.
2) 如果连接上了USB,那么进入下载模式,下载不是简单的只是下载固件,还会下载应用数据,PC上我们开发了一个下载工具。
3)如果上面都没有进入,那么就进入启动linux 的模式. 程序会把nand flash 中的启动代码拷贝到IC的内部RAM区,这个IC的内部RAM有16k, 然后程序再跳转到
内部RAM区执行,内部启动代码把linux内核copy到 SDRAM 区域中,然后启动linux内核
最后,我用一张图片来结束这次说明,这张图片是memory 的分配,是在进入main函数之后,memory 的地址分配情况:
下一次我再讲一讲main函数里面是如何进入 linux内核的.