看过前两篇教程的朋友,发现裸机开发怎么的如此简单,从这篇文章开始,我们来的有点难度的,启动流程。这个可以说是整个裸机开发的核心了,如果这一步无法跨越,您的一颗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
/* 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
接下来就是比较关键的自拷贝了,这里使用了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
/* 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
在自拷贝函数中,需要知道当前的代码的链接地址及范围,而这些都是由链接脚本提供的,下面是完整链接脚本。
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