MTK系统启动流程
启动流程图:
一 BootRom
系统开机,最先执行的是固化在芯片内部的bootrom,其作用比较简单,主要有
a.初始化ISRAM和EMMC
b.当系统全擦后 ,也会配置USB,用来仿真USB端口下载镜像。
c.从EMMC中加载preloader到ISRAM中执行。
二 Preloader
preloader用来初始化外设,配置软件执行环境。
Preloader执行之前,外部Memory没有初始化,故Preloader在内部ISRAM中执行的,其会初始化外部Memory,这样以后的镜像数据就可以加载到外部Memory中来执行。
同时preloader还会初始化UART用来调试,进行META模式握手,配置USB用来下载镜像数据,查找PMT分区表,最后会根据PMT表从EMMC中加载lk/uboot到Memory中来执行。
我们系统有个PMT表,这个是个总的分区表,每个分区数据都可以从这个表中找到。
第一列是该分区起始地址,第二列是该分区占用多少个block,第三列是分区名字。
[0x0000000000000000-0x0000000000ffffff] ( 32768 blocks): "PRELOADER"
[0x0000000001000000-0x000000000107ffff] ( 1024 blocks): "MBR"
[0x0000000001080000-0x00000000010fffff] ( 1024 blocks): "EBR1"
[0x0000000001100000-0x00000000013fffff] ( 6144 blocks): "PRO_INFO"
[0x0000000001400000-0x00000000018fffff] ( 10240 blocks): "NVRAM"
[0x0000000001900000-0x00000000022fffff] ( 20480 blocks): "PROTECT_F"
[0x0000000002300000-0x0000000002cfffff] ( 20480 blocks): "PROTECT_S"
[0x0000000002d00000-0x0000000002d1ffff] ( 256 blocks): "SECURE"
[0x0000000002d20000-0x0000000002d7ffff] ( 768 blocks): "UBOOT"
[0x0000000002d80000-0x000000000417ffff] ( 40960 blocks): "BOOTIMG"
[0x0000000004180000-0x000000000557ffff] ( 40960 blocks): "RECOVERY"
[0x0000000005580000-0x0000000005b7ffff] ( 12288 blocks): "SECSTATIC"
[0x0000000005b80000-0x0000000005bfffff] ( 1024 blocks): "MISC"
[0x0000000005c00000-0x0000000005efffff] ( 6144 blocks): "LOGO"
[0x0000000005f00000-0x0000000005f7ffff] ( 1024 blocks): "EBR2"
[0x0000000005f80000-0x0000000031b7ffff] ( 1433600 blocks): "CUSTPACK"
[0x0000000031b80000-0x000000003237ffff] ( 16384 blocks): "MOBILE_INFO"
[0x0000000032380000-0x0000000032d7ffff] ( 20480 blocks): "APANIC"
[0x0000000032d80000-0x000000005537ffff] ( 1126400 blocks): "ANDSYSIMG"
[0x0000000055380000-0x0000000061b7ffff] ( 409600 blocks): "CACHE"
[0x0000000061b80000-0x00000000a1b7ffff] ( 2097152 blocks): "USER"
三 LK(little kernel)
lk最主要的工作就是加载kernel和ramdisk,然后跳转到kernel中去执行。
同时有几个比较重要工作也是在lk中执行
a.初始化LCD,加载并显示开机logo。
b.对启动模式判断,meta模式,recovery模式,power off charging模式,fastboot模式等等。
c.fastboot也是在lk中实现的,主要作用就是下载我们手机镜像。
LK入口在kmain,它是由crt0.s连入的。
void kmain(void) //bootable/bootloader/lk/kernel/main.c
kmain中主要关注两个下面几个初始化操作
platform_early_init() //mediatek/platform/mt6582/lk/platform.c
platform_init();//mediatek/platform/mt6582/lk/platform.c
apps_init();
1)apps_init原理
apps_init会依次执行__apps_start与__apps_end之间所有的init函数,__apps_start与__apps_end的定义在如下连接脚本中。
bootable/bootloader/lk/arch/arm/system-onesegment.ld
意思是__apps_start跟__apps_end是.apps section的开始和结尾地址,属于.apps section的数据会依次存放在__apps_start跟__apps_end之间。
见如下示例:
APP_START(mt_boot)
.init = mt_boot_init,
APP_END
APP_START(aboot)
.init = aboot_init,
APP_END
#define APP_START(appname) struct app_descriptor _app_##appname __SECTION(".apps") = { .name = #appname,
#define APP_END };
对mt_boot和aboot分别展开宏定义:
struct app_descriptor _app_mt_boot __SECTION(".apps") =
{
.init=mt_boot_init,
}
struct app_descriptor _app_aboot __SECTION(".apps") =
{
.init=aboot_init,
}
可以看到_app_mt_boot和_app_aboot定义的结尾有__SECTION(".apps") ,系统编译连接的时候就会把这两个变量放到.apps section中。
这样我们通过依次遍历__apps_start跟__apps_end就可以找到所有init函数。
这样方式在kernel中用的非常多。
2)启动模式
请参考boot_mode_select函数,mediatek/platform/mt6582/lk/boot_mode.c
进入fastboot一般有两种方式,一是通过开机并按键进入,二是通过adb reboot bootloader命令进入。
a)按键进入就是开机过程中通过按某个键来进入fastboot模式,这个按键是需要在lk配置的,目前有些项目是没有配置的。
b)通过adb命令进入,运行adb reboot bootloader系统会重启,下次开机会自动进入fastboot模式。其原理是执行adb reboot bootloader后,系统是会写一个fastboot标志位到RTC寄存器,下次开机运行到lk中,会在boot_mode_select函数中调用Check_RTC_PDN1_bit13()来检测是否进入fastboot模式。
Fastboot相关代码请参考
bootable/bootloader/lk/app/aboot/fastboot.c
3)lk加载解析bootimage头,加载kernel和ramdisk.
主要相关代码在boot_linux_from_mmc()
首先就是通过PMT表找到bootimage的分区起始地址,然后从分区指定地址开始读取bootimage的头,
struct boot_img_hdr
{
unsigned char magic[BOOT_MAGIC_SIZE];
unsigned kernel_size; /* size in bytes */
unsigned kernel_addr; /* physical load addr */
unsigned ramdisk_size; /* size in bytes */
unsigned ramdisk_addr; /* physical load addr */
unsigned second_size; /* size in bytes */
unsigned second_addr; /* physical load addr */
unsigned tags_addr; /* physical addr for kernel tags */
unsigned page_size; /* flash page size we assume */
unsigned unused[2]; /* future expansion: should be 0 */
unsigned char name[BOOT_NAME_SIZE]; /* asciiz product name */
unsigned char cmdline[BOOT_ARGS_SIZE];
unsigned id[8]; /* timestamp / checksum / sha1 / etc */
};
/*
** +-----------------+
** | boot header | 1 page
** +-----------------+
** | kernel | n pages
** +-----------------+
** | ramdisk | m pages
** +-----------------+
** | second stage | o pages
** +-----------------+
bootimage的头中包含kernel加载的物理内存地址,kernel大小,ramdisk加载的物理内存地址,ramdisk大小,同时还有atag对应的物理地址等待,解析完bootimage头,就可以根据以上信息来从EMMC中加载kernel和ramdisk到指定的内存地址上
memmove((void*) hdr->kernel_addr, (char *)(image_addr + page_size), hdr->kernel_size);
memmove((void*) hdr->ramdisk_addr, (char *)(image_addr + page_size + kernel_actual), hdr->ramdisk_size);
lk传递参数到内核中可以通过atag传递,cmdline其实也是atag中的某一项。配置完atag后,就可以跳转到kernel入口去执行
boot_linux((void *)hdr->kernel_addr, (unsigned *) hdr->tags_addr, (const char *)cmdline, board_machtype(), (void *)hdr->ramdisk_addr, hdr->ramdisk_size);
{
unsigned *ptr = hdr->tags_addr;
if (ramdisk_size) {
*ptr++ = 4; //atag这项长度
*ptr++ = 0x54420005; //标示这项atag是ramdisk,#define ATAG_INITRD2
0x54420005
*ptr++ = (unsigned)hdr->ramdisk_addr; //ramdisk的物理地址
*ptr++ = hdr->ramdisk_size; //ramdisk大小
}
hdr->kernel_addr(0, machtype, hdr->tags_addr); //执行kernel入口
}
四 kernel及init启动
kernel C的入口是start_kernel,从head-common.S汇编跳转过来。
具体kernel启动过程,可以在网上参考文档,这里说下对atag的解析和init进程启动
kernel对atag的解析是在kernel/arch/arm/kernel/setup.c中
static int __init parse_tag(const struct tag *tag)
{
extern struct tagtable __tagtable_begin, __tagtable_end;
struct tagtable *t;
for (t = &__tagtable_begin; t < &__tagtable_end; t++)
if (tag->hdr.tag == t->tag) {
t->parse(tag);
break;
}
return t < &__tagtable_end;
}
__tagtable_begin跟 __tagtable_end,其实也是在连接脚本中定义的,
kernel/arch/arm/kernel/vmlinux.lds.S
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
表示.taglist.init section开始和结尾。
下面的__tagtable()就会把数据放到.taglist.init section。
kernel/arch/arm/include/asm/setup.h
__tagtable(ATAG_CORE, parse_tag_core);
__tagtable(ATAG_MEM, parse_tag_mem32);
__tagtable(ATAG_MEM64, parse_tag_mem64);
__tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);
__tagtable(ATAG_SERIAL, parse_tag_serialnr);
__tagtable(ATAG_REVISION, parse_tag_revision);
__tagtable(ATAG_CMDLINE, parse_tag_cmdline);
__tagtable(ATAG_INITRD2, parse_tag_initrd2);
#define ATAG_INITRD2
0x54420005
static int __init parse_tag_initrd2(const struct tag *tag)
{
phys_initrd_start = tag->u.initrd.start;
phys_initrd_size = tag->u.initrd.size;
return 0;
}
当系统解析到ATAG_INITRD2(0x54420005)这项atag时,就会执行 parse_tag_initrd2函数,从atag中取得ramdisk内存地址和大小。
init进程启动
start_kernel---->rest_init();
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
static int __init kernel_init(void * unused)
{
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
init_post();
}
static noinline int init_post(void)
{
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
}
最终会执行我们系统中的init进程,依次从如下地方查找init进程,/init,/sbin/init,/etc/init,/bin/init,/bin/sh。
init主要处理
1)创建系统相关目录并挂在系统相关文件系统。
mkdir("/dev", 0755);
mkdir("/proc", 0755);
mkdir("/sys", 0755);
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
2)初始化属性系统,并加载解析默认属性文件/build.prop
property_init();
property_load_boot_defaults();
3)解析相关rc文件。
init_parse_config_file("/init.rc");
init_parse_config_file("/init.project.rc");
rc文件包含四种类型语句:Actions, Commands, Services, Options。
Actions
Actions其实就是一组被命名的Commands序列。当满足触发器的事件发生时,这个action就会被置于一个队列中,这个队列存放着将要被执行的action。其格式如下:
on
on是Actions的关键字,它表明下面的序列是Actions序列。
Services
Services是有init进程启动的或者重新启动的程序。其格式如下:
service [ ]
Options
Options是Services的修饰符,由它来指定何时并且如何启动Services程序。
Commands
Commands即是在满足triger条件后,Actions中执行的内容。
init_parse_config_file这个函数负责rc文件的解析。
a.首先判断关键字,只能有两种可能on或者service,通过关键字来判定section范围;
b.根据Actions和Services的格式对section进行逐行解析;
c.将解析出的内容存放到双向循环链表中。
解析完所有的rc文件之后,如果action要执行,就必须有触发器触发,可以通过action_for_each_trigger接口触发相应action,该action对应的command会依次置于一个待执行队列。
action_for_each_trigger("early-fs", action_add_queue_tail);
action_for_each_trigger("fs", action_add_queue_tail);
action_for_each_trigger("post-fs", action_add_queue_tail);
action_for_each_trigger("post-fs-data", action_add_queue_tail);
还有一些没有在rc中定义的action,但这些action是没有执行command参数的,可以通过 queue_builtin_action接口直接放到待执行队列里,而不需要条件触发。
queue_builtin_action(property_service_init_action, "property_service_init");
queue_builtin_action(signal_init_action, "signal_init");
queue_builtin_action(check_startup_action, "check_startup");
其中property_service_init_action 会解析我们系统的属性文件,主要是build.prop和default.prop
load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop"
以USB配置为例,
init.usb.rc有如下片段
on property:sys.usb.config=mtp,adb
write /sys/class/android_usb/android0/enable 0
write /sys/class/android_usb/android0/idVendor $sys.usb.vid
write /sys/class/android_usb/android0/idProduct 0167
write /sys/class/android_usb/android0/functions mtp,adb
write /sys/class/android_usb/android0/enable 1
start adbd
setprop sys.usb.state $sys.usb.config
意思是当 sys.usb.config属性值是“mtp,adb ” 时,这个action就会触发,action对应的command会依次执行。
所以如果我通过命令setprop sys.usb.config mtp,adb来设置 sys.usb.config属性=mtp,adb,就会触发以上action,上面的command会使能USB,并打开adb服务。
4)init进入无限循环等待事件处理
a执行execute_one_command()
该操作会依次从action待执行队列取下action,并执行对应的commad。前面的action_for_each_trigger()和queue_builtin_action()只是加入到action待执行队列中,而这里是从队列中顺序取出并执行。
b 执行restart_processes()
对有SVC_RESTARTING标志的service,执行restart_service_if_needed() 操作,该操作会重启service服务。
c通过poll监控property事件和子进程结束事件。