供应商启动分区

Android 11 引入了通用内核映像 (GKI) 的概念。为了能够使用 GKI 启动任意设备,Android 11 设备可以使用启动映像标头版本 3。在版本 3 中,所有供应商特定的信息都从 boot 分区中分离出来,并重新定位到新的 vendor_boot 分区中。搭载 Android 11 且运行 5.4 Linux 内核的 ARM64 设备必须支持 vendor_boot 分区和更新后的 boot 分区格式,才能通过 GKI 测试。

Android 12 设备可以使用启动映像标头版本 4,该版本支持在 vendor_boot 分区中包含多个供应商 ramdisk。多个供应商 ramdisk 片段在供应商 ramdisk 部分中一个接一个地连接起来。供应商 ramdisk 表用于描述供应商 ramdisk 部分的布局以及每个供应商 ramdisk 片段的元数据。

分区结构

供应商启动分区使用虚拟 A/B 进行 A/B 分区,并受 Android Verified Boot 保护。

版本 3

该分区由标头、供应商 ramdisk 和设备树 Blob (DTB) 组成。

部分 页数
供应商启动标头(n 页) n = (2112 + page_size - 1) / page_size
供应商 ramdisk(o 页) o = (vendor_ramdisk_size + page_size - 1) / page_size
DTB(p 页) p = (dtb_size + page_size - 1) / page_size

版本 4

该分区由标头、供应商 ramdisk 部分(由所有供应商 ramdisk 片段连接而成)、设备树 Blob (DTB) 和供应商 ramdisk 表组成。

部分 页数
供应商启动标头(n 页) n = (2128 + page_size - 1) / page_size
供应商 ramdisk 片段(o 页) o = (vendor_ramdisk_size + page_size - 1) / page_size
DTB(p 页) p = (dtb_size + page_size - 1) / page_size
供应商 ramdisk 表(q 页) q = (vendor_ramdisk_table_size + page_size - 1) / page_size
Bootconfig(r 页) r = (bootconfig_size + page_size - 1) / page_size

供应商启动标头

供应商启动分区标头的内容主要由从启动映像标头重新定位到此处的数据组成。它还包含有关供应商 ramdisk 的信息。

版本 3

struct vendor_boot_img_hdr_v3
{
#define VENDOR_BOOT_MAGIC_SIZE 8
    uint8_t magic[VENDOR_BOOT_MAGIC_SIZE];
    uint32_t header_version;
    uint32_t page_size;           /* flash page size we assume */

    uint32_t kernel_addr;         /* physical load addr */
    uint32_t ramdisk_addr;        /* physical load addr */

    uint32_t vendor_ramdisk_size; /* size in bytes */

#define VENDOR_BOOT_ARGS_SIZE 2048
    uint8_t cmdline[VENDOR_BOOT_ARGS_SIZE];

    uint32_t tags_addr;           /* physical addr for kernel tags */

#define VENDOR_BOOT_NAME_SIZE 16
    uint8_t name[VENDOR_BOOT_NAME_SIZE]; /* asciiz product name */
    uint32_t header_size;         /* size of vendor boot image header in
                                   * bytes */
    uint32_t dtb_size;            /* size of dtb image */
    uint64_t dtb_addr;            /* physical load address */

};

版本 4

struct vendor_boot_img_hdr_v4
{
#define VENDOR_BOOT_MAGIC_SIZE 8
    uint8_t magic[VENDOR_BOOT_MAGIC_SIZE];
    uint32_t header_version;
    uint32_t page_size;           /* flash page size we assume */

    uint32_t kernel_addr;         /* physical load addr */
    uint32_t ramdisk_addr;        /* physical load addr */

    uint32_t vendor_ramdisk_size; /* size in bytes */

#define VENDOR_BOOT_ARGS_SIZE 2048
    uint8_t cmdline[VENDOR_BOOT_ARGS_SIZE];

    uint32_t tags_addr;           /* physical addr for kernel tags */

#define VENDOR_BOOT_NAME_SIZE 16
    uint8_t name[VENDOR_BOOT_NAME_SIZE]; /* asciiz product name */
    uint32_t header_size;         /* size of vendor boot image header in
                                   * bytes */
    uint32_t dtb_size;            /* size of dtb image */
    uint64_t dtb_addr;            /* physical load address */

    uint32_t vendor_ramdisk_table_size; /* size in bytes for the vendor ramdisk table */
    uint32_t vendor_ramdisk_table_entry_num; /* number of entries in the vendor ramdisk table */
    uint32_t vendor_ramdisk_table_entry_size; /* size in bytes for a vendor ramdisk table entry */
    uint32_t bootconfig_size; /* size in bytes for the bootconfig section */
};

#define VENDOR_RAMDISK_TYPE_NONE 0
#define VENDOR_RAMDISK_TYPE_PLATFORM 1
#define VENDOR_RAMDISK_TYPE_RECOVERY 2
#define VENDOR_RAMDISK_TYPE_DLKM 3

struct vendor_ramdisk_table_entry_v4
{
    uint32_t ramdisk_size; /* size in bytes for the ramdisk image */
    uint32_t ramdisk_offset; /* offset to the ramdisk image in vendor ramdisk section */
    uint32_t ramdisk_type; /* type of the ramdisk */
#define VENDOR_RAMDISK_NAME_SIZE 32
    uint8_t ramdisk_name[VENDOR_RAMDISK_NAME_SIZE]; /* asciiz ramdisk name */

#define VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE 16
    // Hardware identifiers describing the board, soc or platform which this
    // ramdisk is intended to be loaded on.
    uint32_t board_id[VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE];
};
  • vendor_ramdisk_size 是所有供应商 ramdisk 片段的总大小。
  • ramdisk_type 表示 ramdisk 的类型,可能的值为
    • VENDOR_RAMDISK_TYPE_NONE 表示未指定值。
    • VENDOR_RAMDISK_TYPE_PLATFORM ramdisk 包含平台特定的位。引导加载程序必须始终将这些位加载到内存中。
    • VENDOR_RAMDISK_TYPE_RECOVERY ramdisk 包含恢复资源。当启动进入恢复模式时,引导加载程序必须将这些资源加载到内存中。
    • VENDOR_RAMDISK_TYPE_DLKM ramdisk 包含动态可加载内核模块。
  • ramdisk_name 是 ramdisk 的唯一名称。
  • board_id 是供应商定义的硬件标识符的向量。

引导加载程序支持

由于供应商启动分区包含先前存在于启动分区中的信息(例如,闪存页大小、内核、ramdisk 加载地址、DTB 本身),因此引导加载程序必须访问启动分区和供应商启动分区,才能获得足够的数据来完成启动。

引导加载程序必须将通用 ramdisk 加载到内存中,紧跟供应商 ramdisk 之后(CPIO、Gzip 和 lz4 格式支持这种类型的连接)。请勿对通用 ramdisk 映像进行页面对齐,也不要在内存中通用 ramdisk 映像和供应商 ramdisk 结尾之间引入任何其他空间。内核解压缩后,会将连接的文件解压缩到 initramfs 中,这将生成一个文件结构,该文件结构是覆盖在供应商 ramdisk 文件结构上的通用 ramdisk。

由于通用 ramdisk 和供应商 ramdisk 将被连接,因此它们必须采用相同的格式。GKI 启动映像使用 lz4 压缩的通用 ramdisk,因此符合 GKI 标准的设备必须使用 lz4 压缩的供应商 ramdisk。此配置如下所示。

对 bootconfig 提供支持的引导加载程序要求在实现 Bootconfig中进行了解释。

多个供应商 ramdisk(版本 4)

借助启动映像标头版本 4,引导加载程序可以选择加载供应商 ramdisk 的子集或全部,作为启动期间的 initramfs。供应商 ramdisk 表包含每个 ramdisk 的元数据,可以帮助引导加载程序决定要加载哪些 ramdisk。引导加载程序可以决定加载所选供应商 ramdisk 的顺序,只要通用 ramdisk 最后加载即可。

例如,引导加载程序可以省略在正常启动期间加载类型为 VENDOR_RAMDISK_TYPE_RECOVERY 的供应商 ramdisk 以节省资源,因此只有类型为 VENDOR_RAMDISK_TYPE_PLATFORMVENDOR_RAMDISK_TYPE_DLKM 的供应商 ramdisk 会加载到内存中。另一方面,当启动进入恢复模式时,类型为 VENDOR_RAMDISK_TYPE_PLATFORMVENDOR_RAMDISK_TYPE_RECOVERYVENDOR_RAMDISK_TYPE_DLKM 的供应商 ramdisk 会加载到内存中。

或者,引导加载程序可以忽略供应商 ramdisk 表并加载整个供应商 ramdisk 部分。这与加载 vendor_boot 分区中的所有供应商 ramdisk 片段具有相同的效果。

构建支持

要为设备实现供应商启动支持

  • BOARD_BOOT_HEADER_VERSION 设置为 3 或更高版本。

  • 如果您的设备符合 GKI 标准,或者以其他方式使用 lz4 压缩的通用 ramdisk,请将 BOARD_RAMDISK_USE_LZ4 设置为 true

  • 考虑到必须放在供应商 ramdisk 上的内核模块,请将 BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE 设置为适合您设备的大小。

  • 更新 AB_OTA_PARTITIONS 以包含 vendor_boot 和设备上任何供应商特定的 OTA 分区列表。

  • 将您的设备 fstab 复制到 vendor_boot 分区中的 /first_stage_ramdisk 中,而不是 boot 分区中。例如,$(LOCAL_PATH)/fstab.hardware:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/first_stage_ramdisk/fstab.$(PRODUCT_PLATFORM)

要在 vendor_boot 中包含多个供应商 ramdisk

  • BOARD_BOOT_HEADER_VERSION 设置为 4
  • BOARD_VENDOR_RAMDISK_FRAGMENTS 设置为要包含在 vendor_boot 中的逻辑供应商 ramdisk 片段名称的列表。

  • 要添加预构建的供应商 ramdisk,请将 BOARD_VENDOR_RAMDISK_FRAGMENT.$(vendor_ramdisk).PREBUILT 设置为预构建路径。

  • 要添加 DLKM 供应商 ramdisk,请将 BOARD_VENDOR_RAMDISK_FRAGMENT.$(vendor_ramdisk).KERNEL_MODULE_DIRS 设置为要包含的内核模块目录的列表。

  • BOARD_VENDOR_RAMDISK_FRAGMENT.$(vendor_ramdisk).MKBOOTIMG_ARGS 设置为 mkbootimg 参数。这些是供应商 ramdisk 片段的 --board_id[0-15]--ramdisk_type 参数。对于 DLKM 供应商 ramdisk,如果未另行指定,则默认 --ramdisk_type 将为 DLKM

要将恢复资源构建为 vendor_boot 中的独立 recovery ramdisk

  • BOARD_BOOT_HEADER_VERSION 设置为 4
  • BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT 设置为 true
  • BOARD_INCLUDE_RECOVERY_RAMDISK_IN_VENDOR_BOOT 设置为 true
  • 这会添加一个供应商 ramdisk 片段,其 ramdisk_namerecoveryramdisk_typeVENDOR_RAMDISK_TYPE_RECOVERY。然后,ramdisk 包含所有恢复文件,这些文件是在 $(TARGET_RECOVERY_ROOT_OUT) 下安装的文件。

mkbootimg 参数

参数 说明
--ramdisk_type ramdisk 的类型,可以是 NONEPLATFORMRECOVERYDLKM 之一。
--board_id[0-15] 指定 board_id 向量,默认为 0

以下是配置示例

BOARD_KERNEL_MODULE_DIRS := foo bar baz
BOARD_BOOT_HEADER_VERSION := 4
BOARD_VENDOR_RAMDISK_FRAGMENTS := dlkm_foobar
BOARD_VENDOR_RAMDISK_FRAGMENT.dlkm_foobar.KERNEL_MODULE_DIRS := foo bar
BOARD_VENDOR_RAMDISK_FRAGMENT.dlkm_foobar.MKBOOTIMG_ARGS := --board_id0 0xF00BA5 --board_id1 0xC0FFEE

生成的 vendor_boot 将包含两个供应商 ramdisk 片段。第一个是“default”ramdisk,其中包含 DLKM 目录 baz$(TARGET_VENDOR_RAMDISK_OUT) 中的其余文件。第二个是 dlkm_foobar ramdisk,其中包含 DLKM 目录 foobar,并且 --ramdisk_type 默认为 DLKM