MTK系统启动流程

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 <trigger> 
          <command> 
          <command> 
    on是Actions的关键字,它表明下面的序列是Actions序列。 


Services 
    Services是有init进程启动的或者重新启动的程序。其格式如下: 
    service <name> <pathname> [ <argument> ] 
          <option> 
          <option> 


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事件和子进程结束事件。

 

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值