程序首先是从head.S里面进行执行的。这个代码有点长,我一次贴出
#define __ASSEMBLY__
#include "s3c2440.h"
#include "smdk2440.h"
#include "parameters.h"
@ Start of executable code
/* Fin = 12MHz */
#define S3C2440_UPLL_48MHZ_Fin12MHz ((0x38<<12)|(0x02<<4)|(0x02))
@
@ Exception vector table (physical address = 0x00000000)
@
.section .text.FirstSector
.globl first_sector
first_sector:
@ 0x00: Reset
b Reset
@ 0x04: Undefined instruction exception
UndefEntryPoint:
b UndefEntryPoint
@ 0x08: Software interrupt exception
SWIEntryPoint:
b SWIEntryPoint
@ 0x0c: Prefetch Abort (Instruction Fetch Memory Abort)
PrefetchAbortEnteryPoint:
b PrefetchAbortEnteryPoint
@ 0x10: Data Access Memory Abort
DataAbortEntryPoint:
b DataAbortEntryPoint
@ 0x14: Not used
NotUsedEntryPoint:
b NotUsedEntryPoint
@ 0x18: IRQ(Interrupt Request) exception
IRQEntryPoint:
b IRQHandle
@ 0x1c: FIQ(Fast Interrupt Request) exception
FIQEntryPoint:
b FIQEntryPoint
@0x20: Fixed address global value. will be replaced by downloader.
.long ZBOOT_MAGIC
.byte OS_TYPE, HAS_NAND_BIOS, (LOGO_POS & 0xFF), ((LOGO_POS >>8) &0xFF)
.long OS_START
.long OS_LENGTH
.long OS_RAM_START
.string LINUX_CMD_LINE
.section .text
Reset:
@ disable watch dog timer
mov r1, #0x53000000 //WTCON
mov r2, #0x0
str r2, [r1]
@ disable all interrupts
mov r1, #INT_CTL_BASE //SRCPND S3c2440.h
mov r2, #0xffffffff
str r2, [r1, #oINTMSK]
ldr r2, =0x7ff
str r2, [r1, #oINTSUBMSK]
@ initialise system clocks
mov r1, #CLK_CTL_BASE
mvn r2, #0xff000000
str r2, [r1, #oLOCKTIME]
mov r1, #CLK_CTL_BASE
ldr r2, clkdivn_value
str r2, [r1, #oCLKDIVN]
mrc p15, 0, r1, c1, c0, 0 @ read ctrl register
orr r1, r1, #0xc0000000 @ Asynchronous
mcr p15, 0, r1, c1, c0, 0 @ write ctrl register
mov r1, #CLK_CTL_BASE
ldr r2, =S3C2440_UPLL_48MHZ_Fin12MHz
str r2, [r1, #oUPLLCON]
nop
nop
nop
nop
nop
nop
nop
nop
nop
ldr sp, DW_STACK_START @ setup stack pointer
ldr r2, mpll_value_USER @ clock user set 12MHz
str r2, [r1, #oMPLLCON]
bl memsetup
@ set GPIO for UART
mov r1, #GPIO_CTL_BASE
add r1, r1, #oGPIO_H
ldr r2, gpio_con_uart
str r2, [r1, #oGPIO_CON]
ldr r2, gpio_up_uart
str r2, [r1, #oGPIO_UP]
bl InitUART
@ get read to call C functions
mov fp, #0 @ no previous frame, so fp=0
mov a2, #0 @ set argv to NULL
bl Main
1: b 1b @
/*
* subroutines
*/
memsetup:
@ initialise the static memory
@ set memory control registers
mov r1, #MEM_CTL_BASE
adrl r2, mem_cfg_val
add r3, r1, #52
1: ldr r4, [r2], #4
str r4, [r1], #4
cmp r1, r3
bne 1b
mov pc, lr
.globl ReadPage512
ReadPage512:
stmfd sp!, {r2-r7}
mov r2, #0x200
1:
ldr r4, [r1]
ldr r5, [r1]
ldr r6, [r1]
ldr r7, [r1]
stmia r0!, {r4-r7}
ldr r4, [r1]
ldr r5, [r1]
ldr r6, [r1]
ldr r7, [r1]
stmia r0!, {r4-r7}
ldr r4, [r1]
ldr r5, [r1]
ldr r6, [r1]
ldr r7, [r1]
stmia r0!, {r4-r7}
ldr r4, [r1]
ldr r5, [r1]
ldr r6, [r1]
ldr r7, [r1]
stmia r0!, {r4-r7}
subs r2, r2, #64
bne 1b;
ldmfd sp!, {r2-r7}
mov pc,lr
@ Initialize UART
@
@ r0 = number of UART port
InitUART:
ldr r1, SerBase
mov r2, #0x0
str r2, [r1, #oUFCON]
str r2, [r1, #oUMCON]
mov r2, #0x3
str r2, [r1, #oULCON]
ldr r2, =0x245
str r2, [r1, #oUCON]
//#define UART_BAUD_RATE 115200
//#define UART_PCLK_400_148 50000000
//#define UART_PCLK UART_PCLK_400_148
#define UART_BRD ((UART_PCLK / (UART_BAUD_RATE * 16)) - 1)
mov r2, #UART_BRD
str r2, [r1, #oUBRDIV]
mov r3, #100
mov r2, #0x0
1: sub r3, r3, #0x1
tst r2, r3
bne 1b
mov pc, lr
IRQHandle:
ldr pc, =0x33f00000+0x18
nop
nop
@
@ Data Area
@
@ Memory configuration values
.align 4
mem_cfg_val:
.long vBWSCON
.long vBANKCON0
.long vBANKCON1
.long vBANKCON2
.long vBANKCON3
.long vBANKCON4
.long vBANKCON5
.long vBANKCON6
.long vBANKCON7
.long vREFRESH
.long vBANKSIZE
.long vMRSRB6
.long vMRSRB7
@ Processor clock values
.align 4
mpll_value_USER:
.long vMPLLCON_NOW_USER
clkdivn_value:
.long vCLKDIVN_NOW
@ initial values for serial
uart_ulcon:
.long vULCON
uart_ucon:
.long vUCON
uart_ufcon:
.long vUFCON
uart_umcon:
.long vUMCON
@ inital values for GPIO
gpio_con_uart:
.long vGPHCON
gpio_up_uart:
.long vGPHUP
.align 2
DW_STACK_START:
.word 0x34000000-4
.align 4
SerBase:
.long UART0_CTL_BASE
程序从第19行开始执行,20~29行定义了异常向量表,可以发现其中第一条指令是一条跳转指令,说以说程序开始后直接跳转到第61行开始进行执行。再上面的异常向量表中,只有复位异常实现了,其余的都没有实现。52~58行是数据的定义。下面开始从61行看,62~65行关闭看门狗,这是一个常见的用法,也是arm汇编指令的LOAD/STORE模式的典型体现。这里以这个作为例子说下,后面很多同样的用法不再重复。0x53000000正好是看门口控制寄存器WTCON的地址,63行就是将立即数0x53000000放入寄存器R1中,然后64行将立即数0放到寄存器R2中,52行将R2寄存器中的内容放入R1寄存器里面存储的值值的地址处。用C实现的话很简单,这三行的意思就是WTCON=0;关闭看门狗。同样,68行~72行是屏蔽中断,75~100设置系统时钟和USB时钟,想具体了解的可以看看手册。102行:设置堆栈。106行:调到标号memsetup处执行,进行存储器控制器设置。也就是说跳转到131行继续执行,135行到142行进行存储器相关的13个控制寄存器的设置。MEM_CTRL_BASE=0X48000000,在s3c2440.h中定义。136行是一条伪指令,其中的mem_val_cfg在215行,215行开始的地方,表示连续分配了13个int型的空间。所以这几行的意思就是将mem_val_cfg开始的13个数值依次赋值给48000000开始的13个寄存器。这13个寄存器如下图所示:
然后执行142行,意思说从调用的这个子函数中返回,程序跳到109行继续执行,109~114设置管脚的功能,也就是设置为UART,115行跳转的inituart对串口进行初始化,179~202实现了此函数。 122行 bl Main,跳到Main.c中的Main()函数去执行。
void Main(void)
{
MMU_EnableICache();
MMU_EnableDCache();
Port_Init();
NandInit();
if (g_page_type == PAGE_UNKNOWN) {
Uart_SendString("\r\nunsupport NAND\r\n");
for(;;);
}
GetParameters();
Uart_SendString("load Image of Linux...\n\r");
ReadImageFromNand();
}
上面3,4行打开缓存。
6行,是端口初始化函数,进入这个函数看下
(在244x_lib.c中)
void Port_Init(void)
{
GPACON = 0x7fffff;
GPBCON = 0x044555;
GPBUP = 0x7ff; // The pull up function is disabled GPB[10:0]
GPCCON = 0xaaaaaaaa;
GPCUP = 0xffff; // The pull up function is disabled GPC[15:0]
GPDCON = 0x00151544;
GPDDAT = 0x0430;
GPDUP = 0x877A;
GPECON = 0xaa2aaaaa;
GPEUP = 0xf7ff; // GPE11 is NC
GPFCON = 0x55aa;
GPFUP = 0xff; // The pull up function is disabled GPF[7:0]
GPGCON = 1<<8;
GPGDAT = 0;
GPHCON = 0x16faaa;
GPHUP = 0x7ff; // The pull up function is disabled GPH[10:0]
EXTINT0 = 0x22222222; // EINT[7:0]
EXTINT1 = 0x22222222; // EINT[15:8]
EXTINT2 = 0x22222222; // EINT[23:16]
}
void NandInit(void)
{
NFCONF = (TACLS << 12) | (TWRPH0 << 8) | (TWRPH1 << 4) | (0 << 0);
NFCONT =
(0 << 13) | (0 << 12) | (0 << 10) | (0 << 9) | (0 << 8) | (0 << 6) |
(0 << 5) | (1 << 4) | (1 << 1) | (1 << 0);
NFSTAT = 0;
NandReset();
NandCheckId();
}
static inline U32 NandCheckId(void)
{
U8 Mid, Did, DontCare, id4th;
NF_nFCE_L();
NF_CMD(0x90);
NF_ADDR(0x0);
delay();
Mid = NF_RDDATA8();
Did = NF_RDDATA8();
DontCare = NF_RDDATA8();
id4th = NF_RDDATA8();
NF_nFCE_H();
switch(Did) {
case 0x76:
g_page_type = PAGE512;
break;
case 0xF1:
case 0xD3:
case 0xDA:
case 0xDC:
g_page_type = PAGE2048;
break;
default:
;
}
return (U32) ((Mid << 24) | (Did << 16) | (DontCare << 8) | id4th);
}
if (g_page_type == PAGE_UNKNOWN) {
Uart_SendString("\r\nunsupport NAND\r\n");
for(;;);
}
main的14行GetParameters(); 进入这个函数看看,就在Main.c文件中
static inline void GetParameters(void)
{
U32 Buf[2048];
g_os_type = OS_LINUX;
g_os_start = 0x60000;
g_os_length = 0x500000;
g_os_ram_start = 0x30008000;
// vivi LINUX CMD LINE
NandReadOneSector((U8 *)Buf, 0x48000);
if (Buf[0] == 0x49564956 && Buf[1] == 0x4C444D43) {
memcpy(g_linux_cmd_line, (char *)&(Buf[2]), sizeof g_linux_cmd_line);
}
}
第4行确定了系统的类型为linux,第5行确定了系统在flash中的开始地址,第六行是系统的长度 ,第8行是系统在ram中的起始地址,第11行从nand地址0x48000地址处读一个扇区。下面两行是判断如果幻数码正确,就能判定下面的相应字节数为命令行参数。对上面的NandReadOneSector((U8 *)Buf, 0x48000);函数进行跟踪下
int NandReadOneSector(U8 * buffer, U32 addr)
{
int ret;
switch(g_page_type) {
case PAGE512:
ret = NandReadOneSectorP512(buffer, addr);
break;
case PAGE2048:
ret = NandReadOneSectorP2048(buffer, addr);
break;
default:
for(;;);
}
return ret;
}
static inline int NandReadOneSectorP2048(U8 * buffer, U32 addr)
{
U32 sector;
sector = addr >> 11;
delay();
NandReset();
#if 0
NF_RSTECC();
NF_MECC_UnLock();
#endif
NF_nFCE_L();
NF_CLEAR_RB();
NF_CMD(0x00);
NF_ADDR(0x00);
NF_ADDR(0x00);
NF_ADDR(sector & 0xff);
NF_ADDR((sector >> 8) & 0xff);
NF_ADDR((sector >> 16) & 0xff);
NF_CMD(0x30);
delay();
NF_DETECT_RB();
ReadPage512(buffer + 0 * 512, &NFDATA);
ReadPage512(buffer + 1 * 512, &NFDATA);
ReadPage512(buffer + 2 * 512, &NFDATA);
ReadPage512(buffer + 3 * 512, &NFDATA);
#if 0
NF_MECC_Lock();
#endif
NF_nFCE_H();
return 1;
}
void ReadImageFromNand(void)
{
unsigned int Length;
U8 *RAM;
unsigned BlockNum;
unsigned pos;
Length = g_os_length;
Length = (Length + BLOCK_SIZE - 1) >> (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT) << (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT); // align to Block Size
BlockNum = g_os_start >> (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT);
RAM = (U8 *) g_os_ram_start;
for (pos = 0; pos < Length; pos += BLOCK_SIZE) {
unsigned int i;
// skip badblock
for (;;) {
if (NandIsGoodBlock
(BlockNum <<
(BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT))) {
break;
}
BlockNum++; //try next
}
for (i = 0; i < BLOCK_SIZE; i += SECTOR_SIZE) {
int ret =
NandReadOneSector(RAM,
(BlockNum <<
(BYTE_SECTOR_SHIFT +
SECTOR_BLOCK_SHIFT)) + i);
RAM += SECTOR_SIZE;
ret = 0;
}
BlockNum++;
}
CallLinux();
}
这个首先进行个地址的转换,然后判断是否是坏块,如果是坏块,就直接跳过,不是坏块就去执行从NAND到ram的复制操作,
static void CallLinux(void)
{
struct param_struct {
union {
struct {
unsigned long page_size; /* 0 */
unsigned long nr_pages; /* 4 */
unsigned long ramdisk_size; /* 8 */
unsigned long flags; /* 12 */
unsigned long rootdev; /* 16 */
unsigned long video_num_cols; /* 20 */
unsigned long video_num_rows; /* 24 */
unsigned long video_x; /* 28 */
unsigned long video_y; /* 32 */
unsigned long memc_control_reg; /* 36 */
unsigned char sounddefault; /* 40 */
unsigned char adfsdrives; /* 41 */
unsigned char bytes_per_char_h; /* 42 */
unsigned char bytes_per_char_v; /* 43 */
unsigned long pages_in_bank[4]; /* 44 */
unsigned long pages_in_vram; /* 60 */
unsigned long initrd_start; /* 64 */
unsigned long initrd_size; /* 68 */
unsigned long rd_start; /* 72 */
unsigned long system_rev; /* 76 */
unsigned long system_serial_low; /* 80 */
unsigned long system_serial_high; /* 84 */
unsigned long mem_fclk_21285; /* 88 */
} s;
char unused[256];
} u1;
union {
char paths[8][128];
struct {
unsigned long magic;
char n[1024 - sizeof(unsigned long)];
} s;
} u2;
char commandline[1024];
};
struct param_struct *p = (struct param_struct *)0x30000100;
memset(p, 0, sizeof(*p));
memcpy(p->commandline, g_linux_cmd_line, sizeof(g_linux_cmd_line));
p->u1.s.page_size = 4 * 1024;
p->u1.s.nr_pages = 64 * 1024 * 1024 / (4 * 1024);
{
unsigned int *pp = (unsigned int *)(0x30008024);
if (pp[0] == 0x016f2818) { // Magic number of zImage
//Uart_SendString("\n\rOk\n\r");
} else {
Uart_SendString("\n\rWrong Linux Kernel\n\r");
for (;;) ;
}
}
asm (
"mov r5, %2\n"
"mov r0, %0\n"
"mov r1, %1\n"
"mov ip, #0\n"
"mov pc, r5\n"
"nop\n" "nop\n": /* no outpus */
:"r"(0), "r"(1999), "r"(g_os_ram_start)
);
}
先定义了一个struct param_struct结构体变量,从这里就可以看出,vboot用的是旧的方式,新的是用tag方式,U-boot里面有实现,可以去看一下,struct param_struct与内核里定义的一样。第41~59行,看注释可以明白,第60~67行,是内核的一些约定:
R0 = 0
R1 = 机器ID
。。。
最后第65行,设置pc为内核映像在内存中的起始地址,直接跳到内核映像的入口,从而开始内核代码的执行......
总结一下:本来看这个bootloader不大,想仔仔细细介绍一下,后来发现不大容易,如果要一条条语句介绍,那也实在没啥意思。如果基本的ARM汇编都懂的话,那这个也不难了。装个sourceinsight软件对这个代码进行跟踪,条理很清晰。vboot的好处是很简单,代码量在4K内,因此可以直接在SOC自带的sram里面执行,进行系统的引导。但是,但是目前只支持2440,只能从NANDFLASH启动,功能化不是单一,是唯一,那就是用来引导系统。不能提供UBOOT那样大的功能。如果学bootloader先从vboot开始,我想是非常好的。因为UBOOT相对来说较为复杂,很容易让人头晕。这些类型的选取,要根据项目而定,也并不是越复杂越好。
需要vboot源码的,请留言留下邮箱。
ZJW