供应商启动分区

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 启动时验证功能的保护。

版本 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 包含平台专用位。引导加载程序必须始终将这些 ramdisk 加载到内存中。
    • VENDOR_RAMDISK_TYPE_RECOVERY ramdisk 包含恢复资源。启动到恢复模式时,引导加载程序必须将这些 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 分区(而不是 boot 分区)的 /first_stage_ramdisk 中。例如,$(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_namerecoveryramdisk_typeVENDOR_RAMDISK_TYPE_RECOVERY 的供应商 ramdisk 片段。该 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