HAL开发规范

Android HAL的软件架构比较简单,其中有两个重要的数据结构:hw_module_t, hw_device_t。Google定义了HAL的开发规范,但这些规范没有形成标准的文档,开发人员可以阅读hardware/libhardware/include/hardware/hardware.h, 这份头文件是所有HAL模块代码需要包含的,其中详细的介绍了HAL的开发规范以及核心的数据结构。

Android HAL软件设计中有两个概念:Module,Device。Module代表整个HAL实现,是HAL模块类别、功能的封装,是外部程序能看到的唯一视角;Deivce代表着一个实际的硬件设备,是设备属性、设备操作的封装,设备提供的所有操作在Device结构中体现,Device由Module的open方法创建。一旦一个应用程序通过通过HAL的API获得了某个具体的HAL Module,便可通过open方法获得一个Device,Module和Device即使HAL的Stub,有了Stub就可以操作HAL模块和设备。

1.定义代表Module的数据结构

所有HAL模块代码均具有类似的数据结构,以Power HAL模块为例,首先是定义一个表示Power模块的数据结构power_module_t,该数据结构须遵循以下准测:

  1. 第一个成员必须是struct hw_module_t;
  2. 创建一个该数据结构的变量,并以HAL_MODULE_INFO_SYM命名;
  3. 结构体hw_module_ttag成员固定赋值为HARDWARE_MODULE_TAG;
  4. 结构体hw_module_tmodule_api_version代表该HAL模块的API版本,开发人员须在模块接口改变时更新该成员;
  5. 结构体hw_module_thal_api_version代表HAL接口的API版本(即harware.h hardware.c中定义的数据结构和接口函数的版本),该成员目前只能赋值为HARDWARE_HAL_API_VERSION;
  6. 结构体hw_module_tid成员是该模块的标识符,其他程序通过该成员来寻找对应的HAL的Stub。
#include <hardware/hardware.h>

typedef struct power_module {
    struct hw_module_t common;
    ...
    ...
} power_module_t;

struct power_module HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = POWER_MODULE_API_VERSION_0_2,
        .hal_api_version = HARDWARE_HAL_API_VERSION,
        .id = POWER_HARDWARE_MODULE_ID,
        .name = "Default Power HAL",
        .author = "The Android Open Source Project",
        .methods = &power_module_methods,   /* hw_module_methods_t */
    },
    ...
};

typedef struct hw_module_methods_t {
    /** Open a specific device */
    int (*open)(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device);

} hw_module_methods_t;

HARDWARE_MODULE_TAGHARDWARE_HAL_API_VERSION等这些宏定义在hardware.h中,hardware.h中还定义了一些宏方便创建APIversion,开发人员可以使用这些宏定义API Version。 methods成员的类型为hw_module_methods_t,定义于hardware.h中,其仅有一个open方法。open方法用于打开一个设备。

2.定义代表Device的数据结构

模块代表一个实际存在的HAL,通过模块你并不能实际操作设备。开发者需要将设备相关的属性以及操作封装在代表设备的数据结构中。例如你可以创建一个指纹设备fingerprint_device_t,并将注册指纹(enroll)、删除指纹(remove)等操作封装在fingerprint_device_t中。代表Device的数据结构体现了设备的属性以及改设备所提供的操作接口,在这个数据结构中通常是一系列的函数指针。如下是一个简单的例子:

typedef struct fingerprint_device {
  struct hw_device_t common;
  int (*enroll)(struct fingerprint_device *dev, const hw_auth_token_t *hat, uint32_t gid, uint32_t timeout_sec);
  int (*remove)(struct fingerprint_device __unused *dev, uint32_t __unused gid, uint32_t __unused fid);
  ...
  ...
} fingerprint_device_t;

于xxx_module_t数据结构的规则类似,xxx_device_t的第一个成员必须是struct hw_device_t。hw_device_t的tag成员必须被赋值为HARDWARE_DEVICE_TAG。

3.实现open方法

其他应用程序使用HAL的步骤通常是:

  1. 通过hw_get_module获得指定HAL模块的hw_module_t的引用;
  2. 调用hw_module_t->methods->open方法获取对device的引用。
  3. 通过device中的Stub(即封装的函数指针)来操作设备。

open方法位于数据结构hw_module_methods_t中,open方法是这个数据结构的唯一成员,而close方法存在于hw_device_t数据结构中。 open方法用于从HAL中获取一个device的stub,从而得到这个设备的详细信息例如设备的属性以及设备提供的操作接口(函数指针)。因此open方法在不同HAL中的实现也较为相似,大致为:

  1. 为xxx_device_t分配内存。
  2. 对common成员(hw_device_t类型)赋值。
  3. 对函数指针赋值。
  4. 将创建的xxx_device通过形参 hw_device_t** device返回

以fingerprint 为例:

static int fingerprint_open(const hw_module_t* module, const char __unused *id,
                            hw_device_t** device)
{
    if (device == NULL) {
        ALOGE("NULL device on open");
        return -EINVAL;
    }
    fingerprint_device_t *dev = malloc(sizeof(fingerprint_device_t));
    memset(dev, 0, sizeof(fingerprint_device_t));
    dev->common.tag = HARDWARE_DEVICE_TAG;
    dev->common.version = FINGERPRINT_MODULE_API_VERSION_2_0;
    dev->common.module = (struct hw_module_t*) module;
    dev->common.close = fingerprint_close;
    dev->pre_enroll = fingerprint_pre_enroll;
    dev->enroll = fingerprint_enroll;
    dev->get_authenticator_id = fingerprint_get_auth_id;
    dev->cancel = fingerprint_cancel;
    dev->remove = fingerprint_remove;
    dev->set_active_group = fingerprint_set_active_group;
    dev->authenticate = fingerprint_authenticate;
    dev->set_notify = set_notify_callback;
    dev->notify = NULL;
    *device = (hw_device_t*) dev;
    return 0;
}

4.实现device的具体操作接口

真正的与实际设备相关的开发工作是实现device的具体操作接口,这对于每一类设备都是不尽相同的。设备相关的属性、操作的细节体现在这些操作接口(函数指针)中,而调用者只需传入参数,复杂的系统调用以及与内核驱动交互的流程都不需要调用者关心。

这部分的开发于普通linux共享库开发一样,你需要熟悉linux用户空间的编程技术,需要对标准c库、系统调用的使用非常熟悉。如下列举了linux用户空间编程设计到技术点,对于Android HAL开发人员来说,有必要掌握这些技术。

  • 文件操作
  • 进程管理
  • 信号处理
  • 多线程
  • 网络编程

results matching ""

    No results matching ""