您的位置:首页 > 其它

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

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

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



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

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



我们整个逻机教程都是从外部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个主分区就是在这里配置的,当然逻辑扇区可以指定到其他位置。



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导出的函数表,可以参考:



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

/* 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: