Android硬件抽象层(HAL)

Android硬件抽象层(HAL)

JNI向上提供本地函数,向下加载HAL文件并调用HAL的函数。

HAL负责访问驱动程序执行硬件

实质:使用dlopen来加载动态库

hw_get_module(“led”)
1、模块名==>文件名
2、加载
这里写图片描述

这里写图片描述

一. 在Android内核源代码工程中编写硬件驱动程序。

这里写图片描述二. 在Android系统中增加C可执行程序来访问硬件驱动程序。

三. 在Android硬件抽象层增加接口模块访问硬件驱动程序。

四. 在Android系统中编写JNI方法在应用程序框架层提供Java接口访问硬件。

五. 在Android系统的应用程序框架层增加硬件服务接口。

六. 在Android系统中编写APP通过应用程序框架层访问硬件服务。

扫描二维码关注公众号,回复: 2609054 查看本文章

Android HAL概念介绍

Android系统硬件抽象层(Hardware Abstraction Layer,HAL)是连接Android Framework与内核设备驱动的重要桥梁。其主要设计意图是向下屏蔽设备以及其驱动的实现细节,向上为系统服务以及Framework提供提供统一的设备访问接口。同时Google基于保护硬件厂商知识产权的考量,选择Apache开源许可证以避开GPL开源协议的约束,允许硬件厂商不公开源码,将设备相关的实现放在HAL层中实现并以共享库(.so)的形式提供。

HAL规范提供了标准的数据结构和接口方法,为不同硬件的系统服务定义了标准接口,不同的硬件厂商只需实现相应的接口即能使设备为Android系统所用。这种设计模式使得上层服务于低层硬件之间的耦合度降低。下图是Android HAL标准组件。

Legacy HAL与Stub HAL

Android HAL有两种实现方式:Legacy HAL以及Stub HAL。Android在初期使用的是Legacy方式的HAL,这种方式的即标准的Linux共享库(Shared Library, .so),其他程序直接调用HAL so库中导出的函数(非静态函数均可以被外部程序调用);Google后来提出了Stub方式的HAL,Stub是一种存根的概念,即把所有的供外部访问的方法(或函数)的入口指针(函数指针)保存在统一的数据结构中。Stub方式的HAL仍然以so库形式存在,其他程序在需要访问HAL中的方法时,先获得Stub然后再通过Stub中的函数指针访问具体的函数。

这里写图片描述

HAL标准接口的定义
Android已经为常用的硬件设备定义了标准的HAL接口,这些设备有Audio、Camera、Bluetooth、Graphics、Input等。如果你要为这些设备编写HAL代码,你应该严格按照Google定义标准接口实现,另辟蹊径则将导致设备无法在Android Framework下正常工作。
HAL源码位于Android源码的hardware目录中,其中所有Stub方式的HAL实现位于libhardware目录下。

  android-src/hardware/libhardware/include/ : 常用设备HAL标准接口的头文件
  android-src/hardware/libhardware/modules/ :常用设备HAL标准接口实现或示例

hw_module_t:用来描述硬件模块
hw_device_t:用来描述硬件设备
hw_module_methods_t:用来打开硬件模块中包含硬件设备,获得指向硬件设备结构体的指针

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

Android HAL软件设计中有两个概念:ModuleDevice

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

Android 系统中 HAL 层是以模块的方式来管理各个硬件访问的接口,每一个硬件模块都对应一个动态链接库文件,而这些动态链接库文件需要符号一定的规范,而上述的这 3 种结构体就是用来建立这种规范。并且一个硬件模块可以管理多个硬件设备,例如 audio HAL 硬件模块中就管理了扬声器、麦克风等多个硬件设备。

//1、Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
//2、and the fields of this data structure must begin with hw_module_t
//3、followed by module specific information.
typedef struct hw_module_t {

    uint32_t tag;
    //版本号:For example, version 1.0 could be represented as 0x0100
    uint16_t module_api_version;

    //有效值:0
    uint16_t hal_api_version;

    /** Identifier of module */
    const char *id;

    /** Name of this module */
    const char *name;

    /** Author/owner/implementor of the module */
    const char *author;

    /** Modules methods */
    struct hw_module_methods_t* methods;

    /** module's dso */
    void* dso;

#ifdef __LP64__
    uint64_t reserved[32-7];
#else
    /** padding to 128 bytes, reserved for future use */
    uint32_t reserved[32-7];
#endif

} hw_module_t;

我们先看下结构体 hw_module_t 定义最前面的一段注释说明,它的意思是:

每个硬件模块中都要定义一个名字叫做 HAL_MODULE_INFO_SYM 结构体变量,而这结构体变量中的第一个成员必须是 hw_module_t 类型。也就是说,每个硬件模块都要自己实现一个结构体,但是这个结构体的第一个成员必须是 hw_module_t 结构体类型。

id:这个成员用一个字符串来表示硬件模块的,用来区别于其他硬件模块。
methods:这个成员是一个 hw_module_methods_t 指针,它表示硬件模块所包含的方法集(其实里面就一个 open 函数指针,用来打开 hw_device_t 硬件设备,获得指向对应的硬件设备的结构体对象的指针)。
dso:我们前面提到,HAL 层中的硬件模块是用动态链接库表示的,所以 dso 指针就是系统使用 dlopen() 函数打开共享动态共享链接库之后获得的句柄。

 //Every device data structure must begin with hw_device_t
 //followed by module specific public methods and attributes.

typedef struct hw_device_t{
    //tag must be initialized to HARDWARE_DEVICE_TAG 
    uint32_t tag;

    //Version of the module-specific device API.
    uint32_t version;

/** reference to the module this device belongs to */
    struct hw_module_t* module;

/** padding reserved for future use */
#ifdef __LP64__
    uint64_t reserved[12];
#else
    uint32_t reserved[12];
#endif

    /** Close this device */
    int (*close)(struct hw_device_t* device);

} hw_device_t;


}

和 hw_module_t 类似,hw_device_t 也可以看做是一个基类,它描述了所有硬件设备都应该具有的属性,然后具体到某个特定的硬件设备(例如,音频播放时需要的扬声器设备)实现时,都需要继承自 audio_device 结构体。所以,每个 HAL 层中硬件设备对应的结构体中的第一个成员必须是 hw_device_t。

下面还是简单提一下结构体 hw_device_t 比较关键的几个成员:

module:这个成员是一个 hw_module_t 指针,表示该结构体 hw_device_t 表示的硬件设备是由哪个 hw_module_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;

结构体 hw_module_methods_t 就比较简单了,它里面就只有一个 open 函数指针,用来打开 module 硬件模块==所管理==的硬件设备id值为 id 的硬件设备,最后将打开的==硬件设备(用 hw_device_t 结构体来描述)==通过 device 返回。

注意:这个 open 函数明确指出第三个参数的类型为 struct hw_device_t**,这主要是为了统一不同硬件设备向上层提供的硬件接口,然后在具体使用到某中硬件设备时,再转换成特定硬件设备的结构体类型。

Audio HAL 模块的实现
Step 1:定义 struct audio_module 模块
我们前面在结构体 hw_module_t 介绍时,有提到具体的硬件模块要定义一个新的结构体并且这个结构体的==第一个成员必须是 hw_module_t 类型==,所以根据这个规则,audio_module 的定义如下所示: 代码路径:/hardware/libhardware/include/hardware/audio.h

struct audio_module {
    struct hw_module_t common;
};

step 2:定义 struct audio_module 类型的 HAL_MODULE_INFO_SYM 变量

struct audio_module HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = AUDIO_MODULE_API_VERSION_0_1,
        .hal_api_version = HARDWARE_HAL_API_VERSION,
        .id = AUDIO_HARDWARE_MODULE_ID,
        .name = "Generic audio HW HAL",
        .author = "The Android Open Source Project",
        .methods = &hal_module_methods,
    },
};

但是我们为什么要在每个硬件模块中都定义一个变量名为 HAL_MODULE_INFO_SYM 的变量呢?

原因是为了统一标准的接口。这个硬件模块类型的变量主要是在 HAL 动态链接库加载时用到,它使得上层的 Framework 层打开所有的 HAL 动态链接库时都能找到名为 HAL_MODULE_INFO_SYM(也就是 hmi)的硬件模块类型变量,然后通过这个变量再来打开它所管理的硬件设备,从而与之进行交互。这么做的好处就是 Framework 层中只需要用相同的 API,就能处理各个厂商所提供的不同的 HAL 动态链接库。

step 3:定义 struct audio_hw_device 硬件设备结构体

每个硬件设备都需要通过一个结构体来表示,并且这个结构体的第一个成员必须是 hw_device_t 类型。而有关的对底层硬件设备的有关操作的函数指针接口,也是在这个结构体中定义。 struct audio_hw_device 硬件设备结构体的定义如下所示:

step 4:定义 struct hw_module_methods_t 函数列表变量
在前面介绍的结构体 hw_module_t 的定义中就有一个 hw_module_methods_t 函数指针类型的成员open,该函数的作用就是让硬件模块打开硬件设备,然后对特定硬件设备(例如上面的 struct audio_device_t结构体)中定义函数指针变量进行赋值绑定,所以在每个硬件模块都要实现这样一个变量。

struct audio_module 结构体中 struct hw_module_t 类型的成员 common 中的 hw_module_methods_t 函数指针成员的实现绑定如下所示:

static struct hw_module_methods_t hal_module_methods = {
    .open = adev_open,
};

可以看到,在 audio HAL 模块中使用了 adev_open 函数来初始化了 struct hw_module_methods_t 中的 open 函数指针成员。我们现在回过头来看 step 2中 struct audio_module HAL_MODULE_INFO_SYM 变量的定义中,audio_module 中的第一个成员 common中的结构体变量 methods 的指针就是被初始化指向了现在这里定义的 hal_module_methods 变量。

step 5:adev_open 函数的实现

最后,我们来看看 adev_open 函数的实现,看看它是如何通过 audio_module 硬件模块对象来打开 audio_hw_device 硬件设备对象。

static int adev_open(const hw_module_t* module, const char* name,
                     hw_device_t** device)
{
     struct generic_audio_device *adev;
    int fd;

    if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0)
        return -EINVAL;

    fd = open(AUDIO_DEVICE_NAME, O_RDWR);
    if (fd < 0)
        return -ENOSYS;

    adev = calloc(1, sizeof(struct generic_audio_device));

    adev->fd = fd;

    adev->device.common.tag = HARDWARE_DEVICE_TAG;
    adev->device.common.version = AUDIO_DEVICE_API_VERSION_2_0;
    adev->device.common.module = (struct hw_module_t *) module;
    adev->device.common.close = adev_close;

    adev->device.init_check = adev_init_check;
    adev->device.set_voice_volume = adev_set_voice_volume;
    adev->device.set_master_volume = adev_set_master_volume;
    adev->device.get_master_volume = adev_get_master_volume;
    adev->device.set_master_mute = adev_set_master_mute;
    adev->device.get_master_mute = adev_get_master_mute;
    adev->device.set_mode = adev_set_mode;
    adev->device.set_mic_mute = adev_set_mic_mute;
    adev->device.get_mic_mute = adev_get_mic_mute;
    adev->device.set_parameters = adev_set_parameters;
    adev->device.get_parameters = adev_get_parameters;
    adev->device.get_input_buffer_size = adev_get_input_buffer_size;
    adev->device.open_output_stream = adev_open_output_stream;
    adev->device.close_output_stream = adev_close_output_stream;
    adev->device.open_input_stream = adev_open_input_stream;
    adev->device.close_input_stream = adev_close_input_stream;
    adev->device.dump = adev_dump;

    *device = &adev->device.common;

    return 0;
}

我们可以从 adev_open 函数中的实现中看到,它里面的主要工作就是做一些对 struct audio_hw_device 对象的初始化,将其定义的函数指针指向对应的已经实现好的函数中。例如,这里将struct audio_hw_device中定义的 open_output_stream 函数指针成员指向了 adev_open_output_stream 函数。这样在 Framework 层调用的 struct audio_hw_device 对象的 open_output_stream 函数,其实最终调用的是 adev_open_output_stream函数。

这里,还有一点需要特别注意的就是我们所打开的硬件设备对象是怎么返回的? 答案就是它是通过 open 函数中的第三个参数 hw_device_t** device返回的。在 open 函数初始化并打开特定的硬件设备之后,它就将硬件设备结构体中的第一个成员 struct audio_hw_device 类型的 common 对象返回。

那么为什么这里返回的是 hw_device_t** 类型的硬件设备,而是不是audio_hw_device** 类型呢? 其实这个问题在前面在介绍 hw_module_methods_t 中的 open 函数时已经提到过了。这里主要也蕴含着面向对象编程中的另外一种重要思想多态,这使得使用的 hw_device_t 指针就可以访问到子类中继承了父类的属性和方法,如果要获得某个子类所特有的属性,那么只要将其进行类型的强制转换即可。
总结
理解 Android HAL 层最关键的还是要弄清楚 hw_module_t 、 hw_device_t、 hw_module_methods_t 这三个结构体的含义即关系,以及如何基于这三个结构体来实现特定硬件的硬件模块结构体、硬件设备结构体、硬件模块方法列表结构体。其实从面向对象编程的角度来考虑,前面三者和后面三者之间的关系,就好比是父类和子类的关系,如下图所示:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_33487044/article/details/80878650