android启动流程之preloader--->lk
对于AArch64而言,exception是指cpu的某些异常状态或者一些系统的事件(可能来自外部,也可能来自内部),这些状态或者事件可以导致cpu去执行一些预先设定的,具有更高执行权利的软件(也叫exception handler)。执行exception handler可以进行异常的处理,从而让系统平滑的运行。exception handler执行完毕之后,需要返回发生异常的现场。
关于异常的基本知识
什么是异常
对于AArch64而言,exception是指cpu的某些异常状态或者一些系统的事件(可能来自外部,也可能来自内部),这些状态或者事件可以导致cpu去执行一些预先设定的,具有更高执行权利的软件(也叫exception handler)。执行exception handler可以进行异常的处理,从而让系统平滑的运行。exception handler执行完毕之后,需要返回发生异常的现场。
异常等级(exception level)
AArch64 异常模型由多个异常级别(EL0 - EL3)组成,EL0 和 EL1 具有安全和非安全两种模式对应。 EL2是hypervisor级别,只存在于non-secure 模式。 EL3 是最高优先级,仅在安全模式下存在。简单可以理解为ELx, x越大,等级越高,执行特权就越高
ARM架构中,EL0/EL1是必须实现的,EL2/EL3是选配,ELx跟层级对应关系。
关于el3,el2,el1,el0可以看下面的图进行理解
模式 | |
---|---|
EL0 | normal用户应用程序,非特权执行 |
EL1 | 操作系统内核 |
EL2 | Hypervisor(vm 虚拟化) 对虚拟化的支持,没有Secure state,只有Non-secure state |
EL3 | 底层固件,包括secure monitor 安全状态与非安装状态之间的切换,支持EL0/EL1的Secure 和Non-secure之间的切换 |
之前说到异常发生后系统将切换到具有更高执行权限的状态,在AArch64是通过exception level来实现的。AArch64最多支持EL0~EL3四个exception level, EL0的execution privilege最低,EL3的execution privilege最高,当发生异常的时候,系统的exception会迁移到(route)到更高的exception level或者 维持不变,但是绝不会降低。 此外, 不会有任何的异常会去到EL0。 而AArch32, cpu没有异常等级,而是采用processor mode模式,例如User, FIQ, IRQ, Abort, Undefined, System, 这些不同的mode对应privilege(其他mode)和no-privilege(User mode)。
关于AArch64的exception level比较复杂,一般会有如下几种情况:
- 不支持security state, 不支持虚拟化; AArch64有两个exception level, 分别是: EL0(对应user mode的application), EL1(guest OS);
- 不支持security state, 支持虚拟化; AArch64有三个exception level, 分别是: EL0(对应的user mode的application), EL1(guest OS)和EL2(Hypervisor)
- 支持security state, 支持虚拟化;AArch64有3个exception level, 分别是:EL0(对应trusted service),EL1(guest OS)和EL3(Secure monitor)
- 支持security, 支持虚拟化:AArch64有4个exception level,分别是:(对应trusted service),EL1(trusted OS kernel),EL2(Hypervisor)和EL3(Secure monitor)
典型的Exception Level使用模型
异常级别 | 运行的软件 |
---|---|
EL0 | Application |
EL1 | Linux kernel |
EL2 | Hypervisor(可以理解为上面跑多个虚拟OS) |
EL3 | Secure Monitor(ARM Tursted Firmware) |
异常相关术语
术语 | 说明 |
---|---|
Taking an exception | PE第一次回应一个异常,此时PE state称为taken from, 之后PE状态为taken to |
Returning from exception | 当异常返回指令被提交运行,PE state就是return from exception |
异常级别 | 不同异常级别,异常的优先级不同如EL3的异常高于EL1的异常 |
精准异常 | 找到某条指令,这条指令前的所有指令都执行完毕,这条指令之后的所有指令都未执行(执行的需要回退),这样PE状态就被记录下载,异常处理完成后就可以恢复。除了SError irq之外,其它的都是精准异常 |
同步异常 | (1)异常的产生是和cpu core执行的指令或者试图改变执行权限引起的异常(2)硬件提供给handler的返回地址就是产生异常的那一条指令所在的地址(3)synchronous exception又可以细分成两个类别:a). 一种我们称之为synchronous abort,例如未定义的指令、data abort、prefetch instruction abort、SP未对齐异常,debug exception等等;b). 还有一种是正常指令执行造成的,包括SVC/HVC/SMC指令,这些指令的使命就是产生异常。 |
异步异常 | asynchronous exception基本上可以类似大家平常说的中断,它是毫无预警的,丝毫不考虑cpu core感受的外部事件(需要注意的是:外部并不是表示外设,这里的外部是针对cpu core而言,有些中断是来自SOC的其他HW block,例如GIC,这时候,对于processor或者cpu(指soc)而言,这些事件是内部的),这些事件打断了cpu core对当前软件的执行,因此称之interrupt。interrupt或者说asynchronous exception有下面的特点:(1)异常和CPU执行的指令无关。(2)返回地址是硬件保存下来并提供给handler,以便进行异常返回现场的处理。这个返回地址并非产生异常时的指令根据这个定义IRQ、FIQ和SError interrupt属于asynchronous exception。 |
SError interrupt | SError interrupt是发生了external abort导致的异步异常(或称中断)。external abort来自memory system, 是访问外部memory system产生的异常(当然不是所有的来自memory system的abort都是external abort,例如来自MMU的abort就不是external abort,这里的external是针对processor而非cpu core而言,因此MMU实际上是internal的)。external abort发生在processor通过bus访问memory的时候(可能是直接对某个地址的读或者写,也可能是取指令导致的memory access),processor在bus上发起一次transaction,在这个过程中发生了abort,abort来自processor之外的memory block、device block或者interconnection block,用来告知processor,搞不定了,你自己看着办吧。external abort可以被实现成synchronous exception(precise exception),也可以实现成asynchronous exception(imprecise exception)。如果external abort是asynchronous的,那么它可以通过SError interrupt来通知cpu core |
异常级别切换
在AARCH64状态下,异常级别的切换只能发生在触发了异常,或者异常处理返回过程中,其中:
- 当发生异常时,异常级别要不增加,要不保持不变;
- 当从异常处理返回时,异常级别只能减小或者保持不变。
在发生异常即将进入或者从异常处理返回到另一个异常级别(包括异常级别不变的情况)时,待进入/返回的异常级别称之为目标异常级别。每个异常级别都有一个明确的目标异常级别,这个目标异常级别要么是默认定义的,要么是通过系统寄存器的相应bit定义的(注意没有异常级别的目标异常级别是EL0)
既然涉及到异常级别,那就不得不说一下使用异常级别需要注意的问题了。安全和非安全这是物理隔离的,但是异常级别却是需要进行切换的。比如我们从非安全到安全,是不能直接切换过去的,需要借助el3这个电梯,可以借助这个过去。
在el1上访问某些寄存器的时候,突然系统hard fault,这时就要看aarch64的芯片手册了,看这个寄存器是在那个异常级别下可以访问的。有些寄存器在不对应的异常级别,读为零,写无效。比如GIC的某些寄存器。
参考: ARMV8 datasheet学习笔记4:AArch64系统级体系结构之编程模型(1)-EL/ET/ST - jasonactions - 博客园 (cnblogs.com)
AArch64之异常处理 – Wenhui’s Rotten Pen
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加载到芯片内部的ISRAM(internal SRAM)中。
4-6: pre-loader初始化好DRAM就将lk从flash(nand/emmc) 中加载到DRAM中运行
7-8: 解压boot-image成ramdisk跟kernel并载入DRAM中,初始化dtb
9-11: lk跳转到kernel初始化,kernel初始化完成后fork出init进程,然后拉起ramdisk中的init程序,进入用户空间初始化,init进程fork出zygote进程,直到整个安卓启动完成。
pre-loader主要干的事情即使初始化某些硬件,比如: UART, GPIO, DRAM, TIMER, RTC, PMIC等等,建立起最基本的运行环境,最重要的就是初始化DRAM。
main函数小结:
- 各种硬件初始化(uart, pmic, wdt, timer, mem…)
- 获取系统启动模式等,保存在全局变量中
- Security check, 跟secro.img相关
- 如果系统已经实现el3, 否则进入tz初始化
- 获取lk加载到DRAM得地址(固定值), 然后从ROM中找到lk分区得地址,如果没有找到jump_addr, 则goto error
- battery check: 没有电池就会陷入while(1)
- jump 到lk (如果有实现el3,则会先jump到el3,然后再回到lk)
重点函数分析:
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);
}
...
}
更多推荐
所有评论(0)