LightsService分析 --- 硬件抽象层

来源:互联网 发布:mv视频制作软件 编辑:程序博客网 时间:2024/06/01 09:48

4 硬件抽象层代码分析

LightsService的HAL(硬件抽象层)物理逻辑代码主要位于hardware\qcom\display\liblight目录下的lights.c以及

hardware\libhardware\include\hardware目录下的hardware.h和lights.h。

4.1 hardware定义

hardware.h定义了Android系统中通用的硬件设备信息,主要包括三个结构体以及用于确定软件版本信息相关的宏定义。以下是三个结构体:

struct hw_module_t;struct hw_module_methods_t;struct hw_device_t;

其中hw_module_t的定义如下:

typedef struct hw_module_t {uint32_t tag;uint16_t module_api_version;uint16_t hal_api_version;const char *id;const char *name;const char *author;/** Modules methods */struct hw_module_methods_t* methods;...} hw_module_t;

这个定义中包含了设备的id号码,设备名称,设备所属用户。比较重要的是还要包含一个函数指针结构体,

这个函数指针用来传递一个open函数,用于打开指定的设备。定义如下:

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;

open函数在JNI方法初始化的时候会调用,参见JNI中的get_device函数。

在使用hw_module_t结构体定义设备时要求每个设备定义都必须包含一个名为HAL_MODULE_INFO_SYM的该结构体,它负责为各属性赋值。

hw_device_t的定义如下:

typedef struct hw_device_t {    uint32_t tag;    uint32_t version;    /** reference to the module this device belongs to */    struct hw_module_t* module;    /** Close this device */    int (*close)(struct hw_device_t* device);} hw_device_t;

hw_device_t引用了一个hw_module_t指针,并且定义了一个close方法,用于关闭设备Android系统中要求每一个设备信息

都必须首先引用hw_device_t结构体信息,然后才定义自己的公共方法和属性。这类似于面向对象中的继承关系,需要首先继承

自父类的特性,然后在扩展自己的特性。这三个结构体如果以面向对象的关系来看,它们之间的关系如图4-1所示:


图 4-1 hardware中结构体之间的关系

Android系统定义的这种硬件模型层次比较清晰,可以看出系统中任意一种类型的硬件设备的顶层逻辑抽象都是相同的。

这里的定义都是所有硬件可以通用的东西,差异化的东西在不同的硬件实现中完成。

hardware定义中还有一个函数hw_get_module,这个函数是根据一个id来获取一类设备的hw_module_t的。

在JNI调用init_native函数时会调用它。lights的hw_module_t类型实现在light.c中,后面会提到。

/** * Get the module info associated with a module by id. * * @return: 0 == success, <0 == error and *module == NULL */int hw_get_module(const char *id, const struct hw_module_t **module);

4.2 lights定义

lights的定义在lights.h中。首先定义了系统中各个灯的名称,也类似于灯的ID,这些ID也是对应的逻辑上的灯而非物理灯。

也就意味着,如果一个指示灯对应的物理按键被按压,而且此时灯已经被点亮,那么对应的逻辑灯也会响应为对应的颜色被点亮。

这里定义的这些ID在JNI的初始化时将会与上层Java定义的ID进行一一对应(见JNI调用的初始化部分)。lights.h还定义了亮度模式

和FLASH模式的常量。比较重要的是light_state_t和light_device_t两个结构体。

struct light_state_t {    unsigned int color;    int flashMode;    int flashOnMS;    int flashOffMS;    int brightnessMode;};

这些属性可以用来设置给指定的灯,并不是所有这些属性都必须指定。其中需要注意的是color这个属性,有时候用户在使用的

时候传递过来的是亮度值,所以需要特别转换,这个在前面也有提到。如果只是希望打开这个,任意非0值都可以。即可以通过

为这个结构体赋值来达到控制灯效果的目的,也可以通过获取这个结构体的值来获取特定灯的当前状态。

struct light_device_t {    struct hw_device_t common;    /**     * Set the provided lights to the provided values.     * Returns: 0 on succes, error code on failure.     *///声明set_light函数指针    int (*set_light)(struct light_device_t* dev,            struct light_state_t const* state);};

这个结构体就是每一个灯设备的逻辑对象,它里面包含了一个hw_device_t型变量common,如同这个变量名所指,也在前面提到的,

这个结构体代表的是所有硬件设备通用的信息。此处light_device_t相当于hw_device_t的实现。另外,它还定义了一个函数指针set_light,

这个函数指针在每一个灯的具体实现时指定函数,这个函数才是真正对灯的控制部分,具体实现将在接下来分析。

4.3 lights的实现

lights的控制原理主要是向每个灯的配置文件写入参数值,硬件驱动会直接读取这些配置文件的值,继而控制物理的灯。

至于驱动是如何去跟这些配置文件相互作用的,本文暂且不考虑。

4.3.1 lights的实例化

前面提到在上层初始化时会通过JNI调用每一个硬件设备的open方法来打开一个具体的设备,那就先来看看一个light实例是如何打开的,

部分代码如下:

/** Open a new instance of a lights device using name */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); //声明set_light函数指针    if (0 == strcmp(LIGHT_ID_BACKLIGHT, name))        set_light = set_light_backlight; //为每一个light指定set_light函数    else if (0 == strcmp(LIGHT_ID_BATTERY, name))        set_light = set_light_battery;    ...    else        return -EINVAL;...//为一个light_device_t分配空间    struct light_device_t *dev = malloc(sizeof(struct light_device_t));    memset(dev, 0, sizeof(*dev));    ...    dev->common.module = (struct hw_module_t*)module;    dev->common.close = (int (*)(struct hw_device_t*))close_lights;    dev->set_light = set_light; //将set_light函数赋给light_device_t//将light_device_t向上转型为hw_device_t*device = (struct hw_device_t*)dev; return 0;}

每次在调用open_lights函数时都会传入一个name参数,也就是每个灯都有一个名称,这个名称跟上层的ID都是绑定在一起的。

每打开一个灯设备,都会分配一个light_device_t类型的实例,并给这其中的各个函数指针指定具体的函数实现。从代码最后可以看到,

这里将light_device_t向上转型为了hw_device_t类型(upcast),这只是为了满足代码语法的需要,也是为了接口更加通用,实际上

并不会造成数据的丢失,因为在JNI调用时再次进行了类型转换为light_device_t。这一点可能跟Java中的向上转型不一样,Java中不能

通过子类对象向上转型为父类的对象后来访问子类特有的属性,就好像数据丢失了。open_lights函数会在JNI初始化时打开每一个灯时调用,

它将被赋值给hw_module_methods_t中的open函数指针:

static struct hw_module_methods_t lights_module_methods = {    .open =  open_lights,};
而最终这个lights_module_methods也会作为hw_device_t的一部分:
struct hw_module_t HAL_MODULE_INFO_SYM = {    .tag = HARDWARE_MODULE_TAG,    .version_major = 1,    .version_minor = 0,    .id = LIGHTS_HARDWARE_MODULE_ID, //这就是lights的hw_module_t的ID    .name = "lights Module",    .author = "Google, Inc.",    .methods = &lights_module_methods, //在此处赋值};

因为hw_module_t是通用的,所以同一类设备只有一个实例,且所有类型的设备的hw_module_t变量名都

为HAL_MODULE_INFO_SYM,上面这个就是lights的module。在JNI中就是通过上述ID:LIGHTS_HARDWARE_MODULE_ID来

获取lights的module的。

hw_device_t中还有一个close函数,这个函数会释放每一个设备分配的空间,light设备的close函数如下:

/** Close the lights device */static intclose_lights(struct light_device_t *dev){    if (dev) {        free(dev);    }    return 0;}

4.3.2 lights的控制

每一个灯都提供了set_light函数,这个函数在初始化这个灯设备时指定给set_light函数指针。lights的控制就在这些函数中处理,

下面以系统中显示电池状态的灯为例分析:

static int set_light_battery(struct light_device_t* dev,        struct light_state_t const* state){    pthread_mutex_lock(&g_lock);    g_battery = *state;    handle_speaker_battery_locked(dev);    pthread_mutex_unlock(&g_lock);    return 0;}

对灯的控制都是通过写灯的配置文件来达到目的的,为了多线程读写文件导致的不一致性,所以在读写文件的时候进行了

加锁处理pthread_mutex_lock(&g_lock)。在这里将需要设置的状态值保存在电池状态对象中,以作他用。

在调用handle_speaker_battery_locked函数时将设备信息传入:

static void handle_speaker_battery_locked(struct light_device_t* dev){    if (is_lit(&g_battery)) {        set_speaker_light_locked(dev, &g_battery);    } else {        set_speaker_light_locked(dev, &g_notification);    }}

可以看出,电池指示灯跟通知指示灯在硬件逻辑上使用的是同一个灯,通过is_lit函数来判断电池指示灯是否被点亮,

如果电池状态被点亮则会忽略通知状态的改变。is_lit会读取传入的state值,并根据亮度值来判断设备是否点亮状态。

set_speaker_light_locked对传进去的状态值做了处理后才真正把值写入到指定路径下的文件中:

static int write_int(char const* path, int value){    int fd;    static int already_warned = 0;    fd = open(path, O_RDWR);    if (fd >= 0) {        char buffer[20];        int bytes = snprintf(buffer, sizeof(buffer), "%d\n", value);        ssize_t amt = write(fd, buffer, (size_t)bytes);        close(fd);        return amt == -1 ? -errno : 0;    } else {        if (already_warned == 0) {            ALOGE("write_int failed to open %s\n", path);            already_warned = 1;        }        return -errno;    }}

传入write_int函数的第一个参数path就是要控制的灯的配置文件所在的路径,第二个参数value就是要写入的值。

如果要控制灯的亮度,path代表的就是brightness文件,value就是亮度值,当值为0时,就会关闭灯,其他任意大于0小于255的值都可以点亮灯;

如果要控制灯的闪烁,path可以是blink文件,也可以是trigger文件,value就代表不同时长的闪烁参数。

对灯的控制在这里还是比较灵活的,由于是面向过程编程,因此可以随着需求的变化而变化,比较麻烦的就是如果要控制的灯比较多,

就需要考虑每一个灯的控制逻辑。当然可以将控制中共通的部分提取出来,这样可以提高代码的复用率。

原创粉丝点击