本编文章的内容主要是分析 boot/recovery 的启动过程,其中的 boot 就是 android 的kernel, 是整个 android 系统的核心。本文的分析是紧接着 aboot_init 的分析内容的,只是因为其重要性才单独成为一章。
以下是前两篇文章回顾:
高通(Qualcomm)LK源码深度分析
高通(Qualcomm)LK源码深度分析(二)
recovery boot & normal boot
recovery
和 normal
使用的是同一套加载流程,所以放在一起分析。在开始分析加载过程之前,先从aboot_init
进入加载过程前的代码入手:
if (target_is_emmc_boot()){if(emmc_recovery_init())dprintf(ALWAYS,"error in emmc_recovery_init\n");if(target_use_signed_kernel()){if((device.is_unlocked) || (device.is_tampered)){ #ifdef TZ_TAMPER_FUSEset_tamper_fuse_cmd(); #endif #if USE_PCOM_SECBOOTset_tamper_flag(device.is_tampered); #endif}}boot_linux_from_mmc();}
由于 msm8916
并没有定义 TZ_TAMPER_FUSE
宏和 USE_PCOM_SECBOOT
宏,所以进入boot_linux_from_mmc
前只有emmc_recovery_init
步骤需要进行。emmc_recovery_init
函数位于target/msm8916/init.c
文件中,只是_emmc_recovery_init
函数的转接层,本身没有任何功能。_emmc_recovery_init
函数位于app/aboot/recovery.c
文件中。_emmc_recovery_init
的代码逻辑并不复杂,但是具有明显的分析,所以依据代码逻辑分块分为以下 2 个部分来分析:
加载 recovery 命令这部分的主要功能是从 emmc
中获取指定的 recovery
预处理命令,为后面的解析和处理提供条件。
int _emmc_recovery_init(void) {int update_status = 0;struct recovery_message *msg;uint32_t block_size = 0;block_size = mmc_get_device_blocksize(); // get recovery message msg = (struct recovery_message *)memalign(CACHE_LINE, block_size);ASSERT(msg);if(emmc_get_recovery_msg(msg)){if(msg)free(msg);return -1;}msg->command[sizeof(msg->command)-1] = '\0'; //Ensure termination if (msg->command[0] != 0 && msg->command[0] != 255) {dprintf(INFO,"Recovery command: %d %s\n",sizeof(msg->command), msg->command);} //... return 0; }
整个加载 recovery 命令的过程比较重要的结构是 recovery_message
, 用作存储读取到的 recovery
命令,它的结构如下:
/* Recovery Message */
struct recovery_message {char command[32];char status[32];char recovery[1024]; };
通过 emmc_get_recovery_msg
从 misc
分区读取到 recovery
命令就通过这个结构体向后传递。
处理 recovery 命令这个部分主要的功能是处理一些需要在启动前处理的 recovery
命令:
int _emmc_recovery_init(void)
{
//..if (!strcmp(msg->command, "boot-recovery")) {boot_into_recovery = 1;}if (!strcmp("update-radio",msg->command)){
/* We're now here due to radio update, so check for update status */int ret = get_boot_info_apps(UPDATE_STATUS, (unsigned int *) &update_status);if(!ret && (update_status & 0x01)){dprintf(INFO,"radio update success\n");strlcpy(msg->status, "OKAY", sizeof(msg->status));}else{dprintf(INFO,"radio update failed\n");strlcpy(msg->status, "failed-update", sizeof(msg->status));}boot_into_recovery = 1; // Boot in recovery mode}if (!strcmp("reset-device-info",msg->command)){reset_device_info();}if (!strcmp("root-detect",msg->command)){set_device_root();}elsegoto out;// do nothingstrlcpy(msg->command, "", sizeof(msg->command)); // clearing recovery commandemmc_set_recovery_msg(msg); // send recovery messageout:if(msg)free(msg);return 0;
}
boot-recovery
这条命令处理逻辑是最简单的,只是将全局变量 boot_into_recovery
设置为 1, 这个变量在后面的加载部分会用到。update-readio
这条命令是检查基带升级是否成功,根据状态设置recovery_message.status
, 然后设置boot_into_recovery
为 1。reset-device-info
根据 abootinit init 部分 的分析,我们知道 deviceinfo 的数据结构,这里是重设device_info.is_tampered
为 0, 并写入emmc
中。root-detect
这条命令正好和reset-device-info
相反,这里会设置device_info.is_tampered
为 1, 也就是说device_info.is_tampered
是手机是否root
的标志位。
当以上 4 条命令任意一条执行后,就会清理掉 recovery_message
并重新协会 misc
分区。
需要预处理的 recovery_message
处理完成后,就到了使用 boot_linux_from_mmc
加载系统的部分。boot_linux_from_mmc
函数位于app/aboot/aboot.c
文件中,代码流程较长,同样使用分块的方法来分析:
启动模式
检测 读取 boot_img_hdr
缓存并验证镜像 解压释放 kernel
和ramdisk
解压释放device tree
调用boot_linux
启动系统
启动模式
检测
启动模式
检测这一部分代码的作用是检测当前的启动将要进入的模式,然后进行相应设置。msm8916
检测以下 3 种启动模式:
boot mode |
---|
ffbm(fast factory boot mode) |
normal mode |
recovery mode |
检测部分的代码如下:
int boot_linux_from_mmc(void) {struct boot_img_hdr *hdr = (void*) buf;struct boot_img_hdr *uhdr;unsigned offset = 0;int rcode;unsigned long long ptn = 0;int index = INVALID_PTN;unsigned char *image_addr = 0;unsigned kernel_actual;unsigned ramdisk_actual;unsigned imagesize_actual;unsigned second_actual = 0;unsigned int dtb_size = 0;unsigned int out_len = 0;unsigned int out_avai_len = 0;unsigned char *out_addr = NULL;uint32_t dtb_offset = 0;unsigned char *kernel_start_addr = NULL;unsigned int kernel_size = 0;int rc;#if DEVICE_TREEstruct dt_table *table;struct dt_entry dt_entry;unsigned dt_table_offset;uint32_t dt_actual;uint32_t dt_hdr_size;unsigned char *best_match_dt_addr = NULL;
#endifstruct kernel64_hdr *kptr = NULL;if (check_format_bit())boot_into_recovery = 1;if (!boot_into_recovery) {memset(ffbm_mode_string, '\0', sizeof(ffbm_mode_string));rcode = get_ffbm(ffbm_mode_string, sizeof(ffbm_mode_string));if (rcode <= 0) {boot_into_ffbm = false;if (rcode < 0)dprintf(CRITICAL,"failed to get ffbm cookie");} elseboot_into_ffbm = true;} elseboot_into_ffbm = false;uhdr = (struct boot_img_hdr *)EMMC_BOOT_IMG_HEADER_ADDR;if (!memcmp(uhdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)) {dprintf(INFO, "Unified boot method!\n");hdr = uhdr;goto unified_boot;}
//...
}
通过 check_format_bit
检查是否进入 recovery
, check_format_bit
的代码比较简单,其代码如下:
static bool check_format_bit()
{bool ret = false;int index;uint64_t offset;struct boot_selection_info *in = NULL;char *buf = NULL;index = partition_get_index("bootselect");if (index == INVALID_PTN){dprintf(INFO, "Unable to locate /bootselect partition\n");return ret;}offset = partition_get_offset(index);if(!offset){dprintf(INFO, "partition /bootselect doesn't exist\n");return ret;}buf = (char *) memalign(CACHE_LINE, ROUNDUP(page_size, CACHE_LINE));ASSERT(buf);if (mmc_read(offset, (uint32_t *)buf, page_size)){dprintf(INFO, "mmc read failure /bootselect %d\n", page_size);free(buf);return ret;}in = (struct boot_selection_info *) buf;if ((in->signature == BOOTSELECT_SIGNATURE) &&(in->version == BOOTSELECT_VERSION)) {if ((in->state_info & BOOTSELECT_FORMAT) &&!(in->state_info & BOOTSELECT_FACTORY))ret = true;} else {dprintf(CRITICAL, "Signature: 0x%08x or version: 0x%08x mismatched of /bootselect\n",in->signature, in->version);ASSERT(0);}free(buf);return ret;
}
check_format_bit
唯一的作用就是读取 bootselect
分区的信息,然后存放到 boot_selection_info
结构体,其结构如下:
/* bootselect partition format structure */ struct boot_selection_info {uint32_t signature; // Contains value BOOTSELECT_SIGNATURE defined aboveuint32_t version;uint32_t boot_partition_selection; // Decodes which partitions to boot: 0-Windows,1-Androiduint32_t state_info; // Contains factory and format bit as definded above };
msm8916
中 boot_selection_info
满足以下条件则或进入 recovery
:
if (in->signature == ('B' | ('S' << 8) | ('e' << 16) | ('l' << 24)) &&(in->version == 0x00010001)) {if ((in->state_info & (1 << 31)) &&!(in->state_info & (1 << 30)))boot_in_recovery = true;}
如果满足条件,则设置全局标志位 boot_into_recovery
为 true。
通过 get_ffbm
检查是否进入 ffbm
1 模式,get_ffbm
所完成的任务很简单,只是读取misc
分区,并判断内容是否为ffbm-
开头,如果是就将读取到的信息保存到全局变量ffbm_mode_string
中,并且设置全局变量boot_into_ffbm
为 true。 现在会检查内存固定位置0x8F6FF000
是否和 boot.img 的 MAGIC 值"ANDROID!"
相同,如果相同,则直接按照这个内存地址来启动系统,不再从emmc
中读取。
启动模式
检测完成后,除了直接从内存启动的方式以外,其他方式都需要将需要启动的 image
从 emmc
中读取并加载到内存中。
读取 boot_img_hdr
normal
和 recovery
的 image
在结构上是相同的,所以可以使用同一套流程加载并启动。这一部分的内容就是从emmc
读取boot_img_hdr
结构,这个结构是image
头部结构,包含基础的加载信息。
int boot_linux_from_mmc(void) {
//...if (!boot_into_recovery) {index = partition_get_index("boot");ptn = partition_get_offset(index);if(ptn == 0) {dprintf(CRITICAL, "ERROR: No boot partition found\n");return -1;}}else {index = partition_get_index("recovery");ptn = partition_get_offset(index);if(ptn == 0) {dprintf(CRITICAL, "ERROR: No recovery partition found\n");return -1;}} /* Set Lun for boot & recovery partitions */mmc_set_lun(partition_get_lun(index));if (mmc_read(ptn + offset, (uint32_t *) buf, page_size)) {dprintf(CRITICAL, "ERROR: Cannot read boot image header\n");return -1;}if (memcmp(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)) {dprintf(CRITICAL, "ERROR: Invalid boot image header\n");return -1;}if (hdr->page_size && (hdr->page_size != page_size)) {if (hdr->page_size > BOOT_IMG_MAX_PAGE_SIZE) {dprintf(CRITICAL, "ERROR: Invalid page size\n");return -1;}page_size = hdr->page_size;page_mask = page_size - 1;} /* ensure commandline is terminated */hdr->cmdline[BOOT_ARGS_SIZE-1] = 0;kernel_actual = ROUND_TO_PAGE(hdr->kernel_size, page_mask);ramdisk_actual = ROUND_TO_PAGE(hdr->ramdisk_size, page_mask);image_addr = (unsigned char *)target_get_scratch_address();#if DEVICE_TREEdt_actual = ROUND_TO_PAGE(hdr->dt_size, page_mask);imagesize_actual = (page_size + kernel_actual + ramdisk_actual + dt_actual);
#elseimagesize_actual = (page_size + kernel_actual + ramdisk_actual);
#endif
//...
}
根据 启动模式
获取需要读取的分区偏移。其中 normal
存储在 boot
分区,recovery
存储在recovery
分区。
读取 boot_img_hdr
, 其结构如下:
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 dt_size; /* device_tree in bytes */unsigned unused; /* 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_img_hdr
合法性检查:
bootimghdr.magic 是否等于 “ANDROID!” bootimghdr.pagesize 是否大于 4096
根据 boot_img_hdr
初始化两个重要的变量:
imageaddr这个值是
image
在内存的缓存地址,缓存的地址由SCRATCH_ADDR
宏指定,这个宏定义在target/msm8916/rules.mk
文件中。在msm8916
平台SCRATCH_ADDR
为0x90000000
。 imagesizeactual这个值是image
加载到内存所需要的内存大小,在msm8916
平台计算方法如下:imagesizeactual = 分页大小 + kernel 大小 + ramdisk 大小 + 设备树大小
当 image_addr
和 imagesize_actual
确定后就可以进行缓存 image
并验证的步骤。
缓存并验证镜像
这一部分代码的作用就是将 image
从 emmc
加载到内存中的image_addr
位置,并且验证image
是否合法。
int boot_linux_from_mmc(void) {struct boot_img_hdr *hdr = (void*) buf;struct boot_img_hdr *uhdr;unsigned offset = 0;int rcode;unsigned long long ptn = 0;int index = INVALID_PTN;unsigned char *image_addr = 0;unsigned kernel_actual;unsigned ramdisk_actual;unsigned imagesize_actual;unsigned second_actual = 0;unsigned int dtb_size = 0;unsigned int out_len = 0;unsigned int out_avai_len = 0;unsigned char *out_addr = NULL;uint32_t dtb_offset = 0;unsigned char *kernel_start_addr = NULL;unsigned int kernel_size = 0;int rc;#if DEVICE_TREEstruct dt_table *table;struct dt_entry dt_entry;unsigned dt_table_offset;uint32_t dt_actual;uint32_t dt_hdr_size;unsigned char *best_match_dt_addr = NULL;
#endifstruct kernel64_hdr *kptr = NULL; //...#if VERIFIED_BOOTboot_verifier_init();
#endifif (check_aboot_addr_range_overlap((uint32_t) image_addr, imagesize_actual)){dprintf(CRITICAL, "Boot image buffer address overlaps with aboot addresses.\n");return -1;} /* * Update loading flow of bootimage to support compressed/uncompressed * bootimage on both 64bit and 32bit platform. * 1. Load bootimage from emmc partition onto DDR. * 2. Check if bootimage is gzip format. If yes, decompress compressed kernel * 3. Check kernel header and update kernel load addr for 64bit and 32bit * platform accordingly. * 4. Sanity Check on kernel_addr and ramdisk_addr and copy data. */ dprintf(INFO, "Loading (%s) image (%d): start\n",(!boot_into_recovery ? "boot" : "recovery"),imagesize_actual);bs_set_timestamp(BS_KERNEL_LOAD_START); /* Read image without signature */if (mmc_read(ptn + offset, (void *)image_addr, imagesize_actual)){dprintf(CRITICAL, "ERROR: Cannot read boot image\n");return -1;}dprintf(INFO, "Loading (%s) image (%d): done\n",(!boot_into_recovery ? "boot" : "recovery"),imagesize_actual);bs_set_timestamp(BS_KERNEL_LOAD_DONE); /* Authenticate Kernel */dprintf(INFO, "use_signed_kernel=%d, is_unlocked=%d, is_tampered=%d.\n",(int) target_use_signed_kernel(),device.is_unlocked,device.is_tampered); /* Change the condition a little bit to include the test framework support. * We would never reach this point if device is in fastboot mode, even if we did * that means we are in test mode, so execute kernel authentication part for the * tests */if((target_use_signed_kernel() && (!device.is_unlocked)) || boot_into_fastboot){offset = imagesize_actual;if (check_aboot_addr_range_overlap((uint32_t)image_addr + offset, page_size)){dprintf(CRITICAL, "Signature read buffer address overlaps with aboot addresses.\n");return -1;} /* Read signature */if(mmc_read(ptn + offset, (void *)(image_addr + offset), page_size)){dprintf(CRITICAL, "ERROR: Cannot read boot image signature\n");return -1;}verify_signed_bootimg((uint32_t)image_addr, imagesize_actual); /* The purpose of our test is done here */if (boot_into_fastboot && auth_kernel_img)return 0;} else {second_actual = ROUND_TO_PAGE(hdr->second_size, page_mask);#ifdef TZ_SAVE_KERNEL_HASHaboot_save_boot_hash_mmc((uint32_t) image_addr, imagesize_actual);#endif /* TZ_SAVE_KERNEL_HASH */#ifdef MDTP_SUPPORT{
/* Verify MDTP lock. * For boot & recovery partitions, MDTP will use boot_verifier APIs, * since verification was skipped in aboot. The signature is not part of the loaded image. */mdtp_ext_partition_verification_t ext_partition;ext_partition.partition = boot_into_recovery ? MDTP_PARTITION_RECOVERY : MDTP_PARTITION_BOOT;ext_partition.integrity_state = MDTP_PARTITION_STATE_UNSET;ext_partition.page_size = page_size;ext_partition.image_addr = (uint32)image_addr;ext_partition.image_size = imagesize_actual;ext_partition.sig_avail = FALSE;mdtp_fwlock_verify_lock(&ext_partition);}
#endif /* MDTP_SUPPORT */} //...
}
初始化对 boot/recovery 的验证, boot_verifier_init
的代码如下:
void boot_verifier_init() {uint32_t boot_state;
/* Check if device unlock */if(device.is_unlocked){boot_verify_send_event(DEV_UNLOCK);boot_verify_print_state();dprintf(CRITICAL, "Device is unlocked! Skipping verification...\n");return;}else{boot_verify_send_event(BOOT_INIT);} /* Initialize keystore */boot_state = boot_verify_keystore_init();if(boot_state == YELLOW){boot_verify_print_state();dprintf(CRITICAL, "Keystore verification failed! Continuing anyways...\n");}
}
如果手机已经解锁 bootloader 则不会进行验证,而是将 boot_state
设置为 ORANGE 状态,在 android 中存在以下几种启动状态2:
green yellow orange red
然后会在 boot_verify_keystore_init
函数中读取两个 key, oem key 和 user key:
oem key 会编译到 lk 代码中,其位置在
platform/msm_shared/include/oem_keystore.h
文件中,作用是为了验证 user key。 user key 存储在 keystore 分区中,作用验证 boot.img。
使用 emmc_read
读取 boot/recovery 到指定的内存地址,由于 boot/recovery 的地址在 bootloader 的高地址处,数据往低地址写可能覆盖 bootloader,所以在读取 boot/recovery 之前,会使用check_aboot_addr_range_overlap
检查将加载到内存的 boot/recovery 是否会覆盖到 aboot 的地址。
读取位于 boot/recovery 尾部的签名,并通过 verify_signed_bootimg
来验证签名是否能够匹配,通过这里可以检查出 boot/recovery 是否被修改,和 APK 签名的作用类似。verify_signed_bootimg
中会调用boot_verify_image
来进行签名验证,但是会根据启动模式的不同传入不同的参数:
if(boot_into_recovery){ret = boot_verify_image((unsigned char *)bootimg_addr,bootimg_size, "/recovery");}else{ret = boot_verify_image((unsigned char *)bootimg_addr,bootimg_size, "/boot");}
boot_verify_image
函数位于 platform/msm_shared/boot_verifier.c
文件中,这个函数的主要作用是是将 boot/recovery 的签名数据转化为VERIFIED_BOOT_SIG *
的结构。
/** * AndroidVerifiedBootSignature DEFINITIONS ::= * BEGIN * FormatVersion ::= INTEGER * AlgorithmIdentifier ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER, * parameters ANY DEFINED BY algorithm OPTIONAL * } * AuthenticatedAttributes ::= SEQUENCE {
* target CHARACTER STRING, * length INTEGER * } * Signature ::= OCTET STRING * END */typedef struct auth_attr_st
{ASN1_PRINTABLESTRING *target;ASN1_INTEGER *len;
}AUTH_ATTR;typedef struct verif_boot_sig_st
{ASN1_INTEGER *version;X509 *certificate;X509_ALGOR *algor;AUTH_ATTR *auth_attr;ASN1_OCTET_STRING *sig;
}VERIFIED_BOOT_SIG;
其中的大多数成员都是 openssl 中的类型,在这里不详细叙述。签名转换完成后就通过 verify_image_with_sig
来验证签名,其中比较重要的参数如下:
char* pname, 即将要验证的分区名称 VERIFIEDBOOTSIG *sig, 分区所带的签名 KEYSTORE *ks, 验证所使用的密钥
static bool verify_image_with_sig(unsigned char* img_addr, uint32_t img_size,char *pname, VERIFIED_BOOT_SIG *sig, KEYSTORE *ks) {bool ret = false;uint32_t len;int shift_bytes;RSA *rsa = NULL;bool keystore_verification = false;int attr = 0;if(!strcmp(pname, "keystore"))keystore_verification = true; /* Verify target name */if(strncmp((char*)(sig->auth_attr->target->data), pname,sig->auth_attr->target->length) ||(strlen(pname) != (unsigned long) sig->auth_attr->target->length)){dprintf(CRITICAL,"boot_verifier: verification failure due to target name mismatch\n");goto verify_image_with_sig_error;}
/* Read image size from signature *//* A len = 0xAABBCC (represented by 3 octets) would be stored in len->data as 0X00CCBBAA and len->length as 3(octets). To read len we need to left shift data to number of missing octets and then change it to host long */len = *((uint32_t*)sig->auth_attr->len->data);shift_bytes = sizeof(uint32_t) - sig->auth_attr->len->length;if(shift_bytes > 0) {len = len << (shift_bytes*8);}len = ntohl(len); /* Verify image size*/if(len != img_size){dprintf(CRITICAL,"boot_verifier: image length is different. (%d vs %d)\n",len, img_size);goto verify_image_with_sig_error;} /* append attribute to image */if(!keystore_verification){
// verifying a non keystore partitionattr = add_attribute_to_img((unsigned char*)(img_addr + img_size),sig->auth_attr);if (img_size > (UINT_MAX - attr)){dprintf(CRITICAL,"Interger overflow detected\n");ASSERT(0);}else img_size += attr;} /* compare SHA256SUM of image with value in signature */if(ks != NULL)rsa = ks->mykeybag->mykey->key_material;ret = boot_verify_compare_sha256(img_addr, img_size,(unsigned char*)sig->sig->data, rsa);if(!ret){dprintf(CRITICAL,"boot_verifier: Image verification failed.\n");}verify_image_with_sig_error:return ret;
}
整个验证过程分为以下几个部分:
签名对应的分区是否正确,签名中携带的分区信息为以下两个成员:
分区名称:sig->authattr->target->data 名称长度:sig->authattr->target->length
检查 boot/recovery 的大小是否和签名中存储的大小信息相等,大小信息存储在以下两个成员中:
大小信息:sig->authattr->len->data 数据长度:sig->authattr->len->length
这里需要注意的是 data 是按网络字节的顺序存储,例如 len 原值为 0xAABBCC 则 data 中实际存储的值为 0x00CCBBAA。而 len->length 的作用就是指明这个 data 占了多少个字节,所以转换为 unsigned int 的算法如下。
len = *((uint32_t*)sig->auth_attr->len->data);
shift_bytes = sizeof(uint32_t) - sig->auth_attr->len->length;
if(shift_bytes > 0) {len = len << (shift_bytes*8);}
len = ntohl(len);
最后一步就是比对 SHA256 的值是否正确,整个过程如下:
从 keystore 中获取 rsa 公钥,ks->mykeybag->mykey->keymaterial。 使用 rsa 解密签名中携带的 SHA256 值,sig->sig->data。 计算传入的 boot/recovery 的 SHA256 hash 值。 将解密后的 hash 和解密前的 hash 进行对比,如果一致则签名验证通过。
解压释放 kernel
和 ramdisk
经过上面部分的加载和验证,需要 lk 启动的 boot/recovery 镜像已经加载到了内存的缓冲区中,但是现在还是完整的一个整体,并没有分开加载。下面的代码就是对每一个部分的代码和数据进行分开加载,然后才能进行系统启动的操作。
int boot_linux_from_mmc(void) {
//.../* * Check if the kernel image is a gzip package. If yes, need to decompress it. * If not, continue booting. */if (is_gzip_package((unsigned char *)(image_addr + page_size), hdr->kernel_size)){out_addr = (unsigned char *)(image_addr + imagesize_actual + page_size);out_avai_len = target_get_max_flash_size() - imagesize_actual - page_size;dprintf(INFO, "decompressing kernel image: start\n");rc = decompress((unsigned char *)(image_addr + page_size),hdr->kernel_size, out_addr, out_avai_len,&dtb_offset, &out_len);if (rc){dprintf(CRITICAL, "decompressing kernel image failed!!!\n");ASSERT(0);}dprintf(INFO, "decompressing kernel image: done\n");kptr = (struct kernel64_hdr *)out_addr;kernel_start_addr = out_addr;kernel_size = out_len;} else {kptr = (struct kernel64_hdr *)(image_addr + page_size);kernel_start_addr = (unsigned char *)(image_addr + page_size);kernel_size = hdr->kernel_size;}
/* * Update the kernel/ramdisk/tags address if the boot image header * has default values, these default values come from mkbootimg when * the boot image is flashed using fastboot flash:raw */update_ker_tags_rdisk_addr(hdr, IS_ARM64(kptr)); /* Get virtual addresses since the hdr saves physical addresses. */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));kernel_size = ROUND_TO_PAGE(kernel_size, page_mask);
/* Check if the addresses in the header are valid. */if (check_aboot_addr_range_overlap(hdr->kernel_addr, kernel_size) ||check_aboot_addr_range_overlap(hdr->ramdisk_addr, ramdisk_actual)){dprintf(CRITICAL, "kernel/ramdisk addresses overlap with aboot addresses.\n");return -1;}#ifndef DEVICE_TREEif (check_aboot_addr_range_overlap(hdr->tags_addr, MAX_TAGS_SIZE)){dprintf(CRITICAL, "Tags addresses overlap with aboot addresses.\n");return -1;}
#endif
/* Move kernel, ramdisk and device tree to correct address */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); //...
}
整个加载过程如下:
由于 emmc 存储空间有限,所以有的时候 kernel 是压缩保存在 emmc 中的,所以需要先判断是否需要解压,由于需要解压的情况较多,所以先分析此过程。调用is_gzip_package
检查 kernel block 是否压缩,这个函数在lib/zlib_inflate/decompress.c
文件中,它的实现如下:
/* check if the input "buf" file was a gzip package. * Return true if the input "buf" is a gzip package. */
int is_gzip_package(unsigned char *buf, unsigned int len) {if (len < 10 || !buf || buf[0] != 0x1f ||buf[1] != 0x8b || buf[2] != 0x08){return false;}return true;
}
为压缩包的条件非常简单,只有以下两个:
长度不小于 10 MAGIC 为 0x1F8B08
只要满足这两个条件即判定为压缩包
设置解压后数据的存储位置和大小,分别为以下两个值:address = imgbufferaddress + imgsize + pagesizesize = 0×10000000 – (imgsize + pagesize)
调用 decompress
函数解压 kernel, 这个函数实现在 lib/zlib_inflate/decompress.c
文件中:
/* decompress gzip file "in_buf", return 0 if decompressed successful, * return -1 if decompressed failed. * in_buf - input gzip file * in_len - input the length file * out_buf - output the decompressed data * out_buf_len - the available length of out_buf * pos - position of the end of gzip file * out_len - the length of decompressed data */
int decompress(unsigned char *in_buf, unsigned int in_len,unsigned char *out_buf,unsigned int out_buf_len,unsigned int *pos,unsigned int *out_len) {struct z_stream_s *stream;int rc = -1;int i;if (in_len < GZIP_HEADER_LEN) {dprintf(INFO, "the input data is not a gzip package.\n");return rc;}if (out_buf_len < in_len) {dprintf(INFO, "the avaiable length of out_buf is not enough.\n");return rc;}stream = malloc(sizeof(*stream));if (stream == NULL) {dprintf(INFO, "allocating z_stream failed.\n");return rc;}stream->zalloc = zlib_alloc;stream->zfree = zlib_free;stream->next_out = out_buf;stream->avail_out = out_buf_len; /* skip over gzip header */stream->next_in = in_buf + GZIP_HEADER_LEN;stream->avail_in = out_buf_len - GZIP_HEADER_LEN;
/* skip over asciz filename */if (in_buf[3] & 0x8) {for (i = 0; i < GZIP_FILENAME_LIMIT && *stream->next_in++; i++) {if (stream->avail_in == 0) {dprintf(INFO, "header error\n");goto gunzip_end;}--stream->avail_in;}}rc = inflateInit2(stream, -MAX_WBITS);if (rc != Z_OK) {dprintf(INFO, "inflateInit2 failed!\n");goto gunzip_end;}rc = inflate(stream, 0);
/* Z_STREAM_END is "we unpacked it all" */if (rc == Z_STREAM_END) {rc = 0;} else if (rc != Z_OK) {dprintf(INFO, "uncompression error \n");rc = -1;}inflateEnd(stream);if (pos)
/* alculation the length of the compressed package */*pos = stream->next_in - in_buf + 8;if (out_len)*out_len = stream->total_out;gunzip_end:free(stream);return rc; /* returns 0 if decompressed successful */
}
这个过程是标准的 gzip 解压,这里就详细分析,都是直接调用 zlib 的接口实现。
保存解压后的 kernel 头地址和大小,这个涉及到一个比较重要的结构体 kernel64_hdr
:
struct kernel64_hdr
{uint32_t insn;uint32_t res1;uint64_t text_offset;uint64_t res2;uint64_t res3;uint64_t res4;uint64_t res5;uint64_t res6;uint32_t magic_64;uint32_t res7;
};
这个结构就是 kernel block 的头部结构,定义在 app/aboot/bootimg.h
文件中。
检查 kernel 和 ramdisk 是否会越界覆盖到 bootloader, 同样是通过 check_aboot_addr_range_overlap
完成。 将 kernel 和 ramdisk 拷贝到boot_img_hdr
指定的加载地址中。
解压释放 device tree
接下来就需要加载 device tree 到内存,由于存在两种情况:
在 boot_img_hdr
中指定了 dtsize 没有指定 dtsize
两种情况分开分析。
指定了 dtsize 如何加载 device tree。
首先需要明确 device tree 在 image 中的位置,其位置计算如下:
dt_table_offset = ((uint32_t)image_addr + page_size + kernel_actual + ramdisk_actual + second_actual);
table = (struct dt_table*) dt_table_offset;
这里涉及到 dttable 结构体,其定义在 platform/msm_shared/include/dev_tree.h
文件中,结构如下:
struct dt_table
{uint32_t magic;uint32_t version;uint32_t num_entries;
};
验证 device tree block 的数据是否合法,调用 dev_tree_validate
函数来确定,其定义在 platform/msm_shared/dev_tree.c
文件中,实现如下:
/* Returns 0 if the device tree is valid. */
int dev_tree_validate(struct dt_table *table, unsigned int page_size, uint32_t *dt_hdr_size)
{int dt_entry_size;uint64_t hdr_size; /* Validate the device tree table header */if(table->magic != DEV_TREE_MAGIC) {dprintf(CRITICAL, "ERROR: Bad magic in device tree table \n");return -1;}if (table->version == DEV_TREE_VERSION_V1) {dt_entry_size = sizeof(struct dt_entry_v1);} else if (table->version == DEV_TREE_VERSION_V2) {dt_entry_size = sizeof(struct dt_entry_v2);} else if (table->version == DEV_TREE_VERSION_V3) {dt_entry_size = sizeof(struct dt_entry);} else {dprintf(CRITICAL, "ERROR: Unsupported version (%d) in DT table \n",table->version);return -1;}hdr_size = (uint64_t)table->num_entries * dt_entry_size + DEV_TREE_HEADER_SIZE; /* Roundup to page_size. */hdr_size = ROUNDUP(hdr_size, page_size);if (hdr_size > UINT_MAX)return -1;else*dt_hdr_size = hdr_size & UINT_MAX;return 0;
}
第一点需要验证的就是 MAGIC 是否为正确,正确的 device tree magic 如下:
#define DEV_TREE_MAGIC 0x54444351 /* "QCDT" */
第二步是检查 device tree 格式的版本是否支持,目前的 lk 支持以下 3 个版本:
#define DEV_TREE_VERSION_V1 1
#define DEV_TREE_VERSION_V2 2
#define DEV_TREE_VERSION_V3 3
每个版本对应不同的 dt_entry
结构体,按照上面的版本顺序,分别是以下 3 个结构体:
struct dt_entry_v1
{uint32_t platform_id;uint32_t variant_id;uint32_t soc_rev;uint32_t offset;uint32_t size;
};struct dt_entry_v2
{uint32_t platform_id;uint32_t variant_id;uint32_t board_hw_subtype;uint32_t soc_rev;uint32_t offset;uint32_t size;
};struct dt_entry
{uint32_t platform_id;uint32_t variant_id;uint32_t board_hw_subtype;uint32_t soc_rev;uint32_t pmic_rev[4];uint32_t offset;uint32_t size;
};
计算并验证所需要内存大小是否正确,计算过程如下:
hdr_size = (uint64_t)table->num_entries * dt_entry_size + DEV_TREE_HEADER_SIZE;/* Roundup to page_size. */
hdr_size = ROUNDUP(hdr_size, page_size);if (hdr_size > UINT_MAX)return -1;else*dt_hdr_size = hdr_size & UINT_MAX;
ROUNDUP 实际上就是按照分页对齐,宏定义为 #define ROUNDUP(a, b) (((a) + ((b)-1)) & ~((b)-1))
。
从 dt_table
中的 numentries 字段可以知道 device tree 在存储中实际上是数组结构,这里就是遍临构造出这个 device tree 数组。这里调用dev_tree_get_entry_info
来实现,其定义在platform/msm_shared/dev_tree.c
文件中,由于有多个版本的 device tree,通过对比可以发现dt_entry
的字段是不断增加的,所以我们只分析 version 3 这一种情况,其实现如下:
/* Function to obtain the index information for the correct device tree * based on the platform data. * If a matching device tree is found, the information is returned in the * "dt_entry_info" out parameter and a function value of 0 is returned, otherwise * a non-zero function value is returned. */
int dev_tree_get_entry_info(struct dt_table *table, struct dt_entry *dt_entry_info) {uint32_t i;unsigned char *table_ptr = NULL;struct dt_entry dt_entry_buf_1;struct dt_entry *cur_dt_entry = NULL;struct dt_entry *best_match_dt_entry = NULL;struct dt_entry_v1 *dt_entry_v1 = NULL;struct dt_entry_v2 *dt_entry_v2 = NULL;struct dt_entry_node *dt_entry_queue = NULL;struct dt_entry_node *dt_node_tmp1 = NULL;struct dt_entry_node *dt_node_tmp2 = NULL;uint32_t found = 0;if (!dt_entry_info) {dprintf(CRITICAL, "ERROR: Bad parameter passed to %s \n",__func__);return -1;}table_ptr = (unsigned char *)table + DEV_TREE_HEADER_SIZE;cur_dt_entry = &dt_entry_buf_1;best_match_dt_entry = NULL;dt_entry_queue = (struct dt_entry_node *)malloc(sizeof(struct dt_entry_node));if (!dt_entry_queue) {dprintf(CRITICAL, "Out of memory\n");return -1;}list_initialize(&dt_entry_queue->node);dprintf(INFO, "DTB Total entry: %d, DTB version: %d\n", table->num_entries, table->version);for(i = 0; found == 0 && i < table->num_entries; i++){memset(cur_dt_entry, 0, sizeof(struct dt_entry));switch(table->version) {case DEV_TREE_VERSION_V1:
//...break;case DEV_TREE_VERSION_V2:
//...break;case DEV_TREE_VERSION_V3:memcpy(cur_dt_entry, (struct dt_entry *)table_ptr,sizeof(struct dt_entry));
/* For V3 version of DTBs we have platform version field as part * of variant ID, in such case the subtype will be mentioned as 0x0 * As the qcom, board-id = <0xSSPMPmPH, 0x0> * SS -- Subtype * PM -- Platform major version * Pm -- Platform minor version * PH -- Platform hardware CDP/MTP * In such case to make it compatible with LK algorithm move the subtype * from variant_id to subtype field */if (cur_dt_entry->board_hw_subtype == 0)cur_dt_entry->board_hw_subtype = (cur_dt_entry->variant_id >> 0x18);table_ptr += sizeof(struct dt_entry);break;default:dprintf(CRITICAL, "ERROR: Unsupported version (%d) in DT table \n",table->version);free(dt_entry_queue);return -1;} /* DTBs must match the platform_id, platform_hw_id, platform_subtype and DDR size. * The satisfactory DTBs are stored in dt_entry_queue */platform_dt_absolute_match(cur_dt_entry, dt_entry_queue);}best_match_dt_entry = platform_dt_match_best(dt_entry_queue);if (best_match_dt_entry) {*dt_entry_info = *best_match_dt_entry;found = 1;}if (found != 0) {dprintf(INFO, "Using DTB entry 0x%08x/%08x/0x%08x/%u for device 0x%08x/%08x/0x%08x/%u\n",dt_entry_info->platform_id, dt_entry_info->soc_rev,dt_entry_info->variant_id, dt_entry_info->board_hw_subtype,board_platform_id(), board_soc_version(),board_target_id(), board_hardware_subtype());if (dt_entry_info->pmic_rev[0] == 0 && dt_entry_info->pmic_rev[0] == 0 &&dt_entry_info->pmic_rev[0] == 0 && dt_entry_info->pmic_rev[0] == 0) {dprintf(SPEW, "No maintain pmic info in DTB, device pmic info is 0x%0x/0x%x/0x%x/0x%0x\n",board_pmic_target(0), board_pmic_target(1),board_pmic_target(2), board_pmic_target(3));} else {dprintf(INFO, "Using pmic info 0x%0x/0x%x/0x%x/0x%0x for device 0x%0x/0x%x/0x%x/0x%0x\n",dt_entry_info->pmic_rev[0], dt_entry_info->pmic_rev[1],dt_entry_info->pmic_rev[2], dt_entry_info->pmic_rev[3],board_pmic_target(0), board_pmic_target(1),board_pmic_target(2), board_pmic_target(3));}return 0;}dprintf(CRITICAL, "ERROR: Unable to find suitable device tree for device (%u/0x%08x/0x%08x/%u)\n",board_platform_id(), board_soc_version(),board_target_id(), board_hardware_subtype());list_for_every_entry(&dt_entry_queue->node, dt_node_tmp1, dt_node, node) {
/* free node memory */dt_node_tmp2 = (struct dt_entry_node *) dt_node_tmp1->node.prev;dt_entry_list_delete(dt_node_tmp1);dt_node_tmp1 = dt_node_tmp2;}free(dt_entry_queue);return -1;
}
首先开始遍历整个数组,每个数组成员是一个 dt_entry
结构体,获取到一个 dt_entry
都保存到cur_dt_entry
变量中,然后调用platform_dt_absolute_match
存储到dt_entry_queue
中,dt_entry_queue
是一个链表结构,node 结构如下:
typedef struct dt_entry_node {struct list_node node;struct dt_entry * dt_entry_m;
}dt_node;
而 platform_dt_absolute_match
的实现如下:
static int platform_dt_absolute_match(struct dt_entry *cur_dt_entry, struct dt_entry_node *dt_list)
{uint32_t cur_dt_hlos_ddr;uint32_t cur_dt_hw_platform;uint32_t cur_dt_hw_subtype;uint32_t cur_dt_msm_id;dt_node *dt_node_tmp = NULL; /* Platform-id * bit no |31 24|23 16|15 0| * |reserved|foundry-id|msm-id| */cur_dt_msm_id = (cur_dt_entry->platform_id & 0x0000ffff);cur_dt_hw_platform = (cur_dt_entry->variant_id & 0x000000ff);cur_dt_hw_subtype = (cur_dt_entry->board_hw_subtype & 0xff); /* Determine the bits 10:8 to check the DT with the DDR Size */cur_dt_hlos_ddr = (cur_dt_entry->board_hw_subtype & 0x700);
/* 1. must match the msm_id, platform_hw_id, platform_subtype and DDR size * soc, board major/minor, pmic major/minor must less than board info * 2. find the matched DTB then return 1 * 3. otherwise return 0 */if((cur_dt_msm_id == (board_platform_id() & 0x0000ffff)) &&(cur_dt_hw_platform == board_hardware_id()) &&(cur_dt_hw_subtype == board_hardware_subtype()) &&(cur_dt_hlos_ddr == (target_get_hlos_subtype() & 0x700)) &&(cur_dt_entry->soc_rev <= board_soc_version()) &&((cur_dt_entry->variant_id & 0x00ffff00) <= (board_target_id() & 0x00ffff00)) &&((cur_dt_entry->pmic_rev[0] & 0x00ffff00) <= (board_pmic_target(0) & 0x00ffff00)) &&((cur_dt_entry->pmic_rev[1] & 0x00ffff00) <= (board_pmic_target(1) & 0x00ffff00)) &&((cur_dt_entry->pmic_rev[2] & 0x00ffff00) <= (board_pmic_target(2) & 0x00ffff00)) &&((cur_dt_entry->pmic_rev[3] & 0x00ffff00) <= (board_pmic_target(3) & 0x00ffff00))) {dt_node_tmp = dt_entry_list_init();memcpy((char*)dt_node_tmp->dt_entry_m,(char*)cur_dt_entry, sizeof(struct dt_entry));dprintf(SPEW, "Add DTB entry %u/%08x/0x%08x/%x/%x/%x/%x/%x/%x/%x\n",dt_node_tmp->dt_entry_m->platform_id, dt_node_tmp->dt_entry_m->variant_id,dt_node_tmp->dt_entry_m->board_hw_subtype, dt_node_tmp->dt_entry_m->soc_rev,dt_node_tmp->dt_entry_m->pmic_rev[0], dt_node_tmp->dt_entry_m->pmic_rev[1],dt_node_tmp->dt_entry_m->pmic_rev[2], dt_node_tmp->dt_entry_m->pmic_rev[3],dt_node_tmp->dt_entry_m->offset, dt_node_tmp->dt_entry_m->size);insert_dt_entry_in_queue(dt_list, dt_node_tmp);return 1;}return 0;
}
这个函数最主要的作用就是将 dt_entry
添加到 dt_entry_queue
链表中,但是需要满足以下所有条件才能加入:
msmid 匹配 platformhwid 匹配 platformsubtype 匹配 ddr size 匹配 soc 版本匹配 board 版本匹配 pmic 版本匹配
通过 platform_dt_match_best
来获取最佳匹配,并且赋值给输出参数 dt_entry_info
中,其实现如下:
static struct dt_entry *platform_dt_match_best(struct dt_entry_node *dt_list) {struct dt_entry_node *dt_node_tmp1 = NULL;
/* check Foundry id * the foundry id must exact match board founddry id, this is compatibility check, * if couldn't find the exact match from DTB, will exact match 0x0. */if (!platform_dt_absolute_compat_match(dt_list, DTB_FOUNDRY))return NULL; /* check PMIC model * the PMIC model must exact match board PMIC model, this is compatibility check, * if couldn't find the exact match from DTB, will exact match 0x0. */if (!platform_dt_absolute_compat_match(dt_list, DTB_PMIC_MODEL))return NULL; /* check panel type * the panel type must exact match board panel type, this is compatibility check, * if couldn't find the exact match from DTB, will exact match 0x0. */if (!platform_dt_absolute_compat_match(dt_list, DTB_PANEL_TYPE))return NULL; /* check boot device subtype * the boot device subtype must exact match board boot device subtype, this is compatibility check, * if couldn't find the exact match from DTB, will exact match 0x0. */if (!platform_dt_absolute_compat_match(dt_list, DTB_BOOT_DEVICE))return NULL; /* check soc version * the suitable soc version must less than or equal to board soc version */if (!update_dtb_entry_node(dt_list, DTB_SOC))return NULL; /*check major and minor version * the suitable major&minor version must less than or equal to board major&minor version */if (!update_dtb_entry_node(dt_list, DTB_MAJOR_MINOR))return NULL;
/*check pmic info * the suitable pmic major&minor info must less than or equal to board pmic major&minor version */if (!update_dtb_entry_node(dt_list, DTB_PMIC0))return NULL;if (!update_dtb_entry_node(dt_list, DTB_PMIC1))return NULL;if (!update_dtb_entry_node(dt_list, DTB_PMIC2))return NULL;if (!update_dtb_entry_node(dt_list, DTB_PMIC3))return NULL;list_for_every_entry(&dt_list->node, dt_node_tmp1, dt_node, node) {if (!dt_node_tmp1) {dprintf(CRITICAL, "ERROR: Couldn't find the suitable DTB!\n");return NULL;}if (dt_node_tmp1->dt_entry_m)return dt_node_tmp1->dt_entry_m;}return NULL;
}
整个过程和刚才添加验证的过程类似,比对与硬件的匹配度,找到最佳匹配的 dt_entry
然后返回。
如果获取到了最佳匹配的 dt_entry
则按照加载 kernel 的步骤来加载到内存地址,即按照如下步骤:根据标志位来解压数据。 拷贝到boot_img_hdr
指定的内存地址。
这样 device tree 的加载就完成了。
没有指定 dtsize 如何加载 device tree。如果没有专门的 device tree block, 则需要判断 kernel block 是否附加了 device tree 信息。整个过程都是通过调用函数dev_tree_appended
函数实现,其实现如下:
/* * Will relocate the DTB to the tags addr if the device tree is found and return * its address * * Arguments: kernel - Start address of the kernel loaded in RAM * tags - Start address of the tags loaded in RAM * kernel_size - Size of the kernel in bytes * * Return Value: DTB address : If appended device tree is found * 'NULL' : Otherwise */
void *dev_tree_appended(void *kernel, uint32_t kernel_size, uint32_t dtb_offset, void *tags) {void *kernel_end = kernel + kernel_size;uint32_t app_dtb_offset = 0;void *dtb = NULL;void *bestmatch_tag = NULL;struct dt_entry *best_match_dt_entry = NULL;uint32_t bestmatch_tag_size;struct dt_entry_node *dt_entry_queue = NULL;struct dt_entry_node *dt_node_tmp1 = NULL;struct dt_entry_node *dt_node_tmp2 = NULL; /* Initialize the dtb entry node*/dt_entry_queue = (struct dt_entry_node *)malloc(sizeof(struct dt_entry_node));if (!dt_entry_queue) {dprintf(CRITICAL, "Out of memory\n");return NULL;}list_initialize(&dt_entry_queue->node);if (dtb_offset)app_dtb_offset = dtb_offset;elsememcpy((void*) &app_dtb_offset, (void*) (kernel + DTB_OFFSET), sizeof(uint32_t));if (((uintptr_t)kernel + (uintptr_t)app_dtb_offset) < (uintptr_t)kernel) {return NULL;}dtb = kernel + app_dtb_offset;while (((uintptr_t)dtb + sizeof(struct fdt_header)) < (uintptr_t)kernel_end) {struct fdt_header dtb_hdr;uint32_t dtb_size; /* the DTB could be unaligned, so extract the header, * and operate on it separately */memcpy(&dtb_hdr, dtb, sizeof(struct fdt_header));if (fdt_check_header((const void *)&dtb_hdr) != 0 ||((uintptr_t)dtb + (uintptr_t)fdt_totalsize((const void *)&dtb_hdr) < (uintptr_t)dtb) ||((uintptr_t)dtb + (uintptr_t)fdt_totalsize((const void *)&dtb_hdr) > (uintptr_t)kernel_end))break;dtb_size = fdt_totalsize(&dtb_hdr);if (check_aboot_addr_range_overlap((uint32_t)tags, dtb_size)) {dprintf(CRITICAL, "Tags addresses overlap with aboot addresses.\n");return NULL;}dev_tree_compatible(dtb, dtb_size, dt_entry_queue);
/* goto the next device tree if any */dtb += dtb_size;}best_match_dt_entry = platform_dt_match_best(dt_entry_queue);if (best_match_dt_entry){bestmatch_tag = (void *)best_match_dt_entry->offset;bestmatch_tag_size = best_match_dt_entry->size;dprintf(INFO, "Best match DTB tags %u/%08x/0x%08x/%x/%x/%x/%x/%x/%x/%x\n",best_match_dt_entry->platform_id, best_match_dt_entry->variant_id,best_match_dt_entry->board_hw_subtype, best_match_dt_entry->soc_rev,best_match_dt_entry->pmic_rev[0], best_match_dt_entry->pmic_rev[1],best_match_dt_entry->pmic_rev[2], best_match_dt_entry->pmic_rev[3],best_match_dt_entry->offset, best_match_dt_entry->size);dprintf(INFO, "Using pmic info 0x%0x/0x%x/0x%x/0x%0x for device 0x%0x/0x%x/0x%x/0x%0x\n",best_match_dt_entry->pmic_rev[0], best_match_dt_entry->pmic_rev[1],best_match_dt_entry->pmic_rev[2], best_match_dt_entry->pmic_rev[3],board_pmic_target(0), board_pmic_target(1),board_pmic_target(2), board_pmic_target(3));}
/* free queue's memory */list_for_every_entry(&dt_entry_queue->node, dt_node_tmp1, dt_node, node) {dt_node_tmp2 = (struct dt_entry_node *) dt_node_tmp1->node.prev;dt_entry_list_delete(dt_node_tmp1);dt_node_tmp1 = dt_node_tmp2;}if(bestmatch_tag) {memcpy(tags, bestmatch_tag, bestmatch_tag_size);
/* clear out the old DTB magic so kernel doesn't find it */*((uint32_t *)(kernel + app_dtb_offset)) = 0;return tags;}dprintf(CRITICAL, "DTB offset is incorrect, kernel image does not have appended DTB\n");dprintf(INFO, "Device info 0x%08x/%08x/0x%08x/%u, pmic 0x%0x/0x%x/0x%x/0x%0x\n",board_platform_id(), board_soc_version(),board_target_id(), board_hardware_subtype(),board_pmic_target(0), board_pmic_target(1),board_pmic_target(2), board_pmic_target(3));return NULL;
}
首先需要获取 device tree table 的偏移,偏移有以下两种情况: kernel 是经过解压的,则指定的位置在解压 kernel 时确定。 kernel 没有经过压缩,则偏移在kernel + 0x2C
的位置上获取。
从 device tree 偏移位置开始到 kernel 尾部的范围内遍历 device tree 数据。这里相当于遍历一个数组,数组的成员为 struct fdt_header
, 这个结构定义在 lib/libfdt/fdt.h
文件中,它的结构如下:
struct fdt_header {uint32_t magic; /* magic word FDT_MAGIC */uint32_t totalsize; /* total size of DT block */uint32_t off_dt_struct; /* offset to structure */uint32_t off_dt_strings; /* offset to strings */uint32_t off_mem_rsvmap; /* offset to memory reserve map */uint32_t version; /* format version */uint32_t last_comp_version; /* last compatible version */ /* version 2 fields below */uint32_t boot_cpuid_phys; /* Which physical CPU id we're booting on *//* version 3 fields below */uint32_t size_dt_strings; /* size of the strings block */ /* version 17 fields below */uint32_t size_dt_struct; /* size of the structure block */
};
检查遍历到的 device tree 的 fdt_header
是否符合以下条件:
是否能通过 fdt_check_header
检查。fdt_check_header
的代码在 lib/libfdt/fdt.c
文件中,其实现如下:
int fdt_check_header(const void *fdt)
{if (fdt_magic(fdt) == FDT_MAGIC) {
/* Complete tree */if (fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION)return -FDT_ERR_BADVERSION;if (fdt_last_comp_version(fdt) > FDT_LAST_SUPPORTED_VERSION)return -FDT_ERR_BADVERSION;} else if (fdt_magic(fdt) == FDT_SW_MAGIC) { /* Unfinished sequential-write blob */if (fdt_size_dt_struct(fdt) == 0)return -FDT_ERR_BADSTATE;} else {return -FDT_ERR_BADMAGIC;}if (fdt_off_dt_struct(fdt) > (UINT_MAX - fdt_size_dt_struct(fdt)))return FDT_ERR_BADOFFSET;if (fdt_off_dt_strings(fdt) > (UINT_MAX - fdt_size_dt_strings(fdt)))return FDT_ERR_BADOFFSET;if ((fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt)) > fdt_totalsize(fdt))return FDT_ERR_BADOFFSET;if ((fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt)) > fdt_totalsize(fdt))return FDT_ERR_BADOFFSET;return 0;
}
如果符合符合以下条件都是正常的 fdt_header
。
如果 MAGIC = 0xd00dfeed。 fdt_header.version
>= 0×10。 fdt_header.last_comp_version
<= 0×11。 如果 MAGIC = 0x2ff20112。fdt_header.size_dt_struct
不等于 0。fdt_header.off_dt_struct
< 0xFFFFFFFF –fd_header.size_dt_struct
。fdt_header.off_dt_strings
< 0xFFFFFFFF –fd_header.size_dt_strings
。fdt_header.off_dt_struct
+fdt_header.size_dt_struct
<fdt_header.totalsize
。 fdt_header.off_dt_strings
+fdt_header.size_dt_strings
<fdt_header.totalsize
。 device tree 偏移加fdt_header.totalsize
是否小于 device tree 的偏移,即fdt_header.totalsize
是否为负数。 device tree 偏移加fdt_header.totalsize
是否大于 kernel end 的偏移,即是否越界。
通过检查的 device tree 调用 dev_tree_compatible
函数检查兼容性,符合条件的添加到链表中,甚于的步骤和指定了 dtsize 的步骤就基本相同了。
调用 boot_linux
启动系统
到这一步 boot/recovery 基本的初始化工作,加载工作就基本完成了,下一步就可以通过 boot_linux
函数来进行启动了。启动完成后就会将控制权移交给 linux kernel,android 系统就开始正式运行了。boot_linux
的代码位于app/aboot/aboot.c
文件中,其实现如下:
void boot_linux(void *kernel, unsigned *tags,const char *cmdline, unsigned machtype,void *ramdisk, unsigned ramdisk_size) {unsigned char *final_cmdline;
#if DEVICE_TREEint ret = 0;
#endifvoid (*entry)(unsigned, unsigned, unsigned*) = (entry_func_ptr*)(PA((addr_t)kernel));uint32_t tags_phys = PA((addr_t)tags);struct kernel64_hdr *kptr = ((struct kernel64_hdr*)(PA((addr_t)kernel)));ramdisk = (void *)PA((addr_t)ramdisk);final_cmdline = update_cmdline((const char*)cmdline);#if DEVICE_TREEdprintf(INFO, "Updating device tree: start\n"); /* Update the Device Tree */ret = update_device_tree((void *)tags,(const char *)final_cmdline, ramdisk, ramdisk_size);if(ret){dprintf(CRITICAL, "ERROR: Updating Device Tree Failed \n");ASSERT(0);}dprintf(INFO, "Updating device tree: done\n");
#else
/* Generating the Atags */generate_atags(tags, final_cmdline, ramdisk, ramdisk_size);
#endiffree(final_cmdline);#if VERIFIED_BOOT
/* Write protect the device info */if (!boot_into_recovery && target_build_variant_user() && devinfo_present && mmc_write_protect("devinfo", 1)){dprintf(INFO, "Failed to write protect dev info\n");ASSERT(0);}
#endif /* Turn off splash screen if enabled */
#if DISPLAY_SPLASH_SCREENtarget_display_shutdown();
#endif /* Perform target specific cleanup */target_uninit();dprintf(INFO, "booting linux @ %p, ramdisk @ %p (%d), tags/device tree @ %p\n",entry, ramdisk, ramdisk_size, (void *)tags_phys);enter_critical_section(); /* Initialise wdog to catch early kernel crashes */
#if WDOG_SUPPORTmsm_wdog_init();
#endif
/* do any platform specific cleanup before kernel entry */platform_uninit();arch_disable_cache(UCACHE);#if ARM_WITH_MMUarch_disable_mmu();
#endifbs_set_timestamp(BS_KERNEL_ENTRY);if (IS_ARM64(kptr))
/* Jump to a 64bit kernel */scm_elexec_call((paddr_t)kernel, tags_phys);else
/* Jump to a 32bit kernel */entry(0, machtype, (unsigned*)tags_phys);
}
首先进行地址转换,不过在目前的实现中 PA
宏是直接返回传入的地址,所以地址不会被转换,相当于一个预留的扩展接口。
#define PA(x) platform_get_virt_to_phys_mapping(x)
#define VA(x) platform_get_phys_to_virt_mapping(x)addr_t platform_get_virt_to_phys_mapping(addr_t virt_addr)
{
/* Using 1-1 mapping on this platform. */return virt_addr;
}addr_t platform_get_phys_to_virt_mapping(addr_t phys_addr)
{
/* Using 1-1 mapping on this platform. */return phys_addr;
}
这个只需要注意的是 kernel 的首地址即是整个 kernel 的入口,入口的函数类型为 entry_func_ptr
, 其定义如下:
typedef void entry_func_ptr(unsigned, unsigned, unsigned*);
*本文原创作者:SetRet,本文属FreeBuf原创奖励计划,未经许可禁止转载调用update_cmdline
更新boot_img_hdr.cmdline
字段中启动命令。
unsigned char *update_cmdline(const char * cmdline) {int cmdline_len = 0;int have_cmdline = 0;unsigned char *cmdline_final = NULL;int pause_at_bootup = 0;bool warm_boot = false;bool gpt_exists = partition_gpt_exists();int have_target_boot_params = 0;char *boot_dev_buf = NULL;bool is_mdtp_activated = 0;
#ifdef MDTP_SUPPORTmdtp_activated(&is_mdtp_activated);
#endif /* MDTP_SUPPORT */if (cmdline && cmdline[0]) {cmdline_len = strlen(cmdline);have_cmdline = 1;}else {dprintf(CRITICAL,"cmdline is NULL\n");ASSERT(0);}if (target_is_emmc_boot()) {cmdline_len += strlen(emmc_cmdline);
#if USE_BOOTDEV_CMDLINEboot_dev_buf = (char *) malloc(sizeof(char) * BOOT_DEV_MAX_LEN);ASSERT(boot_dev_buf);memset((void *)boot_dev_buf, 0, sizeof(*boot_dev_buf));platform_boot_dev_cmdline(boot_dev_buf);cmdline_len += strlen(boot_dev_buf);
#endif}cmdline_len += strlen(usb_sn_cmdline);cmdline_len += strlen(sn_buf);if (boot_into_recovery && gpt_exists)cmdline_len += strlen(secondary_gpt_enable);if(is_mdtp_activated)cmdline_len += strlen(mdtp_activated_flag);if (boot_into_ffbm) {cmdline_len += strlen(androidboot_mode);cmdline_len += strlen(ffbm_mode_string);
/* reduce kernel console messages to speed-up boot */cmdline_len += strlen(loglevel);} else if (boot_reason_alarm) {cmdline_len += strlen(alarmboot_cmdline);} else if ((target_build_variant_user() || device.charger_screen_enabled)&& target_pause_for_battery_charge()) {pause_at_bootup = 1;cmdline_len += strlen(battchg_pause);}if(target_use_signed_kernel() && auth_kernel_img) {cmdline_len += strlen(auth_kernel);}if (get_target_boot_params(cmdline, boot_into_recovery ? "recoveryfs" :"system",&target_boot_params) == 0) {have_target_boot_params = 1;cmdline_len += strlen(target_boot_params);} /* Determine correct androidboot.baseband to use */switch(target_baseband()){case BASEBAND_APQ:cmdline_len += strlen(baseband_apq);break;case BASEBAND_MSM:cmdline_len += strlen(baseband_msm);break;case BASEBAND_CSFB:cmdline_len += strlen(baseband_csfb);break;case BASEBAND_SVLTE2A:cmdline_len += strlen(baseband_svlte2a);break;case BASEBAND_MDM:cmdline_len += strlen(baseband_mdm);break;case BASEBAND_MDM2:cmdline_len += strlen(baseband_mdm2);break;case BASEBAND_SGLTE:cmdline_len += strlen(baseband_sglte);break;case BASEBAND_SGLTE2:cmdline_len += strlen(baseband_sglte2);break;case BASEBAND_DSDA:cmdline_len += strlen(baseband_dsda);break;case BASEBAND_DSDA2:cmdline_len += strlen(baseband_dsda2);break;}if (cmdline) {if ((strstr(cmdline, DISPLAY_DEFAULT_PREFIX) == NULL) &&target_display_panel_node(display_panel_buf,MAX_PANEL_BUF_SIZE) &&strlen(display_panel_buf)) {cmdline_len += strlen(display_panel_buf);}}if (target_warm_boot()) {warm_boot = true;cmdline_len += strlen(warmboot_cmdline);}if (cmdline_len > 0) {const char *src;unsigned char *dst;cmdline_final = (unsigned char*) malloc((cmdline_len + 4) & (~3));ASSERT(cmdline_final != NULL);memset((void *)cmdline_final, 0, sizeof(*cmdline_final));dst = cmdline_final; /* Save start ptr for debug print */if (have_cmdline) {src = cmdline;while ((*dst++ = *src++));}if (target_is_emmc_boot()) {src = emmc_cmdline;if (have_cmdline) --dst;have_cmdline = 1;while ((*dst++ = *src++));
#if USE_BOOTDEV_CMDLINEsrc = boot_dev_buf;if (have_cmdline) --dst;while ((*dst++ = *src++));
#endif}src = usb_sn_cmdline;if (have_cmdline) --dst;have_cmdline = 1;while ((*dst++ = *src++));src = sn_buf;if (have_cmdline) --dst;have_cmdline = 1;while ((*dst++ = *src++));if (warm_boot) {if (have_cmdline) --dst;src = warmboot_cmdline;while ((*dst++ = *src++));}if (boot_into_recovery && gpt_exists) {src = secondary_gpt_enable;if (have_cmdline) --dst;while ((*dst++ = *src++));}if (is_mdtp_activated) {src = mdtp_activated_flag;if (have_cmdline) --dst;while ((*dst++ = *src++));}if (boot_into_ffbm) {src = androidboot_mode;if (have_cmdline) --dst;while ((*dst++ = *src++));src = ffbm_mode_string;if (have_cmdline) --dst;while ((*dst++ = *src++));src = loglevel;if (have_cmdline) --dst;while ((*dst++ = *src++));} else if (boot_reason_alarm) {src = alarmboot_cmdline;if (have_cmdline) --dst;while ((*dst++ = *src++));} else if (pause_at_bootup) {src = battchg_pause;if (have_cmdline) --dst;while ((*dst++ = *src++));}if(target_use_signed_kernel() && auth_kernel_img) {src = auth_kernel;if (have_cmdline) --dst;while ((*dst++ = *src++));}switch(target_baseband()){case BASEBAND_APQ:src = baseband_apq;if (have_cmdline) --dst;while ((*dst++ = *src++));break;case BASEBAND_MSM:src = baseband_msm;if (have_cmdline) --dst;while ((*dst++ = *src++));break;case BASEBAND_CSFB:src = baseband_csfb;if (have_cmdline) --dst;while ((*dst++ = *src++));break;case BASEBAND_SVLTE2A:src = baseband_svlte2a;if (have_cmdline) --dst;while ((*dst++ = *src++));break;case BASEBAND_MDM:src = baseband_mdm;if (have_cmdline) --dst;while ((*dst++ = *src++));break;case BASEBAND_MDM2:src = baseband_mdm2;if (have_cmdline) --dst;while ((*dst++ = *src++));break;case BASEBAND_SGLTE:src = baseband_sglte;if (have_cmdline) --dst;while ((*dst++ = *src++));break;case BASEBAND_SGLTE2:src = baseband_sglte2;if (have_cmdline) --dst;while ((*dst++ = *src++));break;case BASEBAND_DSDA:src = baseband_dsda;if (have_cmdline) --dst;while ((*dst++ = *src++));break;case BASEBAND_DSDA2:src = baseband_dsda2;if (have_cmdline) --dst;while ((*dst++ = *src++));break;}if (strlen(display_panel_buf)) {src = display_panel_buf;if (have_cmdline) --dst;while ((*dst++ = *src++));}if (have_target_boot_params) {if (have_cmdline) --dst;src = target_boot_params;while ((*dst++ = *src++));free(target_boot_params);}}if (boot_dev_buf)free(boot_dev_buf);if (cmdline_final)dprintf(INFO, "cmdline: %s\n", cmdline_final);elsedprintf(INFO, "cmdline is NULL\n");return cmdline_final;
}
更新 cmdline 可以分为以下几个步骤:
通过已有的命令和需要添加的命令计算出 final_cmdline
需要的长度。 申请存储 final_cmdline
的 buffer, 并且清零。 拷贝需要的 cmd 命令到final_cmdline
中。
update 主要涉及到的命令,参数,和会使用的情况可以整理为下表:
cmd | value | condition |
---|---|---|
androidboot.emmc | true | target is emmc |
androidboot.serialno | [serial number] | no condition |
gpt | no value | recovery boot and gpt exists |
mdtp | no value | mdtp activated |
androidboot.mode | [ffbm string] | ffbm boot |
androidboot.alarmboot | true | alarm boot |
androidboot.mode | charger | charger |
androidboot.authorizedkernel | true | signed kernel and kernel authorized |
androidboot.baseband | apq | baseband is apq |
androidboot.baseband | msm | baseband is msm |
androidboot.baseband | csfb | baseband is csfb |
androidboot.baseband | svlte2a | baseband is svlte2a |
androidboot.baseband | mdm | baseband is mdm |
androidboot.baseband | mdm2 | baseband is mdm2 |
androidboot.baseband | sglte | baseband is sglte |
androidboot.baseband | dsda | baseband is dsda |
androidboot.baseband | dsda2 | baseband is dsda2 |
androidboot.baseband | sglte2 | baseband is sglte2 |
qpnp-power-on.warmboot | true | target warm boot |
mdssmdp | [display panel buffer] | have dssmdp cmd in boot image header cmdline |
调用 update_device_tree
更新 device tree 信息。这一步主要是从 device tree 中解析出地址信息,然后在启动 kernel 之时传递给 kernel 方便加载。整个过程由于需要对 device tree 整体结构的详解,这里暂不赘述,有兴趣的读者可以参考高通平台 Android 源码分析之 Linux 内核设备树(DT – Device Tree)。 在未解锁的情况下对 devinfo
分区进行写保护,这个分区存储的是 bootloader 的解锁信息和验证信息,进行写保护避免误操作。 关闭一些针对 lk 开启的特性,清理预设的数据,如:MMU, CACHE 等。
调用 kernel 入口点,进入 kernel 的代码区域。这里需要注意的是 32 位和 64 位的进入方法不同。32 位是直接 call kernel 的入口点,将入口点作为函数调用。而 64 位 kernel 则是通过scm_elexec_call
来进入内核空间。scm_elexec_call
的实现在platform/msm_shared/scm.c
文件中,其实现如下:
/* Execption Level exec secure-os call * Jumps to kernel via secure-os and does not return * on successful jump. System parameters are setup & * passed on to secure-os and are utilized to boot the * kernel. * @ kernel_entry : kernel entry point passed in as link register. @ dtb_offset : dt blob address passed in as w0. @ svc_id : indicates direction of switch 32->64 or 64->32 * * Assumes all sanity checks have been performed on arguments. */void scm_elexec_call(paddr_t kernel_entry, paddr_t dtb_offset) {uint32_t svc_id = SCM_SVC_MILESTONE_32_64_ID;uint32_t cmd_id = SCM_SVC_MILESTONE_CMD_ID;void *cmd_buf;size_t cmd_len;static el1_system_param param __attribute__((aligned(0x1000)));scmcall_arg scm_arg = {
0};param.el1_x0 = dtb_offset;param.el1_elr = kernel_entry; /* Response Buffer = Null as no response expected */dprintf(INFO, "Jumping to kernel via monitor\n");if (!is_scm_armv8_support()){
/* Command Buffer */cmd_buf = (void *)¶m;cmd_len = sizeof(el1_system_param);scm_call(svc_id, cmd_id, cmd_buf, cmd_len, NULL, 0);}else{scm_arg.x0 = MAKE_SIP_SCM_CMD(SCM_SVC_MILESTONE_32_64_ID, SCM_SVC_MILESTONE_CMD_ID);scm_arg.x1 = MAKE_SCM_ARGS(0x2, SMC_PARAM_TYPE_BUFFER_READ);scm_arg.x2 = (uint32_t ) ¶m;scm_arg.x3 = sizeof(el1_system_param);scm_call2(&scm_arg, NULL);} /* Assert if execution ever reaches here */dprintf(CRITICAL, "Failed to jump to kernel\n");ASSERT(0);
}
而我们知道 scm(Secure Channel Manager) 相关的函数是 TrustZone 提供给普通个世界的接口,函数流程比较简单,根据是否支持 armv8 进行不同的跳转,由于 TrustZone 实现是黑盒,所以这里暂不研究。只需要知道 64 位 kernel 是通过 TrustZone 来启动即可。
1 参考资料
Verifying Boot | Android Open Source ProjectVerified Boot | Android Open Source Projectandroid-cdd.pdf高通平台 Android 源码分析之 Linux 内核设备树(DT – Device Tree) | Andy.Lee’s Blog
Footnotes:
1 ffbm (fast factory boot mode) 是高通开发的一套半开机模式下的测试界面,用于工厂测试,提高生产效率。
2 具体的 boot state 模式可以参考 android 官方文档Verifying Boot | Android Open Source Project
lk 源码分析的系列文章到这里就告一段落了。请期待后续其他文章,如果有任何疑问和交流意向可以和我们联系 SecRet201611@gmail.com 。