深入MTK平台bootloader启动之【 Pre-loader -> Lk】分析笔记

1、bootloader到kernel启动总逻辑流程图

ARM架构中,EL0/EL1是必须实现,EL2/EL3是选配,ELx跟层级对应关系:

EL0 -- app

EL1 -- Linux kernel 、lk

EL2 -- hypervisor(虚拟化)

EL3 -- ARM trust firmware 、pre-loader

若平台未实现EL3(atf),pre-loader直接加载lk:

若平台实现EL3,则需要先加载完ATF再由ATF去加载lk:

bootloader 启动分两个阶段,一个是pre-loader加载lk(u-boot)阶段,另一个是lk加载kernel
阶段。下面跟着流程图简述第一个阶段的加载流程。

1-3:设备上电起来后,跳转到Boot ROM(不是flash)中的boot code中执行把pre-loader加载起到ISRAM,
因为当前DRAM(RAM分SRAM跟DRAM,简单来说SRAM就是cache,DRAM就是普通内存)还没有准备好,所以要
先把pre-loader load到芯片内部的ISRAM(Internal SRAM)中。

4-6:pre-loader初始化好DRAM后就将lk从flash(nand/emmc)中加载到DRAM中运行;

7-8:解压bootimage成ramdisk跟kernel并载入DRAM中,初始化dtb;

9-11:lk跳转到kernl初始化, kernel初始化完成后fork出init进程, 然后拉起ramdisk中的init程序,
进入用户空间初始化,init进程fork出zygote进程..直到整个Android启动完成.
2、从pre-loader到lk(mt6580为例)

Pre-loader主要干的事情就是初始化某些硬件,比如: UART,GPIO,DRAM,TIMER,RTC,PMIC 等等,
建立起最基本的运行环境,最重要的就是初始化DRAM.

时序图: 

 源码流程如下:

./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
init.s 主要干的事情是切换系统到管理模式(svc)(如果平台有实现el3,那么pre-loader运行在el3,
否则运行在el1),禁止irq/fiq,设置stack等, 然后jump到c代码main函数入口。

进入源码分析。

./bootloader/preloader/platform/mt6580/src/core/main.c
 
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 Failed\n", 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执行,

 

     如果实现了EL3情况就要
  复杂一些,需要先跳转到EL3初始化,然后再跳回lk,pre-loader执行在EL3,
  LK执行在EL1)
 
   从log可以类似看到这些信息:
    [BLDR] jump to 0x81E00000
    [BLDR] <0x81E00000>=0xEA000007
    [BLDR] <0x81E00004>=0xEA0056E2
*/

#if CFG_ATF_SUPPORT
    /* 64S3,32S1,32S1 (MTK_ATF_BOOT_OPTION = 0)
	 * re-loader jump to LK directly and then LK jump to kernel directly */
    if ( BOOT_OPT_64S3 == g_smc_boot_opt &&
         BOOT_OPT_32S1 == g_lk_boot_opt &&
         BOOT_OPT_32S1 == g_kernel_boot_opt) {
        print("%s 64S3,32S1,32S1, jump to LK\n", MOD);
        bldr_jump(jump_addr, jump_arg, sizeof(boot_arg_t));
    } else {
        // 如果 el3 使用aarch64实现,则jump到atf.
        print("%s Others, jump to ATF\n", MOD);
        bldr_jump64(jump_addr, jump_arg, sizeof(boot_arg_t));
    }
#else
    bldr_jump(jump_addr, jump_arg, sizeof(boot_arg_t));
#endif

// 如果没有取到jump_addr,则打印错误提示,进入while(1)等待.
error:
    platform_error_handler();
}
main 函数小结:

1、各种硬件初始化(uart、pmic、wdt、timer、mem..);

2、获取系统启动模式等,保存在全局变量中;

3、Security check,跟secro.img相关;

4、如果系统已经实现el3,则进入tz初始化;

5、获取lk加载到DRAM的地址(固定值),然后从ROM中找到lk分区的地址, 如果没找到jump_addr,则 goto error;

6、battery check,如果没有电池就会陷入while(1);

7、jump到lk(如果有实现el3,则会先jump到el3,然后再回到lk)

3、重点函数分析

bldr_load_images

函数主要干的事情就是找到lk分区地址和lk加载到DRAM中的地址, 准备好jump到lk执行,如下源码分析:

static int bldr_load_images(u32 *jump_addr)
{
    int ret = 0;
    blkdev_t *bootdev;
    u32 addr = 0;
    char *name;
    u32 size = 0;
    u32 spare0 = 0;
    u32 spare1 = 0;
 
...
/* 这个地址是一个固定值,可以查到定义在:
   ./bootloader/preloader/platform/mt6580/default.mak:95:
   CFG_UBOOT_MEMADDR := 0x81E00000
   从log中可以看到:
   [BLDR] jump to 0x81E00000
*/
    addr = CFG_UBOOT_MEMADDR;
    
/* 然后去ROM找到lk所在分区地址 */
    ret = bldr_load_part("lk", bootdev, &addr, &size);
    if (ret)
       return ret;
    *jump_addr = addr;
    
}
 
// 这个函数逻辑很简单,就不需要多说了.
int bldr_load_part(char *name, blkdev_t *bdev, u32 *addr, u32 *size)
{
    part_t *part = part_get(name);
 
    if (NULL == part) {
        print("%s %s partition not found\n", MOD, name);
        return -1;
    }
 
    return part_load(bdev, part, addr, 0, size);
}
 
// 真正的load实现是在part_load函数.
int part_load(blkdev_t *bdev, part_t *part, u32 *addr, u32 offset, u32 *size)
{
    int ret;
    img_hdr_t *hdr = (img_hdr_t *)img_hdr_buf;
    part_hdr_t *part_hdr = &hdr->part_hdr;
    gfh_file_info_t *file_info_hdr = &hdr->file_info_hdr;
 
    /* specify the read offset */
    u64 src = part->startblk * bdev->blksz + offset;
    u32 dsize = 0, maddr = 0;
    u32 ms;
 
// 检索分区头是否正确。
    /* retrieve partition header. */
    if (blkdev_read(bdev, src, sizeof(img_hdr_t), (u8*)hdr,0) != 0) {
        print("[%s]bdev(%d) read error (%s)\n", MOD, bdev->type, part->name);
        return -1;
    }
 
    if (part_hdr->info.magic == PART_MAGIC) {
 
        /* load image with partition header */
        part_hdr->info.name[31] = '\0';
 
    /*
        输出分区的各种信息,从log中可以看到:
        [PART] Image with part header
        [PART] name : lk
        [PART] addr : FFFFFFFFh mode : -1
        [PART] size : 337116
        [PART] magic: 58881688h
    */
        print("[%s]Img with part header\n", MOD);
        print("[%s]name:%s\n", MOD, part_hdr->info.name);
        print("[%s]addr:%xh\n", MOD, part_hdr->info.maddr);
        print("[%s]size:%d\n", MOD, part_hdr->info.dsize);
        print("[%s]magic:%xh\n", MOD, part_hdr->info.magic);
 
        maddr = part_hdr->info.maddr;
        dsize = part_hdr->info.dsize;
        src += sizeof(part_hdr_t);
 
        memcpy(part_info + part_num, part_hdr, sizeof(part_hdr_t));
        part_num++;
    } else {
        print("[%s]%s img not exist\n", MOD, part->name);
        return -1;
    }
 
// 如果maddr没有定义,那么就使用前面传入的地址addr.
    if (maddr == PART_HEADER_MEMADDR/*0xffffffff*/)
        maddr = *addr;
 
    if_overlap_with_dram_buffer((u32)maddr, ((u32)maddr + dsize));
 
    ms = get_timer(0);
    if (0 == (ret = blkdev_read(bdev, src, dsize, (u8*)maddr,0)))
        *addr = maddr;
    ms = get_timer(ms);
 
/* 如果一切顺利就会打印出关键信息:
   [PART] load "lk" from 0x0000000001CC0200 (dev) to 0x81E00000 (mem) [SUCCESS]
   [PART] load speed: 25324KB/s, 337116 bytes, 13ms
*/
    print("\n[%s]load \"%s\" from 0x%llx(dev) to 0x%x (mem) [%s]\n", MOD,
        part->name, src, maddr, (ret == 0) ? "SUCCESS" : "FAILED");
 
    if( ms == 0 )
        ms+=1;
 
    print("[%s]load speed:%dKB/s,%d bytes,%dms\n", MOD, ((dsize / ms) * 1000) / 1024, dsize, ms);
 
 
    return ret;
}

bldr_post_process

函数主要干的事情就是从pmic去检查是否有电池存在,如果没有就等待, 如下源码分析,比较简单:

// 就是包了一层而已.
static void bldr_post_process(void)
{
    platform_post_init();
}
 
// 重点是这个函数:
void platform_post_init(void)
{
    /* normal boot to check battery exists or not */
    if (g_boot_mode == NORMAL_BOOT && !hw_check_battery() && usb_accessory_in()) {
...
        pl_charging(1);
        do {
            mdelay(300);
            
            /* 检查电池是否存在, 如果使用电源调试则需要修改此函数逻辑 */
            if (hw_check_battery())
                break;
            /* 喂狗,以免超时被狗咬 */
            platform_wdt_all_kick();
        } while(1);
        /* disable force charging mode */
        pl_charging(0);
    }
 
...
}

Pre-loader 到 Lk的源码分析到这就完成了.

<think>好的,我现在需要回答用户关于pre-loaderLK、kernel和Android初始化是否都保存在UFS的boot分区中的问题。首先,我要确认这些组件在启动流程中的位置以及它们通常存储的位置。 用户之前问过mt_boot文件夹,现在转向UFS的boot区域。我需要明确UFS(通用闪存存储)的结构,以及不同启动阶段组件的位置。 首先,pre-loader通常是联发科芯片设备中的底层引导程序,负责初始化最基本的硬件,比如DRAM和时钟。它可能存放在preloader分区或者boot ROM中,而不是标准的boot分区。但用户提到的是UFS的boot区域,这里可能需要区分不同的存储分区。 然后是LK(Little Kernel),作为引导加载程序,比如用于Android的fastboot模式。LK通常存放在boot分区或者独立的lk分区中,但具体要看设备的分区表。 Kernel(Linux内核)通常确实存放在boot分区,这是Android设备的标准做法,所以用户提到的boot区域应该包含内核。 Android初始化涉及initramfs或init进程,这部分通常包含在boot分区的ramdisk中,所以也是boot分区的一部分。 接下来需要确认是否所有这四个部分都位于同一个UFS的boot分区。可能需要指出pre-loader可能不在其中,而是存放在独立分区或boot ROM,而LK、kernel和初始化相关文件在boot分区。同时,不同厂商可能有不同的分区布局,比如有些设备可能将LK放在recovery分区或者单独的分区。 需要确保回答准确,避免错误,比如pre-loader的位置可能因设备而异。此外,UFS的分区结构可能包括多个启动相关分区,如boot、recovery、lkpreloader等,所以不能一概而论全部在boot分区。 还要考虑用户可能的混淆点,比如将UFS的boot区域等同于整个启动流程的存储位置,而实际上启动流程可能涉及多个分区。需要解释每个组件的典型存储位置,并指出可能的例外情况。 最后,总结时要明确哪些通常存放在boot分区,哪些可能在其他分区,并建议具体设备具体分析,可能需要查看分区表或官方文档。</think>在基于 **UFS(通用闪存存储)** 的 Android 设备中,启动流程涉及多个关键组件,包括 `pre-loader`、`LK`(Little Kernel)、`Kernel` 和 Android 初始化系统。它们的存储位置与设备的分区设计密切相关,但并非全部位于 **UFS 的 `boot` 分区**。以下是对各组件存储位置及作用的详细说明: --- ### **1. 各组件的作用与存储位置** | 组件 | 功能描述 | 典型存储位置 | |-------------------|--------------------------------------------------------------------------|---------------------------------------| | **Pre-loader** | 联发科(MTK)芯片特有的底层引导程序,初始化 DRAM、时钟等基础硬件。 | **`preloader` 分区** 或 Boot ROM | | **LK (Little Kernel)** | 二级引导加载程序(如 Fastboot),负责加载内核并传递启动参数。 | **`lk` 分区** 或 `boot` 分区 | | **Kernel** | Linux 内核,驱动硬件并启动用户空间(Android)。 | **`boot` 分区** 或 `kernel` 分区 | | **Android 初始化** | 包含 `init` 进程、`initramfs`(初始内存文件系统)和启动脚本。 | `boot` 分区的 **ramdisk** 中 | --- ### **2. UFS 的 `boot` 分区内容** - **典型包含**: - **Kernel 镜像**(如 `Image.gz-dtb`)。 - **Ramdisk**(含 `init` 进程、启动脚本、基础驱动模块)。 - **设备树二进制(DTB)**(描述硬件布局)。 - **不包含**: - `pre-loader`(通常位于独立分区或固化在芯片 Boot ROM 中)。 - `LK`(可能存储在 `lk` 分区或与 `boot` 分区合并,因厂商而异)。 --- ### **3. 启动流程与分区交互** 1. **Pre-loader** - 从 **`preloader` 分区** 或芯片内部 ROM 加载,初始化关键硬件。 - 移交控制权给 **LK**。 2. **LK (Little Kernel)** - 从 **`lk` 分区** 加载,验证并启动 `boot` 分区的内核。 - 部分设备将 LK 嵌入 `boot` 分区头部(如高通设备)。 3. **Kernel 与 Android 初始化** - 从 `boot` 分区加载内核和 `initramfs`。 - 内核挂载根文件系统,启动 `init` 进程,完成 Android 用户空间初始化。 --- ### **4. 不同厂商的分区差异** - **联发科(MTK)设备**: - `pre-loader` 独立分区,`LK` 可能集成在 `boot` 分区或 `lk` 分区。 - 分区命名示例:`preloader`、`lk`、`boot`。 - **高通(Qualcomm)设备**: - 使用 `aboot`(类似 LK)作为引导加载程序,通常存储在 `aboot` 分区。 - `boot` 分区包含内核和 ramdisk。 --- ### **5. 关键注意事项** - **分区命名非统一性**: 不同厂商可能对分区命名和布局进行定制(例如华为的 `kernel` 和 `ramdisk` 分拆)。 - **安全启动(Secure Boot)**: `pre-loader` 和 `LK` 可能包含签名验证逻辑,防止未授权修改。 - **调试与备份**: - 通过 `fastboot getvar all` 可查看设备分区表。 - 修改 `boot` 分区需解锁 Bootloader,否则会导致启动失败。 --- ### **6. 示例:UFS 启动相关分区** ``` # 联发科设备典型分区(通过 `ls -l /dev/block/bootdevice/by-name` 查看) preloader --> /dev/block/sda1 lk --> /dev/block/sda2 boot --> /dev/block/sda3 system --> /dev/block/sda4 ``` --- ### **总结** - **`boot` 分区**:主要存储 **Kernel** 和 **Android 初始化文件**(ramdisk)。 - **`pre-loader` 和 `LK`**:通常位于独立分区,不直接归属 `boot` 分区。 - **具体存储位置需结合设备厂商的分区表确认**,可通过刷机工具(如 SP Flash Tool)或终端命令(需 root)查看。 如果需要进一步分析特定设备,请提供分区表或机型信息!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值