Exynos4412裸机开发系列教程--启动流程
2014-08-15 11:13
537 查看
看过前两篇教程的朋友,发现裸机开发怎么的如此简单,从这篇文章开始,我们来的有点难度的,启动流程。这个可以说是整个裸机开发的核心了,如果这一步无法跨越,您的一颗LED灯也点不亮,当然,如果您跨越了这一步,那么神马裸机开发都是一小点心而已。好,闲话少说,干货拿来。
在Exynos4412上电后,其内部的IROM会首先运行,下面是一张IROM运行流程图:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202008/30/ddbd18fd0e7431ccc501f9876a899311)
由流程图可以看出,首先关闭看门狗,关闭中断及MMU,关闭数据缓存,打开指令缓存,清除TLB,然后将其他核进入IDLE模式,只留CPU0,这里有了第一个跳转分支,IROM判断当前启动模式,是冷启动还是唤醒,如果是唤醒模式,那么就是直接跳转到BL1,在BL1里面我们会再次判断是否是唤醒模式,如果是就直接跳转到唤醒函数,一般都是linux内核的唤醒句柄。当然在裸机里都是冷启动的哈,休眠唤醒一般是不需要关注的,当然如果你的裸机程序需要支持休眠唤醒,就需要增加相应的代码了。
好了,继续我们的冷启动,设置IRQ及SVC模式的栈空间,这个时间,栈地址是其内部的一片IRAM,这小片RAM是IROM运行的外部随机存储器,没有这片小内存,IROM是无法运行的。接下了就是初始化IROM里面所使用的各种变量,初始化只读数据段,未初始化数据段清零,导出部分核心函数,这个函数可以在BL1中使用,获取当前复位的状态,设置系统时钟分频,获取OM管脚配置模式,这里可以从多种外设启动,具体启动模式如下表:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202008/30/a532a2ca450f6e0cbf9b2858bc1d00eb)
我们整个逻机教程都是从外部SD卡启动的,根据OM启动模式,从相应的存储器拷贝前8K代码,拷贝失败的话,系统就宕机了,只能复位重启了,如果拷贝成功,就验证校验和,BL1的前16个字节就是提供给IROM用来标识BL1相关信息的,具体信息如下:
那拷贝的前8K代码,究竟从SD里的哪里开始拷贝呢,这里有个图可以参考,需要注意的是,拷贝是从第一个扇区开始,前面有一个扇区保留,每个扇区512字节,如果有同学对DOS分区表有过研究,就能明白其中的道理了,第一个扇区是分区表的配置区,一个磁盘里最多的4个主分区就是在这里配置的,当然逻辑扇区可以指定到其他位置。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202008/30/7c507891bba515efa6303021b7638970)
IROM计算校验和且验证通过后并解密BL1成功后就可以跳转到BL1了,至此IROM已执行完备,权限已交由BL1了,补充说明一下,解密BL1是加密模式启动时才需要的,非加密模式启动是无需解密BL1的。
BL1就是我们可以控制编写的代码,但是对于samsung官方的uboot,这个BL1是不提供源码的,只提供一个bin文件,原因嘛,就是这个BL1是加密启动的,没关系,没有我们可以自己写个BL1,一样实现他的功能。
首先,填充16个字节用于后期制作校验和信息,然后就是标准的ARM异常向量表:
复位向量入口,就是我们开始的第一句代码,关闭看门狗,其实这个IROM已经实现了,但再做一遍也不为多,对吧,其实对于健壮的代码本质上就是不相信任何前提条件,都是自给自足的。
下面就是一些初始化代码,比如电源自锁,初始化时钟,初始化外部DDR,这些就不做细节分析了,可以参考源码,自行阅读。
接下来就是比较关键的自拷贝了,这里使用了IROM里的从外部SD卡拷贝到内存的函数,IROM里其实提供了一系列的从各种外部存储器拷贝到内存的方法
其中irom_copyself函数是用C实现的,代码如下:
这里有个define,将某格地址转化为函数的指针,然后执行,这个其实就是IROM里面导出的函数了。下面是一组IROM导出的函数表,可以参考:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202008/30/3e720745b8ec718fba1096238f987c92)
自拷贝完成后,我们就需要初始化最终的栈空间,初始化已初始化数据段,将未初始化数据段清零。
最后就是跳转到DDR中的main函数了,至此整个启动流程已经执行完备。
到这里,大家可能就疑惑了,只有BL1啊,没有BL2,怎么就完了呢,其实这里用了个高级技巧将所谓的BL2跟所谓的BL1合并为一个程序了,细心的朋友可以仔细研究下上面的自拷贝函数以及跳转到DDR中的main函数,这两个函数是实现这个技巧的关键。
在自拷贝函数中,需要知道当前的代码的链接地址及范围,而这些都是由链接脚本提供的,下面是完整链接脚本。
在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
相关文章推荐
- Exynos4412裸机开发系列教程--中断管理
- Exynos4412裸机开发系列教程--源码下载
- Exynos4412裸机开发系列教程--时钟管理
- Exynos4412裸机开发系列教程--TICK机制
- Exynos4412裸机开发系列教程--LED流水灯
- Exynos4412裸机开发系列教程--蜂鸣器
- 跨平台移动开发phonegap/cordova 3.3全系列教程-app启动画面
- ArcGIS Runtime SDK for iOS开发系列教程(6)——Tasks使用的一般流程
- S5P4418裸机开发系列教程--源码下载
- S3C2416裸机开发系列二_汇编入门代码以及sd卡启动
- S3C2416裸机开发系列八_MDK启动代码工程应用实例
- ArcGIS Runtime SDK for iOS开发系列教程(6)——Tasks使用的一般流程
- S3C2416裸机开发系列三_启动代码以及流水灯c代码
- S5P4418裸机开发系列教程--源代码下载
- S3C2416裸机开发系列九_GCC启动代码工程应用实例
- S3C2416裸机开发系列五_Nand驱动以及Nand启动
- S3C2416裸机开发系列一_裸机开发环境以及启动模式
- 教你如何开发VR游戏系列教程二:VR SDK介绍及开发流程介绍