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

来源:互联网 发布:读书有用 知乎 编辑:程序博客网 时间:2024/05/21 06:16

看过前两篇教程的朋友,发现裸机开发怎么的如此简单,从这篇文章开始,我们来的有点难度的,启动流程。这个可以说是整个裸机开发的核心了,如果这一步无法跨越,您的一颗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 */breset/* 0x04: undefined instruction exception */ldrpc, _undefined_instruction/* 0x08: software interrupt exception */ldrpc, _software_interrupt/* 0x0c: prefetch abort */ldrpc, _prefetch_abort/* 0x10: data access memory abort */ldrpc, _data_abort/* 0x14: not used */ldrpc, _not_used/* 0x18: interrupt request exception */ldrpc, _irq/* 0x1c: fast interrupt request exception */ldrpc, _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 */ldrr0, =0x10060000movr1, #0strr1, [r0]
再进入SVC模式,打开NEON及VFP指令支持,关闭MMU,初始化cache等等,这些跟IROM里面做的类似。

/* Set the cpu to supervisor mode */mrsr0, cpsrbicr0, r0, #0x1forrr0, r0, #0xd3msrcpsr, r0/* Enable NEON & VFP unit */mrc p15, #0, r1, c1, c0, #2orr r1, r1, #(0xf << 20)mcr p15, #0, r1, c1, c0, #2mov r1, #0mcr p15, #0, r1, c7, c5, #4mov r0, #0x40000000fmxr fpexc, r0/* Cache init */mrcp15, 0, r0, c0, c0, 0/* read main ID register */andr1, r0, #0x00f00000/* variant */andr2, r0, #0x0000000f/* revision */orrr2, r2, r1, lsr #20-4/* combine variant and revision */cmpr2, #0x30mrceqp15, 0, r0, c1, c0, 1/* read ACTLR */orreqr0, r0, #0x6/* Enable DP1(2), DP2(1) */mcreqp15, 0, r0, c1, c0, 1/* write ACTLR *//* Invalidate L1 I/D */movr0, #0/* set up for MCR */mcrp15, 0, r0, c8, c7, 0/* invalidate TLBs */mcrp15, 0, r0, c7, c5, 0/* invalidate icache *//* Disable mmu stuff and caches */mrcp15, 0, r0, c1, c0, 0bicr0, r0, #0x00002000/* clear bits 13 (--v-) */bicr0, r0, #0x00000007/* clear bits 2:0 (-cam) */orrr0, r0, #0x00000002/* set bit 1 (--a-) align */orrr0, r0, #0x00000800/* set bit 12 (z---) btb */mcrp15, 0, r0, c1, c0, 0

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

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

/* copyself to ram using irom */adrr0, _startldr r1, =_startcmpr0, r1beqhave_copyedblirom_copyselfhave_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 */blinit_stacks/* copy shadow of data section */copy_shadow_data:ldrr0, _data_shadow_startldrr1, _data_startldrr2, _data_shadow_endblmem_copy/* clear bss section */clear_bss:ldrr0, _bss_startldrr1, _bss_endmov r2, #0x00000000blmem_clear/* * initialize stacks */init_stacks:mrsr0, cpsrbicr0, r0, #MODE_MASK | NO_INTorrr1, r0, #UDF_MODEmsrcpsr_cxsf, r1ldrsp, _stack_und_endbicr0, r0, #MODE_MASK | NO_INTorrr1, r0, #ABT_MODEmsrcpsr_cxsf, r1ldrsp, _stack_abt_endbicr0, r0, #MODE_MASK | NO_INTorrr1, r0, #IRQ_MODEmsrcpsr_cxsf, r1ldrsp, _stack_irq_endbicr0, r0, #MODE_MASK | NO_INTorrr1, r0, #FIQ_MODEmsrcpsr_cxsf, r1ldrsp, _stack_fiq_endbicr0, r0, #MODE_MASK | NO_INTorrr1, r0, #SVC_MODEmsrcpsr_cxsf, r1ldrsp, _stack_srv_endmovpc, lr/* * memory copy */mem_copy:subr2, r2, #32cmpr0, r2ble3f1:ldmia r0!, {r3-r10}stmia r1!, {r3-r10}cmpr0, r2ble1b3:addr2, r2, #322:ldrr3, [r0], #4strr3, [r1], #4cmpr0, r2blt2bmovpc, lr/* * memory clear zero */mem_clear:subr1, r1, #32cmpr0, r1blecpmov r3, #0mov r4, #0mov r5, #0mov r6, #0mov r7, #0mov r8, #0mov r9, #0mov r10, #01:stmia r0!, {r3-r10}cmpr0, r1ble1bcp:addr1, r1, #322:strr2, [r0], #4cmpr0, r1blt2bmovpc, lr

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

/* jump to ram */ldrr1, =on_the_rammovpc, r1on_the_ram:/* jump to main fuction */mov r0, #1;mov r1, #0;blmainbon_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


0 0
原创粉丝点击