1 Star 1 Fork 6

YUJIA / hdi_develop_guide

forked from yue / hdi_develop_guide 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
hdi_develop_guide.md 43.82 KB
一键复制 编辑 原始数据 按行查看 历史

HDI开发指导

目录

简介

HDI(Hardware Device Interface)为系统提供统一稳定的硬件接口,是对硬件功能的较高层次的抽象接口,各类外设完成HDI接口定义后便只会在HDI的兼容性规则下进行变更,从而保证接口的稳定性。具体的驱动实现不需要再重复定义HDI接口,只需要按需实现即可接入系统功能。

HDI模式

在不同量级的OpenHarmony系统上,HDI存在两种部署形态,IPC模式和直通模式。

HDI开发步骤

本文档以C++侧用户态驱动IPC模式开发实例展示HDI开发流程。

HDI系统部件开发

HDI系统部件开发旨在将idl文件编译生成出HDI系统接口so文件和提供给服务使用的so文件。

idl接口定义

在drivers_interface仓下添加对应模块的idl文件。

master$ tree ./drivers/interface/foo
./drivers/interface/foo
├── bundle.json
└── v1_0
    ├── BUILD.gn         # 编译idl文件的BUILD.gn
    ├── IBar.idl         # 定义普通接口
    ├── IFoo.idl         # 定义驱动接口
    ├── IFooCallback.idl # 定义用于回调的接口
    └── Types.idl        # 定义自定义类型数据

IFoo.idl

package ohos.hdi.foo.v1_0;  // 包名

import ohos.hdi.foo.v1_0.IFooCallback; // 导入IFooCallback.idl文件
import ohos.hdi.foo.v1_0.IBar;         // 导入IBar.idl文件
import ohos.hdi.foo.v1_0.Types;        // 导入Types.idl文件

// 接口定义 接口名为'IFoo',必须与文件名保持一致
interface IFoo {
    [oneway] Ping([in] String msg);

    InfoTest([in] struct Info inParam, [out] struct Info outParam);

    SendCallback([in] IFooCallback cbObj);

    GetServObj([out] IBar servObj);
}

IBar.idl

package ohos.hdi.foo.v1_0;

interface IBar {
    Echo([in] String sendMsg, [out] String recvMsg);
}

IFooCallback.idl

package ohos.hdi.foo.v1_0;

[callback] interface IFooCallback {
    [oneway] Notify([in] String message);
}

Types.idl

package ohos.hdi.foo.v1_0;

// 枚举类型
enum InfoType {
    FOO_TYPE_ONE = 1,
    FOO_TYPE_TWO = 2,
};

// 结构体类型
struct Info {
    unsigned int id;
    String name;
    enum InfoType type;
};

// 联合体类型
union UInfo {
    byte m1;
    int  m2;
};

编译配置

同级目录下创建BUILD.gn文件

import("//drivers/hdf_core/adapter/uhdf2/hdi.gni")   # 编译idl必须要导入的模板
hdi("foo") {                                         # 目标名称,会生成两个so: libfoo_[proxy/stub]_[major_ver].[minor_ver].z.so
    module_name = "foo"                              # module_name匹配dirver文件中驱动描述符(HdfDriverEntry)的moduleName
    sources = [                                      # 参与编译的idl文件
        "IFoo.idl",
        "IBar.idl",
        "IFooCallback.idl",
        "MyTypes.idl",
    ]

    language = "cpp"                                 # 控制idl生成c或c++代码 可选择`c`或`cpp`
    mode = "ipc"                                     # 指定为ipc模式,亦可指定为"passthrough"表示直通模式,不添加此选项,默认为ipc
    subsystem_name = "hdf"                           # 子系统,统一填写“hdf”
    part_name = "drivers_interface_foo"              # 部件名,如果不属于已有部件,则需要定义新的部件
}

部件配置

部件化bundle.json的基本参考信息

{
    "component": {
      "name": "部件名",
      "subsystem": "子系统名",
      "features":[不同形态下的特定功能],
      "build": {
        "sub_component": [
          "模块:目标"
        ],
        "test": [包含的测试用例集合],
        "inner_kits": [
          {
            "name": "模块:目标名",
            "header": {
              "header_files": [对外暴露的头文件],
              "header_base": "对外暴露头文件的文件夹路径"
            }
          },
        ]
      }
    }
  }

常见配置项:

  • sub_component:部件包含的模块集合,包括这些依赖的模块
  • inner_kits:部件提供给系统内其他部件使用的接口
  • test_list:部件包含的测试用例集合
  • feature:主要解决同一个部件在不同产品下的差异化,一个部件可以指定feature的值,支持同一个部件在不同产品下的实现

这里给出bundle.json模板进行参考,添加部件配置://drivers/interface/foo/bundle.json

{
    "name": "drivers_interface_foo",
    "description": "foo device driver interface",
    "version": "3.2",
    "license": "Apache License 2.0",
    "component": {
      "name": "drivers_interface_foo",
      "subsystem": "hdf",
      "syscap": [""],
      "adapter_system_type": ["standard"],
      "rom": "675KB",
      "ram": "1024KB",
      "deps": {
        "components": [
          "ipc",
          "hdf_core",
          "hiviewdfx_hilog_native",
          "utils_base"
        ],
        "third_part": [
          "bounds_checking_function"
        ]
      },
      "build": {
        "sub_component": [
          "//drivers/interface/foo/v1_0:foo_idl_target"
        ],
        "test": [
        ],
        "inner_kits": [
          {
            "name": "//drivers/interface/foo/v1_0:libfoo_proxy_1.0",
            "header": {
              "header_files": [
              ],
              "header_base": "//drivers/interface/foo"
            }
          },
          {
            "name": "//drivers/interface/foo/v1_0:foo_idl_headers",
            "header": {
              "header_files": [
              ],
              "header_base": "//drivers/interface/foo"
            }
          }
        ]
      }
    }
  }

其中:

  • foo_idl_target包含libfoo_proxy_1.0.z.so与libfoo_stub_1.0.z.so
  • libfoo_proxy_1.0为对外接口so
  • foo_idl_headers为头文件目标,若只需引用头文件,则进行外部依赖添加

部件编译入口配置

以master分支,rk3568产品为例,在此文件中添加部件配置:

//productdefine/common/inherit/rich.json

{
    "component": "drivers_interface_foo",
    "features": []
}

其他产品配置文件如下:

分支 产品 编译入口配置文件
master rk3568 //productdefine/common/inherit/rich.json
master Hi3516DV300 //vendor/hisilicon/hispark_taurus_standard/config.json

idl编译及生成产物

以上配置完成后,即可指定部件名进行编译,以master分支,编译rk3568为例:

./build.sh --product-name rk3568 --ccache --build-target drivers_interface_foo
编译产物

编译成功后,可在out/rk3568/gen/drivers/interface/foo/v1_0目录下生成HDI源码:

master_9_7$ tree ./out/rk3568/gen/drivers/interface/foo/
./out/rk3568/gen/drivers/interface/foo/
└── v1_0
    ├── bar_proxy.cpp
    ├── bar_proxy.h
    ├── bar_service.cpp           // IBar接口实现源文件,代码模板
    ├── bar_service.h             // IBar接口实现头文件,代码模板
    ├── bar_stub.cpp
    ├── bar_stub.h
    ├── drivers_interface_foo__libfoo_proxy_1.0_external_deps_temp.json
    ├── drivers_interface_foo__libfoo_stub_1.0_external_deps_temp.json
    ├── foo_callback_proxy.cpp
    ├── foo_callback_proxy.h
    ├── foo_callback_service.cpp  // IFooCallback接口实现源文件,代码模板
    ├── foo_callback_service.h    // IFooCallback接口实现头文件,代码模板
    ├── foo_callback_stub.cpp
    ├── foo_callback_stub.h
    ├── foo_driver.cpp          // 驱动加载入口代码模板,仅供参考
    ├── foo_proxy.cpp
    ├── foo_proxy.h
    ├── foo_service.cpp         // IFoo接口实现源文件,代码模板
    ├── foo_service.h           // IFoo接口实现头文件,代码模板
    ├── foo_stub.cpp
    ├── foo_stub.h
    ├── ibar.h                  // IBar对外接口头文件
    ├── ifoo.h                  // IFoo对外接口头文件
    ├── ifoo_callback.h         // IFooCallback对外接口头文件
    ├── libfoo_proxy_1.0__notice.d
    ├── libfoo_stub_1.0__notice.d
    ├── types.cpp
    └── types.h                 // 对外头文件
HDI源码编译图示

注意:

  • 绿色标注的头文件为对外头文件,其他头文件不对外
  • libfoo_proxy_1.0.z.so为系统接口so,1.0为版本号,取自idl文件中的package
  • libfoo_stub_1.0.z.so为服务使用的so

HDI芯片部件开发

模块添加

按需新增驱动模块目录,以下为参考:

//drivers/peripheral/foo

./peripheral/foo
├── BUILD.gn       # 模块编译BUILD.gn
├── bundle.json    # 部件化配置
├── hdi_service    # hdi服务代码
│   ├── BUILD.gn        # hdi服务代码编译BUILD.gn
│   ├── bar_impl.cpp    # IBar接口实现源文件
│   ├── bar_impl.h      # IBar接口实现头文件
│   ├── foo_driver.cpp  # 驱动入口
│   ├── foo_impl.cpp    # IFoo接口实现源文件
│   └── foo_impl.h      # IFoo接口实现头文件
└── test           # TDD测试用例
    ├── BUILD.gn               # 测试用例代码编译BUILD.gn
    ├── foo_callback_impl.cpp  # callback实现源文件
    ├── foo_callback_impl.h    # callback实现头文件
    └── foo_hdi_test.cpp       # TDD测试用例代码

注意:

  • foo_driver.cpp为驱动加载入口文件,参考idl生成的代码模板(foo_driver.cpp)按需修改。
  • foo_impl.h/.cpp为接口实现头/源文件,参考idl生成的代码(foo_service.h/.cpp)按需修改。

接口实现

//drivers/peripheral/foo/hdi_service/foo/foo_impl.h

#ifndef OHOS_HDI_FOO_V1_0_FOO_IMPL_H
#define OHOS_HDI_FOO_V1_0_FOO_IMPL_H

#include "v1_0/ifoo.h"

namespace OHOS {
namespace HDI {
namespace Foo {
namespace V1_0 {
class FooImpl : public IFoo {
public:
    FooImpl();
    virtual ~FooImpl() = default;

    int32_t Ping(const std::string& msg) override;

    int32_t InfoTest(const Info& inParam, Info& outParam) override;

    int32_t SendCallback(const sptr<IFooCallback>& cbObj) override;

    int32_t GetServObj(sptr<IBar>& servObj) override;
private:
    sptr<IBar> bar_;
};
} // V1_0
} // Foo
} // HDI
} // OHOS

#endif // OHOS_HDI_FOO_V1_0_FOO_IMPL_H

//drivers/peripheral/foo/hdi_service/foo/foo_impl.cpp

#include "foo_impl.h"
#include <hdf_base.h>
#include <hdf_log.h>
#include "bar_impl.h"

namespace OHOS {
namespace HDI {
namespace Foo {
namespace V1_0 {
extern "C" IFoo *FooImplGetInstance(void)
{
    return new (std::nothrow) FooImpl();
}

FooImpl::FooImpl() : bar_(new BarImpl())
{

}

int32_t FooImpl::Ping(const std::string& msg)
{
    HDF_LOGI("%{public}s: msg:%{public}s", __func__, msg.c_str());
    return HDF_SUCCESS;
}

int32_t FooImpl::InfoTest(const Info& inParam, Info& outParam)
{
    HDF_LOGI("%{public}s:", __func__);
    outParam = inParam;
    return HDF_SUCCESS;
}

int32_t FooImpl::SendCallback(const sptr<IFooCallback>& cbObj)
{
    HDF_LOGI("%{public}s:", __func__);

    int32_t ret = cbObj->Notify("callback message");
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%{public}s: failed to notify callback", __func__);
        return HDF_FAILURE;
    }

    return HDF_SUCCESS;
}

int32_t FooImpl::GetServObj(sptr<IBar>& servObj)
{
    HDF_LOGI("%{public}s:", __func__);
    servObj = bar_;
    return HDF_SUCCESS;
}
} // V1_0
} // Foo
} // HDI
} // OHOS

以上为示例,开发者按需填充业务代码。

驱动入口实现

开发者参考idl编译生成的foo_driver.cpp按需进行修改,并手动配置编译。

编译配置

//drivers/peripheral/foo/hdi_service/BUILD.gn

# Copyright (C) 2022 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import("//build/ohos.gni")
import("//drivers/hdf_core/adapter/uhdf2/uhdf.gni")

ohos_shared_library("libfoo_service_1.0") {

  sources = [
    "foo_impl.cpp",
    "bar_impl.cpp",
  ]

  if (is_standard_system) {
    external_deps = [
      "c_utils:utils",
      "drivers_interface_foo:foo_idl_headers",
      "hiviewdfx_hilog_native:libhilog",
      "ipc:ipc_single",
    ]
  } else {
    external_deps = [
      "hilog:libhilog",
      "ipc:ipc_single",
    ]
  }

  install_images = [ chipset_base_dir ]
  subsystem_name = "hdf"
  part_name = "drivers_peripheral_foo"
}

ohos_shared_library("libfoo_driver") {

  sources = [ "foo_driver.cpp" ]

  deps = [ "//drivers/interface/foo/v1_0:libfoo_stub_1.0" ]

  if (is_standard_system) {
    external_deps = [
      "hdf_core:libhdf_host",
      "hdf_core:libhdf_ipc_adapter",
      "hdf_core:libhdf_utils",
      "hdf_core:libhdi",
      "hiviewdfx_hilog_native:libhilog",
      "ipc:ipc_single",
      "utils_base:utils",
    ]
  } else {
    external_deps = [
      "hilog:libhilog",
      "ipc:ipc_single",
    ]
  }

  install_images = [ chipset_base_dir ]
  subsystem_name = "hdf"
  part_name = "drivers_peripheral_foo"
}

group("hdi_foo_service") {
  deps = [
    ":libfoo_service_1.0",
    ":libfoo_driver",
  ]
}

//drivers/peripheral/foo/BUILD.gn

if (defined(ohos_lite)) {
  group("foo_entry") {
    deps = [ ]
  }
} else {
  group("foo_entry") {
    deps = [
      "//drivers/peripheral/foo/hdi_service:hdi_foo_service"
    ]
  }
}

部件配置

{
    "name": "drivers_peripheral_foo",
    "description": "foo device driver",
    "version": "3.1",
    "license": "Apache License 2.0",
    "component": {
      "name": "drivers_peripheral_foo",
      "subsystem": "hdf",
      "syscap": [""],
      "adapter_system_type": ["standard"],
      "rom": "675KB",
      "ram": "7400KB",
      "deps": {
        "components": [
          "ipc",
          "device_driver_framework",
          "hiviewdfx_hilog_native",
          "utils_base"
        ],
        "third_part": [
          "bounds_checking_function"
        ]
      },
      "build": {
        "sub_component": [
          "//drivers/peripheral/foo:foo_entry"
        ],
        "test": [
          "//drivers/peripheral/foo/test:foo_hdi_test"
        ],
        "inner_kits": [
        ]
      }
    }
  }

部件编译入口配置

以master分支代码、rk3568产品为例://productdefine/common/inherit/chipset_common.json

{
  "component": "drivers_peripheral_foo",
  "features": []
}

其他分支配置文件:

分支 产品 编译入口配置文件
master rk3568 //productdefine/common/inherit/chipset_common.json
master Hi3516DV300 //vendor/hisilicon/hispark_taurus_standard/config.json

服务代码编译

与编译系统部件编译类似:

./build.sh --product-name rk3568 --ccache --build-target drivers_peripheral_foo

HDI基础so图示

通过上面的步骤,HDI基础的so包括以下:

接口实现库命名规则

接口实现库主要为接口实现代码,如本例中的libfoo_service.z.so,为了使得IPC模式兼容直通模式,接口实现库名需按如下规则进行命名:

情况一:当使用IFoo::Get(bool isStub = false)函数时

  1. device_info.hcs中驱动服务的serviceName填入Foo::Get(bool isStub = false)函数中硬编码的serviceName

foo_proxy.cpp

sptr<IFoo> IFoo::Get(bool isStub)
{
    return IFoo::Get("foo_service", isStub); // 默认服务名称为"foo_service"
}
  1. service库名为lib[硬编码服务名]_[主版本号].[次版本号].z.so

情况二:当使用Foo::Get(const std::string serviceName, bool isStub)时:

  1. device_info.hcs中驱动服务的serviceName可自定义填入
  2. service库名指定为:lib[接口名][服务名][主版本号].[次版本号].z.so:
  3. 接口名为idl中接口类名去掉开头的'I'后,将大驼峰转换为小写下划线,例如IFoo -> foo,IFooInterface -> foo_interface
  4. 服务名为hcs中serviceName配置的自定义名称。
  5. 例如,hcs中serviceName为foo_hdi_service,接口类名为IFoo,版本为1.0,则service库名为:libfoo_foo_hdi_service_1.0.z.so

HDI驱动配置

hcs驱动配置

以master分支,rk3568产品为例,在vendor/hihope/rk3568/hdf_config/uhdf/device_info.hcs添加驱动服务配置

foo :: host {
    hostName = "foo_host";
    priority = 50;
    uid = ""; // 用户态进程uid,缺省为空,会被配置为hostName的定义值,即普通用户
    gid = ""; // 用户态进程gid,缺省为空,会被配置为hostName的定义值,即普通用户组
    caps = ["DAC_OVERRIDE", "DAC_READ_SEARCH"]; // 用户态进程Linux capabilities配置,缺省为空,需要业务模块按照业务需要进行配置
    foo_device :: device {
        device0 :: deviceNode {
            policy = 2;
            priority = 100;
            moduleName = "libfoo_driver.z.so";
            serviceName = "foo_service";
        }
    }
}

默认示例配置,仅供参考:

foo :: host {
    hostName = "foo_host";
    priority = 50;
    foo_device :: device {
        device0 :: deviceNode {
            policy = 2;
            priority = 100;
            moduleName = "libfoo_driver.z.so";
            serviceName = "foo_service";
        }
    }
}

注意:

  • hdf的hcs文件在经过编译后,会生成hdf_devhost.cfg和hdf_default.hcb,前者包含host进程的基本信息,后者包含驱动设备信息。
  • 修改hcs文件后,重新编译不会使生成文件更新,所以需要手动删除out目录下的所有cfg和hcb文件后,再重新编译
  • 视情况,配置相应的分支与产品对应的device_info.hcs

用户与组配置

对于在hcs中新增加的host节点,需要新增配置对应进程的uid(用户ID)和gid(组ID)

passwd

passwd文件为系统用户配置文件,存储了系统中所有用户的基本信息,这里以此为例:

base/startup/init/services/etc/passwd

foo_host:x:1089:1089:::/bin/false

每行用户信息使用‘:’作为分隔符,划分为7哥字段,每个字段所表示的含义如下:

用户名:密码:UID(用户ID):GID(组ID):描述信息:主目录:默认shell
group

group为用户组配置文件,存储了所有用户组的信息,以下为例:

base/startup/init/services/etc/group

foo_host:x:1089:

每行代表一个用户组,用户组中以“:”作为分隔符,分为4个字段,每个字段的含义如下:

组名:密码:GID(组ID):该用户组中的用户列表

注意:

  • passwd中foo_host对应device_info.hcs中的uid,若device_info.hcs中uid缺省,则默认为hostName
  • group中foo_host对应device_info.hcs中的gid,若device_info.hcs中gid缺省,则默认为hostName

selinux

selinux目前只应用于开源分支,闭源分支无需配置

//base/security/selinux/sepolicy/ohos_policy/drivers/adapter/vendor/type.te

type foo_host, hdfdomain, domain;

注意:foo_host为device_info.hcs中的hostName

//base/security/selinux/sepolicy/ohos_policy/drivers/adapter/vendor/hdf_host.te

allow foo_host hdf_device_manager:hdf_devmgr_class { get };
allow foo_host hdf_foo_service:hdf_devmgr_class { add };

注意:foo_host为device_info.hcs中的hostName

//base/security/selinux/sepolicy/base/public/hdf_service.te

type hdf_foo_service, hdf_service_attr;

//base/security/selinux/sepolicy/base/public/hdf_service_contexts

foo_service                             u:object_r:hdf_foo_service:s0

HDI接口调用

服务获取

c++侧接口

class IFoo : public HdiBase {
    public:
    DECLARE_HDI_DESCRIPTOR(u"ohos.hdi.foo.v1_0.IFoo");
    
    virtual ~IFoo() = default;
    
    // 获取默认服务名为"foo_service"的HDI服务,isStub = false表示IPC模式,true表示直通模式
    static sptr<IFoo> Get(bool isStub = false);
    // 功能同上,但可指定服务名
    static sptr<IFoo> Get(const std::string &serviceName, bool isStub = false);
};

c侧接口

struct IFoo {
    ...
};

// 获取默认服务名为"foo_service"的HDI服务,isStub = false表示IPC模式,true表示直通模式
struct IFoo *IFooGet(bool isStub);
// 功能同上,但可指定服务名
struct IFooGetInstance(const char *serviceName, bool isStub);

// 释放HDI服务对象
void IFooRelease(struct IFoo *instance, bool isStub);
// 释放指定服务名对应的服务对象
void IFooReleaseInstance(const char *serviceName, struct IFoo *instance, bool isStub);

接口调用

c++侧调用示例

#include <string>
#include "v1_0/ifoo.h" // 包含HDI接口头文件
using namespace OHOS::HDI::Foo::V1_0; // HDI命名空间

sptr<IFoo> client = IFoo::Get();  // 获取HDI对象,以ipc模式

int32_t ret = client->Ping("hello");  // 调用接口

c侧调用示例:

#include "ifoo.h"

struct IFoo *client = IFooGet(false); // ipc模式

int32_t ret = client->Ping("hello world");

IFooRelease(client, false);

callback使用

callback提供自底向上的反向调用功能,开发者按需定义callback接口,在客户端代码中实现callback接口。

在本示例中,IFooCallback为callback接口类,则根据idl编译生成的源码提供的模板(foo_callback_service.h/.cpp),手动实现callback接口,如下:

//drivers/peripheral/foo/test/

$ tree ./peripheral/foo/test/
./peripheral/foo/test/
├── BUILD.gn
├── foo_callback_impl.cpp
├── foo_callback_impl.h
└── foo_hdi_test.cpp

foo_callback_impl.h

#ifndef OHOS_HDI_FOO_V1_0_FOOCALLBACK_IMPL_H
#define OHOS_HDI_FOO_V1_0_FOOCALLBACK_IMPL_H

#include "v1_0/ifoo_callback.h"

namespace OHOS {
namespace HDI {
namespace Foo {
namespace V1_0 {
class FooCallbackImpl : public IFooCallback {
public:
    FooCallbackImpl() = default;
    virtual ~FooCallbackImpl() = default;

    int32_t Notify(const std::string& message) override;
};
} // V1_0
} // Foo
} // HDI
} // OHOS

#endif // OHOS_HDI_FOO_V1_0_FOOCALLBACK_IMPL_H

foo_callback_impl.cpp

#include "foo_callback_impl.h"
#include <hdf_base.h>
#include <hdf_log.h>

#define HDF_LOG_TAG    foo_callback_service

namespace OHOS {
namespace HDI {
namespace Foo {
namespace V1_0 {
int32_t FooCallbackImpl::Notify(const std::string& message)
{
    HDF_LOGI("%{public}s: recv message:%{public}s", __func__, message.c_str());
    return HDF_SUCCESS;
}
} // V1_0
} // Foo
} // HDI
} // OHOS

客户端发送callback对象

sptr<IFooCallback> cbService = new FooCallbackImpl();
int32_t ret = client->SendCallback(cbService);

服务端获取并调用callback对象接口

int32_t FooImpl::SendCallback(const sptr<IFooCallback>& cbObj)
{
    HDF_LOGI("%{public}s:", __func__);
    int32_t ret = cbObj->Notify("callback message");
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%{public}s: failed to notify callback", __func__);
        return HDF_FAILURE;
    }
    return HDF_SUCCESS;
}

死亡监听

死亡监听用于在IPC模式下监听服务对象是否死亡,以下为使用示例:

  • 创建继承IRemoteObject::DeathRecipient的派生类FooDeathRecipient
  • 重写OnRemoteDied方法
  • 创建死亡监听对象
  • 注册死亡监听对象

C++侧使用示例:

#include <iproxy_broker.h>
#include <iremote_object.h>

using OHOS::sptr;
using OHOS::wptr;
using OHOS::IRemoteObject;

// 死亡监听类
class FooDeathRecipient : public IRemoteObject::DeathRecipient {
public:
    void OnRemoteDied(const wptr<IRemoteObject> &object) override  // 死亡监听回调,需重写
    {
        HDF_LOGE("%{public}s: foo service is dead", __func__);
    }
};

int main() {
    sptr<IFoo> fooService = IFoo::Get(false);  // 获取HDI服务对象

    const sptr<IRemoteObject::DeathRecipient> recipient = new FooDeathRecipient();  // 创建死亡监听对象
    sptr<IRemoteObject> remote = OHOS::HDI::hdi_objcast<IFoo>(fooService);          // 获取remote对象
    bool ret = remote->AddDeathRecipient(recipient); // 注册死亡监听对象

    // ...
    ret = remote->RemoveDeathRecipient(recipient);   // 注销死亡监听对象
    return 0;
}

C侧使用示例:

// 死亡监听类
struct FooDeathRecipient {
    struct HdfDeathRecipient recipient;
};

// 死亡监听回调
static void FooOnRemoteDied(struct HdfDeathRecipient *deathRecipient, struct HdfRemoteService *remote)
{
    struct FooDeathRecipient *fooRecipient = CONTAINER_OF(deathRecipient, struct FooDeathRecipient, recipient);
}

int main() {
    struct IFoo *fooService = IFooGet(false);    // 获取HDI服务对象

    struct FooDeathRecipient deathRecipient = {  // 创建死亡监听对象
        .recipient = {
            .OnRemoteDied = FooOnRemoteDied,     // 绑定回调函数
        }
    };
    
    struct HdfRemoteService *remote = fooService->AsObject(fooService);      // 获取remote对象
    HdfRemoteAdapterAddDeathRecipient(remote, &deathRecipient.recipient);    // 注册死亡监听

    // ...
    HdfRemoteAdapterRemoveDeathRecipient(remote, &deathRecipient.recipient); // 注销死亡监听
    IFooRelease(fooService, false); // 释放HDI服务对象
    return 0;
}

服务状态监听

HDI提供服务监听能力,以下为示例:

服务端,驱动加载入口设置驱动类型:

static int HdfFooDriverInit(struct HdfDeviceObject *deviceObject)
{
    // 设置驱动设备类型
    if (!HdfDeviceSetClass(deviceObject, DEVICE_CLASS_DEFAULT)) {
        HDF_LOGI("%{public}s: failed to set class of device object", __func__);
        return HDF_FAILURE;
    }
    return HDF_SUCCESS;
}

驱动设备类型定义于以下头文件中,

//drivers/hdf_core/framework/include/core/hdf_io_service_if.h

/**
 * @brief Enumerates different classes of driver devices.
 *
 * @since 1.0
 */
typedef enum {
    DEVICE_CLASS_DEFAULT  = 0x1 << 0,    /** Default device */
    DEVICE_CLASS_PLAT     = 0x1 << 1,    /** Platform device */
    DEVICE_CLASS_SENSOR   = 0x1 << 2,    /** Sensor device */
    DEVICE_CLASS_INPUT    = 0x1 << 3,    /** Input device */
    DEVICE_CLASS_DISPLAY  = 0x1 << 4,    /** Display device */
    DEVICE_CLASS_AUDIO    = 0x1 << 5,    /** Audio device */
    DEVICE_CLASS_CAMERA   = 0x1 << 6,    /** Camera device */
    DEVICE_CLASS_USB      = 0x1 << 7,    /** USB device */
    DEVICE_CLASS_USERAUTH = 0x1 << 8,    /** UserAuth device */
    DEVICE_CLASS_MAX      = 0x1 << 9,    /** Maximum value of a device class */
} DeviceClass;

C++侧使用

#include <iservmgr_hdi.h>

#define FOO_SERVICE_NAME "foo_service"

using OHOS::sptr;
using OHOS::HDI::ServiceManager::V1_0::IServiceManager;
using OHOS::HDI::ServiceManager::V1_0::ServStatListenerStub;

class FooListener : public ServStatListenerStub {
public:
    FooListener() = default;
	~FooListener() = default;
    
    void OnReceive(const ServiceStatus &status) override {
        if (status.serviceName != FOO_SERVICE_NAME) {
            continue;
        }
        
        // 获取到服务状态信息
    }
};


int main()
{
    sptr<IServiceManager> servmgr = IServiceManager::Get(); // 获取服务管理对象
    
    sptr<FooListener> listener = new FooListener();  // 创建监听对象
    
    int32_t ret = servmgr->RegisterServiceStatusListener(listener, DEVICE_CLASS_DEFAULT);  // 注册监听对象

    ret = servmgr->UnregisterServiceStatusListener(listener);  // 注销监听对象
    return 0;
}

C侧使用:

#include <string.h>

#include <servmgr_hdi.h>
#include <servstat_listener_hdi.h>

#define FOO_SERVICE_NAME "foo_service"

// 监听回调函数
static void OnServiceStatusReceived(struct ServiceStatusListener *listener, struct ServiceStatus *servstat)
{
    if (strcmp(servstat->serviceName, FOO_SERVICE_NAME) == 0) {
        // ...
    }
}

int main()
{
    struct HDIServiceManager *servmgr = HDIServiceManagerGet();  // 创建服务管理对象
    
    struct ServiceStatusListener *listener = HdiServiceStatusListenerNewInstance(); // 创建监听对象
    listener->callback = OnServiceStatusReceived; // 设置监听回调函数
    
    int32_t ret = servmgr->RegisterServiceStatusListener(servmgr, listener, DEVICE_CLASS_DEFAULT);  // 注册监听对象
    
    ret = servmgr->UnregisterServiceStatusListener(servmgr, listener); // 注销监听对象
    HdiServiceStatusListenerFree(listener);   // 释放监听对象
    return 0;
}

HDI服务动态加载

HDI服务提供动态加载能力,系统启动过程中默认不加载,支持手动加载,以下为示例:

  • device_info.hcs配置preload为2

device_info.hcs

foo :: host {
    hostName = "foo_host";
    priority = 50;
    foo_device :: device {
        device0 :: deviceNode {
            policy = 2;
            priority = 100;
            proload = 2; // 设置proload为2,则系统启动过程中默认不加载,后续可手动加载
            moduleName = "libfoo_driver.z.so";
            serviceName = "foo_service";
        }
    }
}
  • 代码中手动加载设备服务

C++侧接口使用示例:

#include <idevmgr_hdi.h>
#include <v1_0/ifoo.h>

#define FOO_SERVICE_NAME "foo_service"

using OHOS::sptr;
using OHOS::HDI::DeviceManager::V1_0::IDeviceManager;
using OHOS::HDI::Foo::V1_0::IFoo;

int main() {
    sptr<IDeviceManager> devmgr = IDeviceManager::Get();  // 获取HDI设备管理服务对象
    int32_t ret = devmgr->LoadDevice(FOO_SERVICE_NAME);   // 加载设备服务

    sptr<IFoo> fooService = IFoo::Get(false);             // 获取HDI服务对象
    
    ret = fooService->Ping();                             // 调用HDI接口

    ret = devmgr->UnloadDevice(FOO_SERVICE_NAME);         // 卸载设备服务
    return 0;
}

C侧接口使用示例:

#include <devmgr_hdi.h>
#include <v1_0/ifoo.h>

#define FOO_SERVICE_NAME "foo_service"

int main()
{
    struct HDIDeviceManager *devmgr = HDIDeviceManagerGet();    // 获取HDI设备管理服务对象
    int32_t ret = devmgr->LoadDevice(devmgr, FOO_SERVICE_NAME); // 加载设备服务

    struct IFoo *fooService = IFooGet(false);    // 获取HDI服务对象
    ret = fooService->Ping();                    // 调用HDI接口
    IFooRelease(fooService, false);              // 释放HDI服务对象

    ret = devmgr->UnloadDevice(devmgr, FOO_SERVICE_NAME); // 卸载设备服务
    HDIDeviceManagerRelease(devmgr);                      // 释放设备管理服务对象
    return 0;
}

HDI调试

HDI调试主要包括:

  • HDI服务进程启动
  • HDI服务加载
  • 接口调用测试

按顺序进行排查相关问题。

HDI服务进程启动

HDI服务进程启动是否正常,主要看HDI服务进程是否存在,参照以下步骤:

  1. 通过ps -ef | grep [hostName] 来查询host进程。若有进程信息,表示host进程拉起正常,开始排查【HDI服务加载】步骤。
  2. 若未进程信息,首先看内核日志,通过dmesg | grep [hostName]来查询报错信息,常见问题详见:服务进程拉起失败

HDI服务加载

HDI服务加载是否正常,主要看HDI服务加载成功,排查步骤如下:

  1. 查看驱动服务加载的hilog日志:驱动服务加载日志

  2. 使用进程号,过滤出服务进程日志,加载正常的日志如下,仅供参考:

08-22 10:34:38.110   467   467 I C02500/hdf_device_host: hdf device host foo_host 21 start  // host服务开启日志 "foo_host"为hostName
08-22 10:34:38.311   467   467 I C02500/devsvc_manager_proxy: DevSvcManagerProxyGetService finish, and status is 0
08-22 10:34:38.332   467   467 I C02500/devhost_service_stub: add device 0x15000101
08-22 10:34:38.400   467   467 I C02500/hcs_blob_if: CheckHcsBlobLength: the blobLength: 27930, byteAlign: 0
08-22 10:34:38.400   467   467 I C02500/device_node: launch devnode foo_service  // 开始加载驱动服务
08-22 10:34:38.400   467   467 I C02500/HDF_LOG_TAG: HdfFooDriverBind enter      // 调用bind函数
08-22 10:34:38.568   467   467 E C02500/load_hdi: LoadHdiImpl failed to get symbol of '<private>', Symbol not found: FooImplRelease, version: null  // 此日志不影响HDI-C++服务加载
08-22 10:34:38.568   467   467 I C02500/HDF_LOG_TAG: HdfFooDriverInit enter      // 调用init函数
08-22 10:34:38.602   467   467 I C02500/devsvc_manager_proxy: servmgr add service foo_service, result is 0
08-22 10:34:38.612   467   467 I C02500/devmgr_service_proxy: Attach device host dispatch finish, status is 0  // 加载成功
08-22 10:34:38.613   467   467 I C02500/hdf_power_manager: HdfPmTaskQueueInit HdfTaskQueueCreate success
  1. 对比以上日志,判断驱动服务是否加载成功,HDI服务加载失败常见问题,详见:驱动加载失败常见问题
驱动服务加载hilog日志

方法1

  1. 使用 “hilog -w start”开启hilog落盘("hilog -w stop" 关闭落盘)
  2. “reboot”重启,并等待板子启动完毕
  3. 使用"hdc file recv /data/log/hilog/xxx.gz .\"获取开机日志(/data/log/hilog目录下为hilog落盘日志)

方法2

  1. “hdc shell”开启两个终端会话
  2. 一个中断会话下进行“hilog > log.txt”保存日志
  3. 另一终端会话下使用”ps -ef | grep [hostName]“查看进程id,并手动kill掉,此时host进程会被重新拉起并重新进行驱动加载。
  4. ctrl + c终止hilog的重定向,log.txt中即为加载日志。

接口调用测试

HDI接口调用测试大部分属于各模块业务范围,自行处理。

常见问题

编译问题

编译问题一般会给出具体报错信息,如无法定位,请联系工具开发者,下面给出常见问题示例:

文件权限问题

出现以下报错:

[OHOS ERROR] [HDI-GEN]: invailed file path '/home/xxx/codespace/master_9_7/drivers/interface/foo/v1_0/IFoo.idl'.

文件路径检查无误,查看idl文件属性:

master$ ll ./drivers/interface/foo/v1_0/
drwxr-xr-x 2 xxx xxx  4096 Sep  8 16:12 ./
drwxr-xr-x 3 xxx xxx  4096 Sep  8 16:10 ../
-rw-r--r-- 1 xxx xxx  1045 Sep  9 11:14 BUILD.gn
-rw-r--r-- 1 xxx xxx   103 Sep  8 16:37 IBar.idl
-rw-r--r-- 1 nobody nogroup  349 Sep  9 11:17 IFoo.idl         # 文件拥有者和所属组错误
-rw-r--r-- 1 xxx xxx   111 Sep  9 11:22 IFooCallback.idl
-rw-r--r-- 1 xxx xxx   181 Sep  9 11:17 Types.idl

修改idl文件拥有者和所属组即可,chown xxx:xxx ./drivers/interface/foo/v1_0/IFoo.idl

更新本地代码后,编译idl报错

一般为hdi-gen工具未重新编译导致,以master分支、rk3568为例,删除以下目录,重新编译即可

//out/rk3568/obj/drivers/framework/tools/hdi-gen

服务进程拉起失败

服务进程拉起失败一般与hdf_devhost.cfg、passwd、group、以及selinux相关,dmesg查看内核日志,搜索hostName,查找失败日志进行定位,以下为常见问题:

未配置psswd和group文件,或配置错误

内核日志中出现如下报错:

[    6.468021] [pid=1][Init][INFO][init_config.c:37]ParseInitCfg /vendor/etc/init/hdf_devhost.cfg
[    6.472081] [pid=1][Init][ERROR][init_utils.c:89]Failed to decode uid for foo_host
[    6.472110] [pid=1][Init][ERROR][init_service_manager.c:887]Failed to get uid for service foo_host

检查/system/etc/passwd和/system/etc/group文件,是否未配置或配置错误,修改或添加配置即可,文件对应路径如下:

代码路径 打包路径 配置路径
//base/startup/init/services/etc/passwd //out/rk3568/packages/phone/system/etc/passwd /system/etc/passwd
//base/startup/init/services/etc/group //out/rk3568/packages/phone/system/etc/group /system/etc/group
type.te文件未配置

内核日志中出现如下报错:

[    6.472110] [pid=1][Init][ERROR][init_common_service.c:224]failed to set service foo_host's secon (u:r:foo_host:s0).

出现此种情况,一般为未配置type.te或配置错误,type.te配置详见selinux配置章节:

修改type.te

//base/security/selinux/sepolicy/ohos_policy/drivers/adapter/vendor/type.te

修改此文件并编译完成后,将其生成的文件

//out/rk3568/packages/phone/system/etc/selinux/targeted/policy/policy.31

push到板子对应目录下:

/system/etc/selinux/targeted/policy/policy.31

重启板子即可

selinux拦截

master分支需配置selinux,可能会出现HDI服务进程因权限不足导致进程拉起失败,具体处理办法如下:

  1. 由于不同模块的HDI服务所需的进程权限不同,只能按需配置,一般建议先将selinux关闭,待业务接口调试通过后,再开启selinux,消除权限问题。

  2. 将/system/etc/selinux/config文件取出,设置SELINUX为permission,再替换掉原来的配置,重启即可

    # Copyright (c) 2021 鍖椾含涓囬噷绾㈢鎶€鏈夐檺鍏徃
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    #
    
    SELINUX=permissive
  3. 业务调试通过后,打开selinux,通过dmesg | grep avc | grep [hostName],过滤出与本进程相关的avc告警,依据此告警信息,添加对应的selinux配置项

进程启动成功,但进程名错误

device_info.hcs中hostName可设置进程名,hostName设置过长会出现hostName默认为“hdf_devhost”的情况。

例如当设置hostName为“foo_too_long_too_long_host”时:

$ps -ef | grep -v "grep" | grep foo

foo_too_long_too_long_host 529 1 0 06:59:17 ? 00:00:00 hdf_devhost 21 foo_too_long_too_long_host

查看hilog启动日志,出现如下报错:

08-17 06:59:18.905   529   529 E C02500/hdf_device_host: failed to set new process title because the 'foo_too_long_too_long_host' is too long

一般设置hostName不超过16字节

驱动加载失败常见问题

hcs配置未生效

当hilog中未搜索到驱动加载的相关日志,一般为hcs文件未参与编译生效,grep -a [驱动服务名] /vendor/etc/hdfconfig/hdf_default.hcb,若搜索不到结果,则为device_info.hcs文件修改后,其相关编译产物hdf_default.hcb文件未重新编译导致,需要删除out目录下的hcb和cfg文件后重新编译:

find ./out/ -name "hdf_default.hcb" -o -name "hdf_devhost.cfg" | xargs rm -f

将生成的以下两个文件:

//out/rk3568/packages/phone/vendor/etc/hdfconfig/hdf_default.hcb
//out/rk3568/packages/phone/vendor/etc/init/hdf_devhost.cfg

替换目录下的对应文件:

/vendor/etc/hdfconfig/hdf_default.hcb
/vendor/etc/init/hdf_devhost.cfg

替换后重启,查看hillog日志继续排查

driver库加载失败

出现以下报错:

driver_loader_full: /vendor/lib/libfoo_driver.z.so no valid, errno:2

此处的libfoo_driver.z.so是hdf_devmgr进程通过读取hdf_default.hcb(hdf_default.hcb是通过hcs文件编译生成)文件获取(此处的libfoo_driver.z.so为hcs驱动配置中的moduleName值),此报错是由于找不到对应的drivers库文件,有两个原因:

  • 编译的driver库名和device_info.hcs中驱动配置的moduleName不一样导致的,删除out下的hdf_default.hcb重新编译即可。
  • 编译driver库时,配置的install_image,subsystem_name,part_name错误,导致driver库文件没有被正确打包(32位在"out/rk3568/packages/phone/vendor/lib/"目录下,64位在"out/rk3568/packages/phone/vendor/lib64/"目录下),修改后,重新编译即可。
service库动态加载失败

service库指接口实现库,驱动服务加载时,bind函数中会调用IFoo::Get(true)以dlopen的方式获取service对象,而dlopen的库名采用接口描述和服务名转换而来,即需按一定规则对service库进行命名,当命名不规范是,会出现以下错误日志:

08-22 11:22:51.046  1816  1816 I C02500/HDF_LOG_TAG: HdfFooDriverBind enter
08-22 11:22:51.046  1816  1816 E C02500/load_hdi: ParseInterface invalid hdi impl so name /vendor/lib/libfoo_service_1.0.z.so  // 这里的库名为转换拼接而成,需要和提供的service库名相等
08-22 11:22:51.047  1816  1816 E C02500/load_hdi: failed to parse hdi interface info from 'ohos.hdi.foo.v1_0.IFoo'
08-22 11:22:51.047  1816  1816 E C02500/foo_stub: failed to load hdi impl ohos.hdi.foo.v1_0.IFoo
08-22 11:22:51.047  1816  1816 E C02500/HDF_LOG_TAG: HdfFooDriverBind: failed to get of implement service  // 获取服务实现对象失败

service库命名规则参考:接口实现库命名规则

驱动加载时间晚

当上层服务调用Get接口获取HDI服务对象时,其对应的Host服务还未被拉起,就会出现此种情况,日志入下:

07-05 11:56:05.323   244   253 E 02500/devsvc_manager_stub: service input_interfaces_service not found   # 服务管理查询host服务失败
07-05 11:56:08.282   520   520 I 02500/device_node: launch devnode input_interfaces_service              # 开始加载host服务
07-05 11:56:11.994   244   251 I 02500/servstat: notify service status input_interfaces_service
07-05 11:56:12.907   244   251 I 02500/servstat: notify service status input_interfaces_service

解决办法:

  1. 轮询获取(循环调用Get(),并判断)
  2. 向服务管理设置监听,待HDI服务加载,客户端收到监听回调后,开始获取HDI服务对象
1
https://gitee.com/JasonYuJia/hdi_develop_guide.git
git@gitee.com:JasonYuJia/hdi_develop_guide.git
JasonYuJia
hdi_develop_guide
hdi_develop_guide
master

搜索帮助