vboot详细分析之-2

时间:2021-10-20 15:54:13

程序首先是从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个寄存器如下图所示:

vboot详细分析之-2

 

然后执行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]
}


上面的各个行很显然,设置各个端口的功能,以及电平状态等
下面还是看Main()中的7行,NANDFLASH的初始化,
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);
}
在上面的检查NAND的ID里面要注意,g_page_tyoe这个决定了页的类型,如果探测到的页的类型,是未定义的,直接进入死循环。
就绪看main()函数的下面几行就知道了,如下
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;
}
上面就是根据一开始检测出来的页大小调用相应的函数,这里是调用下面一个,也就是NandReadOneSectorP2048(buffer, addr);在对这个函数进行跟踪
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;
}
这里不懂的就要去了解下NAND的读写了,这个很重要。这里重要的函数是ReadPage512(buffer + 0 * 512, &NFDATA);,这个函数在head.S中用汇编实现的,在最上面的head.s中的147~174行,这个函数每次读取512字节。
下面就是执行Main()中的ReadImageFromNand();实现linux内核从nandflash复制到ram中,下面是实现
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的复制操作,
最后调用CallLinux();看其实现
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