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

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

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

מבנה המחיצה

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

גרסה 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 = (dtb_size + page_size - 1) / page_size

גרסה 4

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

קטע מספר הדפים
כותרת אתחול של ספק (n דפים) n = (2128 + page_size - 1) / page_size
Vendor ramdisk fragments (o pages) o = (vendor_ramdisk_size + page_size - 1) / page_size
DTB (דפים) p = (dtb_size + page_size - 1) / page_size
טבלת RAMDisk של הספק (q דפים) q = (vendor_ramdisk_table_size + page_size - 1) / page_size
Bootconfig (r pages) 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 ramdisks מכילים ביטים ספציפיים לפלטפורמה. טוען האתחול תמיד צריך לטעון אותם לזיכרון.
    • VENDOR_RAMDISK_TYPE_RECOVERY דיסקים וירטואליים של RAM מכילים משאבי שחזור. ה-bootloader צריך לטעון את הקבצים האלה לזיכרון כשמבצעים אתחול למצב שחזור.
    • VENDOR_RAMDISK_TYPE_DLKM ramdisks מכילים מודולים דינמיים של ליבת מערכת ההפעלה שאפשר לטעון.
  • ramdisk_name הוא שם ייחודי של ה-ramdisk.
  • board_id הוא וקטור של מזהי חומרה שהוגדרו על ידי הספק.

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

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

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

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

הדרישות של תוכנת האתחול לתמיכה ב-bootconfig מוסברות במאמר הטמעה של Bootconfig.

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

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

לדוגמה, במהלך אתחול רגיל, טוען האתחול יכול לדלג על טעינה של ramdisk של ספק מסוג VENDOR_RAMDISK_TYPE_RECOVERY כדי לחסוך במשאבים, כך שרק ramdisk של ספק מסוג 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).

כדי לכלול כמה קובצי ramdisk של ספקים ב-vendor_boot:

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

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

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

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

כדי ליצור משאבי שחזור כ-recovery ramdisk עצמאי ב-vendor_boot:

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

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

ארגומנט תיאור
--ramdisk_type סוג ה-ramdisk, יכול להיות אחד מהערכים 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). השני הוא dlkm_foobar ramdisk, שמכיל את ספריות ה-DLKM‏ foo ו-bar, והערך שמוגדר כברירת מחדל ל---ramdisk_type הוא DLKM.