A1. HIDL接口

1.背景

HIDL 的目标是,可以在无需重新构建 HAL 的情况下替换框架。HAL 将由供应商或 SOC 制造商构建,并放置在设备的/vendor分区中,这样一来,就可以在其自己的分区中通过 OTA 替换框架,而无需重新编译HAL。

2.操作实例

以实例着手,在MTK平台,用HIDL实现调用linux内核驱动,并测试。
本篇文章实现的功能如下:
a. 在linux驱动层实现helloworld驱动,功能打印”hello world!”。
b. 设计HIDL调用内核中helloworld驱动,并提供接口。
c. 测试程序调用HIDL接口,观察是否有打印“helloworld!”
d. 实现HIDL与驱动层的数据交互,HIDL往内核写入数据并读取。

3.流程

HIDL接口文件定义

有关HIDL接口与软件包规则,详见接口和软件包

搭建attempt文件结构:

进入代码HAL层,自定义软件包,先以attempt命名这个例子。
1.创建HIDL目录

mkdir -p hardware/interfaces/attempt/1.0/default

2.接着创建Iattempt.hal

hardware/interfaces/attempt/1.0/IAttempt.hal

3.增加IAttempt.hal接口代码

/*定义一个Iattempt.hal接口文件,简单添加一个helloWorld接口,传入string,返回string,在之后会实现这个接口。*/
package android.hardware.attempt@1.0;

interface IAttempt {
    hello_world(string name) generates (string result);
};

生成HAL相关文件

需要使用Android hidl-gen工具辅助生成。hidl-gen 源码包路径为:system/tools/hidl,需要编译才能生成可用的hidl-gen工具。

(1) 安装hidl-gen:

$ lunch

$ make hidl-gen

编译后hidl-gen生成路径:out/host/linux-x86/bin/hidl-gen

(2) 使用hidl-gen 生成根据.hal文件生成HIDL格式接口

$ LOC= hardware/interfaces/attempt/1.0/default

$ PACKAGE=[android.hardware.attempt@1.0](mailto:android.hardware.attempt@1.0)

/* 生成hardware/interfaces/attempt/1.0/default/(Attempt.hAttempt.cpp) */

$ hidl-gen -o $LOC-Lc++-impl -randroid.hardware:hardware/interfaces-randroid.hidl:system/libhidl/transport $PACKAGE

/* 生成hardware/interfaces/attempt/1.0/Android.bp*/

$ hidl-gen -o $LOC -Landroidbp-impl-randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport$PACKAGE

注: 自定义的hidl路径hardware/interfaces/attempt/1.0/default/ 、自定义的package包名android.hardware.attempt@1.0 ,都要与.hal代码一致

(3) update-makefile.sh 更新Makefile, 自动生成hardware/interfaces/attempt/Android.dp

$./hardware/interfaces/update-makefiles.sh

(4) 添加如下文件用于注册HIDL服务

$touch [hardware/interfaces/attempt/1.0/default/android.hardware.attempt@1.0-service.rc](mailto:hardware/interfaces/attempt/1.0/default/android.hardware.attempt@1.0-service.rc)

$touch hardware/interfaces/attempt/1.0/default/service.cpp

(5)查看一下目录结构

attempt/
└── 1.0
    ├── Android.bp
    ├── default
    │   ├── Android.bp
    │   ├── android.hardware.attempt@1.0-service.rc
    │   ├── Attempt.cpp
    │   ├── Attempt.h
    │   └── service.cpp
    └── IAttempt.hal

自此hal层目录结构搭建完成。

总结一下:

首先是写了一个IAttempt.hal 并定义需要的接口函数,然后通过hdil-gen 和update-makefiles.sh生成HIDL接口文件。最后创建用于注册接口文件的service.cpp,**rc文件。

[android.hardware.attempt@1.0-service.rc] // 用于注册HIDL服务

Attempt.cpp Attempt.h // 提供服务接口

Android.bp //编译文件

实现HIDL接口(共享端服务)

选择使用Passthrouugh模式

打开注释

hardware/interfaces/attempt/1.0/default/Attempt.h

extern "C" IAttempt*HIDL_FETCH_IAttempt(const char* name);

hardware/interfaces/attempt/1.0/default

//Methods from::android::hidl::base::V1_0::IBase follow.

IAttempt* HIDL_FETCH_IAttempt(const char* /*name */) {

   return new Attempt();

}

实现hardware/interfaces/attempt/1.0/default/service.cpp

#include <android/hardware/attempt/1.0/IAttempt.h>
#include <hidl/LegacySupport.h>

using android::hardware::attempt::V1_0::IAttempt;
using android::hardware::defaultPassthroughServiceImplementation;

int main() {
    return defaultPassthroughServiceImplementation<IAttempt>();
}

实现hardware/interfaces/attempt/1.0/default/android.hardware.attempt@1.0-service.rc

service attempt_hal_service /vendor/bin/hw/android.hardware.attempt@1.0-service
    class hal
    user root
    group root
    seclabel u:r:su:s0

添加如下代码,将service.cpp文件加载为入口文件,开机启动服务:

//path:hardware/interfaces/attempt/1.0/default/Android.bp
cc_binary {
    name: "android.hardware.attempt@1.0-service",
    defaults: ["hidl_defaults"],
    relative_install_path: "hw",
    init_rc: ["android.hardware.attempt@1.0-service.rc"],
    vendor: true,
    srcs: ["service.cpp"],
    shared_libs: [
        "android.hardware.attempt@1.0",
        "libhardware",
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "liblog",
    ],
}

编译

mmm ./hardware/interfaces/attempt/1.0/

将自定义的attempt服务开机自启(不确定这一步是否多余)

//path: device/mediatek/mt6739/device.mk
PRODUCT_PACKAGES += \
    android.hardware.attempt@1.0-impl
    PRODUCT_PACKAGES += android.hardware.attempt@1.0-service

然后全编

发现报错:error: VNDK library list has been changed.

解决方法:将out/target/product/k39tv1_bsp_512/obj/PACKAGING/vndk_intermediates/libs.txt

比对到build/make/target/product/gsi/29.txt 和build/make/target/product/gsi/current.txt(以out目录下的为准,网上也有比对到28.txt,视工程报错log定,也可以都同步一下)

重新编译即可解决。

4.客户端测试

实例一个HIDL(C++)客户端:
hardware/interfaces/attempt/1.0/test/attempt_client.cpp

#include <android/hardware/attempt/1.0/IAttempt.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>
#include <stdio.h>

using ::android::hardware::attempt::V1_0::IAttempt;
using ::android::hardware::hidl_array;
using ::android::hardware::hidl_memory;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::sp;

int main()
{
    printf("Entry attempt_client 10:02\n");
    android::sp<IAttempt> client = IAttempt::getService();
    if (client == nullptr) {
        printf("Failed to get service\n");        
        return -1;
    }
    printf("Get service scuessfully!\n");

    client->hello_world("test_20200608", [&](hidl_string result) {
        printf("%s\n", result.c_str());    
    });

    return 0;
}

hardware/interfaces/attempt/1.0/test/Android.bp

cc_binary {
    relative_install_path: "hw",
    defaults: ["hidl_defaults"],
    name: "attempt_client",
    proprietary: true,
    srcs: ["attempt_client.cpp"],

    shared_libs: [
        "liblog",
        "libhardware",
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "android.hardware.attempt@1.0",
    ],
}

编译

mmm ./hardware/interfaces/attempt/1.0/test

生成路径: out/target/product/[target_name]/vendor/bin/hw/attempt_client

执行

将可执行文件push到vendor/bin/hw/

查看attempt 服务是否运行

ps –A | grep attempt

执行客户端程序

./vendor/bin/hw/attempt_client

效果:

4.1 测试效果

成功!!!!(如遇HIDL服务没有开机启动,详见问题2)

5.HIDL与内核驱动交互

上述只是成功实现HIDL接口的建立以及与上层的交互,本段主要介绍上层通过HIDL与kernel驱动层的数据交互。

(1) 创建一个自定义的kernel驱动,并注册到内核中,我这里自定义一个hello_world.c的驱动,注册一个/dev/hello_world设备,cat /proc/devices可见

5.1 驱动设备列表

(2) 在HIDL接口实现对驱动的访问,即使用open、read、write函数访问到内核hello_world.c驱动。主要HIDL接口、kernel驱动hello_world.c与HIDL测试文件主要代码如下:
hardware/interfaces/attempt/1.0/default/Attempt.cpp(HIDL接口文件)

Return<void> Attempt::hello_world(const hidl_string& name, hello_world_cb _hidl_cb) {
    // TODO implement
    char buf[100];
    int fd = -1;
    char write_val[100]= "I can do it 14:00";
    char read_val[100] = {0,0};
    
    ::memset(buf, 0x00, 100);
    ::snprintf(buf, 100, "Hello World, %s", name.c_str());
    printf("service hello world\n");

    fd = open("/dev/hello_world", O_RDWR);
    if(fd < 0){
        printf("Open error\n"); 
        memset(write_val, 0x41, sizeof(write_val));
    }

    write(fd, &write_val, sizeof(write_val));
    memset(read_val, 0, sizeof(read_val));
    read(fd, &read_val, sizeof(read_val));
    close(fd);

    hidl_string result(read_val);
    _hidl_cb(result);

    return Void();
}

kernel-4.14/drivers/misc/mediatek/dx_driver/hello_world/hello_world.c(内核驱动文件)

static ssize_t hello_world_write(struct file *file, const char __user *buf,  size_t size, loff_t *offset)
{
    int ret = 0;

    memset(global_char, 0x00, sizeof(global_char));
    ret = copy_from_user(global_char, buf, 20);

    return 0;
}

ssize_t hello_world_read(struct file *file, char __user *buf,  size_t size, loff_t *offset) 
{
    int ret = 0;

   // sprintf(global_char, "%s,%s", "believe yourself", global_char);
    ret = copy_to_user(buf, global_char, ARRAY_SIZE(global_char));

    return 0;
}

hardware/interfaces/attempt/1.0/test/attempt_client.cpp(HIDL测试文件)

int main()
{
    printf("Entry attempt_client 10:02\n");
    android::sp<IAttempt> client = IAttempt::getService();
    if (client == nullptr) {
        printf("Failed to get service\n");        
        return -1;
    }
    printf("Get service scuessfully!\n");

    client->hello_world("test_20200608", [&](hidl_string result) {
        printf("%s\n", result.c_str());    
    });

    return 0;
}

(其中一些log,主要为测试使用,不必在意)

主要功能:HIDL先将write_val值写入内核驱动,内核驱动将接收到的数据赋值给global_char。然后HIDL从内核驱动读取global_char并赋值给read_val,最后将read_val返回给上层。上层测试文件则负责打印HIDL接口上传的数据。

在实际测试时,发现read_val的值为空,由于HIDL接口log无法吐出(不知什么原因),所以排查起来比较麻烦。期间写了一个可执行C文件调用驱动设备,才发现是因为驱动节点open失败,然后联想到可能是因为驱动节点的权限问题。在查询/dev/hellow_world权限后,看到其权限为crw------- 。果然!因此需要更改一下/dev/hello_world权限

system/core/rootdir/ueventd.rc

# HIDL test /dev/hello_wolrd
/dev/hello_world          0777   root       root

全编之后,adb进入/vendor/bin/hw 执行./attempt_client

5.2 访问内核驱动

发现打印出I can do it ,即HIDL能够将数据写入内核,且可以读取出来。大功告成!

6.问题点

这里列举一下我遇到的且排查起来比较费时的问题:

问题1. 需要把attempt_client push到机器中执行,但是机器在adb remount失败,导致不能push文件。

这里将机器默认开启root权限:

device/mediateksample/k39tv1_bsp_512/system.prop

#root

ro.secure=0

问题2. 运行测试程序时出现卡死 (用的另一个HIDL测试)

解决:

查看log:发现是由于自定义的HIDL服务由于权限问题,开机没有启动起来

6.1 开机log

①关闭selinux模式(Android Q)

参考https://online.mediatek.com/FAQ#/SW/FAQ11484

system/core/init/selinux.cpp

bool IsEnforcing() {

   return false; //2020-06-09 dongxiang close selinux

    if(ALLOW_PERMISSIVE_SELINUX) {

       return StatusFromCmdline() == SELINUX_ENFORCING;

    }

   return true;

}

②或者给访问文件添加权限

参考https://blog.csdn.net/tung214/article/details/72734086

虽然我最后添加了read、excute权限,但是还是会报没execute权限,搞不懂为什么,最后选择关掉selinux模式。

问题3:异常重启

内核驱动中,与上层产生数据交互时,需要调用copy_from_user和copy_to_user,而不能粗暴的选择字符串拷贝API,否则会导致机器异常重启!!!!!(!老泪纵横!)

参考:
https://source.android.google.cn/devices/architecture?hl=zh-cn
https://github.com/fadhel086/Android-HIDL
https://blog.csdn.net/shift_wwx/article/details/86525761
https://online.mediatek.com/FAQ#/SW/FAQ11484
https://blog.csdn.net/tung214/article/details/72734086

文章出处

源自微信公众号“开源519”

嵌入式漫漫修道路

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拂去尘世尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值