Exynos4412裸机开发系列教程--启动流程

看过前两篇教程的朋友,发现裸机开发怎么的如此简单,从这篇文章开始,我们来的有点难度的,启动流程。这个可以说是整个裸机开发的核心了,如果这一步无法跨越,您的一颗LED灯也点不亮,当然,如果您跨越了这一步,那么神马裸机开发都是一小点心而已。好,闲话少说,干货拿来。

在Exynos4412上电后,其内部的IROM会首先运行,下面是一张IROM运行流程图:

Exynos4412裸机开发系列教程--启动流程_第1张图片

由流程图可以看出,首先关闭看门狗,关闭中断及MMU,关闭数据缓存,打开指令缓存,清除TLB,然后将其他核进入IDLE模式,只留CPU0,这里有了第一个跳转分支,IROM判断当前启动模式,是冷启动还是唤醒,如果是唤醒模式,那么就是直接跳转到BL1,在BL1里面我们会再次判断是否是唤醒模式,如果是就直接跳转到唤醒函数,一般都是linux内核的唤醒句柄。当然在裸机里都是冷启动的哈,休眠唤醒一般是不需要关注的,当然如果你的裸机程序需要支持休眠唤醒,就需要增加相应的代码了。

好了,继续我们的冷启动,设置IRQ及SVC模式的栈空间,这个时间,栈地址是其内部的一片IRAM,这小片RAM是IROM运行的外部随机存储器,没有这片小内存,IROM是无法运行的。接下了就是初始化IROM里面所使用的各种变量,初始化只读数据段,未初始化数据段清零,导出部分核心函数,这个函数可以在BL1中使用,获取当前复位的状态,设置系统时钟分频,获取OM管脚配置模式,这里可以从多种外设启动,具体启动模式如下表:

Exynos4412裸机开发系列教程--启动流程_第2张图片

我们整个逻机教程都是从外部SD卡启动的,根据OM启动模式,从相应的存储器拷贝前8K代码,拷贝失败的话,系统就宕机了,只能复位重启了,如果拷贝成功,就验证校验和,BL1的前16个字节就是提供给IROM用来标识BL1相关信息的,具体信息如下:

/*
 * bl1 header infomation for irom
 *
 * 0x0 - bl1 size
 * 0x4 - reserved (should be 0)
 * 0x8 - check sum
 * 0xc - reserved (should be 0)
 */
	.word 0x2000
	.word 0x0
	.word 0x0
	.word 0x0
首先是描述BL1的大小,然后还有一个BL1的校验和,那我们怎么知道BL1的校验和呢,这个是在编译生成最终的二进制文件后,通过mk4412程序制作的,参考源码包里已经提供了相应的制作工具,可直接使用。

那拷贝的前8K代码,究竟从SD里的哪里开始拷贝呢,这里有个图可以参考,需要注意的是,拷贝是从第一个扇区开始,前面有一个扇区保留,每个扇区512字节,如果有同学对DOS分区表有过研究,就能明白其中的道理了,第一个扇区是分区表的配置区,一个磁盘里最多的4个主分区就是在这里配置的,当然逻辑扇区可以指定到其他位置。

Exynos4412裸机开发系列教程--启动流程_第3张图片

IROM计算校验和且验证通过后并解密BL1成功后就可以跳转到BL1了,至此IROM已执行完备,权限已交由BL1了,补充说明一下,解密BL1是加密模式启动时才需要的,非加密模式启动是无需解密BL1的。

BL1就是我们可以控制编写的代码,但是对于samsung官方的uboot,这个BL1是不提供源码的,只提供一个bin文件,原因嘛,就是这个BL1是加密启动的,没关系,没有我们可以自己写个BL1,一样实现他的功能。

首先,填充16个字节用于后期制作校验和信息,然后就是标准的ARM异常向量表:

/*
 * bl1 header infomation for irom
 */
	.word 0x2000
	.word 0x0
	.word 0x0
	.word 0x0

	.global	_start
_start:

/* 0x00: reset */
	b	reset

/* 0x04: undefined instruction exception */
	ldr	pc, _undefined_instruction

/* 0x08: software interrupt exception */
	ldr	pc, _software_interrupt

/* 0x0c: prefetch abort */
	ldr	pc, _prefetch_abort

/* 0x10: data access memory abort */
	ldr	pc, _data_abort

/* 0x14: not used */
	ldr	pc, _not_used

/* 0x18: interrupt request exception */
	ldr	pc, _irq

/* 0x1c: fast interrupt request exception */
	ldr	pc, _fiq


_undefined_instruction:
	.long undefined_instruction
_software_interrupt:
	.long software_interrupt
_prefetch_abort:
	.long prefetch_abort
_data_abort:
	.long data_abort
_not_used:
	.long not_used
_irq:
	.long irq
_fiq:
	.long fiq

复位向量入口,就是我们开始的第一句代码,关闭看门狗,其实这个IROM已经实现了,但再做一遍也不为多,对吧,其实对于健壮的代码本质上就是不相信任何前提条件,都是自给自足的。

	/* Disable watchdog */
	ldr	r0, =0x10060000
	mov	r1, #0
	str	r1, [r0]
再进入SVC模式,打开NEON及VFP指令支持,关闭MMU,初始化cache等等,这些跟IROM里面做的类似。

	/* Set the cpu to supervisor mode */
	mrs	r0, cpsr
	bic	r0, r0, #0x1f
	orr	r0, r0, #0xd3
	msr	cpsr, r0

	/* Enable NEON & VFP unit */
	mrc p15, #0, r1, c1, c0, #2
	orr r1, r1, #(0xf << 20)
	mcr p15, #0, r1, c1, c0, #2
	mov r1, #0
	mcr p15, #0, r1, c7, c5, #4
	mov r0, #0x40000000
	fmxr fpexc, r0

	/* Cache init */
	mrc	p15, 0, r0, c0, c0, 0		/* read main ID register */
	and	r1, r0, #0x00f00000			/* variant */
	and	r2, r0, #0x0000000f			/* revision */
	orr	r2, r2, r1, lsr #20-4		/* combine variant and revision */
	cmp	r2, #0x30
	mrceq	p15, 0, r0, c1, c0, 1	/* read ACTLR */
	orreq	r0, r0, #0x6			/* Enable DP1(2), DP2(1) */
	mcreq	p15, 0, r0, c1, c0, 1	/* write ACTLR */

	/* Invalidate L1 I/D */
	mov	r0, #0						/* set up for MCR */
	mcr	p15, 0, r0, c8, c7, 0		/* invalidate TLBs */
	mcr	p15, 0, r0, c7, c5, 0		/* invalidate icache */

	/* Disable mmu stuff and caches */
	mrc	p15, 0, r0, c1, c0, 0
	bic	r0, r0, #0x00002000			/* clear bits 13 (--v-) */
	bic	r0, r0, #0x00000007			/* clear bits 2:0 (-cam) */
	orr	r0, r0, #0x00000002			/* set bit 1 (--a-) align */
	orr	r0, r0, #0x00000800			/* set bit 12 (z---) btb */
	mcr	p15, 0, r0, c1, c0, 0

下面就是一些初始化代码,比如电源自锁,初始化时钟,初始化外部DDR,这些就不做细节分析了,可以参考源码,自行阅读。

接下来就是比较关键的自拷贝了,这里使用了IROM里的从外部SD卡拷贝到内存的函数,IROM里其实提供了一系列的从各种外部存储器拷贝到内存的方法

	/* copyself to ram using irom */
	adr	r0, _start
	ldr r1, =_start
	cmp	r0, r1
	beq	have_copyed
	bl	irom_copyself
have_copyed:
	nop

其中irom_copyself函数是用C实现的,代码如下:

extern u8_t	__text_start[];
extern u8_t __text_end[];
extern u8_t __data_shadow_start[];
extern u8_t __data_shadow_end[];
extern u8_t __data_start[];
extern u8_t __data_end[];
extern u8_t __bss_start[];
extern u8_t __bss_end[];
extern u8_t __heap_start[];
extern u8_t __heap_end[];
extern u8_t __stack_start[];
extern u8_t __stack_end[];

#define irom_sdmmc_to_mem(sector, count, mem)		\
		(((u32_t(*)(u32_t, u32_t, u32_t *))(*((u32_t *)(0x02020030))))(sector, count, mem))

/*
 * read a 32-bits value from register.
 */
static u32_t reg_read(u32_t addr)
{
	return( *((volatile u32_t *)(addr)) );
}

/*
 * only support irom booting.
 */
void irom_copyself(void)
{
	u32_t om;
	u32_t * mem;
	u32_t size;

	/*
	 * read om register, om[5..1]
	 */
	om = (u32_t)((reg_read(EXYNOS4412_PMU_OM_STAT) >> 1) & 0x1f);

	/* SDMMC CH2 */
	if(om == 0x2)
	{
		/*
		 * the xboot's memory base address.
		 */
		mem = (u32_t *)__text_start;

		/*
		 * the size which will be copyed, the 'size' is
		 * 1 : 256KB, 2 : 512KB, 3 : 768KB, 4 : 1024KB ...
		 */
		size = (__data_shadow_end - __text_start + 0x00040000) >> 18;

		/*
		 * how many blocks the 'size' is , 512 bytes per block.
		 * size * 256 *1024 / 512 = size * 2^9 = size << 9
		 */
		size = size << 9;

		/*
		 * copy xboot to memory from sdmmc ch2.
		 */
		irom_sdmmc_to_mem(1, size, mem);
	}

	/* eMMC43 CH0 */
	else if(om == 0x3)
	{

	}

	/* eMMC44 CH4 */
	else if(om == 0x4)
	{

	}

	/* NAND 512B 8ECC */
	else if(om == 0x8)
	{

	}

	/* NAND 2KB OVER */
	else if(om == 0x9)
	{

	}

	/*=============*/
	/* eMMC43 CH0 */
	else if(om == 0x13)
	{

	}

	/* eMMC44 CH4 */
	else if(om == 0x14)
	{

	}

	/* NAND 512B 8ECC */
	else if(om == 0x18)
	{

	}

	/* NAND 2KB OVER */
	else if(om == 0x19)
	{

	}

	/* Not support */
	else
	{
		return;
	}
}
这个函数的实现千万不能使用switch case语句,因为这条语句很有可能会编译成跳转表,而现在C语言环境还未完全准备起来,只能使用局部变量以及用if elseif来代替。

这里有个define,将某格地址转化为函数的指针,然后执行,这个其实就是IROM里面导出的函数了。下面是一组IROM导出的函数表,可以参考:

Exynos4412裸机开发系列教程--启动流程_第4张图片

自拷贝完成后,我们就需要初始化最终的栈空间,初始化已初始化数据段,将未初始化数据段清零。

	/* initialize stacks */
	bl	init_stacks

	/* copy shadow of data section */
copy_shadow_data:
	ldr	r0, _data_shadow_start
	ldr	r1, _data_start
	ldr	r2, _data_shadow_end
	bl	mem_copy

	/* clear bss section */
clear_bss:
	ldr	r0, _bss_start
	ldr	r1, _bss_end
	mov r2, #0x00000000
	bl	mem_clear

/*
 * initialize stacks
 */
init_stacks:
	mrs	r0, cpsr
	bic	r0, r0, #MODE_MASK | NO_INT
	orr	r1, r0, #UDF_MODE
	msr	cpsr_cxsf, r1
	ldr	sp, _stack_und_end

	bic	r0, r0, #MODE_MASK | NO_INT
	orr	r1, r0, #ABT_MODE
	msr	cpsr_cxsf, r1
	ldr	sp, _stack_abt_end

	bic	r0, r0, #MODE_MASK | NO_INT
	orr	r1, r0, #IRQ_MODE
	msr	cpsr_cxsf, r1
	ldr	sp, _stack_irq_end

	bic	r0, r0, #MODE_MASK | NO_INT
	orr	r1, r0, #FIQ_MODE
	msr	cpsr_cxsf, r1
	ldr	sp, _stack_fiq_end

	bic	r0, r0, #MODE_MASK | NO_INT
	orr	r1, r0, #SVC_MODE
	msr	cpsr_cxsf, r1
	ldr	sp, _stack_srv_end
	mov	pc, lr

/*
 * memory copy
 */
mem_copy:
	sub	r2, r2, #32
	cmp	r0, r2
	ble	3f
1:	ldmia r0!, {r3-r10}
	stmia r1!, {r3-r10}
	cmp	r0, r2
	ble	1b
3:	add	r2, r2, #32
2:	ldr	r3, [r0], #4
	str	r3, [r1], #4
	cmp	r0, r2
	blt	2b
	mov	pc, lr

/*
 * memory clear zero
 */
mem_clear:
	sub	r1, r1, #32
	cmp	r0, r1
	ble	cp
	mov r3, #0
	mov r4, #0
	mov r5, #0
	mov r6, #0
	mov r7, #0
	mov r8, #0
	mov r9, #0
	mov r10, #0
1:	stmia r0!, {r3-r10}
	cmp	r0, r1
	ble	1b
cp:	add	r1, r1, #32
2:	str	r2, [r0], #4
	cmp	r0, r1
	blt	2b
	mov	pc, lr

最后就是跳转到DDR中的main函数了,至此整个启动流程已经执行完备。

	/* jump to ram */
	ldr	r1, =on_the_ram
	mov	pc, r1
on_the_ram:
	/* jump to main fuction */
	mov r0, #1;
	mov r1, #0;
	bl	main
	b	on_the_ram

到这里,大家可能就疑惑了,只有BL1啊,没有BL2,怎么就完了呢,其实这里用了个高级技巧将所谓的BL2跟所谓的BL1合并为一个程序了,细心的朋友可以仔细研究下上面的自拷贝函数以及跳转到DDR中的main函数,这两个函数是实现这个技巧的关键。

在自拷贝函数中,需要知道当前的代码的链接地址及范围,而这些都是由链接脚本提供的,下面是完整链接脚本。

OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)

STACK_FIQ_SIZE 	= 0x0400;
STACK_IRQ_SIZE 	= 0x0400;
STACK_ABT_SIZE 	= 0x0400;
STACK_UND_SIZE 	= 0x0400;
STACK_SRV_SIZE 	= 0x8000;

MEMORY
{
	rom (rx)	: org = 0x40000000, len = 0x02000000	/* 32 MB */
	ram (rwx)	: org = 0x42000000, len = 0x0a000000	/* 160 MB */
}

SECTIONS
{   
	.text :
	{
		. = ALIGN(8);
		PROVIDE (__text_start = .);
		.obj/source/startup/start.o (.text)
		.obj/source/startup/clock_init_smdk4212.o (.text)
		.obj/source/startup/mem_init_smdk4212.o (.text)
		.obj/source/startup/exynos4412-irom.o (.text)
		*(.text)
		*(.text.*)

		. = ALIGN(8);
		*(.rodata);
		*(.rodata.*);

		. = ALIGN(8);
		*(.glue_7);
		*(.glue_7t);

		. = ALIGN(8);
		PROVIDE (__text_end = .);
	} > rom

	.data_shadow ALIGN(8) :
	{
		PROVIDE (__data_shadow_start = .);
		PROVIDE (__data_shadow_end = (. + SIZEOF (.data)) );
	} > rom

	.data : AT ( ADDR (.data_shadow) )
	{
		PROVIDE (__data_start = .);	
		*(.data)
		. = ALIGN(8);
  		PROVIDE (__data_end = .);		
	} > ram

	.ARM.exidx :
	{
		. = ALIGN(8);
		PROVIDE (__exidx_start = .);
		*(.ARM.exidx*)
		PROVIDE (__exidx_end = .);
	} > ram

	.ARM.extab :
	{
		PROVIDE (__extab_start = .);
		*(.ARM.extab*)
		PROVIDE (__extab_end = .);
	} > ram

	.bss ALIGN(8) (NOLOAD) :
	{
		PROVIDE (__bss_start = .);
		*(.bss)
		*(.bss.*)
		*(.sbss)
		*(COMMON)
		PROVIDE (__bss_end = .);
		
		. = ALIGN(8);
		PROVIDE (__heap_start = .);
		*(.heap)
		. = ALIGN(8);
		PROVIDE (__heap_end = .);
		
		. = ALIGN(8);
		PROVIDE (__stack_start = .);
		PROVIDE (__stack_fiq_start = .);
		. += STACK_FIQ_SIZE;
		PROVIDE (__stack_fiq_end = .);
		. = ALIGN(8);
		PROVIDE (__stack_irq_start = .);
		. += STACK_IRQ_SIZE;
		PROVIDE (__stack_irq_end = .);
		. = ALIGN(8);
		PROVIDE (__stack_abt_start = .);
		. += STACK_ABT_SIZE;
		PROVIDE (__stack_abt_end = .);
		. = ALIGN(8);
		PROVIDE (__stack_und_start = .);
		. += STACK_UND_SIZE;
		PROVIDE (__stack_und_end = .);
		. = ALIGN(8);
		PROVIDE (__stack_srv_start = .);
		. += STACK_SRV_SIZE;
		PROVIDE (__stack_srv_end = .);
		. = ALIGN(8);
		PROVIDE (__stack_end = .);
	} > ram

	/*
	 * Stabs debugging sections.
	 */
	.stab 0 : { *(.stab) }
	.stabstr 0 : { *(.stabstr) }
	.stab.excl 0 : { *(.stab.excl) }
	.stab.exclstr 0 : { *(.stab.exclstr) }
	.stab.index 0 : { *(.stab.index) }
	.stab.indexstr 0 : { *(.stab.indexstr) }
	.comment 0 : { *(.comment) }
	.debug_abbrev 0 : { *(.debug_abbrev) }
	.debug_info 0 : { *(.debug_info) }
	.debug_line 0 : { *(.debug_line) }
	.debug_pubnames 0 : { *(.debug_pubnames) }
	.debug_aranges 0 : { *(.debug_aranges) }
}
这个教程算是比较关键的一章了,很多裸机的核心技术都在此教程讲述,大家慢慢消化,有疑问的可以留言,或者直接加QQ咨询:8192542


你可能感兴趣的:(Exynos4412)