מחיצות אתחול של ספקים

ב-Android 11 הושקה רעיון הליבה (kernel) הכללית תמונה (GKI). כדי להפעיל אתחול של מכשיר שרירותי באמצעות GKI, Android 11 מכשירים יכולים להשתמש בגרסה 3 של כותרת תמונת האתחול. לחשבון בגרסה 3, כל המידע הספציפי לספק נלקח מתוך boot מחיצה והועברה למחיצה חדשה של vendor_boot. מכשיר ARM64 שמריץ Android 11 בליבה של Linux 5.4 חייב לתמוך במחיצה vendor_boot ובפורמט המחיצה המעודכן boot כדי לעבור את הבדיקה עם GKI.

במכשירי Android 12 אפשר להשתמש בכותרת של קובץ אימג' האתחול בגרסה 4, שמאפשרת לכלול כמה דיסקים של RAM של ספקים במחיצה vendor_boot. מקטעי ramdisk של ספקים משורשרים אחד אחרי השני בקטע ramdisk של הספק. טבלת ramdisk של ספק משמשת לתיאור הפריסה של החלק ramdisk של הספק והמטא-נתונים של כל ramdisk של ספק מקטע.

מבנה החלוקה

מחיצה האתחול של הספק מוגדרת כמחיצה A/B עם מחיצה A/B וירטואלית ומוגנת על ידי Android Verified Boot.

גרסה 3

המחיצה מורכבת מכותרת, מ-ramdisk של הספק ומ-blob של עץ המכשיר (DTB).

קטע מספר הדפים
כותרת האתחול של הספק (n דפים) n = (2112 + page_size - 1) / page_size
Vendor ramdisk (o pages) 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
טבלת Vendor 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 מציין את סוג הרדיסק. הערכים האפשריים הם:
    • הערך VENDOR_RAMDISK_TYPE_NONE מציין שהערך לא צוין.
    • VENDOR_RAMDISK_TYPE_PLATFORM דיסקים של זיכרון RAM מכילים ביטים ספציפיים לפלטפורמה. תוכנת האתחול חייבת תמיד לטעון אותן לזיכרון.
    • VENDOR_RAMDISK_TYPE_RECOVERY תיקיות ramdisk עם משאבי שחזור. מנהל האתחול צריך לטעון אותם לזיכרון כשמפעילים את המכשיר במצב שחזור.
    • VENDOR_RAMDISK_TYPE_DLKM דיסקים של זיכרון RAM מכילים מודולים דינמיים של ליבה שניתנים לטעינה.
  • ramdisk_name הוא שם ייחודי של דיסק ה-RAM.
  • board_id הוא וקטור של מזהים של חומרה שהוגדרו על ידי הספק.

תמיכה בתוכנת אתחול

מכיוון שמחיצת האתחול של הספק מכילה מידע (כגון גודל דף Flash, כתובות ליבה (kernel), לעומסי ramdisk, ה-DTB עצמו) שהיו קיימים בעבר מחיצת האתחול, תוכנת האתחול חייבת לגשת גם לאתחול וגם לאתחול של הספק למחיצות כך שיהיו מספיק נתונים להשלמת האתחול.

תוכנת האתחול צריכה לטעון את ה-ramdisk הגנרי לזיכרון מיד אחרי ה-ramdisk של הספק (הפורמטים CPIO,‏ Gzip ו-lz4 תומכים בסוג הזה של שרשור). אין ליישר דף לתמונה של ה-Radisk הגנרית או להוסיף רווח אחר ביניהם לבין סוף ה-Radisk של הספק בזיכרון. אחרי מבטל דחיסה של הליבה, הוא מחלץ את הקובץ המשורשר ל-initramfs, שהתוצאה שלו הוא מבנה קובץ שהוא רדיסק גנרי שמוצג כשכבת-על את מבנה קובץ ramdisk של הספק.

מכיוון שה-ramdisk הגנרי ורדיסק הספק משורשרים, הם חייבים להיות אותו פורמט. בתמונת האתחול של GKI נעשה שימוש ב-ramdisk גנרי דחוס ב-lz4, ולכן במכשיר שתואם ל-GKI צריך להשתמש ב-ramdisk של ספק דחוס ב-lz4. בהגדרות האלה מוצגות בהמשך.

הדרישות של תוכנת האתחול לתמיכה בתצורת אתחול מוסברות ב יישום bootconfig.

ramdisk של ספקים מרובים (גרסה 4)

בגרסה 4 של כותרת תמונת האתחול, תוכנת האתחול יכולה לבחור קבוצת משנה או את כל ה-ramdisks של הספקים שייטען כ-initramfs במהלך זמן האתחול. טבלת ramdisk של ספקים מכילה את המטא-נתונים של כל ramdisk, ויכולה לסייע תוכנת אתחול שמחליטה אילו רדיסקים לטעון. מנהל האתחול יכול לקבוע את הסדר שבו נטענים דיסקי ה-RAMdisk של הספקים שנבחרו, כל עוד דיסק ה-RAMdisk הגנרי נטען אחרון.

לדוגמה, כדי לחסוך במשאבים, מנהל האתחול יכול להשמיט את הטעינה של דיסקים זמניים של ספקים מסוג VENDOR_RAMDISK_TYPE_RECOVERY במהלך אתחול רגיל, כך שרק דיסקים זמניים של ספקים מסוג VENDOR_RAMDISK_TYPE_PLATFORM ו-VENDOR_RAMDISK_TYPE_DLKM נטענים בזיכרון. לעומת זאת, ספק מקלטים מסוג VENDOR_RAMDISK_TYPE_PLATFORM, VENDOR_RAMDISK_TYPE_RECOVERY ו-VENDOR_RAMDISK_TYPE_DLKM נטענים לזיכרון כשמפעילים את המכשיר לשחזור במצב 'סינון תוכן'.

לחלופין, תוכנת האתחול יכולה להתעלם מטבלת ה-ramdisk של הספק ולטעון את כל הקטע של Ramdisk של הספק. הפעולה הזו משפיעה על הטעינה של כל את מקטעי ה-ramdisk של הספק במחיצה vendor_boot.

בניית תמיכה

כדי להטמיע תמיכה באתחול של ספק במכשיר:

  • מגדירים את BOARD_BOOT_HEADER_VERSION לערך 3 או יותר.

  • מגדירים את BOARD_RAMDISK_USE_LZ4 לערך true אם המכשיר תואם ל-GKI, או אם הוא משתמש ב-ramdisk גנרי בלחץ lz4.

  • הגדרת BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE בגודל שמתאים בהתאם למודולים של הליבה שצריך להפעיל ב-ramdisk של הספק.

  • מעדכנים את AB_OTA_PARTITIONS כך שיכלול את vendor_boot וכל מידע ספציפי לספק. רשימות של מחיצות OTA במכשיר.

  • מעתיקים את המכשיר fstab אל /first_stage_ramdisk במחיצה vendor_boot, ולא במחיצה boot. לדוגמה, $(LOCAL_PATH)/fstab.hardware:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/first_stage_ramdisk/fstab.$(PRODUCT_PLATFORM).

כדי לכלול כמה דיסקים של זיכרון RAM של ספקים ב-vendor_boot:

  • מגדירים את BOARD_BOOT_HEADER_VERSION לערך 4.
  • מגדירים את BOARD_VENDOR_RAMDISK_FRAGMENTS כרשימה של שמות של קטעי ramdisk לוגיים של הספק שצריך לכלול ב-vendor_boot.

  • כדי להוסיף דיסק RAM מובנה של ספק, מגדירים את BOARD_VENDOR_RAMDISK_FRAGMENT.$(vendor_ramdisk).PREBUILT לנתיב המובנה.

  • כדי להוסיף RAMdisk של ספק DLKM, מגדירים BOARD_VENDOR_RAMDISK_FRAGMENT.$(vendor_ramdisk).KERNEL_MODULE_DIRS ל- רשימה של ספריות מודולי ליבה (kernel) שצריך לכלול.

  • הגדרה של BOARD_VENDOR_RAMDISK_FRAGMENT.$(vendor_ramdisk).MKBOOTIMG_ARGS לערך mkbootimg ארגומנטים. אלה הארגומנטים --board_id[0-15] ו---ramdisk_type של קטע ה-ramdisk של הספק. ב-ramdisk של ספק DLKM, אם לא צוין אחרת, ברירת המחדל של --ramdisk_type תהיה DLKM.

כדי ליצור משאבי שחזור כ-Radisk נפרד ב-recovery ב-vendor_boot:

  • מגדירים את BOARD_BOOT_HEADER_VERSION לערך 4.
  • מגדירים את BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT להיות true.
  • מגדירים את BOARD_INCLUDE_RECOVERY_RAMDISK_IN_VENDOR_BOOT לערך true.
  • הפעולה הזו מוסיפה קטע של דיסק RAM של ספק, שבו הערך של ramdisk_name הוא recovery ו-ramdisk_type הוא VENDOR_RAMDISK_TYPE_RECOVERY. לאחר מכן, דיסק ה-RAM מכיל את כל קובצי השחזור, שהם קבצים שמותקנים בתיקייה $(TARGET_RECOVERY_ROOT_OUT).

ארגומנטים של mkbootimg

ארגומנט תיאור
--ramdisk_type הסוג של דיסק ה-RAM יכול להיות אחד מהערכים הבאים: NONE,‏ PLATFORM,‏ RECOVERY או DLKM.
--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 של הספק. הראשון הוא "ברירת המחדל" ramdisk, שמכיל את ספריית DLKM baz וגם שאר הקבצים ב-$(TARGET_VENDOR_RAMDISK_OUT). השנייה היא ramdisk dlkm_foobar, שמכיל את ספריות ה-DLKM foo ו-bar, וגם ברירת המחדל של --ramdisk_type היא DLKM.