首页 » 互联网 » bootloader启动之「 lk -\u003e kernel」分析笔记_年夜众_初始化

bootloader启动之「 lk -\u003e kernel」分析笔记_年夜众_初始化

admin 2024-12-03 02:33:50 0

扫一扫用手机浏览

文章目录 [+]

Pre-loader 运行在ISRAM,待完成 DRAM 的初始化后,再将lk载入DRAM中,末了通过分外sys call手段实现跳转到lk的实行入口,正式进入lk初始化阶段。

一. lk实行入口:

位于.text.boot 这个section(段),详细定义位置为:

bootloader启动之「 lk -\u003e kernel」分析笔记_年夜众_初始化 互联网

./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

From Lk to Kernel 总时序图:

二. 源码剖析:

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 lk\n\n\"大众); / 实行定义在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, , 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;}

platform_init 时序图:

这里的 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);elsememcpy(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 %s\n\"大众,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] = '\0'; surf_udc_device.serialno = sn_buf;/ mtk平台默认不支持 fastboot /if (g_boot_mode == FASTBOOT)goto fastboot;/ secure boot干系 /#ifdef MTK_SECURITY_SW_SUPPORT#if MTK_FORCE_VERIFIED_BOOT_SIG_VFY g_boot_state = BOOT_STATE_RED;#elseif (0 != sec_boot_check(0)) { g_boot_state = BOOT_STATE_RED; }#endif#endif/ 这里干的事情就比较多了,跟进g_boot_mode选择各种启动模式,例如:normal、facotry、fastboot、recovery等,然后从ROM中的boot.img分区找到(解压)ramdisk跟zImage的地址loader到DRAM的特定地址中,kernel终极load到DRAM中的地址(DRAM_PHY_ADDR + 0x8000) == 0x00008000.read the data of boot (size = 0x811800)/ boot_linux_from_storage();fastboot: target_fastboot_init();if (!usb_init)/Hong-Rong: wait for porting/ udc_init(&surf_udc_device); mt_part_dump(); sz = target_get_max_flash_size(); fastboot_init(target_get_scratch_address(), sz); udc_start();}

mt_boot_init 剖析小结:

获取设备串号字符串、芯片代码、sn号等.

如果实现了secure boot则进行sec boot的check事情;

进入 boot_linux_from_storage 函数初始化,该函数很主要,干了很多事情,如下剖析.

5、boot_linux_from_storage 剖析:

int boot_linux_from_storage(void){int ret=0;...switch (g_boot_mode) {case NORMAL_BOOT:case META_BOOT:case ADVMETA_BOOT:case SW_REBOOT:case ALARM_BOOT:case KERNEL_POWER_OFF_CHARGING_BOOT:case LOW_POWER_OFF_CHARGING_BOOT:/ 检讨boot分区的头部是否有bootopt标识,如果没有就报错 / ret = mboot_android_load_bootimg_hdr(\公众boot\"大众, CFG_BOOTIMG_LOAD_ADDR);if (ret < 0) { msg_header_error(\"大众Android Boot Image\公众); }/ 64bit & 32bit kimg地址获取不一样/if (g_is_64bit_kernel) { kimg_load_addr = (unsigned int)target_get_scratch_address(); } else { kimg_load_addr = (g_boot_hdr!=) ? g_boot_hdr->kernel_addr : CFG_BOOTIMG_LOAD_ADDR; }/ 从EMMC的boot分区取出bootimage载入到DRAM dprintf(CRITICAL, \公众 > from - 0x%016llx (skip boot img hdr)\n\公众,start_addr); dprintf(CRITICAL, \公众 > to - 0x%x (starts with kernel img hdr)\n\"大众,addr); len = dev->read(dev, start_addr, (uchar)addr, g_bimg_sz); <<= 系统调用load到DRAM 开机log: [3380] > from - 0x0000000001d20800 (skip boot img hdr) [3380] > to - 0x80008000 (starts with kernel img hdr) / ret = mboot_android_load_bootimg(\"大众boot\"大众, kimg_load_addr);if (ret < 0) { msg_img_error(\公众Android Boot Image\"大众); } dprintf(CRITICAL,\"大众[PROFILE] ------- load boot.img takes %d ms -------- \n\公众, (int)get_timer(time_load_bootimg));break;case RECOVERY_BOOT:...break;case FACTORY_BOOT:case ATE_FACTORY_BOOT:...break;... }/ 重定位根文件系统(ramdisk)地址 /memcpy((g_boot_hdr!=) ? (char )g_boot_hdr->ramdisk_addr : (char )CFG_RAMDISK_LOAD_ADDR, (char )(g_rmem_off), g_rimg_sz); g_rmem_off = (g_boot_hdr!=) ? g_boot_hdr->ramdisk_addr : CFG_RAMDISK_LOAD_ADDR;.../ 传入cmdline,设置selinux /#if SELINUX_STATUS == 1 cmdline_append(\公众androidboot.selinux=disabled\公众);#elif SELINUX_STATUS == 2 cmdline_append(\公众androidboot.selinux=permissive\"大众);#endif/ 准备启动linux kernel / boot_linux((void )CFG_BOOTIMG_LOAD_ADDR, (unsigned )CFG_BOOTARGS_ADDR, (char )cmdline_get(), board_machtype(), (void )CFG_RAMDISK_LOAD_ADDR, g_rimg_sz);while (1) ;return 0;}

boot_linux_from_storage 小结:

跟据g_boot_mode选择各种启动模式,例如:normal、facotry、fastboot、recovery等,然后从EMMC中的boot分区找到(解压) ramdisk跟zImage的地址通过read系统调用load到DRAM址中, kernel终极load到DRAM的地址:(DRAM_PHY_ADDR + 0x8000);

重定位根文件系统地址;

跳转到 boot_linux,正式拉起kernel;

6、boot_linux 剖析:

boot_linux 实际上跑的是boot_linux_fdt,这个函数有对dtb的加载做出来,期间操作相称繁芜,这里只大略关注主流程。

void boot_linux(void kernel, unsigned tags,char cmdline, unsigned machtype,void ramdisk, unsigned ramdisk_size){...// 新架构都是走fdt分支.#ifdef DEVICE_TREE_SUPPORT boot_linux_fdt((void )kernel, (unsigned )tags, (char )cmdline, machtype, (void )ramdisk, ramdisk_size);while (1) ;#endif...int boot_linux_fdt(void kernel, unsigned tags,char cmdline, unsigned machtype,void ramdisk, unsigned ramdisk_size){...void (entry)(unsigned,unsigned,unsigned) = kernel;...// find dt from kernel imgif (fdt32_to_cpu((unsigned int )dtb_addr) == FDT_MAGIC) { dtb_size = fdt32_to_cpu((unsigned int )(dtb_addr+0x4)); } else { dprintf(CRITICAL,\公众Can't find device tree. Please check your kernel image\n\公众);while (1) ; }...if (!has_set_p2u) {/ 掌握进入kernel后uart的输出,非eng版本默认是关闭的,如果调试须要就可以改这里为 \公众printk.disable_uart=0\"大众 /#ifdef USER_BUILDsprintf(cmdline,\"大众%s%s\公众,cmdline,\"大众 printk.disable_uart=1\"大众);#elsesprintf(cmdline,\"大众%s%s\"大众,cmdline,\公众 printk.disable_uart=0 ddebug_query=\\公众file mediatek +p ; file gpu =_\\"大众\"大众);#endif... }...// led,irq关闭 platform_uninit();// 关闭I/D-cache,关闭MMU,本日kernel的条件. arch_disable_cache(UCACHE); arch_disable_mmu();// sec initextern void platform_sec_post_init(void)__attribute__((weak));if (platform_sec_post_init) { platform_sec_post_init(); }// 如果是正在充电,检测到power key后实行reset.if (kernel_charging_boot() == 1) {if (pmic_detect_powerkey()) { dprintf(CRITICAL,\公众[%s] PowerKey Pressed in Kernel Charging Mode Before Jumping to Kernel, Reboot Os\n\"大众, __func__); mtk_arch_reset(1); } }#endif...// 输出关键信息。
dprintf(CRITICAL,\公众cmdline: %s\n\"大众, cmdline); dprintf(CRITICAL,\"大众lk boot time = %d ms\n\"大众, lk_t); dprintf(CRITICAL,\"大众lk boot mode = %d\n\公众, g_boot_mode); dprintf(CRITICAL,\"大众lk boot reason = %s\n\公众, g_boot_reason[boot_reason]); dprintf(CRITICAL,\公众lk finished --> jump to linux kernel %s\n\n\"大众, g_is_64bit_kernel ? \公众64Bit\公众 : \"大众32Bit\公众);// 实行系统调用,跳转到kernel,这里的entry实际上便是前面的kernel在DRAM的入口地址.if (g_is_64bit_kernel) { lk_jump64((u32)entry, (u32)tags, 0, KERNEL_64BITS); } else { dprintf(CRITICAL,\"大众[mt_boot] boot_linux_fdt entry:0x%08x, machtype:%d\n\"大众,entry,machtype); entry(0, machtype, tags); }while (1);return 0;}

开机log打印信息:

[4260] cmdline: console=tty0 console=ttyMT0,921600n1 root=/dev/ram vmalloc=496M androidboot.hardware=mt6580 androidboot.verifiedbootstate=green bootopt=64S3,32S1,32S1 printk.disable_uart=1 bootprof.pl_t=1718 bootprof.lk_t=2178 boot_reason=0 androidboot.serialno=0123456789ABCDEF androidboot.bootreason=power_key gpt=1[4260] lk boot time = 2178 ms[4260] lk boot mode = 0[4260] lk boot reason = power_key[4260] lk finished --> jump to linux kernel 32Bit[4260] [mt_boot] boot_linux_fdt entry:0x80008000, machtype:6580

boot_linux 小结:

初始化DTB(device tree block);

准备各种cmdline参数传入kernel;

关闭I/D-cache、MMU;

打印关键信息,正式拉起kernel.

到这里,bootloader两个阶段就剖析完了!

Bootloader 启动大略总结:

Pre-loader -》lk紧张干的事情:

1、初始化 DRAM等必须硬件;

2、与flashtool USB握手,download 干系检测 & sec boot检测;

3、将lk载入DRAM,若实现了EL3则把atf载入内存;

4、跳转到lk,若实现了EL3,则先跳转到atf,初始化atf后再跳转回lk初始化;

lk -》 kernel 紧张干的事情:

1、打开MMU,使能I/D-cache,加速lk实行,显示logo、充电干系;

2、从emmc中boot分区取出boot.img解压,将根文件系统(ramdisk)、zImage load到DRAM;

3、解析dtb,写入到DRAM指定区域;

4、关闭MMU、irq / fiq,关闭I/D-cache, 拉起 kernel;

5T技能资源大放送!
包括但不限于:C/C++,Arm, Linux,Android,人工智能,单片机,树莓派,等等。
在"大众年夜众号内回答「peter」,即可免费获取!

标签:

相关文章