Tworzenie interfejsu HAL

Musisz użyć HIDL, aby opisać wszystkie flagi kompilacji używane do warunkowego kompilowania frameworka. Odpowiednie flagi kompilacji muszą być pogrupowane i zawarowane w pojedynczym pliku .hal. Korzystanie z HIDL do określania elementów konfiguracji ma te zalety:

  • z wersją (aby dodać nowe elementy konfiguracji, producenci/OEM muszą wyraźnie rozszerzyć HAL);
  • dobrze udokumentowany,
  • Kontrola dostępu za pomocą SELinux
  • Sprawdzanie poprawności elementów konfiguracji za pomocą pakietu Vendor Test Suite (sprawdzanie zakresu, sprawdzanie wzajemnych zależności między elementami itp.).
  • automatycznie generowane interfejsy API w językach C++ i Java;

Określanie flag kompilacji używanych przez framework

Najpierw zidentyfikuj konfiguracje kompilacji używane do warunkowego kompilowania frameworka, a potem odrzuć przestarzałe konfiguracje, aby zmniejszyć zestaw. Na przykład w przypadku surfaceflinger zidentyfikowano następujący zestaw flag kompilacji:

  • 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

Tworzenie interfejsu HAL

Do konfiguracji kompilacji podsystemu można uzyskać dostęp za pomocą interfejsu HAL, a interfejsy do podawania wartości konfiguracji są zgrupowane w pakiecie HALandroid.hardware.configstore (obecnie w wersji 1.0). Aby na przykład utworzyć plik interfejsu HAL dla surfaceflinger, w pliku hardware/interfaces/configstore/1.0/ISurfaceFlingerConfigs.hal:

package android.hardware.configstore@1.0;

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

Po utworzeniu pliku .hal uruchom polecenie hardware/interfaces/update-makefiles.sh, aby dodać nowy plik .hal do plików Android.bp i Android.mk.

Dodawanie funkcji dla flag kompilacji

Dodaj do interfejsu nową funkcję dla każdej flagi kompilacji. Na przykład w przypadku: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);
};

Podczas dodawania funkcji:

  • Zadbaj o zwięzłość nazw. Unikaj konwertowania nazw zmiennych w makefile na nazwy funkcji i pamiętaj, że prefiksy TARGET_BOARD_ nie są już potrzebne.
  • Dodawanie komentarzy Pomagaj deweloperom zrozumieć cel elementu konfiguracji, sposób, w jaki zmienia on działanie frameworku, prawidłowe wartości i inne istotne informacje.

Typy zwracane przez funkcję mogą być takie: Optional[Bool|String|Int32|UInt32|Int64|UInt64]. Typy są definiowane w types.hal w tym samym katalogu i owijają wartości prymitywne za pomocą pola, które wskazuje, czy wartość jest określona przez HAL. Jeśli nie, używana jest wartość domyślna.

struct OptionalString {
    bool specified;
    string value;
};

W razie potrzeby zdefiniuj typ wyliczeniowy, który najlepiej odpowiada typowi elementu konfiguracji, i użyj go jako typu zwracanego. W przykładzie powyżej enumeracja NumBuffers jest zdefiniowana w celu ograniczenia liczby prawidłowych wartości. Podczas definiowania takich niestandardowych typów danych dodaj pole lub wartość typu enumeracji (np. USE_DEFAULT), aby wskazać, czy wartość jest określona przez HAL.

Nie jest konieczne, aby jedna flaga kompilacji była jedną funkcją w HIDL. Właściciele modułów mogą też zgrupować ze sobą powiązane flagi kompilacji w strukturze i utworzyć funkcję, która zwraca tę strukturę (może to zmniejszyć liczbę wywołań funkcji).

Na przykład opcja zliczania 2 flag kompilacji w jedną strukturę w 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
};

Odpowiedniki pojedynczej funkcji HAL

Jako alternatywę dla używania jednej funkcji HAL dla wszystkich flag kompilacji interfejs HAL udostępnia też proste funkcje, takie jak getBoolean(string key)getInteger(string key). Rzeczywiste pary key=value są przechowywane w osobnych plikach, a usługa HAL udostępnia wartości, odczytując i analizując te pliki.

Chociaż to podejście jest łatwe do zdefiniowania, nie zapewnia zalet HIDL (wymuszone wersjonowanie, łatwość dokumentacji, kontrola dostępu), dlatego nie jest zalecane.

Pojedynczy i wielokrotnie interfejsy

Interfejs HAL dla elementów konfiguracji oferuje 2 opcje:

  • Jeden interfejs obejmujący wszystkie elementy konfiguracji
  • wiele interfejsów, z których każdy obejmuje zestaw powiązanych elementów konfiguracji;

Pojedynczy interfejs jest łatwiejszy w użyciu, ale może stać się niewykonalny do utrzymania, gdy dodawane są do niego kolejne elementy konfiguracji. Ponadto kontrola dostępu nie jest szczegółowa, więc proces, któremu przyznano dostęp do interfejsu, może odczytać wszystkie elementy konfiguracji (nie można przyznać dostępu do częściowego zbioru elementów konfiguracji). Jeśli dostęp nie zostanie przyznany, nie będzie można odczytać elementów konfiguracji.

Z tego powodu Android używa wielu interfejsów z jednym interfejsem HAL dla grupy powiązanych elementów konfiguracji. Na przykład:ISurfaceflingerConfigs dla elementów konfiguracji związanych z surfaceflinger oraz IBluetoothConfigs dla elementów konfiguracji związanych z Bluetooth.