本文首發于: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);用戶態可以感知到,接著通過用戶態注冊的回呼函式來處理內核態發送過來的訊息,具體操作如下:
-
用戶態撰寫驅動上報訊息的處理函式,
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; } -
用戶態注冊接收驅動上報訊息的操作方法,
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; } -
內核態驅動上報事件,
int32_t SampleDriverDispatch(struct HdfDeviceObject *device, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply) { ... // process api call here return HdfDeviceSendEvent(deviceObject, cmdCode, data); }
總結
可以看到,鴻蒙的這幾個介面基于一套service機制實作了用戶態和內核態的之間的相互通信,這就是驅動的大致框架了,開發者只需要用戶態寫寫業務邏輯,通過上述框架將指令傳遞到內核,然后內核根據下發的指令完成相應的功能,這樣一個驅動的功能就可以完整實作了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/238623.html
標籤:其他
