HDC2021技術分論壇:OpenHarmony驅動框架解讀和開發實踐
在IoT時代下,終端設備差異較大、形態各異、尺寸各異、交互方式各異,解決設備適配問題無疑是實現萬物互聯的一個關鍵。但是,在驅動框架的開發和部署過程中,由于終端設備對硬件的計算和存儲能力的需求不同、設備廠商提供的設備軟硬件操作接口不同、內核提供的操作接口不同,這就使得OEM廠商部署系統的時候需要投入大量的精力來適配和維護驅動代碼。
能否提供了一個跨芯片平臺、跨內核的驅動框架,使得設備驅動軟件可以在不同的設備上運行?OpenHarmony作為一個自主研發、全新技術生態的全領域下一代開源操作系統,提供了一套驅動框架來滿足此訴求。
下面我們將帶著大家解讀OpenHarmony驅動框架。
一、OpenHarmony驅動框架解讀
1. 設計目標
為解決在開發和部署過程中遇到的困難,OpenHarmony驅動框架設計目標如下:
- 支持百K級~G級容量的設備部署,如手機、手環等
- 提供統一硬件IO抽象,屏蔽SoC芯片差異,兼容不同內核,如Linux、LiteOS等。
- 屏蔽驅動和系統組件間交互。可動態拆解,滿足不同容量設備的部署。
- 面向不同容量的設備,提供統一的配置界面。
2. 設計思路
OpenHarmony驅動框架(下面簡稱為HDF)通過提供驅動與芯片平臺、內核解耦的底座,規范硬件驅動接口,實現驅動軟件在不同設備中部署。
HDF驅動框架架構如下圖所示。

圖1 驅動架構
為了達成設計目標,OpenHarmony驅動框架采用如下核心設計思路:
(1)彈性化架構
- 框架可動態伸縮: 通過對象管理器,多態加載不同容量設備實現方式,實現彈性伸縮部署。
- 驅動可動態伸縮: 支持統一的設備驅動插件管理,實現設備驅動任意分層,積木式組合拼接
(2)組件化設備模型
- 提供設備功能模型抽象,屏蔽設備驅動與系統交互的實現,為開發者提供統一的驅動開發接口
- 提供主流IC的公版驅動能力,支持配置化部署
(3)歸一化平臺底座
- 提供規范化的內核、SoC硬件IO適配接口,兼容不同內核、SoC芯片,對外開發規范化的平臺驅動接口
(4)統一配置界面
- 構建全新的配置語言,面向不同容量的設備,提供統一配置界面,支持硬件資源配置和設備信息配置
3. 構建策略
面向Liteos的輕量級設備,主要基于HDF構建主流IC驅動,形成公版驅動和通用設備功能模型,支撐不同硬件芯片、不同內核(LiteOS-M/LiteOS-A)部署。

圖2 輕量級設備部署模式
面向標準設備,除了支持內核態驅動,還支持用戶態驅動。用戶態驅動的重點在于構建設備抽象模型,為系統提供統一的設備接口,兼容Linux原生驅動和HDF驅動。內核態則使用Linux驅動與HDF驅動并存的策略,提供端到端的解決方案。

圖3 標準設備部署模式
4. 現狀與演進
目前HDF驅動框架已經支持Liteos-m、Liteos-a、Linux內核,以及OpenHarmony輕量級、標準級上部署,并且在標準系統上同時支持內核態與用戶態部署。

圖4 OpenHarmony驅動框架演進圖
經過開發者的不斷努力,OpenHarmony驅動框架正在不斷完善和增強,在OpenHarmony LTS3.0中,基礎框架新增了對熱插拔設備的管理以及HDI編譯工具hdi-gen,驅動模型部分新增了Audio、Camera、Senso、USB DDK等多個模塊的支持。
二、OpenHarmony驅動開發
OpenHarmony驅動為了避免與具體內核產生依賴,實現可遷移目標,開發時需要遵循以下約定:
系統相關接口使用HDF OSAL接口;
總線和硬件資源相關接口使用平臺驅動提供的相關接口。
基于HDF框架,驅動開發的通常流程包含驅動代碼的實現、編譯腳本、配置文件添加、以及用戶態程序和驅動交互的流程。下面將詳細介紹HDF驅動開發一般步驟。
1. 實現驅動代碼
在HDF驅動框架中,HdfDriverEntry對象被用來描述一個驅動實現。
- struct HdfDriverEntry {
- int32_t moduleVersion;
- const char *moduleName;
- int32_t (*Bind)(struct HdfDeviceObject *deviceObject);
- int32_t (*Init)(struct HdfDeviceObject *deviceObject);
- void (*Release)(struct HdfDeviceObject *deviceObject);
- };
編寫一個簡單的驅動,首先需要實現驅動程序(Driver Entry)入口中的三個主要接口:
- Bind接口: 實現驅動接口實例化綁定,如果需要發布驅動接口,會在驅動加載過程中被調用,實例化該接口的驅動服務并和DeviceObject綁定。當用戶態發起調用時,Bind中綁定的服務對象的Dispatch方法將被回調,在該方法中處理用戶態調用的消息。
- Init接口: 實現驅動或者硬件的初始化,返回錯誤將中止驅動加載流程。
- Release接口: 實現驅動的卸載,在該接口中釋放驅動實例的軟硬件資源。
一個基于HDF框架編寫的簡單驅動代碼如下,其功能是用戶態消息回環,即驅動收到用戶態發送的消息后將相同內容的消息再發送給用戶態:
- #include "hdf_base.h"
- #include "hdf_device_desc.h"
- #include "hdf_log.h"
- #define HDF_LOG_TAG "sample_driver"
- #define SAMPLE_WRITE_READ 0xFF00
- static int EchoString(struct HdfDeviceObject *deviceObject, struct HdfSBuf *data, struct HdfSBuf *reply)
- {
- const char *readData = HdfSbufReadString(data);
- if (readData == NULL) {
- HDF_LOGE("%s: failed to read data", __func__);
- return HDF_ERR_INVALID_PARAM;
- }
- if (!HdfSbufWriteInt32(reply, INT32_MAX)) {
- HDF_LOGE("%s: failed to reply int32", __func__);
- return HDF_FAILURE;
- }
- return HdfDeviceSendEvent(deviceObject, id, data); // 發送事件到用戶態
- }
- int32_t HdfSampleDriverDispatch(struct HdfDeviceObject *deviceObject, int id, struct HdfSBuf *data, struct HdfSBuf *reply)
- {
- const char *readData = NULL;
- int ret = HDF_SUCCESS;
- switch (id) {
- switch SAMPLE_WRITE_READ:
- ret = EchoString(deviceObject, data, reply);
- break;
- default:
- HDF_LOGE("%s: unsupported command");
- ret = HDF_ERR_INVALID_PARAM;
- }
- return ret;
- }
- void HdfSampleDriverRelease(struct HdfDeviceObject *deviceObject)
- {
- // 在這里釋放驅動申請的軟硬件資源
- return;
- }
- int HdfSampleDriverBind(struct HdfDeviceObject *deviceObject)
- {
- if (deviceObject == NULL) {
- return HDF_FAILURE
- }
- static struct IDeviceIoService testService = {
- .Dispatch = HdfSampleDriverDispatch,
- };
- deviceObject->service = &testService;
- return HDF_SUCCESS;
- }
- int HdfSampleDriverInit(struct HdfDeviceObject *deviceObject)
- {
- if (deviceObject == NULL) {
- HDF_LOGE("%s::ptr is null!", __func__);
- return HDF_FAILURE;
- }
- HDF_LOGE("Sample driver Init success");
- return HDF_SUCCESS;
- }
- struct HdfDriverEntry g_sampleDriverEntry = {
- .moduleVersion = 1,
- .moduleName = "sample_driver",
- .Bind = HdfSampleDriverBind,
- .Init = HdfSampleDriverInit,
- .Release = HdfSampleDriverRelease,
- };
- HDF_INIT(g_sampleDriverEntry);
2. 配置設備信息
在HDF框架的配置文件(例如vendor/hisilicon/xxx/config/device_info.hcs)中添加該驅動的配置信息,配置目錄與具體開發板關聯,如下所示:
- root {
- device_info {
- match_attr = "hdf_manager";
- template host {
- hostName = "";
- priority = 100;
- template device {
- template deviceNode {
- policy = 0;
- priority = 100;
- preload = 0;
- permission = 0664;
- moduleName = "";
- serviceName = "";
- deviceMatchAttr = "";
- }
- }
- }
- sample_host :: host{
- hostName = "host0"; // host名稱,host節點是用來存放某一類驅動的容器
- priority = 100; // host啟動優先級(0-200),值越大優先級越低,建議默認配100,優先級相同則不保證host的加載順序
- device_sample :: device { // sample設備節點
- device0 :: deviceNode { // sample驅動的DeviceNode節點
- policy = 1; // policy字段是驅動服務發布的策略,在驅動服務管理章節有詳細介紹
- priority = 100; // 驅動啟動優先級(0-200),值越大優先級越低,建議默認配100,優先級相同則不保證device的加載順序
- preload = 0; // 驅動加載策略,參考《5.2 HDF驅動框架章節》
- permission = 0664; // 驅動創建設備節點權限
- moduleName = "sample_driver"; // 驅動名稱,該字段的值必須和驅動入口結構體的moduleName值一致
- serviceName = "sample_service"; // 驅動對外發布服務的名稱,必須唯一
- deviceMatchAttr = "sample_config"; // 驅動私有數據匹配的關鍵字,必須和驅動私有數據配置表中的match_attr值相等
- }
- }
- }
- }
- }
定義設備列表時使用了HCS的模板語法,template host節點下的內容由HDF框架定義,新增host以及host中的device只需要繼承該模板并填充具體內容即可。
在配置中定義的device將在加載過程中產生一個設備實例,配置中通過moduleName字段指定設備對應的驅動名稱,從而將設備與驅動關聯起來。其中,設備與驅動可以是一對多的關系,即可以實現一個驅動支持多個同類型設備。
3. 用戶態程序與驅動交互
用戶態程序和驅動交互基于HDF IoService模型實現,該設計屏蔽了具體內核的差異,將驅動接口抽象為IoService對象,調用者基于名稱獲取該對象,并可以使用IoService系列接口進行接口調用和事件監聽。值得一提的是消息傳遞時使用了HDF Sbuf對象進行參數的序列化和反序列化,這樣可以避免不受控的內存訪問,也簡化了消息傳遞和分發過程中的內存所有權問題,有利于提升用戶態和內核態數據傳遞的安全性和便利性。HDF Sbuf相關接口可以參考HarmonyOS設備開發官網API Reference中頭文件hdf_sbuf.h部分。
基于HDF框架編寫的用戶態程序和驅動交互的代碼如下:
- #include "hdf_log.h"
- #include "hdf_sbuf.h"
- #include "hdf_io_service_if.h"
- #define HDF_LOG_TAG "sample_test"
- #define SAMPLE_SERVICE_NAME "sample_service"
- #define SAMPLE_WRITE_READ 0xFF00
- int g_replyFlag = 0;
- static int OnDevEventReceived(void *priv, uint32_t id, struct HdfSBuf *data)
- {
- const char *string = HdfSbufReadString(data);
- int ret = HDF_SUCCESS;
- if (string == NULL) {
- HDF_LOGE("failed to read string in event data");
- ret = HDF_FAILURE;
- } else {
- HDF_LOGE("%s", string);
- }
- g_replyFlag = 1;
- return ret;
- }
- static int SendEvent(struct HdfIoService *serv, char *eventData)
- {
- int ret = 0;
- struct HdfSBuf *data = HdfSBufObtainDefaultSize(); // 申請需要發送的序列化對象
- if (data == NULL) {
- HDF_LOGE("failed to obtain sbuf data");
- return 1;
- }
- struct HdfSBuf *reply = HdfSBufObtainDefaultSize(); // 申請返回數據的序列化對象
- if (reply == NULL) {
- HDF_LOGE("failed to obtain sbuf reply");
- ret = HDF_DEV_ERR_NO_MEMORY;
- goto out;
- }
- if (!HdfSbufWriteString(data, eventData)) { // 準備消息內容
- HDF_LOGE("failed to write sbuf");
- ret = HDF_FAILURE;
- goto out;
- }
- ret = serv->dispatcher->Dispatch(&serv->object, SAMPLE_WRITE_READ, data, reply); // 發起接口調用
- if (ret != HDF_SUCCESS) {
- HDF_LOGE("failed to send service call");
- goto out;
- }
- int replyData = 0;
- if (!HdfSbufReadInt32(reply, &replyData)) { // 反序列化返回數據
- HDF_LOGE("failed to get service call reply");
- ret = HDF_ERR_INVALID_OBJECT;
- goto out;
- }
- HDF_LOGE("Get reply is: %d", replyData);
- out:
- HdfSBufRecycle(data);
- HdfSBufRecycle(reply);
- return ret;
- }
- int main()
- {
- struct HdfIoService *serv = HdfIoServiceBind(SAMPLE_SERVICE_NAME); // 通過名稱獲取IoService對象,與驅動配置中的名稱一致
- if (serv == NULL) {
- HDF_LOGE("failed to get service %s", SAMPLE_SERVICE_NAME);
- return HDF_FAILURE;
- }
- static struct HdfDevEventlistener listener = { // 構造驅動事件監聽器對象
- .callBack = OnDevEventReceived, // 填充事件處理方法
- .priv = NULL;
- };
- if (HdfDeviceRegisterEventListener(serv, &listener) != HDF_SUCCESS) { // 注冊事件監聽
- HDF_LOGE("failed to register event listener");
- return HDF_FAILURE;
- }
- if (SendEvent(serv, "Hello World, HDF Driver!")) { // 調用驅動接口,樣例驅動收到事件
- HDF_LOGE("failed to send event");
- return HDF_FAILURE;
- }
- while (g_replyFlag == 0) { // 等待驅動上報事件
- sleep(1);
- }
- HdfDeviceUnregisterEventListener(serv, &listener)); // 去注冊事件監聽器
- HdfIoServiceRecycle(serv); // 回收IoService對象
- return 0;
- }
該示例執行后會在終端中打印出"Hello World, HDF Driver!"字符串,表明我們的用戶態測試程序和驅動成功地進行了一次交互。
三、使用DevEco Device Tool進行驅動開發
上一小節介紹了OpenHarmony驅動的一般開發方法,那么有沒有更簡單的方法添加一款驅動呢?答案就是華為南向開發IDE——DevEco Device Tool。DevEco Device Tool最新版本已經集成了HDF驅動開發功能,下面介紹如何使用DevEco Device Tool進行驅動開發。
DevEco Device Tool下載鏈接:
https://device.harmonyos.com/cn/develop/ide#download_release。
1. 創建驅動
(1)導入工程
參考DevEco Device Tool手冊,通過npm或網絡下載的方式導入OHOS工程。

圖5 DevEco Device Tool啟動界面
(2)使用HDF頁面工具創建新驅動,按照需求填寫Module名稱,工具將根據Module名稱創建對應驅動代碼與。

圖6 Device Eco Tool HDF插件界面
DevEco Device Tool將自動生成驅動實現代碼:

圖7 Device Eco Tool 生成驅動代碼
為源碼文件自動生成編譯腳本:

圖8 Device Eco Tool 生成驅動編譯腳本
DevEco Device Tool還會在對應單板的驅動配置中生成驅動設備配置信息:

圖9 Device Eco Tool 生成驅動配置信息
2. 修改驅動

圖10 Device Eco Tool驅動快速編輯界面
DevEco Device Tool提供了快捷方式直達源碼、編譯腳本、配置文件,點擊鏈接修改相關文件,實現驅動功能。DevEco Device Tool自動生成代碼已經提供了DriverEntry的基礎實現,只需填充對應函數的實際功能即可。
3. 編譯版本
使用DevEco Device Tool build功能一鍵編譯版本,編譯輸出顯示在終端窗口:

圖11 Device Eco Tool編譯界面
4. 燒錄驗證
DevEco Device Tool提供了一站式的燒錄、調試環境。使用upload功能將編譯好的鏡像燒錄進開發板。

圖12 Device Eco Tool燒寫功能界面
燒錄過程和進度顯示在終端窗口。

圖13 Device Eco Tool燒寫輸出
四、總結
除了在此次HDC大會與大家分享驅動框架的設計和最新進展,開放原子基金會還在OpenHarmony公眾號、gitee社區等渠道發布了一系列技術分享、指導文檔等資料,歡迎大家關注并一起建設OpenHarmony驅動生態。