高通8996启动流程-4. lk启动之boot_linux_from_mmc

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的起止地址。
在这里插入图片描述

  1. check_format_bit

  2. get_ffbm

  3. 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

  4. mmc_read(ptn + offset, (uint32_t *) buf, page_size):从boot/recovery分区读取boot_img_hdr到全局变量buf中

  5. 对读取的内容进行基本的合法判断:
    (1)header->MAGIC是否是ANDROID, 如果不是则异常退出
    (2)读取到的header->page_size是否与PAGE_SIZE相等,如果不相等,根据读取到的header->page_size对page_size全局变量重新赋值,重新赋值page_mask

  6. 对读取到的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);

  7. mage_addr = (unsigned char *)target_get_scratch_address()初始化image缓存地址,用于存放从boot分区读取到的image。image_addr这个值是 boot.img 在内存的缓存地址,缓存的地址由 SCRATCH_ADDR 宏指定,这个宏定义在target/msm8916/rules.mk 文件中,我们看到这个地址实际为0x91E00000

  8. memcpy(image_addr, (void *)buf, page_size); 前面说全局变量buf中存放boot_img_hdr,此处将其拷贝到image_addr中,大小为1个page

  9. 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

  10. boot_verifier_init

  11. check_aboot_addr_range_overlap((uint32_t) image_addr, imagesize_actual)
    检查打算存放boot image的内存地址与当前运行的aboot是否有地址空间重叠部分

  12. mmc_read(ptn + offset, (void *)(image_addr + offset), imagesize_actual - page_size)
    从boot/recovery分区读取镜像到image_addr地址(不读取image_header和signature)

  13. mmc_read(ptn + offset, (void *)(image_addr + offset), page_size)
    对于需要验证签名的bootimage,还需要将签名读取到起始地址为image_addr,此处offset为imagesize_actual的位置,也就是将签名读取到image_addr+imagesize_actual的位置

  14. verify_signed_bootimg((uint32_t)image_addr, imagesize_actual);
    对上述签名进行验证

  15. 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处

在这里插入图片描述

  1. 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

  1. 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,虚拟地址与物理地址相同

  2. check_aboot_addr_range_overlap
    检查要拷贝的目标kernel地址和ramdisk地址是否会与aboot重合

  3. 将解压后的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);

  4. dev_tree_validate(table, hdr->page_size, &dt_hdr_size) 验证DTS

  5. dev_tree_get_entry_info(table, &dt_entry)

  6. decompress 如果dtb压缩了,则需要解压缩

  7. check_aboot_addr_range_overlap(hdr->tags_addr, dtb_size) 验证hdr->tags_addr是否会越界到aboot

  8. 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 文件中,我们看到这个地址实际为0x91E00000

Q:bootimg中kernel, ramdisk,dtb的size如何确定?
A:主要是在编译时由mkbootimg工具通过读取输出目录中kernel和ramdisk文件的大小来获取,dtb大小默认为4k,一个page

Q: kernel解压后的应该放到内存的哪个位置? A: 通过boot.img存放地址为起始地址, 在偏移kernel_size +
ramdisk_size + dtb_size大小的位置 其中dtb默认为4K

Q: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.总结

  1. 从boot/recovery分区读取boot.img镜像到image_addr地址
  2. 解压kernel到kernel_start_addr地址处
  3. 重定位kernel ramdisk, dtb
  4. 解析并保存cmd_line
  5. 更新DTS,包括增加memory的起始地址和大小到DTS;将ramdisk起止地址更新到chosen
  6. 启动kernel,启动的同时会向kernel传递dtb地址,dtb的chosen保存了cmdline, 同时保存了ramdisk的起止地址,这样内核起来后会找到cmdline和ramdisk解析和挂载
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值