--- title: 'HAL 設計、加載分析' disqus: kyleAlien --- HAL - HAL 設計、加載分析 === ## Overview of Content 如有引用參考請詳註出處,感謝 :cat: [TOC] ## Android HAL 概述 **Android 的硬體抽象層 (Hardware Abstact Layer, HAL) 運行在 ++使用者空間++**,並對上層 Native 提供硬體存取服務; ### Android HAL 建立 - 理由 * 傳統 Linux 驅動:一般會有兩個主要行為參雜在內部 ^1.^ 訪問硬體暫存器的程式、^2.^ 處理硬體資訊的業務邏輯 ```mermaid graph TB; subgraph 傳統_Linux_驅動 訪問硬體暫存器的程式 處理硬體資訊的業務邏輯 end ``` * Android 裝置則是有更多個硬體廠商會參與(有更多的利益關係),而 Linux 是支援 GPL 協議的,所以必須公開代碼,但這並不是廠商想見的 > 廠商會想保護自身硬體操作的業務邏輯 藉此 Android 系統設計 **兩層來支援硬體** 1. **使用者空間 - HAL 設計** :::info * 相較之下傳統的 Linux 會將硬體支援全部寫在 **核心空間** ::: 2. **核心空間 - Linux 核心代碼** 詳細說說協議吧~ Android 為何畫分兩層 ? 其實就像上面所說的,都是「利益」、「財產關係」呀! :::success * **GPL 規範**:為了規避 GPL 規範,又保證硬體廠商的利益 GPL 是由自由軟體基金會(FSF)開發的一種軟體授權協議,它主要強調軟體的自由和共享。GPL 的主要特點包括: * 任何使用、修改、分發 GPL 軟體的衍生作品的人,都必須遵循 GPL 的條款,這包括「**公開發佈原始碼**」 > 基於廠商利益,廠商通常不喜歡 GPL 協議 * 衍生軟體必須同樣使用 GPL 授權(代表 **衍生軟體必須開源**),以確保後續的版本也能夠自由分發和修改 * **Apache 規範**:允許硬體廠商修改 Android Source Code 而不用公開 Apache 許可協議是一種寬鬆的軟體授權協議,它由 Apache 軟體基金會開發;與 GPL 不同,**Apache 許可協議允許更大的彈性,並且主張開發者可以更容易地將他們的軟體與其他軟體集成**;主要特點包括: * 允許將 Apache 授權的軟體與封閉源碼軟體結合,並可以在封閉源碼軟體中使用。 * **不要求衍生軟體使用相同的授權協議**(只須標注 Notice 文件) > 把對於硬體的支援實現在 使用者空間,就可以隱藏硬體參數,但這是不可能做到的,因為 **只有核心空間有權限操作硬體裝置** ::: ### Android HAL 優點 - 方式 * Android 最終方案:**分別在 `核心` & `使用者` 空間 實現硬體支援** * **核心**:核心空間仍使用硬體驅動來支援,但它只是簡單地提供了硬體存取通道 (跟硬體溝通) > 簡易符合 GPL 規範 * **使用者**:使用者空間以硬體抽象層模組的形式支援 (這樣就可以實現 Apache 2.0 規範,封裝硬體時現的細節、參數) > Apache 規範可以隱藏細節,在程式中只須附上 Notice 文件即可 ```mermaid graph LR; subgraph 使用者空間_HAL 業務邏輯 end 使用者空間_HAL -.-> |符合 Apache| Apache_可不公開 subgraph 核心空間_Linux_驅動 訪問暫存器 end 核心空間_Linux_驅動 -.-> |符合 GPL| GPL_公開 業務邏輯 --> 訪問暫存器 ``` > ![](https://i.imgur.com/aGjTyEx.png) * **Android HAL 設計的優點在於** 1. **統一硬體的調用界面**: 相較於 Linux 對於硬體規劃的多種界面層級(函數指標),Android HAL 會更加有「標準」 2. **解決了利益問題**: 讓硬體廠商保有各自的硬體訪問邏輯,將邏輯建構在 Android HAL 層 3. **讓硬體訪問使用者空間**: 某些特殊硬體會有需求,要訪問使用者空間的一些資源(或是在內核空間不方便完成的工作),這實在使用者空間訪問就相對方便 ### Android HAL - 架構 * Android HAL 架構大致上可以分為兩種 1. **早期 Android HAL** 設計較為簡單,基本原理就是 * Android 系統中使用動態庫(`.so` 文件),並透過 HAL 中的動態庫訪問核心 Linux 驅動 * 上層應用 (Application) 則透過 NDK,或是直接訪問的方式去調用 HAL 中的程序庫(`.so` 文件) ```mermaid graph TB; subgraph 使用者空間 應用層 --> NDK_JNI --> HAL 應用層 --> HAL end subgraph 核心空間 Linux_驅動 end HAL --> Linux_驅動 ``` 2. **穩定版 Android HAL** 基礎的架構沒有變,主要是新增了一些層級、設計,使的這些 `.so` 庫,可以 **自動被 Android 系統識別並調用** > 也就是多了架構設計 * 不須手動加載 `.so` 庫(驅動層程式),而是**透過 ID 訪問** 並由系統幫我們加載 * 在 HAL 跟 JNI 之前之間多了 Service 程序庫(某個 `.so` 庫) ```mermaid graph TB; subgraph 使用者空間 Service.so 應用層 --> NDK_JNI --> Service.so 應用層 --> Service.so -.-> |將 so 文件隱藏 | HAL end subgraph 核心空間 Linux_驅動 end HAL --> Linux_驅動 ``` ### Android HAL 執行檔位置 * Android HAL 源代碼的位置並不固定,但大致上會放在兩個位置 1. **早期設計 `<android source>/hardware/libhardware_legacy` 目錄**:用來儲存舊有 HAL 架構的源代碼 > ![image](https://hackmd.io/_uploads/SJj0M3Bvp.png) 2. **穩定設計 `<android source>/hardware/libhardware` 、`<android source>/hardware` 目錄**: > ![image](https://hackmd.io/_uploads/SyvXQ2Sw6.png) * Android source 經過編譯過後,會在 `<android source> /out/target/product/generic/system/lib/hw` 目錄之下產生對應的 `.so` 文件 ```shell= # 查找 hw 資料夾 find . -name hw -type d ``` > ![image](https://hackmd.io/_uploads/HJxKQhBDT.png) ## Android HAL 結構設計 我們知道 Android HAL 層的功能是定義一個抽象接口(函數指標),**讓 Android 軟體 與 裝置硬體做出隔離** >HAL 也是與 Linux Kernel 之間通信的唯一接口,HAL 有以下特點 1. **抽象接口**:解開與每個硬體裝置的耦合度 (每個 BSP 都會根據硬體實現),有了這個 protocol 就可以不理會硬體實現 2. **穩定性**:HAL 接口必須穩定,不能常常修改,也保證其兼融性 3. **靈活性**:方便上層對於不同硬體使用 對於不同硬體手機廠商而言,**硬體廠商只需要專注在對於 Andoid HAL 層的定義實現**; **軟體廠商只需要將 HAL 的 so 取來放在指定路徑即可** > ![](https://i.imgur.com/swq2VYe.png) ### Android HAL 區塊分類 * 撰寫 Android HAL 的步驟是稍加麻煩的(這是因為多了一層架構的原因,所以比起撰寫 Linux 驅動複雜),但也是因為架構,會讓 Android HAL 驅動更加具有獨立性、好維護性 具體來說會經過以下步驟 1. **編寫 Linux 驅動**(核心空間) 這個步驟並非一般的 Linux 驅動程序,而是只有簡單的訪問(讀取、寫入… 等等)指定硬體的暫存器,如果是驅動的業務邏輯則留在 HAL 層(不留在這層) 2. **編寫 HAL 驅動業務邏輯**(使用者空間) 撰寫 HAL 驅動業務邏輯,並且經過編譯後,就會產出一個 `.so` 文件(相當於 Linux Library) :::info * 並且這類 `.so` 庫只會有一個界面,而 Android 系統會透過 **`HAL_MODULE_INFO_SYM` 符號** 加載 `.so` 庫! ::: 3. **編寫 Service Library**,非必須但 Android 官方建議(使用者空間) Service Library 也是 `.so` 文件,可以是 Linux Library,也可以是 JNILibrary ```mermaid graph TB; Java_應用 --> ServiceManager --> |ServiceLibrary| OS --> HAL --> Linux_驅動 ``` ### Android HAL - 三大結構 > 接下來看 Android HAL 的一些重要結構 1. **`hw_module_t` 結構**:(設定 Android HAL 資訊) * **`hw_module_t` 下的 `tag` 成員必須是 `HARDWARE_MODULE_TAG`** * **模組中必須要有 `HAL_MODULE_INFO_SYM` 符號**,而這個符號的類型就是 `hw_module_t` > `HARDWARE_MODULE_TAG` 就是 `HWMT` > > `HAL_MODULE_INFO_SYM` 就是 `HMI` :::success 這三大結構之中,`hw_module_t` 結構是最先被找到的 ::: ```c= // hardware.h #define HAL_MODULE_INFO_SYM HMI #define MAKE_TAG_CONSTANT(A,B,C,D) (((A) << 24) | ((B) << 16) | ((C) << 8) | (D)) #define HARDWARE_MODULE_TAG MAKE_TAG_CONSTANT('H', 'W', 'M', 'T') typedef struct hw_module_t { // 必須是 `HARDWARE_MODULE_TAG` uint32_t tag; uint16_t module_api_version; #define version_major module_api_version uint16_t hal_api_version; #define version_minor hal_api_version /** Identifier of module */ // 通過 id 可以找到該模塊 const char *id; /** Name of this module */ const char *name; /** Author/owner/implementor of the module */ const char *author; /** Modules methods */ // 與模塊相關的函數指標 struct hw_module_methods_t* methods; /** module's dso */ void* dso; #ifdef __LP64__ uint64_t reserved[32-7]; #else /** padding to 128 bytes, reserved for future use */ uint32_t reserved[32-7]; #endif } hw_module_t; ``` :::info * dso 是載入 module 的控制句柄,載入 so 的簡單順序如下 1. `dlopen` 開啟 xxx.so 2. 設定給 `hw_module_t`#`dso` 成員 ::: 2. **`hw_device_t` 結構**:(驅動相關訊息) 每個硬體必須定義自己的硬體裝置結構體,透過該結構可以關閉硬體裝置;**透過該結構可以 ++設定硬體裝置所需的操作函數++** * **`hw_module_t`其成員 `tag` 必須是 `HARDWARE_DEVICE_TAG`** > `HARDWARE_DEVICE_TAG` 就是 `HWDT` ```c= // hardware.h #define MAKE_TAG_CONSTANT(A,B,C,D) (((A) << 24) | ((B) << 16) | ((C) << 8) | (D)) #define HARDWARE_DEVICE_TAG MAKE_TAG_CONSTANT('H', 'W', 'D', 'T') typedef struct hw_device_t { // 必須是 `HARDWARE_DEVICE_TAG` uint32_t tag; uint32_t version; /** reference to the module this device belongs to */ struct hw_module_t* module; /** padding reserved for future use */ #ifdef __LP64__ uint64_t reserved[12]; #else uint32_t reserved[12]; #endif /** Close this device */ // 關閉設備的指標函數 int (*close)(struct hw_device_t* device); } hw_device_t; ``` :::info * 開啟硬體是由 Module 負責,關閉則是由硬體裝置負責 ::: 3. **`hw_module_methods_t` 結構**: 該結構相當於 HAL 驅動的入口,並會在這之中找到 `hw_module_t`、`hw_device_t` 並初始化結構 ```c= // hardware.h typedef struct hw_module_methods_t { /** Open a specific device */ int (*open)(const struct hw_module_t* module, const char* id, // 這時傳入的是 自己定義的 hw_device_t 結構 struct hw_device_t** device); } hw_module_methods_t; ``` :::info * 一個抽象模組可以對應到多個硬體裝置,而這個我們可以透過 `id` 參數來分辨 ::: 結構的關係如下 > ![](https://i.imgur.com/V0d7yQj.png) ### Android HAL 設定範例 * 舉一個例子 (Power),HAL 任何 Module 都必須按照規則 1. **Symbol 規定**:每個 Module 必須要有一個 `HAL_MODULE_INFO_SYM` 變量; 以下為 [**power**](https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/power/power.c) 模塊定義 ```c= // /qcom/power/power.c // 宣告 HAL_MODULE_INFO_SYM 變量 (符合條件 1) struct power_module HAL_MODULE_INFO_SYM = { // .common 也就是 hw_module_t (符合條件 2) .common = { // 必須使用 `HARDWARE_MODULE_TAG` tag (符合條件 3) .tag = HARDWARE_MODULE_TAG, .module_api_version = POWER_MODULE_API_VERSION_0_2, .hal_api_version = HARDWARE_HAL_API_VERSION, .id = POWER_HARDWARE_MODULE_ID, .name = "Qualcomm Power HAL", .author = "The Android Open Source Project", .methods = &power_module_methods, }, .init = power_init, .setInteractive = power_set_interactive, .powerHint = power_hint, }; ``` 2. **`hw_module_t` 拓展**:每個 struct 必須要用 `hw_module_t` 作為開頭;同樣我們用 power 模塊,查看 `power_module` 結構 ```c= // hardware/libhardware/include/hardware/power.h typedef struct power_module { // 以 hw_module_t 開頭 struct hw_module_t common; ... } power_module_t; ``` > ![](https://i.imgur.com/9Kp2mbz.png) ### 撰寫 Android HAL 順序 > 接下來針對「編寫 HAL 驅動業務邏輯」這個步驟來拓展 * **「宣告」結構**(宣告兩個新構體,並將指定結構至於「第一位置」,類似於「繼承」的概念) 1. **宣告包裝 `hw_module_t` 結構**: HAL 會要求使用者,使用一個結構体(`struct`)包裝 `hw_module_t` 結構,並且要 **++放置第一個位置++** ```c= struct my_module_t { // 包裝指定的 hw_module_t 結構 struct hw_module_t common; }; ``` 2. **宣告包裝 `hw_device_t` 結構** HAL 同樣會要求使用者,使用一個結構体(`struct`)包裝 `hw_device_t` 結構,並且要 **++放置第一個位置++** ```c= struct my_device_t { // 符合規定以 `hw_device_t` 開頭 struct hw_device_t common; // 在這個結構中也可定義操作驅動的私人方法 }; ``` :::info * **設定 HAL Moduel ID**: 由於 Android 會自動幫我們加載驅動(我們不須手動加載 so),而我們需要做的則是設定一個 ID… 之後透過該 ID 來取得驅動服務! > 之後大多是給 NDK 層使用 ```c= #define MY_HARDWARE_ID "my_hardware" ``` ::: * **「實做」結構** 1. **實做 ++關閉驅動++ 裝置的函數** 這個步驟主要是要釋放 `my_device_t` 結構 ```c= int dev_fd = 0; static int my_device_close(struct hw_device_t* device) { my_device_t* dev = (my_device_t*) device; if(dev) { free(dev); // 釋放記憶體空間 } close(dev_fd); // 關閉文件 } ``` 2. **實做 ++初始化驅動++ 裝置的函數**: 並在初始化函數之中使用 `malloc` 創建,自己的 `my_device_t` 結構;並在這裡設定基礎的驅動訊息(像是 tag, version, module, close 函數... 等等) * **`hw_module_t`#tag 必須定義為 HARDWARE_DEVICE_TAG** * **轉型 `hw_module_t`#module** 為自己的 Module 類型 * **設置 `hw_module_t`#close 函數**,當驅動關閉時,自動會呼叫該函數 * **使用 open 函數開啟驅動,並保存返回的 fd 值** ```c= static int my_device_open(const struct hw_module_t* module, const char* id, struct hw_device_t** device) { struct my_device_t *myDev; // 動態分配驅動內存 myDev = (strucr my_device_t *) malloc(sizeof(*myDev)); // 初始化結構 memset(myDev, 0, sizeof(*myDev)); // 基礎,必須設置的成員 myDev->hw_device.tag = HARDWARE_DEVICE_TAG; myDev->hw_device.version = 0; myDev->hw_device.module = (my_module_t*) module; // 強制轉型為自己的 module myDev->hw_device.close = my_device_close; dev_fd = open("/dev/my_dev", O_RDWR); } ``` 3. **實做 `hw_module_methods_t` 結構**: 把開啟驅動的函數(上面的 `my_device_open`)設置給 `hw_module_methods_t`#open 成員 ```c= static struct hw_module_methods_t my_module_methods { open. = my_device_open } ``` 3. **定義「`HAL_MODULE_INFO_SYM`」符號,並實做 `my_module_t` 結構**: 而其中的 `.tag` 要定義為「`HARDWARE_MODULE_TAG`」 :::success * **`HAL_MODULE_INFO_SYM` 符號正是 Android HAL 架構下的入口!**(所有的 HAL 模塊都需要有) ::: ```c= struct my_module_t HAL_MODULE_INFO_SYM = { .common = { .tag = HARDWARE_MODULE_TAG, .version_major = 1, .version_minor = 0, .id = MY_HARDWARE_MODULE_ID, // 定義 Module ID .name = "", .author = MODULE_AUTHOR, .methods = &my_module_methods, // 定義開啟硬體方法(上一步) }, }; ``` ### Android HAL 拓展 - gralloc * 對於某一類硬體設備來說,它提供的 HAL 接口必須穩定不變,而 Android 系統也有預定好的 HAL 接口 ([**libhardware**](https://cs.android.com/android/platform/superproject/+/master:hardware/libhardware/include/hardware/) 資料夾下),而其他硬體廠商只需要實作這個接口即可 > ![](https://i.imgur.com/17g2wXy.png) 從上圖我們可以發現 `gralloc.h` 檔案,它就有使用 HAL 定義的接口 (`hw_module_t`),並在該結構中拓展自己需要的函數指標 ```c= // gralloc.h typedef struct gralloc_module_t { // HAL Module 規範 struct hw_module_t common; // 以下為 gralloc 模組拓展的函數指標 int (*registerBuffer)(struct gralloc_module_t const* module, buffer_handle_t handle); int (*unregisterBuffer)(struct gralloc_module_t const* module, buffer_handle_t handle); int (*lock)(struct gralloc_module_t const* module, buffer_handle_t handle, int usage, int l, int t, int w, int h, void** vaddr); int (*unlock)(struct gralloc_module_t const* module, buffer_handle_t handle); int (*perform)(struct gralloc_module_t const* module, int operation, ... ); int (*lock_ycbcr)(struct gralloc_module_t const* module, buffer_handle_t handle, int usage, int l, int t, int w, int h, struct android_ycbcr *ycbcr); int (*lockAsync)(struct gralloc_module_t const* module, buffer_handle_t handle, int usage, int l, int t, int w, int h, void** vaddr, int fenceFd); int (*unlockAsync)(struct gralloc_module_t const* module, buffer_handle_t handle, int* fenceFd); int (*lockAsync_ycbcr)(struct gralloc_module_t const* module, buffer_handle_t handle, int usage, int l, int t, int w, int h, struct android_ycbcr *ycbcr, int fenceFd); int32_t (*getTransportSize)( struct gralloc_module_t const* module, buffer_handle_t handle, uint32_t *outNumFds, uint32_t *outNumInts); int32_t (*validateBufferSize)( struct gralloc_module_t const* device, buffer_handle_t handle, uint32_t w, uint32_t h, int32_t format, int usage, uint32_t stride); /* reserved for future use */ void* reserved_proc[1]; } gralloc_module_t; ``` ### HAL 編譯方式 * 由於 Android HAL 模塊屬於 Android 系統的一部分(不屬於 Kernel),所以需要依賴 Android source 中許多頭文件… 有以下方式可以作到 1. **將驅動源碼放置到 `<Android source>/hardware/` 目錄下** 2. **透過 `ln` 指令,將驅動目錄鏈結到 `<Android source>/harware` 目錄下** * 並在自己的「驅動」源碼目錄下創建 `Android.mk` 檔 ```shell= ## Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 模組編譯或是源碼編譯都可以 LOCAL_MODULE_TAG := optional # 預編譯模式 LOCAL_PRELINK_MODULE := false LOCAL_MODULE_RELATIVE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw LOCAL_SHARED_LIBRARIES := liblog LOCAL_SRC_FILES := hello.cpp # 驅動 module (最終會輸出的指定目錄) LOCAL_MODULE := hello.default # so library include $(BUILD_SHARED_LIBRARY) ``` 編譯後,實際輸出 `hello.default.so` 到 `<Android source>/system/lib64/hw/` 目錄下 > ![](https://i.imgur.com/dl4iI7K.png) ## Android HAL 加載分析 Android HAL 的加載,官方建議使用 Service 程序庫(也是個 `.so` 檔),通常指的就是 JNI ### hw_get_module 判斷庫 - 路徑規則 * **Android 載入硬體驅動**: **使用 [hardware](https://cs.android.com/android/platform/superproject/+/master:hardware/libhardware/hardware.c)#`hw_get_module` 函數加載 HAL 模塊** 我們不在需要手動加載 so 庫,只要 **給定指定的 ID 就可以加載** (要與註冊的驅動裝置相同 ID),接下來主要分析 `hw_get_module` 函數 ```cpp= // hardware.h // 回傳 0 代表成功,小於 0 代表失敗 int hw_get_module(const char *id, const struct hw_module_t **module); ``` :::info * 範例 - **Gralloc 模塊加載**: ```cpp= // gralloc.h #define GRALLOC_HARDWARE_MODULE_ID "gralloc" // -------------------------------------------------------- // hardware.c // @ 追蹤 hw_get_module hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module); ``` ::: * **分析 `hw_get_module` 方法**: 這個方法可以拿來加載 HAL 硬體廠商實現的 `.so` 或是自己實現的 `.so` 檔(也就是 HAL 驅動的 `.so` 檔) > Android 系統尋找 HAL Module(`.so` 檔)是有一定順序(策略)的 ```cpp= // hardware.c static const char *variant_keys[] = { "ro.hardware", /* This goes first so that it can pick up a different file on the emulator. */ "ro.product.board", "ro.board.platform", "ro.arch" }; static const int HAL_VARIANT_KEYS_COUNT = (sizeof(variant_keys)/sizeof(variant_keys[0])); int hw_get_module(const char *id, const struct hw_module_t **module) { // @ 追蹤 hw_get_module_by_class return hw_get_module_by_class(id, NULL, module); } int hw_get_module_by_class(const char *class_id, const char *inst, const struct hw_module_t **module) { int i = 0; // PATH_MAX 是 4096 char prop[PATH_MAX] = {0}; // 最終 so 的名稱 char path[PATH_MAX] = {0}; char name[PATH_MAX] = {0}; // so 的起頭名稱 char prop_name[PATH_MAX] = {0}; // 屬性名稱 if (inst) ... 省略目前傳入 NULL else // 將 "Module_ID" 複製到 name 中 strlcpy(name, class_id, PATH_MAX); // 1. 判斷是否有 `ro.hardware.<Module_ID>` 屬性 snprintf(prop_name, sizeof(prop_name), "ro.hardware.%s", name); // 取得對應的 so 名稱 if (property_get(prop_name, prop, NULL) > 0) { if (hw_module_exists(path, sizeof(path), name, prop) == 0) { goto found; } } /* Loop through the configuration variants looking for a module */ for (i=0 ; i<HAL_VARIANT_KEYS_COUNT; i++) { // 2. 判斷是否有指定屬性,有的話則將該屬性的值放到 prop 陣列 if (property_get(variant_keys[i], prop, NULL) == 0) { continue; } if (hw_module_exists(path, sizeof(path), name, prop) == 0) { goto found; } } // 3. 以上預設都找不到,則呼叫 hw_module_exists 函數(並帶入 `default`) // @ 查看 hw_module_exists 方法 if (hw_module_exists(path, sizeof(path), name, "default") == 0) { goto found; } return -ENOENT; found: // 加載 Module // @ 查看 load 函數 return load(class_id, path, module); } ``` **Android HAL 加載 so 庫的判斷策略如下**: 1. **載入 `ro.hardware.*` 開頭屬性**:如果有設定 `ro.hardware.<Module_ID>` 屬性,取得對應的 so 庫 Android 系統的屬性文件共有 4 個 * `/default/prop` * `/system/build.prop` * `/system/default.prop` * `/data/local.prop` :::info * 每個版本的屬性設置都有可能不同 我們可以透過 `_system_properties.h` 文件查看到對應的宏設置 ```shell= # 切換到 android source 目錄 cd android_source/ # 查看定義屬性的宏設定 ./bionic/libc/include/sys/_system_properties.h ``` > ![image](https://hackmd.io/_uploads/H1lo3Sg_6.png) ::: 2. **找屬性文件… 同樣先判斷系統是否有指定的屬性,再查找指定預設的檔案**:這些屬性都會對應到另外一個數值 (就是 key / value) * <Module_ID>.`ro.hardware`.so :::success * `ro.hardware` 具體的設定值為 init 進程負責 1. init 進程首先會讀取 `/proc/cmdline` 檔案,查看是否有一個 `androidboot.hardware` 屬性,如果有則取出當成 `ro.hardware` 值 > ![](https://i.imgur.com/KIx7kX8.png) 2. 如果第一點沒有,則判斷 `/proc/cpuinfo` 的內容,並賦予 `ro.hardware` 值 > ::: * <Module_ID>.`ro.product.board`.so * <Module_ID>.`ro.board.platform`.so * <Module_ID>.`ro.arch`.so 3. **如果都找不到 so ! 則使用預設的 <MODULE_ID\>.`default`.so 檔案** ```cpp= hw_module_exists(path, sizeof(path), name, "default") ``` :::info * 這邊可以看到,**它帶入了 "default" 關鍵字** 這有就是為甚麽我們在編寫驅動程式時,`LOCAL_MODULE` 的值都會設置有一個 `default` 名; 因為要讓最終編譯出來的驅動 so 檔名為 `<module>.default.so` ```shell= ## Android.mk ... LOCAL_MODULE := hello.default ``` ::: * **分析 `hw_module_exists` 函數**:判斷指定路徑(`path`)下是否有檔案,其路徑判斷同樣有順序 (`{64}` 是 64 位元裝置使用)… 預設路徑則如下 1. 預設路徑:`/system/lib{64}/hw` > 我們自己撰寫的驅動檔經過編譯後,都會保存到 `/system/lib{64}/hw` 目錄下 2. 預設路徑:`/vendor/lib{64}/hw` 3. 預設路徑:`/odm/lib{64}/hw` ```c= // hardware.c // HAL 模塊所在的位置 #if defined(__LP64__) // 64 位元 #define HAL_LIBRARY_PATH1 "/system/lib64/hw" #define HAL_LIBRARY_PATH2 "/vendor/lib64/hw" #define HAL_LIBRARY_PATH3 "/odm/lib64/hw" #else // 32 位元 #define HAL_LIBRARY_PATH1 "/system/lib/hw" #define HAL_LIBRARY_PATH2 "/vendor/lib/hw" #define HAL_LIBRARY_PATH3 "/odm/lib/hw" #endif static int hw_module_exists(char *path, size_t path_len, const char *name, const char *subname) { snprintf(path, path_len, "%s/%s.%s.so", HAL_LIBRARY_PATH3, name, subname); if (path_in_path(path, HAL_LIBRARY_PATH3) && access(path, R_OK) == 0) return 0; snprintf(path, path_len, "%s/%s.%s.so", HAL_LIBRARY_PATH2, name, subname); if (path_in_path(path, HAL_LIBRARY_PATH2) && access(path, R_OK) == 0) return 0; #ifndef __ANDROID_VNDK__ snprintf(path, path_len, "%s/%s.%s.so", HAL_LIBRARY_PATH1, name, subname); if (path_in_path(path, HAL_LIBRARY_PATH1) && access(path, R_OK) == 0) return 0; #endif return -ENOENT; } ``` ### load 加載庫 * 在上一步中 `hw_get_module` 函數找到驅動 `.so` 的路徑後,就會呼叫 `load` 函數來加載,我們先來概略看一下 `load` 函數 ```c= // hardware.c #if defined(__LP64__) #define HAL_LIBRARY_PATH1 "/system/lib64/hw" #define HAL_LIBRARY_PATH2 "/vendor/lib64/hw" #define HAL_LIBRARY_PATH3 "/odm/lib64/hw" #else #define HAL_LIBRARY_PATH1 "/system/lib/hw" #define HAL_LIBRARY_PATH2 "/vendor/lib/hw" #define HAL_LIBRARY_PATH3 "/odm/lib/hw" #endif static int load(const char *id, const char *path, const struct hw_module_t **pHmi) { int status = -EINVAL; void *handle = NULL; struct hw_module_t *hmi = NULL; #ifdef __ANDROID_VNDK__ const bool try_system = false; #else const bool try_system = true; #endif /* * load the symbols resolving undefined symbols before * dlopen returns. Since RTLD_GLOBAL is not or'd in with * RTLD_NOW the external symbols will not be global */ if (try_system && strncmp(path, HAL_LIBRARY_PATH1, strlen(HAL_LIBRARY_PATH1)) == 0) { // 如果 lib 在系統,就不用確認 sphal namespace,直接使用 `dlopen` 開啟 handle = dlopen(path, RTLD_NOW); } else { ... } if (handle == NULL) { // 加載失敗 char const *err_str = dlerror(); ALOGE("load: module=%s\n%s", path, err_str?err_str:"unknown"); status = -EINVAL; goto done; } // 取得 `hal_module_info` 地址 const char *sym = HAL_MODULE_INFO_SYM_AS_STR; // 其值為 "HMI" // 尋找 so 中的 HMI 符號 ! // 也就是我們 Moudle 中有規範的 `HAL_MODULE_INFO_SYM` hmi = (struct hw_module_t *)dlsym(handle, sym); if (hmi == NULL) { ALOGE("load: couldn't find symbol %s", sym); status = -EINVAL; goto done; } // 成功取得硬體模組後,判斷 id 是否相同 if (strcmp(id, hmi->id) != 0) { // 不同 ALOGE("load: id=%s != hmi->id=%s", id, hmi->id); status = -EINVAL; goto done; } // 設定訪問硬體設備的句柄 hmi->dso = handle; /* success */ status = 0; done: if (status != 0) { hmi = NULL; if (handle != NULL) { dlclose(handle); handle = NULL; } } else { ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p", id, path, hmi, handle); } *pHmi = hmi; // 到這裡完成硬體驅動模組的取得 ! return status; } ``` **分析 load 函數**:我們來分析幾個關鍵步驟 1. 取得硬體驅動模組 (so) 的操控句柄 ## 其他 ### C 語言的抽象 - struct 繼承 * Java 來這種物件導向的語言來說,定義抽象接口並實做(繼承)是相當容易的 (如下示範) ```java= // 抽象接口 public interface IHAL { void start(); void close(); } // -------------------------------------- public class Board_AT_32_403 implements IHAL { public void start() { // 細節 } public void close() { // 細節 } } ``` 但對於底層 C 語言來說就不容易 (應該說有別種方法),要達成繼承的概念,**須使用 struct 數據結構,並 ++定義第一個成員變量是父類結構++**,以下以 [**gralloc.h**](https://cs.android.com/android/platform/superproject/+/master:hardware/libhardware/include/hardware/gralloc.h) 為例 > ![](https://i.imgur.com/dfiOrZO.png) ### sturct 繼承範例 * struct 繼承範例如下 ```c= #include <malloc.h> #include <stdio.h> struct Person { long id; }; struct Boy { struct Person person; char* boyName; }; struct Girl { struct Person person; char* girlName; }; void createInformation(struct Person **person, char isBoy) { if(isBoy == 1) { struct Boy *boy = (struct Boy*) malloc(sizeof(struct Boy)); boy->person.id = 5566; boy->boyName = "Kyle"; // 重新設定指標位置 *person = (struct Person*) boy; } else { struct Girl *girl = (struct Girl*) malloc(sizeof(struct Girl)); girl->person.id = 2020; girl->girlName = "Lulu"; // 重新設定指標位置 *person = (struct Person*) girl; } } int main() { // 創建指標 struct Person *girl, *boy; // 傳入指標的位置 createInformation(&girl, 0); printf("Girl id:%ld, name:%s\n", girl->id, ((struct Girl*) girl)->girlName); createInformation(&boy, 1); printf("Boy id:%ld, name:%s", boy->id, ((struct Boy*) boy)->boyName); return 0; } ``` > ![image](https://hackmd.io/_uploads/r12g_BnP6.png) :::danger * 如果沒有將要繼承的 struct 放置在第一個位置,就會導致 C 語言轉型 struct 失敗,如下圖 ```c= struct Boy { char* boyName; // 沒有置為第一位 struct Person person; }; ``` > ![image](https://hackmd.io/_uploads/SyYLuH3DT.png) ::: ## Appendix & FAQ :::info ::: ###### tags: `Template`