Android Bootconfig 说明

在早期的 Android 版本中,我们在 bootloader 阶段传递内核启动参数的方法是:构建 androidboot.* 内核参数,然后传递到 kernel cmdline。init 进程起来后,会将 androidboot.* 转换为 ro.boot.* 属性。

在 Android 12 中,新增 bootconfig 功能,用于取代 kernel cmdline 传递内核启动参数的方法。将 androidboot.* 属性移至 bootconfig 文件,将 Android 用户空间的配置参数与内核的配置参数分开。

In Android 12, the bootconfig feature replaces the androidboot.* kernel cmdline options in use with Android 11 and lower. The bootconfig feature is a mechanism for passing configuration details from the build and bootloader to Android 12.

前提条件

使用 Bootconfig 功能,需要内核和 Android 用户空间都必须支持 bootconfig

  • 具有此功能的第一个 Android 版本:Android 12
  • 具有此功能的第一个内核版本:5.4.xx 内核

使用方法

bootconfig 格式与 kernel cmdline 的格式有差异:

  • 参数必须由换行转义字符 \n 分隔,而不是使用空格。

Bootloader 阶段

Bootloader 阶段的配置可以参考以下两笔提交实现。第一笔提交是将 boot header version 支持到最新版本,示例修改中是支持到 V4;第二笔提交主要包含两个内容:一是添加 bootconfig 处理机制,二是示例如何在运行时添加 androidboot.* 属性。

Build

需要对 bootconfig 做一下编译适配。第一笔修改是修改 vendor_boot.img header 为 V4 ,第二笔是将 androidboot.* 加到 bootconfig,然后将 bootconfig 添加到 cmdline。

  • Use (or uprev to) the vendor boot header version v4.

    diff --git a/shared/BoardConfig.mk b/shared/BoardConfig.mk
    index f5d421a..e9bccf0 100644
    --- a/shared/BoardConfig.mk
    +++ b/shared/BoardConfig.mk
    @@ -221,7 +221,7 @@
     endif
     
     BOARD_INCLUDE_DTB_IN_BOOTIMG := true
    -BOARD_BOOT_HEADER_VERSION := 3
    +BOARD_BOOT_HEADER_VERSION := 4
     BOARD_MKBOOTIMG_ARGS += --header_version $(BOARD_BOOT_HEADER_VERSION)
     PRODUCT_COPY_FILES += \
         device/google/cuttlefish/dtb.img:dtb.img \
    
  • Add bootconfig to the kernel cmdline and move selected parameters to bootconfig.

    diff --git a/shared/BoardConfig.mk b/shared/BoardConfig.mk
    index e9bccf0..70f2301 100644
    --- a/shared/BoardConfig.mk
    +++ b/shared/BoardConfig.mk
    @@ -195,7 +195,7 @@
     BOARD_KERNEL_CMDLINE += firmware_class.path=/vendor/etc/
     
     BOARD_KERNEL_CMDLINE += init=/init
    -BOARD_KERNEL_CMDLINE += androidboot.hardware=cutf_cvm
    +BOARD_BOOTCONFIG += androidboot.hardware=cutf_cvm
     
     # TODO(b/176860479): Remove once goldfish and cuttlefish share a wifi implementation
     BOARD_KERNEL_CMDLINE += mac80211_hwsim.radios=0
    @@ -213,13 +213,14 @@
         vmw_vsock_virtio_transport_common.virtio_transport_max_vsock_pkt_buf_size=16384
     
     ifeq ($(TARGET_USERDATAIMAGE_FILE_SYSTEM_TYPE),f2fs)
    -BOARD_KERNEL_CMDLINE += androidboot.fstab_suffix=f2fs
    +BOARD_BOOTCONFIG += androidboot.fstab_suffix=f2fs
     endif
     
     ifeq ($(TARGET_USERDATAIMAGE_FILE_SYSTEM_TYPE),ext4)
    -BOARD_KERNEL_CMDLINE += androidboot.fstab_suffix=ext4
    +BOARD_BOOTCONFIG += androidboot.fstab_suffix=ext4
     endif
     
    +BOARD_KERNEL_CMDLINE += bootconfig
     BOARD_INCLUDE_DTB_IN_BOOTIMG := true
     BOARD_BOOT_HEADER_VERSION := 4
     BOARD_MKBOOTIMG_ARGS += --header_version $(BOARD_BOOT_HEADER_VERSION)
    

注意事项

使用 bootconfig 需要注意以下几点:

  • androidboot.* 参数都应该添加到 bootconfig 中,可以通过 /proc/bootconfig 查看。同时,/proc/cmdline 中不应该存在 androidboot.* 参数。如果 /proc/cmdline 中有存在 androidboot.* 参数,VTS 测试将无法通过。

  • bootconfig 中的 androidboot.* 会被自动转换为 ro.boot.* 属性,我们不需要进行额外的适配。

  • boot header 应该被更新到 V4:

    - BOARD_BOOT_HEADER_VERSION := 3
    + BOARD_BOOT_HEADER_VERSION := 4
    
  • 添加 bootconfig kernel cmdline 参数。

    BOARD_KERNEL_CMDLINE += bootconfig
    
  • 添加 androidboot.* 参数到 bootconfig,而不是 cmdline。

    - BOARD_KERNEL_CMDLINE += androidboot..selinux=enforcing
    + BOARD_BOOTCONFIG += androidboot..selinux=enforcing
    

Bootloader Changes

Bootloader 在跳转到 kernel 前需要设置 initramfsbootconfig 位于 initramfs 的末端。kernel boot configureation 在 initramfs 中搜索 bootconfig 。

在这里插入图片描述

Bootloader 阶段会在内存中创建 bootconfig section,bootconfig section 包含以下内容:

  • Parameters
  • 4 B size parameters size
  • 12 B bootconfig magic string (#BOOTCONFIG\n

参数(Parameters)来源有两处:

  • Parameters known at build time,即编译时已知的参数。这部分被打包在 vendor_boot image 的 bootconfig 区域,bootconfig 大小被保存在 vendor boot header 字段: vendor_bootconfig_size
  • Parameters that aren’t known at build time,即 bootloader 加载阶段的 bootconfig 参数,这一部分会在 bootloader 阶段追加到 bootconfig 参数末尾。

添加 bootconfig 参数

通常情况下,我们需要在 bootloader 阶段添加 androidboot.* 参数到 cmdline,比如 boardid hwid 等。按照最新要求,我们得把这些参数从 cmdline 移除,并且全部添加到 bootconfig 。接下来我们看看,如何在 bootconfig 中添加新的 androidboot.* 参数。

The parameters that aren’t known at build time are only known at runtime in the bootloader. These must be added to the end of the bootconfig parameters section before the bootconfig trailer is applied.

If you need to add any parameters after the bootconfig trailer has been applied, overwrite the trailer and reapply it.

高通平台

对于高通平台,bootloader 阶段就是 abl 阶段。abl 源码路径:/bootable/bootloader/edk2

如流程图所示,高通平台 BootLinux 中会去调用 UpdateCmdline ,而 UpdateCmdline 最主要的操作就是:调用 UpdateCmdlineParams 将内核参数添加到 cmdline;调用 UpdateBootConfigParamsandroidboot 参数添加到 bootconfig

graph TB
LinuxLoaderEntry[LinuxLoaderEntry] --> BootLinux[BootLinux] --> UpdateCmdLine[UpdateCmdLine];

UpdateCmdLine --> UpdateCmdLineParams[UpdateCmdLineParams];
UpdateCmdLine --> UpdateBootConfigParams[UpdateBootConfigParams]

看一下这两个函数原型。

/********************************************************************
 @Param: cmdline 参数列表集合,包含所有cmdline参数(包括 androidboot.* 参数)
 @FinalCmdLine:用于保存需要输出到 /proc/cmdline 的参数
*********************************************************************/
STATIC EFI_STATUS UpdateCmdLineParams (UpdateCmdLineParamList *Param, CHAR8 **FinalCmdLine)

/********************************************************************
 @BootConfigListHead: 用于保存 androidboot.* 参数的链表,每一个结点对应一个 androidboot.* 参数
 @BootConfigLen: bootconfig length
 @FinalBootConfig: 保存最终的 bootconfig,bootconfig中每一个 androidboot.* 属性以 '\n' 切割。
 @FinalBootConfigLen: 最终的 bootconfiglist 长度
*********************************************************************/
EFI_STATUS UpdateBootConfigParams (LIST_ENTRY *BootConfigListHead, UINT32 BootConfigLen, CHAR8 **FinalBootConfig, UINT32 	  									   *FinalBootConfigLen)
    

androidboot.fstab_suffix=default 说明,如何新增一个 androidboot.* 参数到 bootconfig。

注意:我们可以将整个 androidboot.fstab_suffix=default 作为一个整体传给 bootconfig ,也可以以键值对的形式传递给 bootconfig。高通代码逻辑里面将 androidboot.fstab_suffix= 作为 key,将 default 作为 value。

  • UpdateCmdLineParamList 结构体中,新增一个结构体变量,该参数对应 androidboot.* 参数。

    /*file: bootable/bootloader/edk2/QcomModulePkg/Include/Library/UpdateCmdLine.h */
    typedef struct UpdateCmdLineParamList {
    	BOOLEAN Recovery;
        BOOLEAN MultiSlotBoot;
        BOOLEAN AlarmBoot;
        ...
        CONST CHAR8 *AndroidBootFstabSuffix; // " androidboot.fstab_suffix=" ==> key
        ...
        CHAR8 *DtbIdxStr;
        CHAR8 *LEVerityCmdLine;
        CHAR8 *FstabSuffix;  // default/emmc  ==> value
        UINT32 HeaderVersion;
    } UpdateCmdLineParamList;
    
  • 定义新增加参数的内容。

    // 注意:androidboot.* 前有空格
    STATIC CONST CHAR8 *AndroidBootFstabSuffix = " androidboot.fstab_suffix=";
    
  • 将自定义的参数输出到 cmdline,bootconfig。

    /*Update command line: appends boot information to the original commandline
     *that is taken from boot image header*/
    EFI_STATUS
    UpdateCmdLine (CONST CHAR8 *CmdLine, CHAR8 *FfbmStr, BOOLEAN Recovery, BOOLEAN AlarmBoot,
                   CONST CHAR8 *VBCmdLine, CHAR8 **FinalCmdLine, CHAR8 **FinalBootConfig,
                   UINT32 *FinalBootConfigLen, UINT32 HeaderVersion, VOID *fdt)
    {
    	...
        // 计算参数长度,这里的 AndroidBootFstabSuffix 就是我们定义的const char* 字符串 " androidboot.fstab_suffix="
    	ParamLen = AsciiStrLen (AndroidBootFstabSuffix);
        // 判断该属性是不是 androidboot 参数,即是不是以 androidboot. 开头
    	BootConfigFlag = IsAndroidBootParam (AndroidBootFstabSuffix, ParamLen, HeaderVersion);
        // 根据 BootConfigFlag,更新 CmdLineLen 和 BootConfigLen
        // BootConfigFlag 为 true,BootConfigL += (ParamLen + SIZE_OF_DELIM);  CmdLineL += ParamLen;
        // BootConfigFlag 为 false,CmdLineL += ParamLen; 
        // 需注意:更新 BootConfigLen 时,需要加 SIZE_OF_DELIM 
    	ADD_PARAM_LEN (BootConfigFlag, ParamLen, CmdLineLen, BootConfigLen);
    	GetRootDeviceType (RootDevStr, BOOT_DEV_NAME_SIZE_MAX);
        // 这里是获取 value,即 “default”,并传递给 Param.FstabSuffix
    	if (!AsciiStriCmp (FstabSuffixEmmc, RootDevStr)) {
    		Param.FstabSuffix = FstabSuffixEmmc;
    	} else {
    		Param.FstabSuffix = FstabSuffixDefault;
    	}
    	...
        // 将 " androidboot.fstab_suffix=" 赋值给 Param.AndroidBootFstabSuffix
    	Param.AndroidBootFstabSuffix = AndroidBootFstabSuffix;
        // 将 androidboot 参数添加到 bootconfig
        AddtoBootConfigList (BootConfigFlag, AndroidBootFstabSuffix, Param.FstabSuffix, 
        					 BootConfigListHead, ParamLen, AsciiStrLen (Param.FstabSuffix));
        // 新增加的参数为 " androidboot.fstab_suffix=default" 
        // 前面只是将 key 的长度更新给 CmdLineLen BootConfigLen,这里将 value (default) 更新到 CmdLineLen BootConfigLen
        ADD_PARAM_LEN (BootConfigFlag, AsciiStrLen (Param.FstabSuffix), CmdLineLen, BootConfigLen);
        ...
        // 将所有参数添加到 cmdline
        Status = UpdateCmdLineParams (&Param, FinalCmdLine);
        ...
        // 将所有 androidboot 参数添加到 bootconfig
        Status = UpdateBootConfigParams (BootConfigListHead, BootConfigLen, 
                                         FinalBootConfig, FinalBootConfigLen);
        ...
    }
    
  • 如果是低版本我们需要将 androidboot.* 传递到 cmdline;

    STATIC EFI_STATUS UpdateCmdLineParams (UpdateCmdLineParamList *Param, CHAR8 **FinalCmdLine)
    {
    	CONST CHAR8 *Src;
    	CHAR8 *Dst;
    	UINT32 MaxCmdLineLen = Param->CmdLineLen;
    	...
        // 如果 HeadVersion <= 3, 我们就将 Param->AndroidBootFstabSuffix 加到 cmdline
    	if ((Param->BootDevBuf) && (Param->HeaderVersion <= BOOT_HEADER_VERSION_THREE)) {
    		...
            // 该部分对应的是 " androidboot.fstab_suffix="
         	Src = Param->AndroidBootFstabSuffix;
         	AsciiStrCatS (Dst, MaxCmdLineLen, Src);
            
            // 该部分对应的是 "default"
            Src = Param->FstabSuffix;
    		AsciiStrCatS (Dst, MaxCmdLineLen, Src);
         	...
         }
    }
    
  • 如果想了解 bootconfig 添加过程,下面这些函数应该被看看。

    #define SIZE_OF_DELIM 2
    #define PARAM_DELIM "\n"
    #define ADD_PARAM_LEN(BootConfigFlag, ParamLen, CmdLineL, BootConfigL) \
                         do { \
                           if (BootConfigFlag == FALSE) { \
                             CmdLineL += ParamLen; \
                           } else { \
                             BootConfigL += (ParamLen + SIZE_OF_DELIM); \
                             CmdLineL += ParamLen; \
                           }\
                         } while (0);  
    
    BOOLEAN IsAndroidBootParam (CONST CHAR8 *param, UINT32 ParamLen, UINT32 HeaderVersion)
    {
      if (ParamLen < 12) {
        return FALSE;
      }
      if (HeaderVersion <= BOOT_HEADER_VERSION_THREE) {
        return FALSE;
      }
      if (AsciiStrStr (param, "androidboot.")) {
        return TRUE;
      }
      else {
        return FALSE;
      }
    }
    
    
    VOID AddtoBootConfigList (BOOLEAN BootConfigFlag,
                    CONST CHAR8 *ParamKey,
                    CONST CHAR8 *ParamValue,
                    LIST_ENTRY *list,
                    UINT32 ParamKeyLen,
                    UINT32 ParamValueLen)
    {
      struct BootConfigParamNode* NewNode = NULL;
      if (!BootConfigFlag) {
        return;
      }
      NewNode = (struct BootConfigParamNode *)
                   AllocateBootConfigNode (ParamKeyLen + SIZE_OF_DELIM +
                   SIZE_OF_DELIM + ParamValueLen);
      if (!NewNode) {
        DEBUG ((EFI_D_ERROR, "Failed to add %s to bootconfig! Out of memory\n",
                ParamKey));
        return;
      }
      // 一个结点保存一个参数
      gBS->CopyMem (NewNode->param, (CHAR8*)ParamKey, ParamKeyLen);
      if (ParamValue) {
        gBS->CopyMem (&NewNode->param[ParamKeyLen], (CHAR8*)ParamValue,
                      ParamValueLen);
      }
      NewNode->ParamLen = ChangeFormattoBootConfig (NewNode->param,
                                                   (ParamKeyLen + ParamValueLen));
      // 将结点添加到链表尾部
      InsertTailList (list, &(NewNode->ListNode));
    }
    
    EFI_STATUS UpdateBootConfigParams (LIST_ENTRY *BootConfigListHead,
                            UINT32 BootConfigLen,
                            CHAR8 **FinalBootConfig,
                            UINT32 *FinalBootConfigLen)
    {
     CHAR8* Dst = NULL;
     LIST_ENTRY *Link = BootConfigListHead; 
     struct BootConfigParamNode* Node = NULL;
     BootConfigLen += SIZE_OF_DELIM;
    
     if (BootConfigLen == 0) {
       return EFI_D_ERROR;
     }
    
     if (!BootConfigListHead) {
       return EFI_D_ERROR;
     }
    
     Dst = (CHAR8 *) AllocateZeroPool (BootConfigLen + SIZE_OF_DELIM);
     if (!Dst) {
       return EFI_OUT_OF_RESOURCES;
      }
      Link = GetFirstNode (BootConfigListHead);
      if (!Link) {
        DEBUG ((EFI_D_INFO, "Error in Node entry \n"));
        return EFI_D_ERROR;
      }
    
      gBS->CopyMem (Dst, "\n", SIZE_OF_DELIM);
      
      // 遍历链表,将结点内容 copy 到 DST
      while (!IsNull (BootConfigListHead, Link)) {
        Node = BASE_CR (Link, struct BootConfigParamNode, ListNode);
        if (!Node) {
          DEBUG ((EFI_D_INFO, "Unable to read bsae struct \n"));
          return EFI_INVALID_PARAMETER;
        }
    
        AsciiStrCatS (Dst, BootConfigLen, (CHAR8*)Node->param);
        // 每一个 androidboot.* 参数都以 \n 换行符结束,对应 /proc/cmdline 就是每一个 androidboot.* 参数都是单独的一行
        AsciiStrCatS (Dst, BootConfigLen, "\n");
        Link = GetNextNode (BootConfigListHead, Link);
    
      }
      Dst[AsciiStrLen (Dst) + 1] = '\0';
      // 我们最终需要的就是 FinalBootConfig FinalBootConfigLen
      *FinalBootConfig = Dst;
      *FinalBootConfigLen = AsciiStrLen (Dst) + 1;
      return EFI_SUCCESS;
    }
    

从上面配置 androidboot.fstab_suffix=default 参数的流程可以看出:

如果我们要添加 androidboot.* 属性到 bootconfig ,整个过程无非就是组建 key 和 value,最终调用 AddtoBootConfigList 加到 List 链表中,当然还得更新 CmdLineLenBootConfigLen

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值