高通平台下的UEFI由XBL+ABL组成,主要完成各种客制化的需求实现,例如通过拉特定的gpio进入fastboot/recovery模式,读取ufs寿命,LCD兼容框架的实现等,想要实现客制化首先要搞明白源码种的框架组成,这篇文章先剖析一下abl阶段主要做了什么事情。

要分析abl框架,首先我们需要找到整个框架的入口,根据LinuxLoader.inf内的描述可以得到,abl的入口就在LinuxLoader.c内的LinuxLoaderEntry函数

文件路径:bootable/bootloader/edk2/QcomModulePkg/Application/LinuxLoader/LinuxLoader.inf

[Defines]
        INF_VERSION                    = 0x00010006
        BASE_NAME                      = LinuxLoader
        FILE_GUID                      = f536d559-459f-48fa-8bbc-43b554ecae8d
        MODULE_TYPE                    = UEFI_APPLICATION
        VERSION_STRING                 = 0.1
        ENTRY_POINT                    = LinuxLoaderEntry

[Sources]
        LinuxLoader.c
...

那么就从入口函数LinuxLoaderEntry开始代码分析,分析内容直接附在代码注释中。

文件路径

bootable/bootloader/edk2/QcomModulePkg/Application/LinuxLoader/LinuxLoader.c

/**
  Linux Loader Application EntryPoint

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval other             Some error occurs when executing this entry point.

 **/

EFI_STATUS EFIAPI  __attribute__ ( (no_sanitize ("safe-stack")))
LinuxLoaderEntry (IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable)
{
  EFI_STATUS Status;

  UINT32 BootReason = NORMAL_MODE;
  UINT32 KeyPressed = SCAN_NULL;
  /* MultiSlot Boot */
  BOOLEAN MultiSlotBoot;

  DEBUG ((EFI_D_INFO, "Loader Build Info: %a %a\n", __DATE__, __TIME__));
  DEBUG ((EFI_D_VERBOSE, "LinuxLoader Load Address to debug ABL: 0x%llx\n",
         (UINTN)LinuxLoaderEntry & (~ (0xFFF))));
  DEBUG ((EFI_D_VERBOSE, "LinuxLoaderEntry Address: 0x%llx\n",
         (UINTN)LinuxLoaderEntry));

  Status = AllocateUnSafeStackPtr ();
  if (Status != EFI_SUCCESS) {
    DEBUG ((EFI_D_ERROR, "Unable to Allocate memory for Unsafe Stack: %r\n",
            Status));
    goto stack_guard_update_default;
  }

  StackGuardChkSetup ();
  
  //获取内核启动地址以及打印时间等
  BootStatsSetTimeStamp (BS_BL_START);

  //获取设备信息,涉及到oem unlock功能等
  Status = DeviceInfoInit ();
  if (Status != EFI_SUCCESS) {
    DEBUG ((EFI_D_ERROR, "Initialize the device info failed: %r\n", Status));
    goto stack_guard_update_default;
  }
  //枚举分区,根据provision文件内分配的lun卷进行枚举
  Status = EnumeratePartitions ();
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "LinuxLoader: Could not enumerate partitions: %r\n",
            Status));
    goto stack_guard_update_default;
  }

  UpdatePartitionEntries ();
  //判断本次启动是从slot_a还是slot_b启动
  MultiSlotBoot = PartitionHasMultiSlot ((CONST CHAR16 *)L"boot");
  if (MultiSlotBoot) {
    DEBUG ((EFI_D_VERBOSE, "Multi Slot boot is supported\n"));
    FindPtnActiveSlot ();
  }
  
  //判断是否此时存在按键事件选择进入不同模式
  Status = GetKeyPress (&KeyPressed);
  if (Status == EFI_SUCCESS) {
    if (KeyPressed == SCAN_DOWN)
      BootIntoFastboot = TRUE;
    if (KeyPressed == SCAN_UP)
      BootIntoRecovery = TRUE;
    if (KeyPressed == SCAN_ESC)
      RebootDevice (EMERGENCY_DLOAD);
  } else if (Status == EFI_DEVICE_ERROR) {
    DEBUG ((EFI_D_ERROR, "Error reading key status: %r\n", Status));
    goto stack_guard_update_default;
  }

  //获取重启原因并根据原因决定设备进入的模式
  Status = GetRebootReason (&BootReason);
  if (Status != EFI_SUCCESS) {
    DEBUG ((EFI_D_ERROR, "Failed to get Reboot reason: %r\n", Status));
    goto stack_guard_update_default;
  }

  switch (BootReason) {
  case FASTBOOT_MODE:
    BootIntoFastboot = TRUE;
    break;
  case RECOVERY_MODE:
    BootIntoRecovery = TRUE;
    break;
  case ALARM_BOOT:
    BootReasonAlarm = TRUE;
    break;
  case DM_VERITY_ENFORCING:
    // write to device info
    Status = EnableEnforcingMode (TRUE);
    if (Status != EFI_SUCCESS)
      goto stack_guard_update_default;
    break;
  case DM_VERITY_LOGGING:
    /* Disable MDTP if it's Enabled through Local Deactivation */
    Status = MdtpDisable ();
    if (EFI_ERROR (Status) && Status != EFI_NOT_FOUND) {
      DEBUG ((EFI_D_ERROR, "MdtpDisable Returned error: %r\n", Status));
      goto stack_guard_update_default;
    }
    // write to device info
    Status = EnableEnforcingMode (FALSE);
    if (Status != EFI_SUCCESS)
      goto stack_guard_update_default;

    break;
  case DM_VERITY_KEYSCLEAR:
    Status = ResetDeviceState ();
    if (Status != EFI_SUCCESS) {
      DEBUG ((EFI_D_ERROR, "VB Reset Device State error: %r\n", Status));
      goto stack_guard_update_default;
    }
    break;
  default:
    if (BootReason != NORMAL_MODE) {
      DEBUG ((EFI_D_ERROR,
             "Boot reason: 0x%x not handled, defaulting to Normal Boot\n",
             BootReason));
    }
    break;
  }
  
  //recovery模式初始化
  Status = RecoveryInit (&BootIntoRecovery);
  if (Status != EFI_SUCCESS)
    DEBUG ((EFI_D_VERBOSE, "RecoveryInit failed ignore: %r\n", Status));

  /* Populate board data required for fastboot, dtb selection and cmd line */
  Status = BoardInit ();
  if (Status != EFI_SUCCESS) {
    DEBUG ((EFI_D_ERROR, "Error finding board information: %r\n", Status));
    return Status;
  }

  DEBUG ((EFI_D_INFO, "KeyPress:%u, BootReason:%u\n", KeyPressed, BootReason));
  DEBUG ((EFI_D_INFO, "Fastboot=%d, Recovery:%d\n",
                                          BootIntoFastboot, BootIntoRecovery));
  if (!GetVmData ()) {
    DEBUG ((EFI_D_ERROR, "VM Hyp calls not present\n"));
  }

  //选择正常启动,开始加载镜像
  if (!BootIntoFastboot) {
    BootInfo Info = {0};
    Info.MultiSlotBoot = MultiSlotBoot;
    Info.BootIntoRecovery = BootIntoRecovery;
    Info.BootReasonAlarm = BootReasonAlarm;
    Status = LoadImageAndAuth (&Info);
    if (Status != EFI_SUCCESS) {
      DEBUG ((EFI_D_ERROR, "LoadImageAndAuth failed: %r\n", Status));
      goto fastboot;
    }

    BootLinux (&Info);
  }

fastboot:
  DEBUG ((EFI_D_INFO, "Launching fastboot\n"));
  Status = FastbootInitialize ();
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "Failed to Launch Fastboot App: %d\n", Status));
    goto stack_guard_update_default;
  }

stack_guard_update_default:
  /*Update stack check guard with defualt value then return*/
  __stack_chk_guard = DEFAULT_STACK_CHK_GUARD;
  return Status;
}

LinuxLoader.c作为整个abl的入口,要完成的事情有点多,对于uefi功能开发,我们不需要把全部代码都记住,但是必须要了解其中与客制化开发关系较为紧密的部分。下面进行几个重要函数的代码剖析

  1. DeviceInfoInit

DeviceInfoInit函数根据使用的DevInfo内部成员就知道,与设备locked功能,verity_mode,user_public_key有关,事实上DeviceInfoInit会去读取devcfg分区内的数据,并且会对unlocked功能进行初始化设定,对于不是专门做于设备安全的朋友来说,了解个大概就好了。

typedef struct device_info {
  CHAR8 magic[DEVICE_MAGIC_SIZE];
  BOOLEAN is_unlocked;
  BOOLEAN is_unlock_critical;
  BOOLEAN is_charger_screen_enabled;
  CHAR8 bootloader_version[MAX_VERSION_LEN];
  CHAR8 radio_version[MAX_VERSION_LEN];
  BOOLEAN verity_mode; // TRUE = enforcing, FALSE = logging
  UINT32 user_public_key_length;
  CHAR8 user_public_key[MAX_USER_KEY_SIZE];
  UINT64 rollback_index[MAX_VB_PARTITIONS];
  struct usb_composition usb_comp;
} DeviceInfo;

EFI_STATUS DeviceInfoInit (VOID)
{
  EFI_STATUS Status = EFI_SUCCESS;

  if (FirstReadDevInfo) {
    Status =
        ReadWriteDeviceInfo (READ_CONFIG, (VOID *)&DevInfo, sizeof (DevInfo));
    if (Status != EFI_SUCCESS) {
      DEBUG ((EFI_D_ERROR, "Unable to Read Device Info: %r\n", Status));
      return Status;
    }

    FirstReadDevInfo = FALSE;
  }

  if (CompareMem (DevInfo.magic, DEVICE_MAGIC, DEVICE_MAGIC_SIZE)) {
    DEBUG ((EFI_D_ERROR, "Device Magic does not match\n"));
    gBS->SetMem (&DevInfo, sizeof (DevInfo), 0);
    gBS->CopyMem (DevInfo.magic, DEVICE_MAGIC, DEVICE_MAGIC_SIZE);
    DevInfo.user_public_key_length = 0;
    gBS->SetMem (DevInfo.rollback_index, sizeof (DevInfo.rollback_index), 0);
    gBS->SetMem (DevInfo.user_public_key, sizeof (DevInfo.user_public_key), 0);
    /*重点,判断设备是否已经开启安全融丝功能,默认设备开启安全熔丝功能的话unlocked功能是无法打开的,
    代表着fastboot模式下无法进行镜像烧录,大部分厂家为了防止非法镜像刷写,都不会去修改这部分*/
    if (IsSecureBootEnabled ()) {
      DevInfo.is_unlocked = FALSE;
      DevInfo.is_unlock_critical = FALSE;
    } else {
      DevInfo.is_unlocked = TRUE;
      DevInfo.is_unlock_critical = TRUE;
    }
    DevInfo.is_charger_screen_enabled = FALSE;
    DevInfo.verity_mode = TRUE;
    Status =
        ReadWriteDeviceInfo (WRITE_CONFIG, (VOID *)&DevInfo, sizeof (DevInfo));
    if (Status != EFI_SUCCESS) {
      DEBUG ((EFI_D_ERROR, "Unable to Write Device Info: %r\n", Status));
      return Status;
    }
  }

  return Status;
}

  1. FindPtnActiveSlot&&GetActiveSlot

FindPtnActiveSlot函数只是设定了一个默认启动slot为0,真正的工作都是放在GetActiveSlot内完成的,GetActiveSlot会查找当前寄存器内哪个slot是active状态,从而选择加载对应的slot镜像。

GetActiveSlot会获取当前系统启动槽(slot)为0,0表示slot_a,1表示slot_b。由于高通soc平台存在的a/b系统的设计,因此abl阶段会判断当前系统会从哪个slot启动。GetActiveSlot会去读取寄

存器内存放slot_a/b的active状态,默认是slot_a启动,正常情况下只有ota后才会设置为slot_b启动。

STATIC EFI_STATUS
GetActiveSlot (Slot *ActiveSlot)
{
  EFI_STATUS Status = EFI_SUCCESS;
  Slot Slots[] = {{L"_a"}, {L"_b"}};
  UINT64 Priority = 0;

  if (ActiveSlot == NULL) {
    DEBUG ((EFI_D_ERROR, "GetActiveSlot: bad parameter\n"));
    return EFI_INVALID_PARAMETER;
  }

  for (UINTN SlotIndex = 0; SlotIndex < ARRAY_SIZE (Slots); SlotIndex++) {
    //这里只需要知道PartitionEntry结构体内的成员EFI_PARTITION_ENTRY为分区入口地址,lun为启动分区对应的lun卷即可
    struct PartitionEntry *BootPartition =
        GetBootPartitionEntry (&Slots[SlotIndex]);
    UINT64 BootPriority = 0;
    if (BootPartition == NULL) {
      DEBUG ((EFI_D_ERROR, "GetActiveSlot: No boot partition "
                           "entry for slot %s\n",
              Slots[SlotIndex].Suffix));
      return EFI_NOT_FOUND;
    }
    //各种寄存器计算
    BootPriority =
        (BootPartition->PartEntry.Attributes & PART_ATT_PRIORITY_VAL) >>
        PART_ATT_PRIORITY_BIT;

    if ((BootPartition->PartEntry.Attributes & PART_ATT_ACTIVE_VAL) &&
        (BootPriority > Priority)) {
      GUARD (StrnCpyS (ActiveSlot->Suffix, ARRAY_SIZE (ActiveSlot->Suffix),
                       Slots[SlotIndex].Suffix,
                       StrLen (Slots[SlotIndex].Suffix)));
      Priority = BootPriority;
    }
  }
  DEBUG ((EFI_D_VERBOSE, "GetActiveSlot: found active slot %s, priority %d\n",
          ActiveSlot->Suffix, Priority));

  if (IsSuffixEmpty (ActiveSlot) == TRUE) {
    /* Check for first boot and set default slot */
    /* For First boot all A/B attributes for the slot would be 0 */
    UINT64 BootPriority = 0;
    UINT64 RetryCount = 0;
    struct PartitionEntry *SlotA = GetBootPartitionEntry (&Slots[0]);
    if (SlotA == NULL) {
      DEBUG ((EFI_D_ERROR, "GetActiveSlot: First Boot: No boot partition "
                           "entry for slot %s\n",
              Slots[0].Suffix));
      return EFI_NOT_FOUND;
    }

    BootPriority = (SlotA->PartEntry.Attributes & PART_ATT_PRIORITY_VAL) >>
                   PART_ATT_PRIORITY_BIT;
    RetryCount = (SlotA->PartEntry.Attributes & PART_ATT_MAX_RETRY_COUNT_VAL) >>
                 PART_ATT_MAX_RETRY_CNT_BIT;

    if ((SlotA->PartEntry.Attributes & PART_ATT_ACTIVE_VAL) == 0 &&
        (SlotA->PartEntry.Attributes & PART_ATT_SUCCESSFUL_VAL) == 0 &&
        (SlotA->PartEntry.Attributes & PART_ATT_UNBOOTABLE_VAL) == 0 &&
        BootPriority == 0) {

      DEBUG ((EFI_D_INFO, "GetActiveSlot: First boot: set "
                          "default slot _a\n"));
      SlotA->PartEntry.Attributes &=
          (~PART_ATT_SUCCESSFUL_VAL & ~PART_ATT_UNBOOTABLE_VAL);
      SlotA->PartEntry.Attributes |=
          (PART_ATT_PRIORITY_VAL | PART_ATT_ACTIVE_VAL |
           PART_ATT_MAX_RETRY_COUNT_VAL);

      GUARD (StrnCpyS (ActiveSlot->Suffix, ARRAY_SIZE (ActiveSlot->Suffix),
                       Slots[0].Suffix, StrLen (Slots[0].Suffix)));
      UpdatePartitionAttributes (PARTITION_ATTRIBUTES);
      FirstBoot = TRUE;
      return EFI_SUCCESS;
    }

    DEBUG ((EFI_D_ERROR, "GetActiveSlot: No active slot found\n"));
    DEBUG ((EFI_D_ERROR, "GetActiveSlot: Slot attr: Priority %ld, Retry "
                         "%ld, Active %ld, Success %ld, unboot %ld\n",
            BootPriority, RetryCount,
            (SlotA->PartEntry.Attributes & PART_ATT_ACTIVE_VAL) >>
                PART_ATT_ACTIVE_BIT,
            (SlotA->PartEntry.Attributes & PART_ATT_SUCCESSFUL_VAL),
            (SlotA->PartEntry.Attributes & PART_ATT_UNBOOTABLE_VAL)));

    return EFI_NOT_FOUND;
  }

  return EFI_SUCCESS;
}
  1. GetRebootReason

GetRebootReason根据函数名称就能猜到,是获取本次重启的原因,并且会对读取重启原因变量BootReason进行判断,如果是进入fastboot或者recovery的话那么就会将对应的属性值设置为true,

这里我们只需要知道:如果进行客制化需求实现,例如判断reboot reason从而执行某些操作,可以利用GetRebootReason (&BootReason)这个函数即可。

STATIC UINT8
GetRebootReason (UINT32 *ResetReason)
{
  EFI_RESETREASON_PROTOCOL *RstReasonIf;
  EFI_STATUS Status;

  Status = gBS->LocateProtocol (&gEfiResetReasonProtocolGuid, NULL,
                                (VOID **)&RstReasonIf);
  if (Status != EFI_SUCCESS) {
    DEBUG ((EFI_D_ERROR, "Error locating the reset reason protocol\n"));
    return Status;
  }

  RstReasonIf->GetResetReason (RstReasonIf, ResetReason, NULL, NULL);
  if (RstReasonIf->Revision >= EFI_RESETREASON_PROTOCOL_REVISION)
    RstReasonIf->ClearResetReason (RstReasonIf);
  return Status;
}
  1. RecoveryInit

RecoveryInit 会对misc分区内的数据进行解析,如果解析到的misc分区字段存在boot-recovery的话,会将BootIntoRecovery标志设置为TRUE,在LoadImageAndAuth内会对这个标志进行判断。

struct RecoveryMessage {
  CHAR8 command[32];
  CHAR8 status[32];
  CHAR8 recovery[1024];
};


EFI_STATUS
RecoveryInit (BOOLEAN *BootIntoRecovery)
{
  EFI_STATUS Status;
  struct RecoveryMessage *Msg = NULL;
  //misc分区的guid地址
  EFI_GUID Ptype = gEfiMiscPartitionGuid;
  MemCardType CardType = UNKNOWN;
  VOID *PartitionData = NULL;
  UINT32 PageSize;

  CardType = CheckRootDeviceType ();
  if (CardType == NAND) {
    Status = GetNandMiscPartiGuid (&Ptype);
    if (Status != EFI_SUCCESS) {
      return Status;
    }
  }

  GetPageSize (&PageSize);

  /* Get the first 2 pages of the misc partition.
   * If the device type is NAND then read the recovery message from page 1,
   * Else read from the page 0
   */
  Status = ReadFromPartition (&Ptype, (VOID **)&PartitionData, (PageSize * 2));
  if (Status != EFI_SUCCESS) {
    DEBUG ((EFI_D_ERROR, "Error Reading from misc partition: %r\n", Status));
    return Status;
  }

  if (!PartitionData) {
    DEBUG ((EFI_D_ERROR, "Error in loading Data from misc partition\n"));
    return EFI_INVALID_PARAMETER;
  }

  Msg = (CardType == NAND) ?
           (struct RecoveryMessage *) ((CHAR8 *) PartitionData + PageSize) :
           (struct RecoveryMessage *) PartitionData;
  // Ensure NULL termination
  Msg->command[sizeof (Msg->command) - 1] = '\0';
  if (Msg->command[0] != 0 && Msg->command[0] != 255)
    DEBUG ((EFI_D_VERBOSE, "Recovery command: %d %a\n", sizeof (Msg->command),
            Msg->command));
  
  //判断msg内的command属性值,如果为boot-recovery的话,那么BootIntoRecovery为true
  if (!AsciiStrnCmp (Msg->command, RECOVERY_BOOT_RECOVERY,
                       AsciiStrLen (RECOVERY_BOOT_RECOVERY))) {
    *BootIntoRecovery = TRUE;
  }
  
  //判断设备是否打开了动态分区,并且判断misc分区内的command是否为boot-fastboot,是的话则设定为进入recovery模式,而后在进入fastboot模式(后者是假设)
  /* Boot recovery partition to start userspace fastboot */
  if ( IsDynamicPartitionSupport () &&
       !AsciiStrnCmp (Msg->command, RECOVERY_BOOT_FASTBOOT,
                          AsciiStrLen (RECOVERY_BOOT_FASTBOOT))) {
    *BootIntoRecovery = TRUE;
  }

  FreePool (PartitionData);
  PartitionData = NULL;
  Msg = NULL;

  return Status;
}
  1. LoadImageAndAuth

LoadImageAndAuth 会传入一个BootInfo类型的变量&Info,Info内的MultiSlotBoot、BootIntoRecovery以及BootReasonAlarm,并且会查找可启动slot、进行avb校验等。由于代码量大,因此选择对每个函数进行截取单独分析

5.1 FindBootableSlot

FindBootableSlot针对可启动slot进行各种寄存器值的判断,以及通过设定一个retry count来统计slot启动次数,厂商可以通过判断retry count来进行功能添加,如重启超过多少次则判定为slot无法起订,另外添加切换slot功能,让系统继续尝试重启等。

EFI_STATUS
FindBootableSlot (Slot *BootableSlot)
{
  EFI_STATUS Status = EFI_SUCCESS;
  struct PartitionEntry *BootEntry = NULL;
  UINT64 Unbootable = 0;
  UINT64 BootSuccess = 0;
  UINT64 RetryCount = 0;

  if (BootableSlot == NULL) {
    DEBUG ((EFI_D_ERROR, "FindBootableSlot: input parameter invalid\n"));
    return EFI_INVALID_PARAMETER;
  }
  //获取当前被激活的slot,默认为a
  GUARD (GetActiveSlot (BootableSlot));
  //根据GetActiveSlot返回的激活slot,去寻找对应的boot分区索引
  /* Validate Active Slot is bootable */
  BootEntry = GetBootPartitionEntry (BootableSlot);
  if (BootEntry == NULL) {
    DEBUG ((EFI_D_ERROR, "FindBootableSlot: No boot partition entry "
                         "for slot %s\n",
            BootableSlot->Suffix));
    return EFI_NOT_FOUND;
  }
  //gpt分区内的寄存器值获取
  Unbootable = (BootEntry->PartEntry.Attributes & PART_ATT_UNBOOTABLE_VAL) >>
               PART_ATT_UNBOOTABLE_BIT;
  BootSuccess = (BootEntry->PartEntry.Attributes & PART_ATT_SUCCESSFUL_VAL) >>
                PART_ATT_SUCCESS_BIT;
  RetryCount =
      (BootEntry->PartEntry.Attributes & PART_ATT_MAX_RETRY_COUNT_VAL) >>
      PART_ATT_MAX_RETRY_CNT_BIT;
  //如果当前slot之前没有被设置过unbootable标志,并且已经成功启动过了。那么就不需要做后续判断
  if (Unbootable == 0 && BootSuccess == 1) {
    DEBUG (
        (EFI_D_VERBOSE, "Active Slot %s is bootable\n", BootableSlot->Suffix));
  } else if (Unbootable == 0 && BootSuccess == 0 && RetryCount > 0) {
    //判断是否打开了ab分区计数切换宏AB_RETRYCOUNT_DISABLE,有些厂商会在这里进行系统异常后自行切换slot的功能添加
    if ((!IsABRetryCountDisabled () &&
        !IsBootDevImage ()) &&
      IsABRetryCountUpdateRequired ()) {
      RetryCount--;
      BootEntry->PartEntry.Attributes &= ~PART_ATT_MAX_RETRY_COUNT_VAL;
      BootEntry->PartEntry.Attributes |= RetryCount
                                         << PART_ATT_MAX_RETRY_CNT_BIT;
      UpdatePartitionAttributes (PARTITION_ATTRIBUTES);
      DEBUG ((EFI_D_INFO, "Active Slot %s is bootable, retry count %ld\n",
              BootableSlot->Suffix, RetryCount));
    } else {
      DEBUG ((EFI_D_INFO, "A/B retry count NOT decremented\n"));
    }
  } else {
    DEBUG ((EFI_D_INFO, "Slot %s is unbootable, trying alternate slot\n",
            BootableSlot->Suffix));
    //当前slot尝试重启次数已经超过了设定的retry count,将当前slot设置为unbootable
    GUARD_OUT (HandleActiveSlotUnbootable ());
  }

  /* Validate slot suffix and partition guids */
  if (Status == EFI_SUCCESS) {
    GUARD_OUT (ValidateSlotGuids (BootableSlot));
  }
  MarkPtnActive (BootableSlot->Suffix);
out:
  if (Status != EFI_SUCCESS) {
    /* clear bootable slot */
    BootableSlot->Suffix[0] = '\0';
  }
  return Status;
}
     

5.2 LoadImageAndAuthVB2

LoadImageAndAuthxxx,这个xxx主要取决于GetAVBVersion返回的结果,通过switch函数判断当前系统应该进行那种类型的avb校验,当前我用的是android 10,默认为avb2,那么就进入LoadImageAndAuthVB2。由于avb部分不是专门做系统安全的朋友一般不会接触,因此这里我们简单介绍一下android的avb即可。

android avb分为两个阶段:
1.bootloader阶段:bootloader阶段会对vbmeta、vbmeta_system、boot、dtbo等镜像进行安全性校验,其中vbmeta、vbmeta_system内,这部分是在镜像编译的时候,编译脚本会将待校验的分区的hash值写到分区内,同时也会写到vbmeta分区内,在avb校验的时候vbmeta会根据记录的hash值与待校验分区的进行比较,如果不一致那么就会报错。
2.init阶段:init阶段会对vendor、system、product(实际上就是super分区)进行校验,也可以认为就是hash值。原理应该同bootloader阶段的一样,如果在init阶段校验失败的话,内核会出现dm-verity failed的打印

对于avb我们需要了解的应该就是如下几点:

  1. 编译启动开关:

android/device/qcom/qssi/qssi.mk
# Enable AVB 2.0
BOARD_AVB_ENABLE := true
  1. 对于hash值的计算方式:

andrioid/build/core/Makefile
# vbmeta image
ifeq ($(BOARD_AVB_ENABLE),true)

BUILT_VBMETAIMAGE_TARGET := $(PRODUCT_OUT)/vbmeta.img
AVB_CHAIN_KEY_DIR := $(TARGET_OUT_INTERMEDIATES)/avb_chain_keys

ifdef BOARD_AVB_KEY_PATH
$(if $(BOARD_AVB_ALGORITHM),,$(error BOARD_AVB_ALGORITHM is not defined))
else
# If key path isn't specified, use the 4096-bit test key.
BOARD_AVB_ALGORITHM := SHA256_RSA4096
BOARD_AVB_KEY_PATH := external/avb/test/data/testkey_rsa4096.pem
endif
  1. 了解当前系统运行的是安全熔丝版本还是非熔丝版本,avb对于非熔丝版本的话,即使校验失败也不会影响系统启动。

  1. FastbootInitialize

最后一个主要功能函数就是FastbootInitialize,主函数内如果存在BootIntoFastboot=TRUE的语句的话,那么就会执行goto fastboot,进入fastboot的初始化。

/* Initialize and start fastboot */
EFI_STATUS FastbootInitialize (VOID)
{
  EFI_STATUS Status = EFI_SUCCESS;

  DEBUG ((EFI_D_INFO, "Fastboot Build Info: %a %a\n", __DATE__, __TIME__));
  BootStatsSetTimeStamp (BS_BL_START);

  //枚举usb设备   
  Status = FastbootUsbDeviceStart ();
  if (Status != EFI_SUCCESS) {
    DEBUG ((EFI_D_ERROR, "couldnt Start fastboot usb device, exiting"));
    return Status;
  }
  //屏幕显示fastboot菜单
  DisplayFastbootMenu ();

  //等待usb事件响应,进入fastboot时,需要通过usb进行指令发送,直到我们发送fastboot reboot这个指令,才会退出fastboot模式
  while (1) {
    Status = HandleUsbEvents ();
    if (EFI_ERROR (Status) && (Status != EFI_ABORTED)) {
      DEBUG ((EFI_D_ERROR, "Error, failed to handle USB event\n"));
      break;
    }

    if (FastbootFatal ()) {
      DEBUG ((EFI_D_ERROR, "Continue detected, Exiting App...\n"));
      break;
    }
  }

  //关闭usb时间,退出fastboot模式
  Status = FastbootCmdsUnInit ();
  if (Status != EFI_SUCCESS) {
    DEBUG ((EFI_D_ERROR, "couldnt uninit fastboot\n"));
    return Status;
  }

  ExitMenuKeysDetection ();

  Status = FastbootUsbDeviceStop ();
  return Status;
}

这篇的文章目的就是简单的介绍一下bootloader内的各个重要api,对于里面的一些框架本人还不是特别熟,例如avb解析逻辑、fastboot模式的usb枚举、事件上报等,另外还有一些重要的例如cmdline的构成,如何通过cmdline完成内核驱动的选择性加载,这些后续会更新在abl第二篇文章内。

如有不对,欢迎指出,谢谢

Logo

更多推荐