在早期的 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 前需要设置 initramfs
,bootconfig
位于 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;调用 UpdateBootConfigParams
将 androidboot
参数添加到 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 链表中,当然还得更新 CmdLineLen
和 BootConfigLen
。