在编写HAL代码之前,我们先回顾一下之前的硬件访问服务,安卓的应用程序是用java语音写的,如果想访问硬件,必须调用C函数,他怎么调用C函数呢? 1.loadLibrary( 加载C库),在C库中,他会存在一个JNI_onLoad函数,在加载C库时,该函数会被调用。在该函数内会通过jniRegisterNativeMethods注册本地方法(把C函数转化为java方法),我们把具有该功能的文件称为JNI文件。他会向上提供java调用C函数的接口,向下调用HAL(硬件操作)程序。 2.JNI(cpp文件)通过hw_get_module获取hw_module_t结构体,在根据hw_module_t调用open函数,获取一个我们自定义的结构体(如:light_device_t)的结构体,该结构体存在对硬件操作的函数,即HAL文件中实现的函数。 那我们我们怎么写HAL的文件呢?
- 实现一个hw_module_t结构体
- 实现一个open函数,返回一个自定义的结构体,并且这个结构体的第一个成员必须是hw_module_t。
灯光系统对应的JNI文件为com_android_server_lights_LightsService.cpp,在文件的setLight_native函数中,有如下代码:
static JNINativeMethod method_table[] = {
{ "init_native", "()J", (void*)init_native },
{ "finalize_native", "(J)V", (void*)finalize_native },
{ "setLight_native", "(JIIIIII)V", (void*)setLight_native },
};
int register_android_server_LightsService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/lights/LightsService",
method_table, NELEM(method_table));
}
可知注册了山个本地方法,init_native,finalize_native",setLight_native,这些方法对应的C函数中,肯定加载HAL文件的相关操作,先查看init_native函数:
tatic jlong init_native(JNIEnv *env, jobject clazz)
{
int err;
hw_module_t* module;
Devices* devices;
devices = (Devices*)malloc(sizeof(Devices));
err = hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
if (err == 0) {
devices->lights[LIGHT_INDEX_BACKLIGHT]
= get_device(module, LIGHT_ID_BACKLIGHT);
devices->lights[LIGHT_INDEX_KEYBOARD]
......................
}
........................
}
其中使用了hw_get_module获取一个 hw_module_t结构体指针 module,然后通过get_device,根据传入不同的name获取相应的light_device_t*结构体,从其中我们可以知道,一个HAL文件中,可以对应多个light_device_t。
java应用层分析我们之前提到过,安卓系统中存在多种逻辑上的灯光如下:
#define LIGHT_ID_BACKLIGHT "backlight"
#define LIGHT_ID_KEYBOARD // "keyboard"
#define LIGHT_ID_BUTTONS //"buttons"
#define LIGHT_ID_BATTERY "battery"
#define LIGHT_ID_NOTIFICATIONS "notifications"
#define LIGHT_ID_ATTENTION // "attention"
#define LIGHT_ID_BLUETOOTH // "bluetooth"
#define LIGHT_ID_WIFI // "wifi"
其中灰色部分代表手机没有实现的, 现在手机没有键盘了,所以"keyboard"没有实现,现在的几个手机按键一般由特殊材料做成,发光的原理是利用屏幕的光进行反射所以没有实等等。故此一般只实现了以上没有被注释的三个:“backlight”,“battery”,“notifications”。其中backlight比较特殊,是独有的硬件,我们暂时不讲解,我们想先分析"battery"与"notifications",他们是共用一个硬件。
电池灯一般电池的灯光现象如下: 当然,这是我们经验的依据,那么他代码是怎么实现的呢?我们怎么去查找他对应的代码呢?我们可以通过 #define LIGHT_ID_BATTERY “battery” 在源码中搜索LIGHT_ID_BATTERY ,我们可以找到一个名为BatteryService.java的文件,有如下代码:
mBatteryLight = lights.getLight(LightsManager.LIGHT_ID_BATTERY);
调用getLight获取一个mBatteryLight,那么这个mBatteryLight怎么使用呢?我们往下搜索mBatteryLight可以看到updateLightsLocked() 方法中使用了mBatteryLight:
public void updateLightsLocked() {
final int level = mBatteryProps.batteryLevel; //获取当前电量
/*获得电池的状态,由一下状态:正在充电,断开充电器,没有充电,满电等*/
final int status = mBatteryProps.batteryStatus;
/*如果电量少于设点的mLowBatteryWarningLevel*/
if (level = 90) {
// Solid green when full or charging and nearly full
mBatteryLight.setColor(mBatteryFullARGB);
} else {
// Solid orange when charging and halfway full
mBatteryLight.setColor(mBatteryMediumARGB);
}
} else {
// No lights if not charging and not low
mBatteryLight.turnOff();
}
}
代码注解比较详细,不再进行多次分析,其中 mBatteryLight.setColor(mBatteryLowARGB)根据mBatteryLowARGB设定灯的颜色,我们查看mBatteryLowARGB可以看到如下
mBatteryLowARGB = context.getResources().getInteger(
com.android.internal.R.integer.config_notificationsBatteryLowARGB);
然后我们在搜索config_notificationsBatteryLowARGB找到Config.xml文件中的:
0xFFFF0000
我们可以知道ARGB为0xFFFF0000,所以设置颜色为mBatteryLowARGB实际为红色,其余的颜色分析也是同理,不再分析。
通知灯同样,我们不知道通知灯的源代码在哪里,使用同样的方法,根据
#define LIGHT_ID_NOTIFICATIONS "notifications"
在源码中搜索LIGHT_ID_NOTIFICATIONS,我们可以找到.NotificationManagerService.java文件,首先,他也是根据 "notifications"获取一个mNotificationLight,继续往下追踪mNotificationLight,可以看到他也同电池灯一样,实现了一个updateLightsLocked函数,该函数的注解在源码中很详细就不再进行讲解了,主要实现了以下功能 1.当打电话或者屏幕是亮着的时候,会关闭通知灯。 2.如果没有打电话,根据应用程序传进去的颜色值设定LED灯。
注意:通知灯的优先级比电池灯高。
编写HAL在我们的GEC3399开发板上,只有两个LED并且都为蓝色,所以没有办法模拟RGB多种颜色,在这里,我们把LED1单做R红色,把LED2单做绿色,来进行模拟。
根据前面的分析,编写灯光系统的HAL,我们需要实现以下几点: 1.实现一个名为HMI的hw_module_t结构体 2.实现一个open函数,他会根据name返回一个light_device_t结构体 3.实现多个light_device_t结构体,每一个对应一个DEVICE,light_device_t的第一个成员为hw_module_t,紧接着一个set.light函数
在编写该HAL时,我们不需要全部自己编写,我们可以参考原代码中的SDK/hardware/rockchip/liblights/lights.cpp,编写我们自己的lights.c如下:
#define LOG_NDEBUG 0
#define LOG_TAG "mylights"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
char const*const RED_LED_FILE = "/sys/class/leds/led1/brightness";
char const*const GREEN_LED_FILE = "/sys/class/leds/led2/brightness";
char const*const RED_LED_FILE_TRIGGER = "/sys/class/leds/led1/trigger";
char const*const GREEN_LED_FILE_TRIGGER = "/sys/class/leds/led2/trigger";
char const*const RED_LED_FILE_DELAYON = "/sys/class/leds/led1/delay_on";
char const*const GREEN_LED_FILE_DELAYON = "/sys/class/leds/led2/delay_on";
char const*const RED_LED_FILE_DELAYOFF = "/sys/class/leds/led1/delay_off";
char const*const GREEN_LED_FILE_DELAYOFF= "/sys/class/leds/led2/delay_off";
char const*const LCD_BACKLIGHT_FILE = "sys/class/backlight/backlight/brightness";
/* Synchronization primities */
static pthread_once_t g_init = PTHREAD_ONCE_INIT;
static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
/* Mini-led state machine */
static struct light_state_t g_notification;
static struct light_state_t g_battery;
static int write_int (const char *path, int value) {
int fd;
static int already_warned = 0;
fd = open(path, O_RDWR);
if (fd color & 0x00ffffff;
return ((77*((color>>16)&0x00ff))
+ (150*((color>>8)&0x00ff)) + (29*(color&0x00ff))) >> 8;
}
/* The actual lights controlling section */
static int set_light_backlight (struct light_device_t *dev, struct light_state_t const *state) {
int brightness = rgb_to_brightness(state);
int als_mode;
ALOGV("%s brightness=%d color=0x%08x",__func__,brightness,state->color);
pthread_mutex_lock(&g_lock);
write_int (LCD_BACKLIGHT_FILE, brightness);
pthread_mutex_unlock(&g_lock);
return 0;
}
static void set_shared_light_locked (struct light_device_t *dev, struct light_state_t *state) {
int r, g, b;
int delayOn,delayOff;
r = (state->color >> 16) & 0xFF;
g = (state->color >> 8) & 0xFF;
b = (state->color) & 0xFF;
delayOn = state->flashOnMS;
delayOff = state->flashOffMS;
if (state->flashMode != LIGHT_FLASH_NONE) {
write_string (RED_LED_FILE_TRIGGER, "timer");
write_string (GREEN_LED_FILE_TRIGGER, "timer");
write_int (RED_LED_FILE_DELAYON, delayOn);
write_int (GREEN_LED_FILE_DELAYON, delayOn);
write_int (RED_LED_FILE_DELAYOFF, delayOff);
write_int (GREEN_LED_FILE_DELAYOFF, delayOff);
} else {
write_string (RED_LED_FILE_TRIGGER, "none");
write_string (GREEN_LED_FILE_TRIGGER, "none");
}
write_int (RED_LED_FILE, r);
write_int (GREEN_LED_FILE, g);
}
static void handle_shared_battery_locked (struct light_device_t *dev) {
if (is_lit (&g_notification)) {
set_shared_light_locked (dev, &g_notification);
} else {
set_shared_light_locked (dev, &g_battery);
}
}
static int set_light_battery (struct light_device_t *dev, struct light_state_t const* state) {
ALOGV("%s flashMode=%d onMS=%d offMS color=0x%08x", __func__,state->flashMode,state->flashOnMS,state->flashOffMS,state->color);
pthread_mutex_lock (&g_lock);
g_battery = *state;
handle_shared_battery_locked(dev);
pthread_mutex_unlock (&g_lock);
return 0;
}
static int set_light_notifications (struct light_device_t *dev, struct light_state_t const* state) {
ALOGV("%s flashMode=%d onMS=%d offMS color=0x%08x", __func__,state->flashMode,state->flashOnMS,state->flashOffMS,state->color);
pthread_mutex_lock (&g_lock);
g_notification = *state;
handle_shared_battery_locked(dev);
pthread_mutex_unlock (&g_lock);
return 0;
}
/* Initializations */
void init_globals () {
pthread_mutex_init (&g_lock, NULL);
}
/* Glueing boilerplate */
static int close_lights (struct light_device_t *dev) {
if (dev)
free(dev);
return 0;
}
static int open_lights (const struct hw_module_t* module, char const* name,
struct hw_device_t** device) {
int (*set_light)(struct light_device_t* dev,
struct light_state_t const *state);
if (0 == strcmp(LIGHT_ID_BACKLIGHT, name)) {
set_light = set_light_backlight;
}
else if (0 == strcmp(LIGHT_ID_BATTERY, name)) {
set_light = set_light_battery;
}
else if (0 == strcmp(LIGHT_ID_NOTIFICATIONS, name)) {
set_light = set_light_notifications;
}
else {
return -EINVAL;
}
pthread_once (&g_init, init_globals);
struct light_device_t *dev = malloc(sizeof (struct light_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_lights;
dev->set_light = set_light;
*device = (struct hw_device_t*)dev;
return 0;
}
static struct hw_module_methods_t lights_module_methods = {
.open = open_lights,
};
struct hw_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = LIGHTS_HARDWARE_MODULE_ID,
.name = "GEC-3399 lights module",
.author = "GEC-3399",
.methods = &lights_module_methods,
};
首先我们在文件末尾定义了一个HAL_MODULE_INFO_SYM结构体,其中id = LIGHTS_HARDWARE_MODULE_ID不能缺少,JNI文件在需要灯光进行操作时,就是通过该ID获取hw_module_t结构体。在hw_module_t中存在成员lights_module_methods,其子成员,我们实现了一个open函数,该函数主要是根据JNI文件com_android_server_lights_LightsService.cpp在调用get_device时传入的参数,返回对应的hw_device_t结构体,hw_device_t结构体的不同点在于成员set_light的不同,当需要控制通知灯时,我们当然返回set_light指向设置LED灯的函数,当需要控制背光灯时,我们当然返回set_light指向设置背光灯的函数…
在我们编写的lights.c点C文件中,我们主要实现了3个函数,分别为set_light_backlight,set_light_battery,set_light_notifications。他们的实现也很简单,在上一节中,我们知道,只需要往以下文件写值,就是实现对灯的控制:
char const*const RED_LED_FILE = "/sys/class/leds/led1/brightness";
char const*const GREEN_LED_FILE = "/sys/class/leds/led2/brightness";
char const*const RED_LED_FILE_TRIGGER = "/sys/class/leds/led1/trigger";
char const*const GREEN_LED_FILE_TRIGGER = "/sys/class/leds/led2/trigger";
char const*const RED_LED_FILE_DELAYON = "/sys/class/leds/led1/delay_on";
char const*const GREEN_LED_FILE_DELAYON = "/sys/class/leds/led2/delay_on";
char const*const RED_LED_FILE_DELAYOFF = "/sys/class/leds/led1/delay_off";
char const*const GREEN_LED_FILE_DELAYOFF= "/sys/class/leds/led2/delay_off";
char const*const LCD_BACKLIGHT_FILE = "sys/class/backlight/backlight/brightness";
所以,我们的lights.c文件实质就是这些文件的操作,我们知道,电池灯和通知灯是使用同一个设备,通知灯的优先级是高于电池灯的,所以在电池灯和通知灯不能同时对led进行操作,并且通知灯有权打断电池灯的显示。为了实现该功能,定义了两个全局变量static struct light_state_t g_notification, static struct light_state_t g_battery,用来表示指示灯的状态为电池灯或者为通知灯,亦或者两者都是,或者都不是。所以电池灯和 通知灯都调用函数
static void handle_shared_battery_locked (struct light_device_t *dev) {
if (is_lit (&g_notification)) {
set_shared_light_locked (dev, &g_notification);
} else {
set_shared_light_locked (dev, &g_battery);
}
}
他会先判断此时灯是否处于点亮状态,没有点亮,直接操作即可,如果此时处于点亮状态,则在判断是否为通知点亮状态,如果是,则继续维持通知状态,如果不是则显示电量指示灯。
其中还实现set_light_backlight函数,该函数是对背光灯的操作,过程比较简单,直接操作"sys/class/backlight/backlight/brightness"文件。
编译烧写编译完成lights.c文件后,我们在hardware/libhardware/modules目录下创建文件夹lights,把lights.c拷贝到新创建的目录下,并且在新建目录下编写Android.mk文件,内容如下
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := lights.rk3399
# HAL module implementation stored in
# hw/.default.so
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_C_INCLUDES := hardware/libhardware
LOCAL_SRC_FILES := lights.c
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_MODULE_TAGS := userdebug
include $(BUILD_SHARED_LIBRARY)
然后在SDK目录下执行: source build/envsetup.sh lunch rk3399_all-userdebug mmm hardware/libhardware/modules
完成之后,我们还需要删除系统自带自动生成的lights.rk3399.so,但是为了下次方便,我们还需要备份,即把SDK/out/target/product/rk3399_all/system/vendor/lib64/hw/lights.rk3399.so(该文文件由之前提到的SDK/hardware/rockchip/liblights/lights.cpp编译生成)重命名为1lights.rk3399.so,在前面加上一个1,这样系统就找不到自带的lights.rk3399.so文件,就会加载我们生成的lights.rk3399.so,我们生成的lights.rk3399.so在out/target/product/rk3399_all/system/lib64/hw目录下。 最后执行 make snod -j3 等待编译完成,烧写system.img到开发板,烧写完成完成之后,我们在终端执行logcat mylights:V *:S,然后通过触摸屏调整屏幕的亮度,我们可以看到类似如下打印
01-18 15:29:15.729 596 617 V mylights: set_light_backlight brightness=102 color=0xff666666
01-18 15:29:22.565 596 697 V mylights: set_light_backlight brightness=103 color=0xff676767
01-18 15:29:22.581 596 697 V mylights: set_light_backlight brightness=106 color=0xff6a6a6a
01-18 15:29:22.598 596 697 V mylights: set_light_backlight brightness=109 color=0xff6d6d6d
01-18 15:29:22.615 596 697 V mylights: set_light_backlight brightness=113 color=0xff717171
并且可以调整屏幕亮度,则代表实验成功。
小节结语该小节首先简单分析了一下java应用层,然后参考hardware/rockchip/liblights/lights.cpp编写了HAL文件lights.c,并且分析其原理。下小节将为大家分析安卓源码中的电池灯部分,敬请期待。