鴻蒙驅動框架

本文首發於:LHM’s notes 歡迎關注我的新博客

鴻蒙驅動是基於HDF(Harmony Driver Foundation)驅動框架,爲開發者提供了一系列統一接口供其調用,包括驅動加載、驅動服務管理和驅動消息機制。我們要做的,是學習如何使用這些接口,並基於這些接口的使用實現某些業務功能。

設備驅動概述

相信每個人都有給電腦安裝驅動的經歷,驅動的使用就是去某個官網去下載個軟件包,然後一路點擊安裝就行了。這裏可以明確一個定義:驅動是一段程序代碼。那麼設備呢? 鼠標、鍵盤、顯示器、這些都叫做設備,但是和我們這個裏面的驅動設備裏面的設備不一樣,設備也是一段代碼,而這段代碼可以描述這個設備的各種信息,比如設備版本號、mac地址等等各種關於這個設備的信息。而驅動的作用是讓這個設備能夠正常運行起來,因此從模型的意義上來說,設備和驅動是兩個模塊,一個驅動可以對應多個設備,比如你電腦上插入兩個鼠標,那麼分別是鼠標設備1和鼠標設備2,但是他們的驅動是同一個,這也就是每個驅動只用安裝一次的道理。

驅動和設備如何綁定在一起,在linux內核的源碼中,是通過bind將一個設備和某個驅動 進行捆綁。同時考慮到驅動的豐富多樣性,A電腦只需要一個打印機的驅動,不需要顯示器驅動;而B電腦可能只需要顯示器的驅動。各個用戶對驅動的需求不一致,如果都放到內核裏面,會導致內核的代碼越來越大,且有大量冗餘,因此驅動的加載自己可配置。在Windows上,除了常見功能的驅動是自帶之外,其他設備的驅動一般都要自己安裝。

驅動加載

在linux上,驅動的自我加載就是在內核上有選擇的插入某個驅動模塊(.ko文件),而鴻蒙也是基於這個原理。HDF驅動加載包括按需加載和按序加載。

HDF驅動加載包括按需加載和按序加載。

1、按需加載

​ HDF框架支持驅動在系統啓動過程中默認加載,或者在系統啓動之後動態加載。

2、按序加載

​ HDF框架支持驅動在系統啓動過程中按照驅動的優先級進行加載

需要注意的是:我們在此之後所提到的所有驅動,其實是包含設備和驅動兩部分,設備和驅動的代碼實現整體稱之爲驅動加載

驅動服務管理

HDF框架可以集中管理驅動服務,使用者可以通過HDF框架對外提供的能力接口獲取驅動相關的服務。

當前服務有三種:

1、服務綁定
驅動服務結構的定義
struct ISampleDriverService {
    struct IDeviceIoService ioService;   // 服務結構的首個成員必須是IDeviceIoService類型的成員
    int32_t (*ServiceA)(void);               // 驅動的第一個服務接口
    int32_t (*ServiceB)(uint32_t inputCode); // 驅動的第二個服務接口,有多個可以依次往下累加
};

int32_t SampleDriverDispatch(struct HdfDeviceObject *device, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply)
{
    HDF_LOGE("sample driver lite A dispatch");
    return 0;
}

int32_t SampleDriverServiceA(void)
{
    // 驅動開發者實現業務邏輯
    return 0;
}

int32_t SampleDriverServiceB(uint32_t inputCode)
{
    // 驅動開發者實現業務邏輯
    return 0;
}

ServiceA 和ServiceB都是開發者可以隨意修改定製的類型,而第一個ioService的類型是固定的,這個在驅動消息機制小節中會講到爲啥是固定的。

由開發者實現的驅動服務接口都會註冊到ISampleDriverService 結構體中,如下第9、10行; 接着將ISampleDriverService結構體註冊到deviceObject中; 這樣,內核就可以通過deviceObject調用開發者實現的服務。

int32_t SampleDriverBind(struct HdfDeviceObject *deviceObject)
{
    // deviceObject爲HDF框架給每一個驅動創建的設備對象,用來保存設備相關的私有數據和服務接口
    if (deviceObject== NULL) {
        HDF_LOGE("Sample device object is null!");
        return -1;
    }
    static struct ISampleDriverService sampleDriverA = {
        .ioService.Dispatch = SampleDriverDispatch,
        .ServiceA = SampleDriverServiceA,
        .ServiceB = SampleDriverServiceB,
        
    };
    deviceObject->service = &sampleDriverA.ioService;
    return 0;
}
2、服務獲取

當明確驅動已經加載完成時,獲取該驅動的服務可以通過HDF框架提供的能力接口直接獲取,如下所示:

const struct ISampleDriverService *sampleService =
        (const struct ISampleDriverService *)DevSvcManagerClntGetService("sample_driver");
if (sampleService == NULL) {
   
   
    return -1;
}
sampleService->ServiceA();
sampleService->ServiceB(5);
3、服務訂閱

服務訂閱也是服務獲取的一種,只是是通過訂閱的方式進行實現,因爲上一小節服務獲取的前提條件是已經明確驅動已經加載完成了。但是這個條件不一定已經實現,通過服務訂閱的方式,當被訂閱的驅動加載完成後,系統會自己調用callback函數從而可以調用開發者實現的服務函數。

// 訂閱回調函數的編寫,當被訂閱的驅動加載完成後,HDF框架會將被訂閱驅動的服務發佈給訂閱者,通過這個回調函數給訂閱者使用
// object爲訂閱者的私有數據,service爲被訂閱的服務對象
int32_t TestDriverSubCallBack(struct HdfDeviceObject *deviceObject, const struct HdfObject *service)
{
    const struct ISampleDriverService *sampleService =
        (const struct ISampleDriverService *)service;
    if (sampleService == NULL) {
        return -1;
    }
    sampleService->ServiceA();
    sampleService->ServiceB(5);
}
// 訂閱過程的實現
int32_t TestDriverInit(struct HdfDeviceObject *deviceObject)
{
    if (deviceObject== NULL) {
        HDF_LOGE("Test driver init failed, deviceObject is null!");
        return -1;
    }
    struct SubscriberCallback callBack;
    callBack.deviceObject = deviceObject;
    callBack.OnServiceConnected = TestDriverSubCallBack;
    int32_t ret = HdfDeviceSubscribeService(deviceObject, "sample_driver", callBack);
    if (ret != 0) {
        HDF_LOGE("Test driver subscribe sample driver failed!");
    }
    return ret;
}

驅動消息機制

HDF框架提供統一的驅動消息機制,支持用戶態應用向內核態驅動發送消息,也支持內核態驅動向用戶態應用發送消息。在linux內核中,常用的指令ioctl用來用戶態傳遞指令到內核態, 但是ioctl不能主動將內核態消息傳遞到用戶態。內核態與用戶態可以相互傳遞消息可以使用netlink機制。

鴻蒙系統中也有固定的一套接口,在服務綁定小節中講到第一個服務的函數指針類型是固定的,如下

int32_t SampleDriverDispatch(struct HdfDeviceObject *device, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply);

這個固定類型的服務其實是一個基本的通信接口,類似於linux中的ioctl,有比較固定的使用方法,第一個參數是設備對象,第二個是讀寫指令,第三個是發送數據地址,第四個參數是回覆消息地址。

個人感覺這就是個模板,供開發者去參考,當然開發者也可以定義像ServiceA、ServiceB這種沒有參數或者一個參數的服務,但是遠沒有這種模板的功能齊全。

用戶態通過服務獲取或者服務訂閱定製機制獲取到該服務集,接着調用裏面的dispatcher觸發第一個服務。

 int ret = serv->dispatcher->Dispatch(&serv->object, SAMPLE_WRITE_READ, data, reply);
    if (ret != HDF_SUCCESS) {
   
   

用戶態給內核態寫數據時:將第二個參數置爲 WRITE; 將要發送的數據指針傳到第三個參數(data);再調用上述接口即可進行消息下發操作。

用戶態獲取內核態數據時:將第二個參數置爲READ; Dispatch執行成功後,讀取reply中的數據即可。

WRITE和READ指令可以同時使用。

但是這還沒有解決一個問題,就是內核態主動給用戶態發送消息。這個linux的ioctl機制也不支持,鴻蒙採用的解決方式是用戶態採用監聽的方式,如果內核態調用了HdfDeviceSendEvent(deviceObject, cmdCode, data);用戶態可以感知到,接着通過用戶態註冊的回調函數來處理內核態發送過來的消息。具體操作如下:

  1. 用戶態編寫驅動上報消息的處理函數。

    static int OnDevEventReceived(void *priv,  uint32_t id, struct HdfSBuf *data)
    {
         
         
        OsalTimespec time;
        OsalGetTime(&time);
        HDF_LOGE("%s received event at %llu.%llu", (char *)priv, time.sec, time.usec);
    
        const char *string = HdfSbufReadString(data);
        if (string == NULL) {
         
         
            HDF_LOGE("fail to read string in event data");
            return -1;
        }
        HDF_LOGE("%s: dev event received: %d %s",  (char *)priv, id, string);
        return 0;
    }
    
  2. 用戶態註冊接收驅動上報消息的操作方法。

    int RegisterListen()
    {
         
         
        struct HdfIoService *serv = HdfIoServiceBind("sample_driver", 0);
        if (serv == NULL) {
         
         
            HDF_LOGE("fail to get service");
            return -1;
        }
        static struct HdfDevEventlistener listener = {
         
         
            .callBack = OnDevEventReceived,
            .priv ="Service0"
        };
        if (HdfDeviceRegisterEventListener(serv, &listener) != 0) {
         
         
            HDF_LOGE("fail to register event listener");
            return -1;
        }
        ......
        HdfDeviceUnregisterEventListener(serv, &listener);
        HdfIoServiceRecycle(serv);
        return 0;
    }
    
  3. 內核態驅動上報事件。

    int32_t SampleDriverDispatch(struct HdfDeviceObject *device, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply)
    {
         
         
        ... // process api call here
        return HdfDeviceSendEvent(deviceObject, cmdCode, data);
    }
    

總結

可以看到,鴻蒙的這幾個接口基於一套service機制實現了用戶態和內核態的之間的相互通信,這就是驅動的大致框架了,開發者只需要用戶態寫寫業務邏輯,通過上述框架將指令傳遞到內核,然後內核根據下發的指令完成相應的功能,這樣一個驅動的功能就可以完整實現了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章