前面文章一说完HAL的基本架构,下面以实例展现整个添加新模块的过程。
工作中自己编写的一个基于stub结构的HAL程序,包含:hal,jni,java/service三个层次,依次被后一个调用。hal完全是调用内核驱动的接口,jni就是一个让java能调用c的转换接口,java/service就是将硬件接口以java的形式提供给framework。
程序是关于触摸屏相关接口,我将简化只保留一个接口,其实框架函数保留。
(1)hal层:
包含ctp.h和ctp.c两个文件,还有对应的Android.mk
msm8x12\hardware\libhardware\include\hardware\ctp.h
#ifndef ANDROID_CTP_INTERFACE_H
#define ANDROID_CTP_INTERFACE_H
#include <stdint.h>
#include <sys/cdefs.h>
#include <sys/types.h>
#include <hardware/hardware.h>
__BEGIN_DECLS
/**
* The id of this module
*/
#define CTP_HARDWARE_MODULE_ID "ctp"
/**
* Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
* and the fields of this data structure must begin with hw_module_t
* followed by module specific information.
*/
struct ctp_module_t {
struct hw_module_t common;
};
/**
* Every device data structure must begin with hw_device_t
* followed by module specific public methods and attributes.
*/
struct ctp_device_t {
struct hw_device_t common;
/** Input device number */
int input_number;
/**
*GetCtpVer
*
* Returns: firmware CTP version
*/
int (*GetCtpVer)(struct ctp_device_t* dev,char *ctp_ver);
};
__END_DECLS
#endif // ANDROID_CTP_INTERFACE_H
ctp.h其实就是在继承hardware.h的基本结构体来自定义自己模块的结构体,在ctp_device_t中第一个成员必须是hw_device_t,这样便于后边代码进行指针强制转换,实际上他们的首地址确实是一样的,后面的部分对应于增加的方法接口或属性;同样ctp_module_t第一个成员也必须是hw_module_t,后面是自定义方法或属性,但是一般做法让后面为空,即ctp_module_t在内存上就是hw_module_t;另外ctp.h还要定义该模块的module ID,用于hw_get_module以此识别来查找ctp的module。
其中的__BEGIIN_DECLS和__END_DECLS表示让c++代码能用C编译出的代码,在cdefs.h中定义如下:
- #if defined(__cplusplus)
- #define __BEGIN_DECLS extern "C" {
- #define __END_DECLS }
- #else
- #define __BEGIN_DECLS
- #define __END_DECLS
- #endif
#include <cutils/log.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <hardware/ctp.h>
#ifdef CONFIG_ARCH_MSM8610_D510 //atmel TP
char const* const GLOVE_ATTR_DEV
= "/sys/class/input/input%d/device/glove_mode";
#else //synaptics TP
char const* const GLOVE_ATTR_DEV
= "/sys/class/input/input%d/rmidev/glove_mode";
#endif
#ifdef CONFIG_ARCH_MSM8610_D510
char const* const CTP_VER_DEV
= "/sys/class/input/input%d/device/fw_version";
#else
char const* const CTP_VER_DEV
= "/sys/class/input/input%d/config_id";
#endif
#ifdef CONFIG_ARCH_MSM8610_D510
char const* const CTP_ENABLE_DEV
= "/sys/class/input/input%d/device/touch_enable";
#else
char const* const CTP_ENABLE_DEV
= "/sys/class/input/input%d/rmidev/touch_enable";
#endif
char const* const CTP_VKey_DISABLED_DEV
= "/sys/class/input/input%d/rmidev/virtualKey_disabled";
/*return input_index*/
static int GetCtpInputNo(const char *name )
{
int i;
char input_path[128] = { 0 };
char name_value[64] = { 0 };
int fd;
const int INPUT_MAX = 10;
for(i = 0;i < INPUT_MAX ;i++){
sprintf(input_path,"/sys/class/input/input%d/name",i);
int fd = open(input_path,O_RDONLY);
if(fd < 0){
continue;
}
if(read(fd,name_value,sizeof(name_value)) < 0)
continue;
if(strstr(name_value,name)) // find synaptics CTP
{
return i;
}
}
return -1;
}
/*return 0 get OK,or -1 get fail
*pass ctp_ver out
*/
int GetCtpVer(struct ctp_device_t* dev, char *ctp_ver)
{
int fd_ctp;
int cnt = 0;
char filename[128];
sprintf(filename, CTP_VER_DEV, dev->input_number);
if ((fd_ctp = open(filename,O_RDONLY)) < 0)
{
return -1;
}
if((cnt = read(fd_ctp,ctp_ver,15)) < 0)
{
close(fd_ctp);
return -1;
}
ctp_ver[strlen(ctp_ver)-1]='\0';
close(fd_ctp);
return 0;
}
/** Close the ctp device */
static int close_ctp(struct ctp_device_t *dev)
{
if (dev) {
free(dev);
}
return 0;
}
/******************************************************************************/
/**
* module methods
*/
/** Open a new instance of a ctp device using name */
static int open_ctp(const struct hw_module_t* module, const char* name,
struct hw_device_t** device)
{
int input_number = 0;
if (!name)
return -EINVAL;
input_number = GetCtpInputNo(name);
if (input_number < 0)
return -ENODEV;
struct ctp_device_t *dev = malloc(sizeof(struct ctp_device_t));
memset(dev, 0, sizeof(*dev));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (struct hw_module_t*)module;
dev->common.close = (int (*)(struct hw_device_t*))close_ctp;
dev->input_number = input_number;
dev->GetCtpVer = GetCtpVer;
*device = (struct hw_device_t*)dev;
return 0;
}
static struct hw_module_methods_t ctp_module_methods = {
.open = open_ctp,
};
/*
* The ctp Module :stub
*/
struct ctp_module_t HAL_MODULE_INFO_SYM = {
common : {
tag : HARDWARE_MODULE_TAG, //必须填写HARDWARE_MODULE_TAG,
version_major : 1,
version_minor : 0, //当前必须为0
id : CTP_HARDWARE_MODULE_ID, //ctp.h中定义的module ID
name : "CTP Module",
author : "Seuic, Inc.",
methods : &ctp_module_methods,
}
};
ctp.c实际上就是Android系统中众多stub中的一员,代码中最后定义的module变量名必须为HAL_MODULE_INFO_SYM,其他就是根据ctp.h中增加的方法和属性进行实现,方法open_ctp就是当hw_get_module获取module/stub后,再调用module中的open函数指针,就会获得hw_device_t,也就获得了所有的方法和属性。
ctp.c对应的Android.mk如下:
LOCAL_PATH:= $(call my-dir)
# HAL module implemenation stored in
# hw/<COPYPIX_HARDWARE_MODULE_ID>.<ro.board.platform>.so
include $(CLEAR_VARS)
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
# NEED TO MODIFY BY USER
LOCAL_CFLAGS := $(common_flags) -DLOG_TAG=\"CTP\"
ifeq ($(strip $(SEUIC_PRODUCT_D510)),true)
$(warning ATMEL CTP HAL for D510)
LOCAL_CFLAGS += -DCONFIG_ARCH_MSM8610_D510
endif
LOCAL_SRC_FILES := ctp.c
LOCAL_MODULE := ctp.$(TARGET_BOARD_PLATFORM)
LOCAL_MODULE_TAGS := optional eng
include $(BUILD_SHARED_LIBRARY)
以我的msm8x10平台编译后为ctp.msm8610.so,放在安卓系统的/system/lib/hw目录下。
msm8x12\hardware\qcom\ctp\jni\com_seuic_touch_TouchService.cpp
#define LOG_TAG "CTPService JNI"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include <utils/misc.h>
#include <utils/Log.h>
#include <hardware/hardware.h>
#include <hardware/ctp.h>
#include <stdio.h>
#include <string.h>
#ifdef CONFIG_ARCH_MSM8610_D510
#define CTP_NAME "atmel_mxt_ts" //contain "atmel_mxt_ts" device name
#else
#define CTP_NAME "synaptics" //contain "synaptics" device name
#endif
namespace android
{
// These values must correspond with the Mode constants in
// CTPService.java
enum {
MODE_GLOVE_OFF = 0,
MODE_GLOVE_ON = 1,
};
static ctp_device_t* get_device(hw_module_t* module, const char* name)
{
int err;
hw_device_t* device;
err = module->methods->open(module, name, &device);
if (err == 0) {
return (ctp_device_t*)device;
} else {
return NULL;
}
}
/*return !NULL when OK,or NULL*/
static jint open(JNIEnv *env, jobject clazz)
{
int err;
hw_module_t* module;
ctp_device_t* dev = NULL;
err = hw_get_module(CTP_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
if (err == 0) {
dev = get_device(module, CTP_NAME);
}
return (jint)dev;
}
static void close(JNIEnv *env, jobject clazz, int ptr)
{
ctp_device_t* dev = (ctp_device_t*)ptr;
if (dev) {
dev->common.close((struct hw_device_t*)dev);
}
}
static jstring GetCtpVer(JNIEnv *env, jobject clazz, int ptr)
{
char ver[15];
memset(ver, 0 , sizeof(char) * 15);
ctp_device_t* dev = (ctp_device_t*)ptr;
if (!dev) {
return NULL;
}
dev->GetCtpVer(dev,(char *)ver);
return env->NewStringUTF(ver);
}
static JNINativeMethod method_table[] ={
{ "open", "()I", (void*)open },
{ "close", "(I)V", (void*)close },
{ "GetCtpVer", "(I)Ljava/lang/String;", (void*)GetCtpVer},
};
int register_CTPService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/seuic/touch/TouchService",
method_table, NELEM(method_table));
}
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("GetEnv failed!");
return result;
}
ALOG_ASSERT(env, "Could not retrieve the env!");
register_CTPService(env);
return JNI_VERSION_1_4;
}
}//end of namespace android
此JNI就是获取stub/module并获取对应设备的方法指针,然后进一步封装ctp.h中增加的方法或属性。用jniRegisterNativeMethods来注册提供给java框架的接口,第二个参数表示这些接口导出给com.seuic.touch.TouchService这个类用。至于java和C代码的参数传递(基本数据类型和java应用类型如string class等)会在以后开辟文章记录下。
com_seuic_touch_TouchService.cpp对应的Android.mk:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
com_seuic_touch_TouchService.cpp \
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) \
libcore/include \
libcore/include/libsuspend \
$(call include-path-for, libhardware)/hardware \
$(call include-path-for, libhardware_legacy)/hardware_legacy \
LOCAL_SHARED_LIBRARIES := \
libandroid_runtime \
libandroidfw \
libcutils \
liblog \
libhardware \
libhardware_legacy \
libnativehelper \
libsystem_server \
libutils \
libui \
libinput \
libskia \
libgui \
libusbhost \
libsuspend
ifeq ($(WITH_MALLOC_LEAK_CHECK),true)
LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK
endif
ifeq ($(strip $(SEUIC_PRODUCT_D510)),true)
$(warning ATMEL CTP JNI for D510)
LOCAL_CFLAGS += -DCONFIG_ARCH_MSM8610_D510
endif
LOCAL_MODULE:= libctp_jni
LOCAL_MODULE_TAGS := optional
include $(BUILD_SHARED_LIBRARY)
(3)java层:
3个文件TouchService.java,对应的Android.mk,还有权限com.seuic.touch.xml文件(不过不开放当然也可以不要xml)
java层实际上就是一个硬件service,这个概念不同于android中的标准service类。
msm8x12\hardware\qcom\ctp\java\com\seuic\touch\TouchService.java
package com.seuic.touch;
import android.content.Context;
import android.content.Intent;
public class TouchService {
private static final String TAG = "TouchService";
private static final boolean DEBUG = false;
private static TouchService touchService;
public static TouchService getInstance() {
if (touchService == null)
touchService = new TouchService();
return touchService;
}
public String getTouchVersion() {
return GetCtpVer(mNativeDevPtr);
}
private TouchService() {
mNativeDevPtr = open();
}
protected void finalize() throws Throwable {
close(mNativeDevPtr);
super.finalize();
}
private static native int open();//JNI方法用native声明
private static native void close(int ptr);
private static native String GetCtpVer(int ptr);
static {
System.loadLibrary("ctp_jni");//加载库
}
private int mNativeDevPtr;
}
这个硬件服务,需要加载动态库libctp_jni.so,并且JNI注册到这个TouchService的方法需要在此用native关键字声明。其实当时我写的这个代码不好,层次分的不好,此处的mNativeDevPtr就是ctp_device_t设备指针,其实应该将其在JNI中设一个静态变量保存,此Java层就应该省掉这个参数。
对应的Android.mk
LOCAL_PATH:= $(call my-dir)
# the library
# ============================================================
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
$(call all-subdir-java-files) \
LOCAL_MODULE:= touch
LOCAL_MODULE_TAGS:= optional
include $(BUILD_JAVA_LIBRARY)
include $(BUILD_DROIDDOC)
# the xml
# ============================================================
include $(CLEAR_VARS)
LOCAL_MODULE:= com.seuic.touch.xml
LOCAL_MODULE_TAGS:= optional
LOCAL_MODULE_CLASS:= ETC
LOCAL_MODULE_PATH:= $(PRODUCT_OUT)/system/etc/permissions
LOCAL_SRC_FILES:= $(LOCAL_MODULE)
include $(BUILD_PREBUILT)
权限是否开放的xml,实际上此处原理我也不是很明白。
如果要对全Android系统开放,还要在init.rc中的 export BOOTCLASSPATH增加/system/framework/touch.jar
msm8x12\hardware\qcom\ctp\java\com.seuic.touch.xml
<?xml version="1.0" encoding="UTF-8"?>
<permissions>
<library
name="com.seuic.touch"
file="/system/framework/touch.jar" />
</permissions>
总结:
硬件抽象层三层次:hal ,jni, java.
hal中根据hardware.h基本结构定义xx_device_t和xx_module_t,并定义当前模块的module ID,这样形成xx.h。对应的实现为xx.c,那么你需要在xx.c中实现硬件的操作方法,还有stub的定义struct xx_module_t HAL_MODULE_INFO_SYM,最终编译假设为xx.xx2.so,为借尸so的stub。
jni中就是hal和java的转接口。hw_get_module根据module ID获取stub/module,然后调用module中的methods->open方法,即可将xx_device_t的指针通过参数传出,这样jni就得到了xx模块的所有方法或属性。最终编译假设为xx_jni.so
java一般就将jni中导出的接口封装为java格式。java/service中利用System.loadLibrary(xx_jni)来加载jni导出函数,并且函数要声明native。
这个java格式的硬件service可以被java应用直接调用,也可以加入到systemserver用Manager来调用。
JNI和java格式的service参数传递(包括普通类型如int,还有类),相互访问属性和方法此处不提,后面有空会单独说明。