אופטימיזציה של DTO

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

שורת הפקודה של הליבה

שורת הפקודה המקורית של הליבה בעץ המכשיר (DT) נמצאת בצומת chosen/bootargs. תוכנת האתחול צריכה לשרשר את המיקום הזה עם מקורות אחרים של שורת הפקודה בליבה (kernel):

/dts-v1/;

/ {
  chosen: chosen {
    bootargs = "...";
  };
};

ל-DTO אין אפשרות לשרשר ערכים מ-DT הראשי ומ-DT שכבת-העל, לכן צריך להוסיף את שורת הפקודה של הליבה של ה-DT הראשי לקובץ chosen/bootargs ואת שורת הפקודה של הליבה של ה-DT שכבת-העל לקובץ chosen/bootargs_ext. לאחר מכן, Bootloader יכול לשרשר את המיקומים האלה ולהעביר את התוצאה לליבה.

main.dts שכבת-על.dts
/dts-v1/;

/ {
  chosen: chosen {
    bootargs = "...";
  };
};
/dts-v1/;
/plugin/;

&chosen {
  bootargs_ext = "...";
};

libufdt

הגרסה העדכנית של libfdt תומכת ב-DTO, אבל מומלץ להשתמש ב-libufdt כדי להטמיע DTO (המקור ב-AOSP נמצא בכתובת platform/system/libufdt). ‏libufdt יוצר מבנה עץ אמיתי (עץ מכשיר לא שטוח, או ufdt) מעץ המכשיר השטוח (FDT), כך שהוא יכול לשפר את המיזוג של שני קובצי .dtb מ-O(N2) ל-O(N), כאשר N הוא מספר הצמתים בעץ.

בדיקת ביצועים

בבדיקות הפנימיות של Google, השימוש ב-libufdt ב-2,405 צמתים של .dtb וב-283 צמתים של .dtbo DT מניב קבצים בגודל 70,618 ו-8,566 בייטים אחרי הידור. בהשוואה להטמעה של DTO שנויד מ-FreeBSD (זמן ריצה של 124 אלפיות השנייה), libufdt זמן הריצה של DTO הוא 10 אלפיות השנייה.

בדיקת ביצועים של מכשירי Pixel בהשוואה בין libufdt ל-libfdt. ההשפעה של מספר הצמתים הבסיסיים דומה אבל כוללת את ההבדלים הבאים:

  • 500 פעולות שכבת-על (הוספה או שינוי) – הפרש זמן של פי 6 עד פי 8
  • 1,000 פעולות שכבת-על (הוספה או שינוי) נמשכות פי 8 עד פי 10 יותר זמן

דוגמה שבה מספר הוספת התווים מוגדר כ-X:

איור 1. מספר הוספות הקובץ הוא X.

דוגמה שבה המספר שקובע את הערך שמחליף את הערך שמוגדר כברירת מחדל מוגדר כ-X:

איור 2. המספר שקובע הוא X.

libufdt פותח באמצעות ממשקי API וחלק ממבני הנתונים של libfdt. כשמשתמשים ב-libufdt, צריך לכלול ולקשר את libfdt (עם זאת, בקוד אפשר להשתמש ב-API של libfdt כדי להפעיל את DTB או DTBO).

libufdt DTO API

ה-API הראשי ל-DTO ב-libufdt הוא:

struct fdt_header *ufdt_apply_overlay(
        struct fdt_header *main_fdt_header,
        size_t main_fdt_size,
        void *overlay_fdt,
        size_t overlay_size);

הפרמטר main_fdt_header הוא ה-DT הראשי ו-overlay_fdt הוא מאגר הזיכרון שמכיל את התוכן של הקובץ .dtbo. ערך ההחזרה הוא מאגר חדש שמכיל את ה-DT הממוזג (או null במקרה של שגיאה). ה-DT הממוזג מעוצב בפורמט FDT, שאפשר להעביר לליבה כשמפעילים אותה.

המאגר החדש מהערך המוחזר נוצר על ידי dto_malloc(), שצריך להטמיע כשמעבירים את libufdt למחולל האתחול. להטמעות לדוגמה, אפשר לעיין במאמר sysdeps/libufdt_sysdeps_*.c.

הגבלות על צומת בסיס

אי אפשר ליצור שכבת-על של צומת או מאפיין חדשים בצומת השורש של ה-DT הראשי כי פעולות שכבת-על מסתמכות על תוויות. מכיוון ש-DT הראשי חייב להגדיר תווית ו-DT שכבת-העל מקצה את הצמתים שעליהן יוצגו תוויות, אי אפשר להקצות תווית לצומת הבסיס (ולכן אי אפשר להציג שכבת-על על צומת הבסיס).

ספקי SoC חייבים להגדיר את יכולת ההוספה של שכבות-על ב-DT הראשי. יצרני ODM/OEM יכולים רק להוסיף או לשנות צמתים עם תוויות שהוגדרו על ידי ספק ה-SoC. כדי לפתור את הבעיה הזו, אפשר להגדיר צומת odm מתחת לצומת הרמה הבסיסית (root) ב-DT הבסיסי, וכך לאפשר לכל צומתי ה-ODM בשכבת-העל של DT להוסיף צמתים חדשים. לחלופין, אפשר להוסיף את כל הצמתים שקשורים ל-SoC ב-DT הבסיסי לצומת soc מתחת לצומת הבסיס, כפי שמתואר בהמשך:

main.dts overlay.dts
/dts-v1/;

/ {
    compatible = "corp,bar";
    ...

    chosen: chosen {
        bootargs = "...";
    };

    /* nodes for all soc nodes */
    soc {
        ...
        soc_device@0: soc_device@0 {
            compatible = "corp,bar";
            ...
        };
        ...
    };

    odm: odm {
        /* reserved for overlay by odm */
    };
};
/dts-v1/;
/plugin/;

/ {
};

&chosen {
    bootargs_ex = "...";
};

&odm {
    odm_device@0 {
        ...
    };
    ...
};

שימוש בשכבות-על דחוסות

ב-Android 9 נוספה תמיכה בשימוש בשכבות-על דחוסות בתמונה של DTBO כשמשתמשים בגרסה 1 של כותרת טבלת DT. כשמשתמשים בכותרת DTBO v1, ארבעת הביטים המשמעותיים הכי פחות בשדה הדגלים ב-dt_table_entry מציינים את פורמט הדחיסה של רשומת ה-DT.

struct dt_table_entry_v1 {
  uint32_t dt_size;
  uint32_t dt_offset;  /* offset from head of dt_table_header */
  uint32_t id;         /* optional, must be zero if unused */
  uint32_t rev;        /* optional, must be zero if unused */
  uint32_t flags;      /* For version 1 of dt_table_header, the 4 least significant bits
                        of 'flags' are used to indicate the compression
                        format of the DT entry as per the enum 'dt_compression_info' */
  uint32_t custom[3];  /* optional, must be zero if unused */
};

בשלב הזה יש תמיכה בקודקים zlib ו-gzip.

enum dt_compression_info {
    NO_COMPRESSION,
    ZLIB_COMPRESSION,
    GZIP_COMPRESSION
};

ב-Android 9 נוספה תמיכה בבדיקת שכבות-על דחוסות לבדיקת VtsFirmwareDtboVerification, כדי לעזור לכם לוודא שהאפליקציה של שכבת-העל תקינה.

דוגמה להטמעה של DTO

בהוראות הבאות מוסבר איך מטמיעים DTO עם libufdt (קוד לדוגמה בהמשך).

הוראות לדוגמה בנושא DTO

  1. לכלול ספריות. כדי להשתמש ב-libufdt, צריך לכלול את libfdt למבנים של נתונים ולממשקי API:
    #include <libfdt.h>
    #include <ufdt_overlay.h>
    
  2. טעינת DT הראשי ו-DT שכבת-העל. טוענים את .dtb ואת .dtbo מהאחסון לזיכרון (השלבים המדויקים תלויים בתכנון). בשלב הזה, מאגר הנתונים הזמני והגודל צריכים להיות .dtb/.dtbo:
    main_size = my_load_main_dtb(main_buf, main_buf_size)
    
    overlay_size = my_load_overlay_dtb(overlay_buf, overlay_buf_size);
    
  3. מניחים שכבת-על על ה-DTs:
    1. משתמשים ב-ufdt_install_blob() כדי לקבל את כותרת ה-FDT ל-DT הראשי:
      main_fdt_header = ufdt_install_blob(main_buf, main_size);
      main_fdt_size = main_size;
      
    2. קוראים ל-ufdt_apply_overlay() ב-DTO כדי לקבל DT ממוזג בפורמט FDT:
      merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                      overlay_buf, overlay_size);
      
    3. משתמשים ב-merged_fdt כדי לקבל את הגודל של dtc_totalsize():
      merged_fdt_size = dtc_totalsize(merged_fdt);
      
    4. מעבירים את ה-DT הממוזג כדי להפעיל את הליבה:
      my_kernel_entry(0, machine_type, merged_fdt);
      

קוד DTO לדוגמה

#include <libfdt.h>
#include <ufdt_overlay.h>

…

{
  struct fdt_header *main_fdt_header;
  struct fdt_header *merged_fdt;

  /* load main dtb into memory and get the size */
  main_size = my_load_main_dtb(main_buf, main_buf_size);

  /* load overlay dtb into memory and get the size */
  overlay_size = my_load_overlay_dtb(overlay_buf, overlay_buf_size);

  /* overlay */
  main_fdt_header = ufdt_install_blob(main_buf, main_size);
  main_fdt_size = main_size;
  merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                  overlay_buf, overlay_size);
  merged_fdt_size = dtc_totalsize(merged_fdt);

  /* pass to kernel */
  my_kernel_entry(0, machine_type, merged_fdt);
}