概述
1、框架
MTK 平台的启动过程经过四个模块,分别是BootRom,Preloader,LK,Kernel.
2 、bootloader到kernel启动总逻辑流程图
Boot ROM的主要功能流程如下:
1)设备上电起来后,跳转到Boot ROM,Boot ROM从Reset Vector 开始执行ARM 核。
2)内部SRAM 中初始化栈
3)初始化nand flash 或 emmc
4)把pre-loader加载起到ISRAM
5)跳到pre-loader
4、Preloader
4.1 Preloader主要功能
1)复位寄存器、堆栈的SP,禁止中断IRQ,建立起C运行环境
2)初始化Timer、Clock、UART、DDR等关键硬件
3)对代码进行鉴权
4)pre-loader初始化好DRAM后就将lk从flash(nand/emmc)中加载到DRAM中运行
PreLoader entry位于 mediatek /platform/MTXXXX//Preloader/src/init/init.s
源码流程如下:
./bootloader/preloader/platform/mt6580/src/init/init.s
.section .text.start
...
.globl _start
...
/* set the cpu to SVC32 mode */
MRS r0,cpsr
BIC r0,r0,#0x1f
ORR r0,r0,#0xd3
MSR cpsr,r0
/* disable interrupt */
MRS r0, cpsr
MOV r1, #INT_BIT
ORR r0, r0, r1
MSR cpsr_cxsf, r0
...
setup_stk :
/* setup stack */
LDR r0, stack
LDR r1, stacksz
...
entry :
LDR r0, =bldr_args_addr
/* 跳转到C代码 main 入口 */
B main
4.3 Preloader- main
alps/mediatek /platform/$MTXXXX//Preloader/
void main(u32 *arg)
{
struct bldr_command_handler handler;
u32 jump_addr, jump_arg;
/* get the bldr argument */
bldr_param = (bl_param_t *)*arg;
// 初始化uart
mtk_uart_init(UART_SRC_CLK_FRQ, CFG_LOG_BAUDRATE);
// 这里干了很多事情,包括各种的平台硬件(timer,pmic,gpio,wdt...)初始化工作.
bldr_pre_process();
handler.priv = NULL;
handler.attr = 0;
handler.cb = bldr_cmd_handler;
// 这里是获取启动模式等信息保存到全局变量g_boot_mode和g_meta_com_type 中.
BOOTING_TIME_PROFILING_LOG("before bldr_handshake");
bldr_handshake(&handler);
BOOTING_TIME_PROFILING_LOG("bldr_handshake");
// 下面跟 secro img 相关,跟平台设计强相关.
/* security check */
sec_lib_read_secro();
sec_boot_check();
device_APC_dom_setup();
BOOTING_TIME_PROFILING_LOG("sec_boot_check");
/* 如果已经实现EL3,那么进行tz预初始化 */
#if CFG_ATF_SUPPORT
trustzone_pre_init();
#endif
/* bldr_load_images
此函数要做的事情就是把lk从ROM中指定位置load到DRAM中,开机log中可以看到具体信息:
[PART] load "lk" from 0x0000000001CC0200 (dev) to 0x81E00000 (mem) [SUCCESS]
这里准备好了jump到DRAM的具体地址,下面详细分析.
*/
if (0 != bldr_load_images(&jump_addr)) {
print("%s Second Bootloader Load Failedn", MOD);
goto error;
}
/*
该函数的实现体是platform_post_init,这里要干的事情其实比较简单,就是通过
hw_check_battery去判断当前系统是否存在电池(判断是否有电池ntc脚来区分),
如果不存在就陷入while(1)卡住了,所以在es阶段调试有时候
需要接电源调试的,就需要改这里面的逻辑才可正常开机
*/
bldr_post_process();
// atf 正式初始化,使用特有的系统调用方式实现.
#if CFG_ATF_SUPPORT
trustzone_post_init();
#endif
/* 跳转传入lk的参数,包括boot time/mode/reason 等,这些参数在
platform_set_boot_args 函数获取。
*/
jump_arg = (u32)&(g_dram_buf->boottag);
/* 执行jump系统调用,从 pre-loader 跳转到 lk执行,
5、lk
5.1 lk主要功能
1)从Preloader 获取参数
2)MMU cache 使能
3)外设初始化
4)设置boot 模式
5)加载kernel
6) 跳转到kernel
5.2 lk-reset
lk执行入口:
位于.text.boot 这个section(段),具体定义位置为:
./lk/arch/arm/system-onesegment.ld:10: .text.boot : { *(.text.boot) }
./lk/arch/arm/system-twosegment.ld:10: .text.boot : { *(.text.boot) }
该段的代码执行入口是crt0.S文件,位置为:
./lk/arch/arm/crt0.S
crt0.S 中会经过一系列的初始化准备操作,最终跳转到C代码入口kmain函数开始执行,这个是 我们需要重点分析关注的,kmain的位置:
./lk/kernel/main.c
5.3 lk-main
5.4代码分析
1、crt0.S
.section ".text.boot"
...
.Lstack_setup:
/* ==set up the stack for irq, fi==q, abort, undefined, system/user, and lastly supervisor mode */
mrs r0, cpsr
bic r0, r0, #0x1f
ldr r2, =abort_stack_top
orr r1, r0, #0x12 // irq
msr cpsr_c, r1
ldr r13, =irq_save_spot /* save a pointer to a temporary dumping spot used during irq delivery */
orr r1, r0, #0x11 // fiq
msr cpsr_c, r1
mov sp, r2
orr r1, r0, #0x17 // abort
msr cpsr_c, r1
mov sp, r2
orr r1, r0, #0x1b // undefined
msr cpsr_c, r1
mov sp, r2
orr r1, r0, #0x1f // system
msr cpsr_c, r1
mov sp, r2
orr r1, r0, #0x13 // supervisor
msr cpsr_c, r1
mov sp, r2
...
bl kmain
crt0.S 小结:
这里主要干的事情就是建立fiq/irq/abort等各种模式的stack,初始化向量表,然后切换到管理模式(pre-loader运行在EL3, lk运行在EL1),最后跳转到C代码入口 kmain 执行.
2、kmain :
void kmain(void)
{
boot_time = get_timer(0);
/* 早期初始化线程池的上下文,包括运行队列、线程链表的建立等,
lk架构支持多线程,但是此阶段只有一个cpu处于online,所以也只有一条代码执行路径.
*/
thread_init_early();
/* 架构初始化,包括DRAM,MMU初始化使能,使能协处理器,
preloader运行在ISRAM,属于物理地址,而lk运行在DRAM,可以选择开启MMU或者关闭,开启MMU可以加速lk的加载过程.
*/
arch_early_init();
/*
平台硬件早期初始化,包括irq、timer,wdt,uart,led,pmic,i2c,gpio等,
初始化平台硬件,建立lk基本运行环境。
*/
platform_early_init();
boot_time = get_timer(0);
// 这个是保留的空函数.
target_early_init();
dprintf(CRITICAL, "welcome to lknn");
/*
执行定义在system-onesegment.ld 描述段中的构造函数,不太清楚具体机制:
__ctor_list = .;
.ctors : { *(.ctors) }
__ctor_end = .;
*/
call_constructors();
//内核堆链表上下文初始化等.
heap_init();
// 线程池初始化,前提是PLATFORM_HAS_DYNAMIC_TIMER需要支持.
thread_init();
// dpc系统是什么?据说是一个类似work_queue的东东,dpc的简称是什么就不清楚了.
dpc_init();
// 初始化内核定时器
timer_init();
// 创建系统初始化工作线程,执行app初始化,lk把业务部分当成一个app.
thread_resume(thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
// 使能中断.
exit_critical_section();
// become the idle thread
thread_become_idle();
}
kmain 小结:
。初始化线程池,建立线程管理链表、运行队列等;
。初始化各种平台硬件,包括irq、timer,wdt,uart,led,pmic,i2c,gpio等,建立lk基本运行环境;
。初始化内核heap、内核timer等;
。创建系统初始化主线程,进入bootstrap2执行,使能中断,当前线程进入idle;
3、bootstrap2 分析:
static int bootstrap2(void *arg)
{
...
/*
平台相关初始化,包括nand/emmc,LCM显示驱动,启动模式选择,加载logo资源,
具体代码流程如下时序图.
*/
platform_init();
...
/*
app初始化,跳转到mt_boot_init入口开始执行,对应的 ".apps" 这个section.
*/
apps_init();
return 0;
}
这里的 apps_init 跳转机制还有点特别:
extern const struct app_descriptor __apps_start;
extern const struct app_descriptor __apps_end;
void apps_init(void)
{
const struct app_descriptor *app;
/* 这里具体干了什么?如何跳转到mt_boot_init入口?有点不知所云
依次遍历 从__apps_start 到__apps_end 又是什么东东?
*/
for (app = &__apps_start; app != &__apps_end; app++) {
if (app->init)
app->init(app);
}
...
}
这个__apps_start 跟 __apps_end哪里定义的? 是怎么回事呢? 这里就需要了解一点编译链接原理跟memory 布局的东东, 这个实际上是指memory中的一个只读数据段的起始&结束地址区间, 它定义在这个文件中:
./lk/arch/arm/system-onesegment.ld:47: __apps_start = .;
.rodata : {
...
. = ALIGN(4);
__apps_start = .;
KEEP (*(.apps))
__apps_end = .;
. = ALIGN(4);
__rodata_end = . ;
}
该mem地址区间是[__apps_start, __apps_end],显然区间就是“.apps” 这个section内容了. 那么这个section是在哪里初始化的呢?继续看:
./lk/app/mt_boot/mt_boot.c:1724:
APP_START(mt_boot)
.init = mt_boot_init,
APP_END
展开APP_START:
#define APP_START(appname) struct app_descriptor _app_##appname __SECTION(".apps") = { .name = #appname,
#define APP_END };
到这里就很明显了,编译链接系统会将mt_boot_init这个地址记录到".apps"这个section中!所以下面代码要干的事情就很清晰了,执行app->init(app)后就等价于调用了void mt_boot_init(const struct app_descriptor *app) 函数.
for (app = &__apps_start; app != &__apps_end; app++) {
if (app->init)
app->init(app);
}
bootstrap2 函数小结:
。平台相关初始化,包括nand/emmc,显现相关驱动,启动模式选择,加载logo资源 检测是否DA模式,检测分区中是否有KE信息,如果就KE信息,就从分区load 到DRAM, 点亮背光,显示logo,禁止I/D-cache和MMU,跳转到DA(??),配置二级cache的size 获取bat电压,判断是否低电量是否显示充电logo等,总之此函数干的事情比较多.时序图(platform_init)可以比较清晰直观的描述具体细节
。跳转到到mt_boot_init函数,对应的 ".apps" 这个section,相关机制上面已经详细描述,不再复述.
4、mt_boot_init 分析
void mt_boot_init(const struct app_descriptor *app)
{
unsigned usb_init = 0;
unsigned sz = 0;
int sec_ret = 0;
char tmp[SN_BUF_LEN+1] = {0};
unsigned ser_len = 0;
u64 key;
u32 chip_code;
char serial_num[SERIALNO_LEN];
/* 获取串号字符串 */
key = get_devinfo_with_index(13);
key = (key << 32) | (unsigned int)get_devinfo_with_index(12);
/* 芯片代码 */
chip_code = board_machtype();
if (key != 0)
get_serial(key, chip_code, serial_num);
else
memcpy(serial_num, DEFAULT_SERIAL_NUM, SN_BUF_LEN);
/* copy serial from serial_num to sn_buf */
memcpy(sn_buf, serial_num, SN_BUF_LEN);
dprintf(CRITICAL,"serial number %sn",serial_num);
/* 从特定分区获取产品sn号,如果获取失败就使用默认值 DEFAULT_SERIAL_NUM */
#ifdef SERIAL_NUM_FROM_BARCODE
ser_len = read_product_info(tmp);
if (ser_len == 0) {
ser_len = strlen(DEFAULT_SERIAL_NUM);
strncpy(tmp, DEFAULT_SERIAL_NUM, ser_len);
}
memset( sn_buf, 0, sizeof(sn_buf));
strncpy( sn_buf, tmp, ser_len);
#endif
sn_buf[SN_BUF_LEN] = '