יצירת ממשק HAL

חובה להשתמש ב-HIDL כדי לתאר את כל דגלי הבנייה שמשמשים להידור מותנה של ה-framework. צריך לקבץ את דגלי ה-build הרלוונטיים ולכלול אותם בקובץ .hal אחד. השימוש ב-HIDL לציון פריטי הגדרה כולל את היתרונות הבאים:

  • עם ניהול גרסאות (כדי להוסיף פריטי הגדרה חדשים, ספקים או יצרני ציוד מקורי צריכים להרחיב באופן מפורש את HAL)
  • מתועד היטב
  • בקרת גישה באמצעות SELinux
  • בדיקת תקינות של פריטי ההגדרה באמצעות הכלי לבדיקת ספקים (בדיקת טווח, בדיקת תלות הדדית בין פריטים וכו')
  • ממשקי API שנוצרו באופן אוטומטי ב-C++‎ וב-Java

זיהוי של דגלי build שמשמשים את המסגרת

מתחילים בזיהוי הגדרות הבנייה שמשמשות להידור מותנה של המסגרת, ואז מבטלים הגדרות מיושנות כדי לצמצם את הקבוצה. לדוגמה, קבוצת דגלי הבנייה הבאה מזוהה עבור surfaceflinger:

  • TARGET_USES_HWC2
  • TARGET_BOARD_PLATFORM
  • TARGET_DISABLE_TRIPLE_BUFFERING
  • TARGET_FORCE_HWC_FOR_VIRTUAL_DISPLAYS
  • NUM_FRAMEBUFFER_SURFACE_BUFFERS
  • TARGET_RUNNING_WITHOUT_SYNC_FRAMEWORK
  • VSYNC_EVENT_PHASE_OFFSET_NS
  • SF_VSYNC_EVENT_PHASE_OFFSET_NS
  • PRESENT_TIME_OFFSET_FROM_VSYNC_NS
  • MAX_VIRTUAL_DISPLAY_DIMENSION

יצירת ממשק HAL

הגישה להגדרות build של מערכת משנה מתבצעת דרך ממשק HAL, ואילו הממשקים למתן ערכי הגדרה מקובצים בחבילת HAL‏ android.hardware.configstore (בגרסה 1.0 נכון לעכשיו). לדוגמה, כדי ליצור קובץ ממשק HAL בשביל surfaceflinger, ב-hardware/interfaces/configstore/1.0/ISurfaceFlingerConfigs.hal:

package android.hardware.configstore@1.0;

interface ISurfaceFlingerConfigs {
    // TO-BE-FILLED-BELOW
};

אחרי שיוצרים את הקובץ .hal, מריצים את הפקודה hardware/interfaces/update-makefiles.sh כדי להוסיף את הקובץ .hal החדש לקבצים Android.bp ו-Android.mk.

הוספת פונקציות לסימון build

לכל דגל build, מוסיפים פונקציה חדשה לממשק. לדוגמה, ב-hardware/interfaces/configstore/1.0/ISurfaceFlingerConfigs.hal:

interface ISurfaceFlingerConfigs {
    disableTripleBuffering() generates(OptionalBool ret);
    forceHwcForVirtualDisplays() generates(OptionalBool ret);
    enum NumBuffers: uint8_t {
        USE_DEFAULT = 0,
        TWO = 2,
        THREE = 3,
    };
    numFramebufferSurfaceBuffers() generates(NumBuffers ret);
    runWithoutSyncFramework() generates(OptionalBool ret);
    vsyncEventPhaseOffsetNs generates (OptionalUInt64 ret);
    presentTimeOffsetFromSyncNs generates (OptionalUInt64 ret);
    maxVirtualDisplayDimension() generates(OptionalInt32 ret);
};

כשמוסיפים פונקציה:

  • השם צריך להיות תמציתי. לא כדאי להמיר שמות של משתני makefile לשמות של פונקציות, וחשוב לזכור שאין יותר צורך בתחיליות TARGET_ ו-BOARD_.
  • הוספת תגובות עוזרים למפתחים להבין את המטרה של פריט ההגדרה, איך הוא משנה את ההתנהגות של המסגרת, מהם הערכים התקינים ומידע רלוונטי אחר.

סוגי ההחזרה של הפונקציות יכולים להיות Optional[Bool|String|Int32|UInt32|Int64|UInt64]. הסוגים מוגדרים ב-types.hal באותה ספרייה, והם עוטפים ערכים פרימיטיביים בשדה שמציין אם הערך מוגדר על ידי HAL. אם לא, נעשה שימוש בערך ברירת המחדל.

struct OptionalString {
    bool specified;
    string value;
};

כשמתאים, מגדירים את ה-enum שמייצג בצורה הכי טובה את הסוג של פריט ההגדרה ומשתמשים ב-enum הזה כסוג ההחזרה. בדוגמה שלמעלה, סוג הנתונים המנויים NumBuffers מוגדר להגבלת מספר הערכים התקפים. כשמגדירים סוגי נתונים מותאמים אישית כאלה, מוסיפים שדה או ערך enum (לדוגמה, USE_DEFAULT) כדי לציין אם הערך מוגדר על ידי HAL או לא.

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

לדוגמה, אפשרות לצבירה של שני דגלי בנייה למבנה יחיד ב-hardware/interfaces/configstore/1.0/ISurfaceFlingerConfigs.hal היא:

 interface ISurfaceFlingerConfigs {
    // other functions here
    struct SyncConfigs {
        OptionalInt64 vsyncEventPhaseoffsetNs;
        OptionalInt64 presentTimeoffsetFromSyncNs;
    };
    getSyncConfigs() generates (SyncConfigs ret);
    // other functions here
};

חלופות לפונקציית HAL יחידה

כחלופה לשימוש בפונקציית HAL אחת לכל דגלי ה-build, ממשק ה-HAL מספק גם פונקציות פשוטות כמו getBoolean(string key) ו-getInteger(string key). הזוגות key=value בפועל מאוחסנים בקבצים נפרדים, ושירות ה-HAL מספק ערכים על ידי קריאה או ניתוח של הקבצים האלה.

הגישה הזו קלה להגדרה, אבל היא לא כוללת את היתרונות של HIDL (ניהול גרסאות, תיעוד קל, בקרת גישה) ולכן לא מומלצת.

ממשק יחיד וממשקים מרובים

העיצוב של ממשק HAL לפריטי הגדרה מציג שתי אפשרויות:

  • ממשק יחיד שכולל את כל פריטי ההגדרה
  • ממשקים מרובים, שכל אחד מהם כולל קבוצה של פריטי הגדרה קשורים

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

בגלל הבעיות האלה, מערכת Android משתמשת בכמה ממשקים עם ממשק HAL יחיד לקבוצה של פריטי הגדרה קשורים. לדוגמה, ISurfaceflingerConfigs לפריטי הגדרה שקשורים ל-surfaceflinger, ו-IBluetoothConfigs לפריטי הגדרה שקשורים ל-Bluetooth.