BootLoader代码是芯片复位后,进入操作系统之前执行的一段代码。主要用于完成由硬件启动到操作系统启动的过渡,从而为操作系统提供基本的运行环境。
BootLoder主要的启动流程可以概括为:PBL阶段、SBL阶段、LK阶段。之后会加载并启动kernel。
5个处理器:
启动相关:
PBL部分并不开源,所以从SBL开始。
sbl1入口: sbl1.s
此部分代码路径在:boot_images/core/boot/secboot3/hw/msm89xx/sbl1/sbl1.s,此文件引导处理器,主要有实现如下操作:
部分源码:
IMPORT |Image$$SBL1_SVC_STACK$$ZI$$Limit|
IMPORT |Image$$SBL1_UND_STACK$$ZI$$Limit|
IMPORT |Image$$SBL1_ABT_STACK$$ZI$$Limit|
IMPORT boot_undefined_instruction_c_handler
IMPORT boot_swi_c_handler
IMPORT boot_prefetch_abort_c_handler
IMPORT boot_data_abort_c_handler
IMPORT boot_reserved_c_handler
IMPORT boot_irq_c_handler
IMPORT boot_fiq_c_handler
IMPORT boot_nested_exception_c_handler
IMPORT sbl1_main_ctl #主要关注此函数
IMPORT boot_crash_dump_regs_ptr
...
sbl1_main_ctl函数
路径:...\sbl1\sbl1_mc.c
/* Calculate the SBL start time for use during boot logger initialization. */
sbl_start_time = CALCULATE_TIMESTAMP(HWIO_IN(TIMETICK_CLK));
boot_clock_debug_init();
/* Enter debug mode if debug cookie is set */
sbl1_debug_mode_enter();
/* Initialize the stack protection canary */
boot_init_stack_chk_canary();
/* Initialize boot shared imem */
boot_shared_imem_init(&bl_shared_data);
/*初始化RAM*/
boot_ram_init(&sbl1_ram_init_data);
/*初始化log系统,即串口驱动*/
sbl1_boot_logger_init(&boot_log_data, pbl_shared);
/*检索PBL传递过来的数据*/
sbl1_retrieve_shared_info_from_pbl(pbl_shared);
/* Initialize the QSEE interface */
sbl1_init_sbl_qsee_interface(&bl_shared_data,&sbl_verified_info);
/* Initialize SBL memory map. Initializing early because drivers could be located in RPM Code RAM. */
sbl1_populate_initial_mem_map(&bl_shared_data);
/*初始化DAL*/
boot_DALSYS_InitMod(NULL);
/*配置PMIC芯片,以便我们能通过PS_HOLD复位*/
sbl1_hw_init();
/*执行sbl1的目标依赖进程*/
boot_config_process_bl(&bl_shared_data, SBL1_IMG, sbl1_config_table);
sbl1_config_table函数
路径:boot_images\core\boot\secboot3\hw\msmxxxx\sbl1\sbl1_config.c
sbl1_config_table为一个结构体数组,里面存储了加载QSEE、RPM、APPSBL等镜像所需要的配置参数及执行函数。
boot_configuration_table_entry sbl1_config_table[] =
{
/* SBL1 -> QSEE */
{
SBL1_IMG, /* host_img_id */
CONFIG_IMG_QC, /* host_img_type */
GEN_IMG, /* target_img_id */
CONFIG_IMG_ELF, /* target_img_type */
...
load_qsee_pre_procs, /* pre_procs */
load_qsee_post_procs, /* post_procs */
}
/* SBL1 -> QHEE */
...
/* SBL1 -> RPM */
...
/* SBL1 -> APPSBL (即lk部分) */
...
LK大概流程:
bootloader/lk目录结构:
-app #应用相关
-arch #arm体系、CPU架构
-dev #设备驱动
-images #image图片资源
-include #头文件
-kernel #LK系统、主文件,main.c
-lib #库文件
-platform #平台文件,如:msm8916
-project #mk文件
-scripts #脚本文件
-target #目标设备文件
代码流程:
lk 是使用 arm 汇编
和 c
语言联合编译而成的,其中偏向硬件和底层的代码使用 arm 汇编
编写,而偏上层提供功能的代码则使用 c
编写。
入口:
lk 代码的入口点是在 arch/arm
目录下的以 .ld
为后缀的 link 脚本文件中指定。
指定的入口点均为位于 arch/arm/crt0.S
文件中的 _start
函数:
_start
最主要的作用是设置一些 cpu 的特性,然后初始化各种 c 程序运行需要的栈环境,完成后直接跳转到 kmian
函数进入 c 语言环境。
Kmain()函数:
void kmain(void)
{
thread_init_early(); // 初始化化lk线程上下文
arch_early_init(); // 架构初始化,如关闭cache,使能mmu
platform_early_init(); // 平台硬件早期初始化
target_early_init(); //目标设备早期初始化
bs_set_timestamp(BS_BL_START);
call_constructors(); //静态构造函数初始化
heap_init(); // 堆初始化
thread_init(); // 初始化线程
dpc_init(); //lk系统控制器初始化
timer_init(); //kernel时钟初始化
#if (!ENABLE_NANDWRITE)
thread_resume(thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE)); // 创建一个线程初始化系统
exit_critical_section(); //使能中断
thread_become_idle(); //本线程切换为idle线程
#else
bootstrap_nandwrite();
#endif
}
bootstrap2
此函数由kmain中创建的线程调用,路径为:bootable\bootloader\lk\kernel\main.c。
static int bootstrap2(void *arg)
{
arch_init(); //架构初始化
bio_init();
fs_init();
platform_init(); //平台初始化, 主要初始化系统时钟,超频等
target_init(); //目标设备初始化,主要初始化Flash,整合分区表等
apps_init(); // 应用功能初始化,调用aboot_init,加载kernel等
}
apps_init()函数:
app_init
函数位于 app/app.c
文件中
/* app entry point */
struct app_descriptor;
typedef void (*app_init)(const struct app_descriptor *);
typedef void (*app_entry)(const struct app_descriptor *, void *args);
/* app startup flags */
#define APP_FLAG_DONT_START_ON_BOOT 0x1
/* each app needs to define one of these to define its startup conditions */
struct app_descriptor {
const char *name;
app_init init;
app_entry entry;
unsigned int flags;
};
void apps_init(void)
{
const struct app_descriptor *app;
/* call all the init routines */
for (app = &__apps_start; app != &__apps_end; app++) {
if (app->init)
app->init(app);
}
/* start any that want to start on boot */
for (app = &__apps_start; app != &__apps_end; app++) {
if (app->entry && (app->flags & APP_FLAG_DONT_START_ON_BOOT) == 0) {
start_app(app);
}
}
}
static void start_app(const struct app_descriptor *app)
{
thread_t *thr;
printf("starting app %s\n", app->name);
thr = thread_create(app->name, &app_thread_entry, (void *)app, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
if(!thr)
{
return;
}
thread_resume(thr);
}
整个遍历 app 并启动的过程并不复杂,有趣的是 __apps_start 和 __apps_end 的定义,这两个变量符号在所有源文件中并不存在,而是在 arch/arm/*.ld 链接脚本中存在, __apps_start 和 __apps_end 是自定义的两个符号,代表了自定义段 .apps 的开始位置和结束位置。也就是说所有的 app 都通过在特殊的段 .apps 中注册实现了一套插件系统,是一个十分精巧的设计。后续的任何新的 app 只需要使用以下两个宏声明即可注册到 .apps 段中:
#define __SECTION(x) __attribute((section(x)))
#define APP_START(appname) struct app_descriptor _app_##appname __SECTION(".apps") = { .name = #appname,
#define APP_END };
在bootloader/lk/app/aboot/aboot.c 结尾:
APP_START(aboot)
.init = aboot_init,
APP_END
说明apps_init函数调用了aboot_init函数。
aboot_init函数:
是通过 APP_START
注册的 aboot
入口函数, aboot
的所有功能都是由此开始。位于 bootloader/lk/app/aboot/aboot.c文件中。
简单流程:
主要函数:boot_linux_from_mmc
boot.img 解析:https://blog.csdn.net/ty3219/article/details/78879398
boot.image加签:
签名校验:
Boot流程的粗略理解。