MT6572平台加入呼吸灯功能——编写HAL模块

本文深入解析Android硬件抽象层(HAL)的开发流程,包括HAL模块的结构体定义、HAL_MODULE_INFO_SYM变量的作用、以及HAL模块的open和close函数编写方法。通过实例说明如何在Android系统中实现一个LED呼吸灯的HAL模块。

    HAL(Hardware Abstraction Layer,硬件抽象层)是建立在Linux驱动上的一套程序库,这套程序库并不属于Linux内核,而属于Linux内核层之上的应用层。但是,HAL却可以看作是Android真正意义上的驱动层,Google增加HAL的主要目的是为了保护“私人财产”,编写于HAL层的代码并不用遵守Linux内核的GPL协议,也就避免了被公开。实际上,如果不考虑商业因素,仅仅对于开发者来说,HAL层的东西基本上都可以写入驱动层的。

    Android HAL的源代码存储位置并不固定,一般会存储在<Android源码根目录>/hardware目录中。其中hardware/libhardware_legacy目录存储的是旧HAL框架的源代码文件,新HAL框架的源代码放在hardware/libhardware目录中。而MTK的HAL层代码,依旧放在其自建的mediatek目录下面,为了体现大众性,这里我们还是加在hardware/libhardware/目录下。HAL层最终编译生成的.so文件主要放在Android系统的/system/lib/hw目录下。

    这里先介绍下HAL模块的编写步骤,HAL模块一般由一个.c文件和一个.h文件组成,本实验为hw_breath_leds.c和hw_breath_leds.h。

    第一步:定义结构体和代表HAL ID的宏

        编写HAL模块需要使用到3个非常重要的结构体(hw_module_t,hw_device_t和hw_module_methods_t,必须要用到)。首先要定义两个新的结构体,而且这两个结构体的第1个变量类型分别必须是hw_module_t和hw_device_t,以满足两个新结构体与hw_module_t和hw_device_t进行强制转换的条件。除此之外,还需要为HAL模块定义一个ID,上层会通过该ID与该HAL模块建立联系。实际上第一步就是编写.h文件的内容,本实验hw_breath_leds.h完整源码如下:

  1. #ifndef ANDROID_INCLUDE_HW_BREATH_LEDS_H
  2. #define ANDROID_INCLUDE_HW_BREATH_LEDS_H
  3. #include <hardware/hardware.h>
  4. __BEGIN_DECLS
  5. //定义模块id,jni层通过该id查找该模块
  6. #define BREATH_LEDS_HW_MODULE_ID "breath_leds"
  7. //定义硬件模块结构体,HAL规定不能直接使用hw_module_t结构体,需要在该结构体外再套一层结构体,但hw_module_t结构体
  8. //必须是该结构体的第一个成员变量数据类型,以方便两者之间的强制转换
  9. struct breath_leds_module_t
  10. {
  11. struct hw_module_t breath_module; //表示HAL模块的相关信息
  12. };
  13. //硬件接口结构体,同上,该结构体第一个成员变量数据类型必须是struct hw_device_t
  14. struct breath_leds_device_t
  15. {
  16. struct hw_device_t breath_device;
  17. int (*set_breath_value)(struct breath_leds_device_t *dev, int val); //对外接口
  18. };
  19. __END_DECLS
  20. #endif
为什么直接使用hw_module_t和hw_device_t结构体呢?这是HAL规范建议的,为了拓展的需要,需要向外提供交互的接口,如breath_leds_device_t结构体中的set_breath_value函数指针。

        下面看一看这3个重要结构体的定义代码。

        1  描述HAL模块的hw_module_t结构体

  1. typedef struct hw_module_t {
  2. /** 模块Tag,值必须是HARDWARE_MODULE_TAG */
  3. uint32_t tag;
  4. /** 模块主版本号 */
  5. uint16_t version_major;
  6. /** 模块从版本号 */
  7. uint16_t version_minor;
  8. /** 模块ID,通过该ID找到当前模块 */
  9. const char *id;
  10. /** 模块名称 */
  11. const char *name;
  12. /** 模块作者 */
  13. const char *author;
  14. /** 与模块相关的函数指针,都包含在hw_module_methods_t结构体中 */
  15. struct hw_module_methods_t* methods;
  16. /** 模块的dso */
  17. void *dso;
  18. /** 保留的空间 */
  19. uint32_t reserved[32-7];
  20. } hw_module_t;

        2  描述HAL设备的hw_device_t结构体

  1. typedef struct hw_device_t {
  2. /** 设备Tag,值必须是HARDWARE_DEVICE_TAG */
  3. uint32_t tag;
  4. /** HAL设备版本号 */
  5. uint32_t version;
  6. /** 指向描述HAL模块的hw_module_t结构体指针 */
  7. struct hw_module_t* module;
  8. /** 保留的内存空间 */
  9. uint32_t reserved[12];
  10. /** 关闭设备的函数指针 */
  11. int (*close)(struct hw_device_t* device);
  12. } hw_device_t;
        3  描述模块入口函数的hw_module_methods_t结构体

  1. typedef struct hw_module_methods_t {
  2. /** 打开设备时调用的open函数指针 */
  3. int (*open)(const struct hw_module_t* module, const char* id, struct hw_device_t** device);
  4. } hw_module_methods_t;

    在这三个结构体中,hw_module_t是最先使用的,然后通过hw_module_t.methods找到hw_module_methods_t结构体,并调用hw_module_methods_t.open函数。这个open函数相当于HAL模块入口,一般在其中打开设备文件,初始化hw_device_t结构体等。

    第二步:定义HAL_MODULE_INFO_SYM变量

    所有的HAL模块都必须有一个HAL_MODULE_INFO_SYM变量,该变量类型为hw_module_t或包含hw_module_t且第一个变量类型是hw_module_t的结构体(如本实验的brearh_leds_module_t)。在定义HAL_MODULE_INFO_SYM变量的代码中一般会初始化hw_module_t或brearh_leds_module_t结构体的一些成员变量,其中id和methods特别重要。id表示HAL模块在Android系统中的标识,使用HAL模块的程序会通过该id找到并装载HAL模块,methods变量需要指向一个hw_module_methods_t结构体变量地址,当调用者通过id找到并装载HAL模块后,就会通过methods变量找到hw_module_methods_t结构体,并调用其open函数。如本例:

  1. struct breath_leds_module_t HAL_MODULE_INFO_SYM =
  2. {
  3. breath_module:
  4. {
  5. tag: HARDWARE_MODULE_TAG, //HAL初始化标志,必须为该值
  6. version_major: 1, //初始化HAL模块的主版本号
  7. version_minor: 0, //初始化HAL模块的此版本号
  8. id: BREATH_LEDS_HW_MODULE_ID, //jni通过匹配该id找到该HAL模块,该id在hw_breath_leds.h中定义
  9. name: HAL_NAME,
  10. author: HAL_AUTHOR,
  11. methods: &breath_module_methods, //初始化HAL模块open函数指针,hw_module_methods_t结构体变量
  12. }
  13. };
    第三步:定义上步中指定的hw_module_methods_t结构体变量

    HAL模块需要hw_module_methods_t结构体的open函数指针变量指定入口函数。如本例:

  1. static struct hw_module_methods_t breath_module_methods =
  2. {
  3. open: hw_breath_leds_open
  4. };
    第四步:编写HAL模块的open函数

    即编写上步指定的hw_breath_leds_open函数。在该函数中初始化hw_device_t或包含hw_device_t变量的结构体,打开设备文件等一些初始化工作。

    第五步:编写HAL模块的close函数

    即hw_device_t.close指针指向的函数,该函数需要在上步的open函数中通过hw_device_t.close变量指定才能调用。

    如上五步即为HAL模块的一般编写步骤,下面是hw_breath_leds.c的完整代码:

  1. #include <hardware/hw_breath_leds.h>
  2. #include <cutils/log.h>
  3. #include <fcntl.h>
  4. #define DEV_NAME "/dev/breath_leds"
  5. #define HAL_NAME "Breath Leds HAL Stub" //HAL模块名称
  6. #define HAL_AUTHOR "vip-wming"
  7. //设备文件句柄,open函数返回值,映射/dev/breath_leds设备文件
  8. static int fd = 0;
  9. //相当于对外接口的回调的函数
  10. static int hw_set_breath_value(struct breath_leds_device_t *dev, int val)
  11. {
  12. unsigned char buf[2] = {0};
  13. buf[0] = val & 0xff; //bit0~7
  14. buf[1] = (val >> 8) & 0xff; //bit8~15
  15. write(fd, buf, 2); //将数据写入驱动设备
  16. return 0;
  17. }
  18. //关闭HAL设备
  19. static int hw_breath_device_close(struct hw_device_t* device)
  20. {
  21. //将hw_device_t类型强制转换成breath_leds_device_t类型,hw_device_t类型变量必须作为breath_leds_device_t第一个参数的原因
  22. struct breath_leds_device_t* dev = (struct breath_leds_device_t*) device;
  23. if (dev)
  24. {
  25. free(dev); //释放设备
  26. }
  27. close(dev); //关闭设备
  28. return 0;
  29. }
  30. //打开设备入口函数
  31. static int hw_breath_leds_open(const struct hw_module_t* module, const char* name,
  32. struct hw_device_t** device)
  33. {
  34. struct breath_leds_device_t *dev;
  35. dev = (struct breath_leds_device_t *)malloc(sizeof(*dev)); //分配空间
  36. memset(dev, 0, sizeof(*dev)); //为分配的空间清零
  37. dev->breath_device.tag = HARDWARE_DEVICE_TAG; //设置HAL设备标志
  38. dev->breath_device.version = 0; //设置HAL设备版本号
  39. dev->breath_device.module = (struct hw_module_t*) module;
  40. dev->breath_device.close = hw_breath_device_close; //设值关闭HAL设备的函数指针,自定义该函数的实现
  41. dev->set_breath_value = hw_set_breath_value;
  42. *device = &(dev->breath_device); //将设置好的hw_device_t结构体传递给device参数指向的地址空间
  43. //打开breath_leds设备驱动文件,与相应驱动关联起来
  44. if((fd = open(DEV_NAME, O_RDWR)) < 0)
  45. {
  46. // LOGE("BREATH LEDS Stub: open /dev/breath_leds fail.\n");
  47. }
  48. return 0;
  49. }
  50. //描述模块入口函数的hw_module_methods_t结构体
  51. static struct hw_module_methods_t breath_module_methods =
  52. {
  53. open: hw_breath_leds_open
  54. };
  55. //初始化HAL模块信息,该结构体变量名必须为HAL_MODULE_INFO_SYM,HAL能被Android自动
  56. //调用,靠的就是该名,类似于每个C执行程序都有一个main函数一样
  57. struct breath_leds_module_t HAL_MODULE_INFO_SYM =
  58. {
  59. breath_module:
  60. {
  61. tag: HARDWARE_MODULE_TAG, //HAL初始化标志,必须为该值
  62. version_major: 1, //初始化HAL模块的主版本号
  63. version_minor: 0, //初始化HAL模块的此版本号
  64. id: BREATH_LEDS_HW_MODULE_ID, //jni通过匹配该id找到该HAL模块,该id在hw_breath_leds.h中定义
  65. name: HAL_NAME,
  66. author: HAL_AUTHOR,
  67. methods: &breath_module_methods, //初始化HAL模块open函数指针,即上面定义的hw_module_methods_t结构体变量
  68. }
  69. };
    下面记录下本实验HAL层详细开发过程:

    1)编写hw_breath_leds.h头文件

        进入hardware/libhardware/include/hardware/目录,新建hw_breath_leds.h文件,内容上面已展现。

    2)编写hw_breath_leds.c源代码

        进入hardware/libhardware/modules/目录,新建hw_breath_leds目录,进入该目录,新建hw_breath_leds.c,内容上面已展现。

    3)打开设备文件读写权限

        由于设备文件“/dev/breath_leds”是在内核驱动里面通过device_create创建的,该函数创建的设备文件默认只有root用户可读写,上层APP一般不具备root权限,调用open函数打开时就会导致打开设备文件失败。

        解决办法,进入system/core/rootdir目录,打开ueventd.rc,添加:

/dev/breath_leds          0666   root       root
    4)编写Android.mk

        在hardware/libhardware/modules/hw_breath_leds目录下新建Android.mk:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := hw_breath_leds.c
#注意LOCAL_MODULE的定义规则,后面跟有default能保证我们的模块总能被硬件抽象层加载到
LOCAL_MODULE := breath_leds.default
LOCAL_MODULE_TAGS := optional
LOCAL_PRELINK_MODULE := false

include $(BUILD_SHARED_LIBRARY)然后打开hardware/libhardware/modules/Android.mk,将新建的目录名添加进hardware_modules变量:

hardware_modules := gralloc hwcomposer audio nfc local_time power usbaudio audio_remote_submix hw_breath_leds
    5)将该HAL模块添加进Android编译系统

        完成上述操作后,可以进行模块编译并手动打包进system.img,但是new时却不能被自动编译进去,打开build/target/product/common.mk,在PRODUCT_PACKAGES变量中加入breath_leds.default(模块Android.mk中的LOCAL_MODULE值)然后编译即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值