1.前言
本文档主要对MSM8996的启动流程进行一个简要的分析,目的在于展现启动流程的概貌,不会对每个细节做很详细的表述,但会对启动流程的关键节点进行重点说明。在lk正常启动时会进入boot_linux_from_mmc。
2. boot_linux_from_mmc
boot_linux_from_mmc主要完成了bootimg读取到缓存,解压kernel,重定位kernel, ramdisk, dtb,并最终启动kernel,启动的同时会向kernel传递dtb地址,dtb的chosen保存了cmdline, 同时保存了ramdisk的起止地址。
-
check_format_bit
-
get_ffbm
-
uhdr = (struct boot_img_hdr *)EMMC_BOOT_IMG_HEADER_ADDR;
此处获取到了boot image头部的信息所在的内存地址,通过调用 memcmp(uhdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE),来判断0x8F6FF000 (EMMC_BOOT_IMG_HEADER_ADDR)是否和 boot.img 头的 MAGIC 值"ANDROID!" 相同。如果相同,则直接按照这个内存地址来启动系统,不再从emmc 中读取,直接启动Linux -
mmc_read(ptn + offset, (uint32_t *) buf, page_size):从boot/recovery分区读取boot_img_hdr到全局变量buf中
-
对读取的内容进行基本的合法判断:
(1)header->MAGIC是否是ANDROID, 如果不是则异常退出
(2)读取到的header->page_size是否与PAGE_SIZE相等,如果不相等,根据读取到的header->page_size对page_size全局变量重新赋值,重新赋值page_mask -
对读取到的image header的kernel大小,ramdisk大小 向上取到PAGE_SIZE的整数倍
kernel_actual = ROUND_TO_PAGE(hdr->kernel_size, page_mask);
ramdisk_actual = ROUND_TO_PAGE(hdr->ramdisk_size, page_mask);
second_actual = ROUND_TO_PAGE(hdr->second_size, page_mask); -
mage_addr = (unsigned char *)target_get_scratch_address()初始化image缓存地址,用于存放从boot分区读取到的image。image_addr这个值是 boot.img 在内存的缓存地址,缓存的地址由 SCRATCH_ADDR 宏指定,这个宏定义在target/msm8916/rules.mk 文件中,我们看到这个地址实际为0x91E00000
-
memcpy(image_addr, (void *)buf, page_size); 前面说全局变量buf中存放boot_img_hdr,此处将其拷贝到image_addr中,大小为1个page
-
imagesize_actual = (page_size + kernel_actual + ramdisk_actual + second_actual + dt_actual);
初始化image大小,page_size为image header大小,kernel, ramdisk, dt大小均从boot image header中获取。dt大小在mkbootimg命令运行时写入,看mkbootimg中注释说是假设是一个page -
boot_verifier_init
-
check_aboot_addr_range_overlap((uint32_t) image_addr, imagesize_actual)
检查打算存放boot image的内存地址与当前运行的aboot是否有地址空间重叠部分 -
mmc_read(ptn + offset, (void *)(image_addr + offset), imagesize_actual - page_size)
从boot/recovery分区读取镜像到image_addr地址(不读取image_header和signature) -
mmc_read(ptn + offset, (void *)(image_addr + offset), page_size)
对于需要验证签名的bootimage,还需要将签名读取到起始地址为image_addr,此处offset为imagesize_actual的位置,也就是将签名读取到image_addr+imagesize_actual的位置 -
verify_signed_bootimg((uint32_t)image_addr, imagesize_actual);
对上述签名进行验证 -
is_gzip_package((unsigned char *)(image_addr + page_size), hdr->kernel_size)
判断内核是否是压缩的,其中page_size为boot image头部大小
decompress((unsigned char *)(image_addr + page_size),
hdr->kernel_size, out_addr, out_avai_len,
&dtb_offset, &out_len)
如果内核是压缩的,则需要对内核进行解压缩,对kernel进行解压缩操作,解压缩后的内核存放到kernel_start_addr=image_addr +imagesize_actual+4k=0x91E00000+imagesize_actual+4k, kernel的大小为hdr->kernel_size
注:imageaddr这个值是boot image 在内存的缓存地址,缓存的地址由 SCRATCH_ADDR 宏指定,这个宏定义在target/msm8916/rules.mk 文件中
到目前为止已经将boot.img读取到缓存地址0x91E00000处,并且将压缩的kernel解压到kernel_start_addr处
- update_ker_tags_rdisk_addr(hdr, IS_ARM64(kptr));
一般情况下kernel地址,dtb地址,ramdisk地址由mkbootimage工具来指定为默认值,如果没有指定默认值则采用platform/msm8996/include/platform/iomap.h中定义的kernel地址,dtb地址,ramdisk地址来更新hdr(boot image header),此处使用了iomap.h中定义的如下:
其中DDR_STRT的地址为0x80000000
-
hdr->kernel_addr = VA((addr_t)(hdr->kernel_addr));
hdr->ramdisk_addr = VA((addr_t)(hdr->ramdisk_addr));
hdr->tags_addr = VA((addr_t)(hdr->tags_addr));
获取虚拟地址,此处由于没有开MMU,虚拟地址与物理地址相同 -
check_aboot_addr_range_overlap
检查要拷贝的目标kernel地址和ramdisk地址是否会与aboot重合 -
将解压后的kernel镜像和ramdisk镜像拷贝到image header所指定的位置,也就是重定位
memmove((void*) hdr->kernel_addr, kernel_start_addr, kernel_size);
memmove((void*) hdr->ramdisk_addr, (char *)(image_addr + page_size + kernel_actual), hdr->ramdisk_size); -
dev_tree_validate(table, hdr->page_size, &dt_hdr_size) 验证DTS
-
dev_tree_get_entry_info(table, &dt_entry)
-
decompress 如果dtb压缩了,则需要解压缩
-
check_aboot_addr_range_overlap(hdr->tags_addr, dtb_size) 验证hdr->tags_addr是否会越界到aboot
-
memmove((void *)hdr->tags_addr, (char *)best_match_dt_addr, dtb_size) 将解压后的dts拷贝到hdr->tags_addr
到目前为止,我们可以看到形成如下的内存布局,当前kernel,dtb,ramdisk分别位于如下红色框内.
至此我们所关心的几个问题:
Q: 磁盘中boot分区的boot.img被读取到内存的哪个位置?
A: image_addr这个值是boot.img 在内存的缓存地址,缓存的地址由 SCRATCH_ADDR宏指定,这个宏定义在target/msm8916/rules.mk 文件中,我们看到这个地址实际为0x91E00000Q:bootimg中kernel, ramdisk,dtb的size如何确定?
A:主要是在编译时由mkbootimg工具通过读取输出目录中kernel和ramdisk文件的大小来获取,dtb大小默认为4k,一个pageQ: kernel解压后的应该放到内存的哪个位置? A: 通过boot.img存放地址为起始地址, 在偏移kernel_size +
ramdisk_size + dtb_size大小的位置 其中dtb默认为4KQ:kernel解压后被重定位到哪个位置?
A:由LK的platform/msm8996/include/platform/iomap.h指定了kernel, ramdisk,
dtb的重定位的位置注:看4.9的内核对于ARM64,编译的时候会将vmlinux拷贝成Image,然后将Image进行gzip压缩为Image.gz。之后通过cat将Image.gz和DTB写入Image.gz-dtb,Image.gz-dtb与ramdisk一起组成了boot.img,此处dtb应该是被与kernel打包l组合在一起
25.boot_linux((void *)hdr->kernel_addr, (void *)hdr->tags_addr, (const char *)hdr->cmdline, board_machtype(), (void *)hdr->ramdisk_addr, hdr->ramdisk_size)
从kernel_addr启动kernel,同时传递了如下几个参数:
kernel地址,dtb地址,cmdline地址,板子类型,ramdisk地址,ramdisk大小
注:此处的tags_addr就是dts地址
3.boot_linux
1.upadate_cmdline
解析当初通过mkbootimg命令传入的—cmdline参数,更新到final_cmdline中。命令mkbootimg通过—cmdline参数传递的值会被保存到boot.img的头部,此处就是通过解析boot.img的头部来更新final_cmdline
2.update_device_tree
主要更新了如下内容:
(1)更新了memory的起始地址和大小到DTS;
(2)将ramdisk起止地址更新到chosen
3.scm_elexec_call
调用执行Linux内核,此处可以看到,通过scm_arg传递了如下的信息:
(1)kernel地址
(2)dts地址, dtb中的chosen中保存了cmdline以及ramdisk的起止地址
4.通过SMC调用会进入到安全世界QSEE,并由安全世界引导kernel执行
注:trustzone可以理解为安全世界,它跑的代码为QSEE对标ARM的arm trust firmware
4.总结
- 从boot/recovery分区读取boot.img镜像到image_addr地址
- 解压kernel到kernel_start_addr地址处
- 重定位kernel ramdisk, dtb
- 解析并保存cmd_line
- 更新DTS,包括增加memory的起始地址和大小到DTS;将ramdisk起止地址更新到chosen
- 启动kernel,启动的同时会向kernel传递dtb地址,dtb的chosen保存了cmdline, 同时保存了ramdisk的起止地址,这样内核起来后会找到cmdline和ramdisk解析和挂载