Tworzenie interfejsu HAL

Do opisywania wszystkich flag kompilacji używanych do warunkowego kompilowania platformy musisz używać HIDL. Odpowiednie flagi kompilacji muszą być zgrupowane i umieszczone w jednym pliku .hal. Używanie HIDL do określania elementów konfiguracji ma następujące zalety:

  • Wersjonowany (aby dodać nowe elementy konfiguracji, dostawcy/producenci OEM muszą wyraźnie rozszerzyć HAL)
  • Dobrze udokumentowane
  • Kontrola dostępu za pomocą SELinux
  • Sprawdzenie poprawności elementów konfiguracji za pomocą zestawu testów dostawcy (sprawdzenie zakresu, wzajemnej zależności między elementami itp.).
  • Automatycznie generowane interfejsy API w językach C++ i Java

Określanie flag kompilacji używanych przez platformę

Zacznij od zidentyfikowania konfiguracji kompilacji używanych do warunkowej kompilacji platformy, a następnie porzuć przestarzałe konfiguracje, aby zmniejszyć ich liczbę. Na przykład dla surfaceflinger zidentyfikowano ten 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

Konfiguracje kompilacji dla podsystemu są dostępne 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 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 do flag kompilacji

Do każdej flagi kompilacji dodaj nową funkcję do interfejsu. 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:

  • Używaj zwięzłych nazw. Unikaj przekształcania nazw zmiennych pliku makefile w nazwy funkcji i pamiętaj, że prefiksy TARGET_BOARD_ nie są już potrzebne.
  • Dodaj komentarze. Pomóż deweloperom zrozumieć przeznaczenie elementu konfiguracji, sposób, w jaki zmienia on działanie platformy, prawidłowe wartości i inne istotne informacje.

Typy zwracane przez funkcje mogą być Optional[Bool|String|Int32|UInt32|Int64|UInt64]. Typy są zdefiniowane w types.hal w tym samym katalogu i zawierają wartości pierwotne z polem, 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 odpowiednich przypadkach zdefiniuj wyliczenie, które najlepiej reprezentuje typ elementu konfiguracji, i użyj go jako typu zwracanego. W powyższym przykładzie wyliczenie NumBuffers zostało zdefiniowane w celu ograniczenia liczby prawidłowych wartości. Podczas definiowania takich niestandardowych typów danych dodaj pole lub wartość wyliczeniową (np. USE_DEFAULT), aby oznaczyć, czy wartość jest określona przez HAL.

Nie jest konieczne, aby pojedyncza flaga kompilacji stała się pojedynczą funkcją w HIDL. Właściciele modułów mogą też agregować ściśle powiązane flagi kompilacji w strukturę i mieć funkcję, która zwraca tę strukturę (może to zmniejszyć liczbę wywołań funkcji).

Na przykład opcja agregowania 2 flag kompilacji w jedną strukturę w hardware/interfaces/configstore/1.0/ISurfaceFlingerConfigs.hal to:

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

Alternatywy dla pojedynczej funkcji HAL

Interfejs HAL udostępnia też proste funkcje, takie jak getBoolean(string key)getInteger(string key), które mogą być używane zamiast jednej funkcji HAL dla wszystkich flag kompilacji. Rzeczywiste pary key=value są przechowywane w oddzielnych plikach, a usługa HAL udostępnia wartości, odczytując i parsując te pliki.

Chociaż to podejście jest łatwe do zdefiniowania, nie obejmuje korzyści zapewnianych przez HIDL (wymuszone wersjonowanie, łatwość dokumentowania, kontrola dostępu) i dlatego nie jest zalecane.

Jeden i wiele interfejsów

Projekt interfejsu HAL dla elementów konfiguracji ma 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 obsłudze, ale może stać się trudny w utrzymaniu, gdy do jednego pliku zostanie dodanych więcej elementów konfiguracji. Dodatkowo kontrola dostępu nie jest szczegółowa, więc proces, który ma dostęp do interfejsu, może odczytywać wszystkie elementy konfiguracji (nie można przyznać dostępu do częściowego zestawu 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 w przypadku elementów konfiguracji związanych z surfaceflingerIBluetoothConfigs w przypadku elementów konfiguracji związanych z Bluetooth.