品讀鴻蒙HDF架構(二)

悠然紅茶發表於2020-10-02

品讀鴻蒙HDF架構(二)

               侯 亮

在前一篇文章裡,我們闡述了在啟動DeviceManager這個核心服務時,是如何生成所有的host配套設施的,下面我們來進一步剖析細節。

我們已經知道,一個Host對應一個DevHostServiceClnt和一個DevHostService,很明顯主要行為都包含在後者內部。當後者啟動時,會執行到DriverInstallerStartDeviceHost(),該函式又會呼叫DevHostServiceStartServie(),這些內容在前一篇文章裡都說過。

我們不用去想太多呼叫細節,反正說起來就是要讓一個DevHostServiceClnt和一個DevHostService“掛接”(attach)起來,掛接的動作裡會進一步在DevHostService裡安裝裝置驅動。這個掛接動作具體對應的函式就是DevmgrServiceClntAttachDeviceHost()。在上一篇文章裡,我們沒有展開講這個函式,現在就從它說起。為了便於閱讀,我將掛接動作的呼叫順序先繪製出來,如下圖所示:

1 掛接device Host

我用黃色框表達了DevmgrServiceClntAttachDeviceHost()一步,該函式程式碼截選如下:
【drivers/hdf/frameworks/core/host/src/Devmgr_service_clnt.c】

int DevmgrServiceClntAttachDeviceHost(uint16_t hostId, struct IDevHostService *hostService)
{
    struct IDevmgrService *devMgrSvcIf = NULL;
    . . . . . .
    devMgrSvcIf = inst->devMgrSvcIf;
    . . . . . .
    // 實際呼叫的是DevmgrServiceAttachDeviceHost()
    return devMgrSvcIf->AttachDeviceHost(devMgrSvcIf, hostId, hostService);
}

最後一句實際呼叫的是DevmgrServiceAttachDeviceHost(),程式碼截選如下:
【drivers/hdf/frameworks/core/manager/src/Devmgr_service.c】

static int DevmgrServiceAttachDeviceHost(
    struct IDevmgrService *inst, uint16_t hostId, struct IDevHostService *hostService)
{
    struct DevHostServiceClnt *hostClnt = DevmgrServiceFindDeviceHost(inst, hostId);
    . . . . . .
    hostClnt->deviceInfos = HdfAttributeManagerGetDeviceList(hostClnt->hostId, hostClnt->hostName);
    . . . . . .
    hostClnt->hostService = hostService;
    return DevHostServiceClntInstallDriver(hostClnt);
}

首先,遍歷DevmgrService的hosts列表,根據hostId找到對應的DevHostServiceClnt物件,並給該DevHostServiceClnt物件的deviceInfos域和hostService域賦值,然後呼叫重頭戲DevHostServiceClntInstallDriver()。

在獲取這個host範疇的所有device資訊時,也是去查詢上一篇文章提到的配置樹,樹節點的型別為DeviceResourceNode,只不過上一次系統是去查詢具有“hdf_manager”屬性的節點,而此次是查詢名字為hostName的節點,這個節點裡包含著若干裝置的資訊,現在這些裝置資訊會被組織成一個HdfDeviceInfo連結串列。最終形成下面圖中的結構:

1.1 安裝host範疇內的裝置驅動

1.1.1 在每個host的DevHostService裡新增裝置

Attach動作的最後一步就是安裝驅動啦,我們看一下這個DevHostServiceClntInstallDriver()函式:
【drivers/hdf/frameworks/core/manager/src/Devhost_service_clnt.c】

int DevHostServiceClntInstallDriver(struct DevHostServiceClnt *hostClnt)
{
    . . . . . .
    struct HdfSListIterator it;
    struct HdfDeviceInfo *deviceInfo = NULL;
    struct IDevHostService *devHostSvcIf = NULL;
    . . . . . .
    devHostSvcIf = (struct IDevHostService *)hostClnt->hostService;
    . . . . . .
    HdfSListIteratorInit(&it, hostClnt->deviceInfos);
    while (HdfSListIteratorHasNext(&it)) {
        deviceInfo = (struct HdfDeviceInfo *)HdfSListIteratorNext(&it);
        if ((deviceInfo == NULL) || (deviceInfo->preload != DEVICE_PRELOAD_ENABLE)) {
            continue;
        }
        // 實際呼叫的是 DevHostServiceAddDevice()
        ret = devHostSvcIf->AddDevice(devHostSvcIf, deviceInfo);
        . . . . . .
    }
    return HDF_SUCCESS;
}

其實就是遍歷一下該host範疇內的所有HdfDeviceInfo節點,如果節點的preload是“使能”的,就執行對應的AddDevice操作,即DevHostServiceAddDevice()函式,其程式碼截選如下:
【drivers/hdf/frameworks/core/host/src/Devhost_service.c】

static int DevHostServiceAddDevice(struct IDevHostService *inst, const struct HdfDeviceInfo *deviceInfo)
{
    int ret = HDF_FAILURE;
    struct HdfDevice *device = NULL;
    struct HdfDeviceNode *devNode = NULL;
    struct DevHostService *hostService = (struct DevHostService *)inst;
    struct IDriverLoader *driverLoader =  HdfDriverLoaderGetInstance();
    . . . . . .
    device = DevHostServiceGetDevice(hostService, deviceInfo->deviceId);
    . . . . . .
    // 實際呼叫的是 HdfDriverLoaderLoadNode()
    devNode = driverLoader->LoadNode(driverLoader, deviceInfo);
    . . . . . .
    devNode->hostService = hostService;
    // 實際呼叫的是 HdfDeviceAttach()
    ret = device->super.Attach(&device->super, devNode);
    . . . . . .
    return HDF_SUCCESS;
    . . . . . .
}

在這個函式裡,先呼叫DevHostServiceGetDevice()嘗試從DevHostService的devices列表裡查詢與deviceId匹配的節點,如果找不到就建立一個新HdfDevice節點,並插入該列表。

當然,一開始devices列表是個空列表,此時只會建立新節點。反正經此一步,我們一般可以拿到一個可用的HdfDevice物件。接著利用驅動載入器載入一個和deviceInfo匹配的HdfDeviceNode節點。最後還需把得到的HdfDevice和HdfDeviceNode掛接起來。

1.1.1.1 載入HdfDeviceNode

載入HdfDeviceNode的動作實際上是HdfDriverLoaderLoadNode(),程式碼截選如下:
【drivers/hdf/frameworks/core/host/src/Hdf_driver_loader.c】

static struct HdfDeviceNode *HdfDriverLoaderLoadNode(
    struct IDriverLoader *loader, const struct HdfDeviceInfo *deviceInfo)
{
    struct HdfDriverEntry *driverEntry = NULL;
    struct HdfDeviceNode *devNode = NULL;
    . . . . . .
    // 實際呼叫的是 HdfDriverLoaderGetDriverEntry()
    driverEntry = loader->GetDriverEntry(deviceInfo);
    . . . . . .
    devNode = HdfDeviceNodeNewInstance();
    . . . . . .
    devNode->driverEntry = driverEntry;
    devNode->deviceInfo = deviceInfo;
    devNode->deviceObject.property = HcsGetNodeByMatchAttr(HcsGetRootNode(), deviceInfo->deviceMatchAttr);
    . . . . . .
    if ((deviceInfo->policy == SERVICE_POLICY_PUBLIC) || 
        (deviceInfo->policy == SERVICE_POLICY_CAPACITY)) {
        . . . . . .
        if (driverEntry->Bind(&devNode->deviceObject) != 0) {
            HDF_LOGE("bind driver failed");
            HdfDeviceNodeFreeInstance(devNode);
            return NULL;
        }
    }
    return devNode;
}

HdfDeviceNode的定義如下:
【drivers/hdf/frameworks/core/host/include/Hdf_device_node.h】

struct HdfDeviceNode {
    struct IDeviceNode super;
    struct HdfSListNode entry;
    struct PowerStateToken *powerToken;
    struct DevHostService *hostService;
    struct HdfDeviceObject deviceObject;
    struct IHdfDeviceToken *token;
    struct HdfDriverEntry *driverEntry;
    const struct HdfDeviceInfo *deviceInfo;
};

可以看到,驅動載入器在建立HdfDeviceNode節點時,還是有一些工作要做的:
1)得載入相應裝置的驅動程式入口,最終體現為HdfDriverEntry;
2)建立一個HdfDeviceNode物件,經過研究,我們可以看到最終建立的其實是HdfDeviceNode的派生類(DeviceNodeExt)物件;
3)把HdfDeviceNode節點和裝置驅動程式繫結起來;

1.1.1.1.1 獲取驅動入口
驅動載入器獲取HdfDriverEntry的實際動作是HdfDriverLoaderGetDriverEntry():
【drivers/hdf/lite/manager/src/Lite_driver_loader.c】

struct HdfDriverEntry *HdfDriverLoaderGetDriverEntry(const struct HdfDeviceInfo *deviceInfo)
{
    int count = (int) (((uint8_t *)(HDF_DRIVER_END()) - (uint8_t *)(HDF_DRIVER_BEGIN())) / sizeof(size_t));
    size_t *addrBegin = (size_t*)(HDF_DRIVER_BEGIN());
    if ((deviceInfo == NULL) || (deviceInfo->moduleName == NULL) || (deviceInfo->svcName == NULL)) {
        HDF_LOGE("Hdf get device entry failed, input deviceInfo is NULL!");
        return NULL;
    }

    for (int i = 0; i < count; i++) {
        struct HdfDriverEntry *driverEntry = (struct HdfDriverEntry *)(*addrBegin);
        if (strcmp(deviceInfo->moduleName, driverEntry->moduleName) == 0) {
            return driverEntry;
        }
        addrBegin++;
    }
    HDF_LOGE("Hdf get %s device entry failed!", deviceInfo->svcName);
    return NULL;
}

其中,HdfDriverEntry的定義如下:
【drivers/hdf/frameworks/include/core/Hdf_device_desc.h】

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);
};

現在我們來解釋一下,HdfDriverLoaderGetDriverEntry()到底在幹什麼。我們設想,HDF會先載入需要的所有驅動程式,每個驅動程式內部都會構造一個HdfDriverEntry物件,而且會填好那個Bind域,這其實就是在填寫一個回撥函式指標,當然,也只有驅動程式自己知道該填寫哪個函式指標。

HDF會把載入的所有驅動的HdfDriverEntry物件的起始地址彙總起來,形成一個類似地址陣列的東西,這個陣列的第一項的地址對應上面程式碼中的HDF_DRIVER_BEGIN(),最後一項的地址對應HDF_DRIVER_END()(最後一項不填內容)。示意圖如下:

獲取驅動入口時,就是在遍歷這個指標陣列,查詢與moduleName匹配的節點。

1.1.1.1.2 建立HdfDeviceNode物件
接著嘗試建立HdfDeviceNode物件,此時呼叫的HdfDeviceNodeNewInstance()函式如下:
【drivers/hdf/frameworks/core/host/src/Hdf_device_node.c】

struct HdfDeviceNode *HdfDeviceNodeNewInstance()
{
    return (struct HdfDeviceNode *)HdfObjectManagerGetObject(HDF_OBJECT_ID_DEVICE_SERVICE);
}

又需要去查我們熟悉的物件建立表(g_liteObjectCreators),最終查到會呼叫DeviceNodeExtCreate():
【drivers/hdf/lite/manager/src/Hdf_device_node_ext.c】

struct HdfObject *DeviceNodeExtCreate()
{
    struct DeviceNodeExt *instance =
        (struct DeviceNodeExt *)OsalMemCalloc(sizeof(struct DeviceNodeExt));
    if (instance != NULL) {
        DeviceNodeExtConstruct(instance);
        instance->ioService = NULL;
    }
    return (struct HdfObject *)instance;
}

可以看到,實際建立的是一個DeviceNodeExt物件。DeviceNodeExt繼承於HdfDeviceNode,定義如下:
【drivers/hdf/lite/include/manager/Hdf_device_node_ext.h】

struct DeviceNodeExt {
    struct HdfDeviceNode super;
    struct HdfIoService *ioService;
};

其建構函式如下:
【drivers/hdf/lite/manager/src/Hdf_device_node_ext.c】

static void DeviceNodeExtConstruct(struct DeviceNodeExt *inst)
{
    struct IDeviceNode *nodeIf = (struct IDeviceNode *)inst;
    if (nodeIf != NULL) {
        HdfDeviceNodeConstruct(&inst->super);
        nodeIf->PublishService = DeviceNodeExtPublishService;
    }
}

注意,它修改了繼承來的PublishService域,將函式指標設為DeviceNodeExtPublishService了。

HdfDriverLoaderLoadNode()會給DeviceNodeExt的driverEntry域、deviceInfo域、deviceObject.property賦值,那麼在進行繫結之前,DeviceNodeExt的示意圖大概是這樣的:


1.1.1.1.3繫結驅動入口
接下來要將剛剛建立的DeviceNodeExt節點和驅動入口繫結起來:

driverEntry->Bind(&devNode->deviceObject)

前文我們已經說了,每個程式會實現自己的Bind動作,而HDF只負責回撥Bind。注意,回撥時HDF需要傳入DeviceNodeExt節點的deviceObject部分的指標,因為需要驅動程式填寫其中的域。當然,我們從上圖中可以看到,deviceObject部分只剩下service域(IDeviceIoService*)需要填寫。那麼很明顯,一個驅動程式要能被HDF使用,那麼它就得包含一個IDeviceIoService物件。IDeviceIoService的定義如下:
【drivers/hdf/frameworks/include/core/Hdf_device_desc.h】

struct IDeviceIoService {
    struct HdfObject object;
    int32_t (*Open)(struct HdfDeviceIoClient *client);
    int32_t (*Dispatch)(struct HdfDeviceIoClient *client, int cmdId, struct HdfSBuf *data, struct HdfSBuf *reply);
    void (*Release)(struct HdfDeviceIoClient *client);
};

現在我們可以基於前文示意圖,繪製一張DeviceNodeExt和驅動程式繫結後的示意圖了,如下圖:

1.1.1.2 掛接HdfDeviceNode
DevHostServiceAddDevice()在載入好DeviceNodeExt之後,呼叫了一句Attach:

    ret = device->super.Attach(&device->super, devNode);

嘗試把HdfDevice節點和DeviceNodeExt聯絡起來,這一句其實是呼叫HdfDeviceAttach(),相關程式碼如下:
【drivers/hdf/frameworks/core/host/include/Hdf_device.h】

struct IHdfDevice {
    struct HdfObject object;
    int (*Attach)(struct IHdfDevice *, struct HdfDeviceNode *);
};

struct HdfDevice {
    struct IHdfDevice super;
    struct HdfSListNode node;
    struct HdfSList services;
    uint16_t deviceId;
    uint16_t hostId;
};

【drivers/hdf/frameworks/core/host/src/Hdf_device.c】

static int HdfDeviceAttach(struct IHdfDevice *devInst, struct HdfDeviceNode *devNode)
{
    struct HdfDevice *device = (struct HdfDevice *)devInst;
    struct IDeviceNode *nodeIf = (struct IDeviceNode *)devNode;
    . . . . . .
    HdfSListAdd(&device->services, &devNode->entry);
    // 實際呼叫的是 HdfDeviceLaunchNode()
    return nodeIf->LaunchNode(devNode, devInst);
}

程式碼裡先將DeviceNodeExt新增進HdfDevice的services列表裡,然後呼叫了HdfDeviceLaunchNode()。

我們前文已經說過,HdfDevice節點在之前已經新增進DevHostService的devices列表了,現在它又和DeviceNodeExt聯絡起來了,再結合前文中的知識,我們可以畫一張大一點兒的關係示意圖了,如下:

至此,相信大家已經基本瞭解掛接裝置host所形成的資料結構了,正如上圖所示,每個host都會對應上圖中紅、綠、藍三個範疇。大家不妨自己試著畫畫這張圖,看看還會發現什麼。至於HDF的其他方面,我們可以在其他文章裡再探討。

(我正在參加 CSDN 的【鴻蒙技術徵文】活動,請給我點贊支援。)

相關文章