版本管理

HIDL 要求對使用 HIDL 編寫的每個接口進行版本控制。發布 HAL 接口後,它會被凍結,並且必須對該接口的新版本進行任何進一步的更改。雖然不能修改給定的已發布接口,但它可以由另一個接口擴展。

HIDL 代碼結構

HIDL 代碼按用戶定義的類型、接口和包進行組織:

  • 用戶定義類型 (UDT) 。 HIDL 提供對一組原始數據類型的訪問,這些數據類型可用於通過結構、聯合和枚舉組成更複雜的類型。 UDT 被傳遞給接口的方法,並且可以在包級別(所有接口通用)或本地接口定義。
  • 接口。作為 HIDL 的基本構建塊,接口由 UDT 和方法聲明組成。接口也可以從另一個接口繼承。
  • 。組織相關的 HIDL 接口及其操作的數據類型。包由名稱和版本標識,包括以下內容:
    • 名為types.hal的數據類型定義文件。
    • 零個或多個接口,每個接口都在自己的.hal文件中。

數據類型定義文件types.hal僅包含 UDT(所有包級 UDT 都保存在單個文件中)。目標語言的表示可用於包中的所有接口。

版本控制理念

HIDL 包(例如android.hardware.nfc )在針對給定版本(例如1.0 )發布後是不可變的;它不能改變。對包中接口的修改或對其 UDT 的任何更改只能在另一個包中進行。

在 HIDL 中,版本控制適用於包級別,而不是接口級別,並且包中的所有接口和 UDT 共享相同的版本。包版本遵循語義版本控制,沒有補丁級別和構建元數據組件。在給定的包中,次要版本凸起意味著包的新版本與舊包向後兼容,主要版本凸起意味著包的新版本與舊包不向後兼容。

從概念上講,一個包可以通過以下幾種方式之一與另一個包相關聯:

  • 一點也不
  • 包級向後兼容的可擴展性。這發生在包的新次要版本升級(下一個增量修訂);新包與舊包具有相同的名稱和主要版本,但次要版本更高。在功能上,新包是舊包的超集,意思是:
    • 父包的頂級接口存在於新包中,儘管這些接口可能有新方法、新接口本地 UDT(下面描述的接口級擴展)和types.hal中的新 UDT。
    • 新接口也可以添加到新包中。
    • 父包的所有數據類型都存在於新包中,並且可以由舊包中的(可能重新實現的)方法處理。
    • 也可以添加新的數據類型以供升級現有接口的新方法或新接口使用。
  • 接口級向後兼容的可擴展性。新包還可以通過包含邏輯分離的接口來擴展原始包,這些接口僅提供附加功能,而不是核心功能。為此,可能需要以下內容:
    • 新包中的接口需要使用舊包的數據類型。
    • 新包中的接口可以擴展一個或多個舊包的接口。
  • 擴展原來的向後不兼容。這是軟件包的主要版本升級,兩者之間不需要任何關聯。在某種程度上,它可以用舊版本包中的類型組合以及舊包接口子集的繼承來表示。

結構化接口

對於結構良好的界面,添加不屬於原始設計的新功能類型應該需要修改 HIDL 界面。相反,如果您可以或期望在界面的兩側進行更改以引入新功能而不更改界面本身,則該界面不是結構化的。

Treble 支持單獨編譯的供應商和系統組件,其中設備上的vendor.imgsystem.img可以單獨編譯。 vendor.imgsystem.img之間的所有交互都必須明確而徹底地定義,以便它們可以繼續工作多年。這包括許多 API 表面,但一個主要表面是 HIDL 用於在system.img / vendor.img邊界上進行進程間通信的 IPC 機制。

要求

通過 HIDL 傳遞的所有數據都必須明確定義。為確保實現和客戶端即使在單獨編譯或獨立開發時也能繼續協同工作,數據必須遵守以下要求:

  • 可以用語義名稱和含義直接在 HIDL 中描述(使用結構體枚舉等)。
  • 可以用 ISO/IEC 7816 等公共標準來描述。
  • 可以通過硬件標准或硬件的物理佈局來描述。
  • 如有必要,可以是不透明的數據(例如公鑰、id 等)。

如果使用不透明數據,則只能由 HIDL 接口的一側讀取。例如,如果vendor.img代碼為system.img上的組件提供了字符串消息或vec<uint8_t>數據,則該數據無法由system.img本身解析;只能傳回vendor.img進行解釋。當將值從vendor.img傳遞到system.img上的供應商代碼或另一個設備時,必須準確描述數據的格式及其解釋方式,並且仍然是接口的一部分

指導方針

您應該能夠僅使用 .hal 文件編寫 HAL 的實現或客戶端(即,您不需要查看 Android 源代碼或公共標準)。我們建議指定確切的所需行為。諸如“一個實現可以做 A 或 B”之類的陳述鼓勵實現與他們一起開發的客戶交織在一起。

HIDL 代碼佈局

HIDL 包括核心和供應商軟件包。

核心 HIDL 接口是由 Google 指定的接口。它們所屬的包以android.hardware.並由子系統命名,可能具有嵌套的命名級別。例如,NFC 包名為android.hardware.nfc ,相機包名為android.hardware.camera 。通常,核心包的名稱為android.hardware. [ name1 ]。[ name2 ]…。除了名稱之外,HIDL 包還有一個版本。例如,包android.hardware.camera可能是3.4版本;這很重要,因為包的版本會影響它在源代碼樹中的位置。

所有核心包都放在構建系統中的hardware/interfaces/下。包android.hardware. [ name1 ].[ name2 ]… at version $m.$n is under hardware/interfaces/name1/name2//$m.$n/ ;包android.hardware.camera版本3.4位於目錄hardware/interfaces/camera/3.4/.包前綴android.hardware.和路徑hardware/interfaces/

非核心(供應商)封裝是由 SoC 供應商或 ODM 生產的。非核心包的前綴是vendor.$(VENDOR).hardware.其中$(VENDOR)是指 SoC 供應商或 OEM/ODM。這映射到樹中的路徑vendor/$(VENDOR)/interfaces (此映射也是硬編碼的)。

完全限定的用戶定義類型名稱

在 HIDL 中,每個 UDT 都有一個完全限定名稱,該名稱由 UDT 名稱、定義 UDT 的包名稱和包版本組成。完全限定名稱僅在聲明類型的實例時使用,而不是在定義類型本身的地方使用。例如,假設包android.hardware.nfc,版本1.0定義了一個名為NfcData的結構。在聲明的位置(無論是在types.hal中還是在接口的聲明中),聲明只是聲明:

struct NfcData {
    vec<uint8_t> data;
};

聲明此類型的實例時(無論是在數據結構中還是作為方法參數),請使用完全限定的類型名稱:

android.hardware.nfc@1.0::NfcData

一般語法是PACKAGE @ VERSION :: UDT ,其中:

  • PACKAGE是 HIDL 包的以點分隔的名稱(例如android.hardware.nfc )。
  • VERSION是包的以點分隔的major.minor-version 格式(例如, 1.0 )。
  • UDT是 HIDL UDT 的點分隔名稱。由於 HIDL 支持嵌套 UDT,並且 HIDL 接口可以包含 UDT(一種嵌套聲明),因此使用點來訪問名稱。

例如,如果在包android.hardware.example版本1.0的通用類型文件中定義了以下嵌套聲明:

// types.hal
package android.hardware.example@1.0;
struct Foo {
    struct Bar {
        // …
    };
    Bar cheers;
};

Bar的完全限定名稱是android.hardware.example@1.0::Foo.Bar 。如果除了在上述包中之外,嵌套聲明還在名為IQuux的接口中:

// IQuux.hal
package android.hardware.example@1.0;
interface IQuux {
    struct Foo {
        struct Bar {
            // …
        };
        Bar cheers;
    };
    doSomething(Foo f) generates (Foo.Bar fb);
};

Bar的完全限定名稱是android.hardware.example@1.0::IQuux.Foo.Bar

在這兩種情況下, Bar只能在Foo的聲明範圍內被稱為Bar 。在包或接口級別,您必須通過Foo引用Bar : Foo.Bar ,如上面方法doSomething的聲明中所示。或者,您可以更詳細地聲明該方法:

// IQuux.hal
doSomething(android.hardware.example@1.0::IQuux.Foo f) generates (android.hardware.example@1.0::IQuux.Foo.Bar fb);

完全限定的枚舉值

如果 UDT 是枚舉類型,則枚舉類型的每個值都有一個完全限定名稱,該名稱以枚舉類型的完全限定名稱開頭,後跟一個冒號,然後是枚舉值的名稱。例如,假設包android.hardware.nfc,版本1.0定義了一個枚舉類型NfcStatus

enum NfcStatus {
    STATUS_OK,
    STATUS_FAILED
};

當引用STATUS_OK時,完全限定名稱是:

android.hardware.nfc@1.0::NfcStatus:STATUS_OK

一般語法是PACKAGE @ VERSION :: UDT : VALUE ,其中:

  • PACKAGE @ VERSION :: UDT與枚舉類型的完全限定名稱完全相同。
  • VALUE是值的名稱。

自動推理規則

不需要指定完全限定的 UDT 名稱。 UDT 名稱可以安全地省略以下內容:

  • 包,例如@1.0::IFoo.Type
  • 包和版本,例如IFoo.Type

HIDL 嘗試使用自動干擾規則來完成名稱(較低的規則編號意味著較高的優先級)。

規則1

如果未提供包和版本,則嘗試本地名稱查找。例子:

interface Nfc {
    typedef string NfcErrorMessage;
    send(NfcData d) generates (@1.0::NfcStatus s, NfcErrorMessage m);
};

本地查找NfcErrorMessage ,找到上面的typedefNfcData也在本地查找,但由於它沒有在本地定義,所以使用規則 2 和 3。 @1.0::NfcStatus提供了一個版本,因此規則 1 不適用。

規則 2

如果規則 1 失敗並且缺少完全限定名稱的組件(包、版本或包和版本),則該組件將自動填充當前包中的信息。然後,HIDL 編譯器會在當前文件(以及所有導入)中查找自動填充的完全限定名稱。使用上面的示例,假設ExtendedNfcData的聲明是在與NfcData相同版本 ( 1.0 ) 的同一包 ( android.hardware.nfc ) 中進行的,如下所示:

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

HIDL 編譯器從當前包中填寫包名稱和版本名稱,以生成完全限定的 UDT 名稱android.hardware.nfc@1.0::NfcData 。由於名稱存在於當前包中(假設已正確導入),因此它用於聲明。

僅當滿足以下條件之一時,才會導入當前包中的名稱:

  • 它是使用import語句顯式導入的。
  • 在當前包的types.hal中定義

如果NfcData僅由版本號限定,則遵循相同的過程:

struct ExtendedNfcData {
    // autofill the current package name (android.hardware.nfc)
    @1.0::NfcData base;
    // … additional members
};

規則 3

如果規則 2 未能生成匹配項(當前包中未定義 UDT),則 HIDL 編譯器會在所有導入的包中掃描匹配項。使用上面的例子,假設在android.hardware.nfc包的1.1版本中聲明了ExtendedNfcData1.1導入了1.0 (參見Package-Level Extensions ),並且定義只指定了 UDT 名稱:

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

編譯器會查找任何名為NfcData的 UDT,並在android.hardware.nfc版本1.0中找到一個,從而生成完全限定的android.hardware.nfc@1.0::NfcData的 UDT。如果為給定的部分限定 UDT 找到多個匹配項,則 HIDL 編譯器會引發錯誤。

例子

使用規則 2,當前包中定義的導入類型優先於另一個包中的導入類型:

// hardware/interfaces/foo/1.0/types.hal
package android.hardware.foo@1.0;
struct S {};

// hardware/interfaces/foo/1.0/IFooCallback.hal
package android.hardware.foo@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/types.hal
package android.hardware.bar@1.0;
typedef string S;

// hardware/interfaces/bar/1.0/IFooCallback.hal
package android.hardware.bar@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/IBar.hal
package android.hardware.bar@1.0;
import android.hardware.foo@1.0;
interface IBar {
    baz1(S s); // android.hardware.bar@1.0::S
    baz2(IFooCallback s); // android.hardware.foo@1.0::IFooCallback
};
  • S被內插為android.hardware.bar@1.0::S ,並在bar/1.0/types.hal中找到(因為types.hal是自動導入的)。
  • IFooCallback使用規則 2 插入為android.hardware.bar@1.0::IFooCallback ,但由於bar/1.0/IFooCallback.hal沒有自動導入(如types.hal ),因此無法找到它。因此,規則 3 將其解析為android.hardware.foo@1.0::IFooCallback ,它是通過import android.hardware.foo@1.0;的。 )。

類型.hal

每個 HIDL 包都包含一個types.hal文件,其中包含在參與該包的所有接口之間共享的 UDT。 HIDL 類型始終是公共的;無論 UDT 是在types.hal中還是在接口聲明中聲明,這些類型都可以在定義它們的範圍之外訪問。 types.hal的目的不是描述包的公共 API,而是託管包內所有接口使用的 UDT。由於 HIDL 的性質,所有 UDT 都是接口的一部分。

types.hal由 UDT 和import語句組成。因為types.hal可用於包的每個接口(它是隱式導入),所以這些import語句根據定義是包級的。 types.hal中的 UDT 也可以包含由此導入的 UDT 和接口。

例如,對於IFoo.hal

package android.hardware.foo@1.0;
// whole package import
import android.hardware.bar@1.0;
// types only import
import android.hardware.baz@1.0::types;
// partial imports
import android.hardware.qux@1.0::IQux.Quux;
// partial imports
import android.hardware.quuz@1.0::Quuz;

導入以下內容:

  • android.hidl.base@1.0::IBase (隱式)
  • android.hardware.foo@1.0::types (隱式)
  • android.hardware.bar@1.0中的所有內容(包括所有接口及其types.hal
  • types.hal from android.hardware.baz@1.0::typesandroid.hardware.baz@1.0中的接口沒有被導入)
  • 來自android.hardware.qux@1.0IQux.haltypes.hal
  • Quuz from android.hardware.quuz@1.0 (假設Quuz定義在types.hal中,整個types.hal文件被解析,但Quuz以外的類型不被導入)。

接口級版本控制

包中的每個接口都駐留在自己的文件中。接口所屬的包使用package語句在接口頂部聲明。在包聲明之後,可以列出零個或多個接口級導入(部分或整個包)。例如:

package android.hardware.nfc@1.0;

在 HIDL 中,接口可以使用extends關鍵字從其他接口繼承。對於擴展另一個接口的接口,它必須能夠通過import語句訪問它。被擴展的接口(基本接口)的名稱遵循上面解釋的類型名稱限定規則。一個接口只能從一個接口繼承; HIDL 不支持多重繼承。

下面的 uprev 版本控制示例使用以下包:

// types.hal
package android.hardware.example@1.0
struct Foo {
    struct Bar {
        vec<uint32_t> val;
    };
};

// IQuux.hal
package android.hardware.example@1.0
interface IQuux {
    fromFooToBar(Foo f) generates (Foo.Bar b);
}

更新規則

要定義一個包package@major.minor ,A 或所有 B 必須為真:

規則 A “是一個開始的次要版本”:所有以前的次要版本, package@major.0 , package@major.1 , ..., package@major.(minor-1)不得定義。
或者
規則 B

以下所有內容均屬實:

  1. “以前的次要版本有效”:必須定義package@major.(minor-1)並遵循相同的規則 A( package@major.0package@major.(minor-2) )或規則 B (如果它是來自@major.(minor-2)的升級版);



  2. “繼承至少一個同名接口”:存在一個接口package@major.minor::IFoo擴展了package@major.(minor-1)::IFoo (如果之前的包有接口);



  3. “沒有具有不同名稱的繼承接口”:不得存在擴展package@major.(minor-1)::IBaz package@major.minor::IBar package@major.minor::IBar ,其中IBarIBaz是兩個不同的名稱。如果存在同名接口,則package@major.minor::IBar必須擴展package@major.(minor-k)::IBar使得不存在具有較小 k 的 IBar。

由於規則 A:

  • 包可以以任何次要版本號開頭(例如, android.hardware.biometrics.fingerprint@2.1開頭。)
  • 要求“未定義android.hardware.foo@1.0 ”意味著目錄hardware/interfaces/foo/1.0甚至不應該存在。

但是,規則 A 不會影響具有相同包名但版本不同的包(例如, android.hardware.camera.device同時定義了@1.0@3.2@3.2不需要與@1.0交互.) 因此, @3.2::IExtFoo可以擴展@1.0::IFoo

如果包名不同, package@major.minor::IBar可以從具有不同名稱的接口擴展(例如, android.hardware.bar@1.0::IBar可以擴展android.hardware.baz@2.2::IBaz )。如果接口沒有使用extend關鍵字顯式聲明超類型,它將擴展android.hidl.base@1.0::IBaseIBase本身除外)。

必須同時遵循 B.2 和 B.3。例如,即使android.hardware.foo@1.1::IFoo擴展android.hardware.foo@1.0::IFoo以通過規則 B.2,如果android.hardware.foo@1.1::IExtBar擴展android.hardware.foo@1.0::IBar ,這仍然不是有效的升級。

升級界面

android.hardware.example@1.0 (如上定義)升級為@1.1

// types.hal
package android.hardware.example@1.1;
import android.hardware.example@1.0;

// IQuux.hal
package android.hardware.example@1.1
interface IQuux extends @1.0::IQuux {
    fromBarToFoo(Foo.Bar b) generates (Foo f);
}

這是types.halandroid.hardware.example版本1.0的包級import 。雖然1.1版包中沒有添加新的 UDT,但仍然需要對1.0版中的 UDT 的引用,因此在types.hal中進行了包級導入。 (通過IQuux.hal中的接口級導入可以實現相同的效果。)

在 IQuux 的聲明中的extends @1.0::IQuux IQuux ,我們指定了被繼承的IQuux的版本(需要消除歧義,因為IQuux用於聲明接口並從接口繼承)。由於聲明只是在聲明位置繼承所有包和版本屬性的名稱,因此消歧必須在基本接口的名稱中;我們也可以使用完全限定的 UDT,但這將是多餘的。

新接口IQuux不會重新聲明從@1.0::IQuux繼承的fromFooToBar()方法;它只是列出了它添加的新方法fromBarToFoo() 。在 HIDL 中,繼承的方法可能不會在子接口中再次聲明,因此IQuux接口無法顯式聲明fromFooToBar()方法。

更新約定

有時接口名稱必須重命名擴展接口。我們建議枚舉擴展、結構和聯合與它們擴展的名稱相同,除非它們的差異足以保證使用新名稱。例子:

// in parent hal file
enum Brightness : uint32_t { NONE, WHITE };

// in child hal file extending the existing set with additional similar values
enum Brightness : @1.0::Brightness { AUTOMATIC };

// extending the existing set with values that require a new, more descriptive name:
enum Color : @1.0::Brightness { HW_GREEN, RAINBOW };

如果一個方法可以有一個新的語義名稱(例如fooWithLocation ),那麼這是首選。否則,它的名稱應與它所擴展的名稱類似。例如,如果沒有更好的替代名稱, @1.1::IFoo中的foo_1_1方法可能會替換@1.0::IFoofoo方法的功能。

包級版本控制

HIDL 版本控制發生在包級別;包發布後,它是不可變的(它的接口和 UDT 集不能更改)。包可以通過多種方式相互關聯,所有這些都可以通過接口級繼承和組合構建 UDT 的組合來表達。

但是,一種類型的關係是嚴格定義的並且必須強制執行:包級向後兼容繼承。在這種情況下,包是繼承自的包,包是繼承父包的包。包級向後兼容的繼承規則如下:

  1. 父包的所有頂級接口都繼承自子包中的接口。
  2. 新接口也可以添加到新包中(對與其他包中其他接口的關係沒有限制)。
  3. 也可以添加新的數據類型以供升級現有接口的新方法或新接口使用。

這些規則可以使用 HIDL 接口級繼承和 UDT 組合來實現,但需要元級知識才能知道這些關係構成向後兼容的包擴展。該知識推斷如下:

如果包滿足此要求, hidl-gen強制執行向後兼容規則。