一、分析HAL module 架构
Android硬件抽象层有三个核心数据结构,分别是hw_module_t , hw_module_methods_t, hw_device_t。定义在hardware/libhardware/include/hardware/hardware.h文件中:
1、hw_module_t:
typedef struct hw_module_t {
/** tag must be initialized to HARDWARE_MODULE_TAG */
uint32_t tag;
uint16_t module_api_version;
#define version_major module_api_version
uint16_t hal_api_version;
#define version_minor hal_api_version
const char *id;
const char *name;
const char *author;
struct hw_module_methods_t* methods;
void* dso;
#ifdef __LP64__
uint64_t reserved[32-7];
#else
uint32_t reserved[32-7];
#endif
} hw_module_t;
需要注意的:
- 每个硬件模块都必须有一个名为 HAL_MODULE_INFO_SYM 的数据结构,该数据结构的第一个成员变量必须以hw_module_t 开头,作为模块特定的信息。
- HAL_MODULE_INFO_SYM 指向一个自定义硬件抽象层模块结构体。
- tag 的值必须为 HARDWARE_MODULE_TAG,用来标志这是一个硬件抽象层模块结构体。
- dso 用来保存加载硬件抽象层模块后得到的句柄值。因为每一个硬件抽象层模块都对应以一个动态链接库文件。加载硬件抽象层的过程实际就是调用 dlopen 来加载对应的硬件动态链接库的过程。对应于调用 dlclose 的时候需要卸载对应的硬件抽象层需要用到这个句柄,所以要将其保存到这个成员变量中。
- hw_module_methods_t 定义了硬件抽象层模块的操作方法列表。
硬件抽象层HAL由一个一个的模块组成,Android规定,每一个模块都是一个命名为HAL_MODULE_INFO_SYM的自定义结构体,并且该结构体的第一个成员必须为hw_module_t类型的变量,其它成员变量根据需要由开发者设置。
2、hw_module_methods_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;
一个指针函数用来打开硬件抽象层模块中的硬件设备。
- module:硬件设备所在的模块
- id:要打开硬件设备对应的ID。因为硬件抽象层中可能有多个设备,所以打开时候需要制定ID。
- device:输出参数,用来描述一个已经打开的硬件设备。
3、hw_device_t :
typedef struct hw_device_t {
/** tag must be initialized to HARDWARE_DEVICE_TAG */
uint32_t tag;
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_device_t。
- tag 必须是 HARDWARE_DEVICE_TAG
- close 是一个指针函数,用来关闭硬件设备。
Example:
sensor 模块对应的结构体定义在hardware/libhardware/include/hardware/sensors.h文件中
/**
* 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 sensors_module_t {
struct hw_module_t common;
/**
* Enumerate all available sensors. The list is returned in "list".
* @return number of sensors in the list
*/
int (*get_sensors_list)(struct sensors_module_t* module,
struct sensor_t const** list);
/**
* Place the module in a specific mode. The following modes are defined
*
* 0 - Normal operation. Default state of the module.
* 1 - Loopback mode. Data is injected for the supported
* sensors by the sensor service in this mode.
* @return 0 on success
* -EINVAL if requested mode is not supported
* -EPERM if operation is not allowed
*/
int (*set_operation_mode)(unsigned int mode);
};
sensor设备对应的结构体如下:
/*
* sensors_poll_device_t is used with SENSORS_DEVICE_API_VERSION_0_1
* and is present for backward binary and source compatibility.
* See the Sensors HAL interface section for complete descriptions of the
* following functions:
* http://source.android.com/devices/sensors/index.html#hal
*/
struct sensors_poll_device_t {
struct hw_device_t common;
int (*activate)(struct sensors_poll_device_t *dev,
int sensor_handle, int enabled);
int (*setDelay)(struct sensors_poll_device_t *dev,
int sensor_handle, int64_t sampling_period_ns);
int (*poll)(struct sensors_poll_device_t *dev,
sensors_event_t* data, int count);
};
例如三星:对应的 /device/samsung/manta/libsensors/sensors.cpp文件对其具体实现:
static struct hw_module_methods_t sensors_module_methods = {
open: open_sensors
};
struct sensors_module_t HAL_MODULE_INFO_SYM = {
common: {
tag: HARDWARE_MODULE_TAG,
version_major: 1,
version_minor: 0,
id: SENSORS_HARDWARE_MODULE_ID,
name: "Samsung Sensor module",
author: "Samsung Electronic Company",
methods: &sensors_module_methods,
dso: 0,
reserved: {},
},
get_sensors_list: sensors__get_sensors_list,
};
struct sensors_poll_context_t {
struct sensors_poll_device_t device; // must be first
sensors_poll_context_t();
~sensors_poll_context_t();
int activate(int handle, int enabled);
int setDelay(int handle, int64_t ns);
int pollEvents(sensors_event_t* data, int count);
// Will return true if the constructor completed
bool isValid() { return mInitialized; };
private:
...
};
在对应的文件中实现了对应的 结构体中的成员方法。
二、Android如何使用硬件抽象层(hardware.c)
硬件抽象层的作用是对上层Application Framework屏蔽Linux底层驱动程序,那么Application Framework与硬件抽象层通信的接口是谁呢?答案是hw_get_module函数,该函数定义在hardware/libhardware/hardware.c文件中:
int hw_get_module(const char *id, const struct hw_module_t **module)
{
return hw_get_module_by_class(id, NULL, module);
}
int hw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module)
{
int status;
int i;
const struct hw_module_t *hmi = NULL;
char prop[PATH_MAX];
char path[PATH_MAX];
char name[PATH_MAX];
if (inst)
snprintf(name, PATH_MAX, "%s.%s", class_id, inst);
else
strlcpy(name, class_id, PATH_MAX);
/*
* Here we rely on the fact that calling dlopen multiple times on
* the same .so will simply increment a refcount (and not load
* a new copy of the library).
* We also assume that dlopen() is thread-safe.
*/
/* Loop through the configuration variants looking for a module */
for (i=0 ; i<HAL_VARIANT_KEYS_COUNT+1 ; i++) {
if (i < HAL_VARIANT_KEYS_COUNT) {
if (property_get(variant_keys[i], prop, NULL) == 0) {
continue;
}
snprintf(path, sizeof(path), "%s/%s.%s.so",
HAL_LIBRARY_PATH2, name, prop);
if (access(path, R_OK) == 0) break;
snprintf(path, sizeof(path), "%s/%s.%s.so",
HAL_LIBRARY_PATH1, name, prop);
if (access(path, R_OK) == 0) break;
} else {
snprintf(path, sizeof(path), "%s/%s.default.so",
HAL_LIBRARY_PATH2, name);
if (access(path, R_OK) == 0) break;
snprintf(path, sizeof(path), "%s/%s.default.so",
HAL_LIBRARY_PATH1, name);
if (access(path, R_OK) == 0) break;
}
}
status = -ENOENT;
if (i < HAL_VARIANT_KEYS_COUNT+1) {
/* load the module, if this fails, we're doomed, and we should not try
* to load a different variant. */
status = load(class_id, path, module);
}
return status;
}
hw_get_module 函数的作用是由第一个参数id指定的模块ID,找到模块对应的 hw_module_t 结构体,保存在第二个参数 module中。
首先通过for循环获取模块名及路径,保存在path中。循环次数为 HAL_VARIANT_KEYS_COUNT 次,HAL_VARIANT_KEYS_COUNT 是下面要用到的 variant_keys 数组的数组元素个数。
为了说明这个for循环是如何获得模块名及其路径,我们要先来看一下variant_keys数组的定义:
/**
* There are a set of variant filename for modules. The form of the filename
* is "<MODULE_ID>.variant.so" so for the led module the Dream variants
* of base "ro.product.board", "ro.board.platform" and "ro.arch" would be:
*
* led.trout.so
* led.msm7k.so
* led.ARMV6.so
* led.default.so
*/
static const char *variant_keys[] = {
"ro.hardware", /* This goes first so that it can pick up a different
file on the emulator. */
"ro.product.board",
"ro.board.platform",
"ro.arch"
};
static const int HAL_VARIANT_KEYS_COUNT =
(sizeof(variant_keys)/sizeof(variant_keys[0]));
上面的注释说明了 module文件的命名规则 <MODULE_ID>.variant.so
1、MODULE_ID是模块对应的ID ,不同模块对应一个唯一固定的ID。
2、那么variant是什么呢?又怎么获得variant呢?这就跟下面的variant_keys数组有关了。
定义的variant_keys[] 有四个值 "ro.hardware" 、"ro.product.board"、"ro.board.platform"、"ro.arch" 的四个字符串的指针, 可以把这些值理解为属性,系统会对这些值进行属性的set 。
ro.hardware”属性的属性值是在系统启动时由init进程负责设置的。它首先会读取/proc/cmdline文件,检查里面有没有一个名为androidboot.hardware的属性,如果有,就把它的值赋值给“ro.hardware”,否则,就将/proc/cpuinfo文件的内容读取出来,并解析出Haredware字段的内容赋值给“ro.hardware”。例如在Android模拟器中,从/proc/cpuinfo文件中读取出来的Hardware字段内容为goldfish,于是,init进程就会将 “ro.hardware” 属性设置为goldfish。
“ ro.product.board”、“ ro.board.platform”、“ ro.arch”属性是从/system/build.prop文件读取出来的。/system/build.prop文件是由编译系统中的编译脚本build/core/Makefile和shell脚本build/tools/buildinfo.sh生成的,这里不再详细分析。
3、HAL_VARIANT_KEYS_COUNT变量,它是variant_keys数组的大小。
4、通过 snprintf(path, sizeof(path), "%s/%s.%s.so", HAL_LIBRARY_PATH2, name, prop)找到 对应的so 文件路径 保存在path中。
5、if (access(path, R_OK) == 0) break; 通过access 来读取动态链接库是否存在。
6、如果没有找到variant_keys[i]对应的属性,则使用<MODULE_ID>.default.so
7、最后调用 load 方法来尝试打开在上面找到的so库,并获得hw_module_t 结构体。
/**
* Load the file defined by the variant and if successful
* return the dlopen handle and the hmi.
* @return 0 = success, !0 = failure.
*/
static int load(const char *id,
const char *path,
const struct hw_module_t **pHmi)
{
int status;
void *handle;
struct hw_module_t *hmi;
/*
* load the symbols resolving undefined symbols before
* dlopen returns. Since RTLD_GLOBAL is not or'd in with
* RTLD_NOW the external symbols will not be global
*/
handle = dlopen(path, RTLD_NOW);
if (handle == NULL) {
char const *err_str = dlerror();
ALOGE("load: module=%s\n%s", path, err_str?err_str:"unknown");
status = -EINVAL;
goto done;
}
/* Get the address of the struct hal_module_info. */
const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
hmi = (struct hw_module_t *)dlsym(handle, sym);
if (hmi == NULL) {
ALOGE("load: couldn't find symbol %s", sym);
status = -EINVAL;
goto done;
}
/* Check that the id matches */
if (strcmp(id, hmi->id) != 0) {
ALOGE("load: id=%s != hmi->id=%s", id, hmi->id);
status = -EINVAL;
goto done;
}
hmi->dso = handle;
/* success */
status = 0;
done:
if (status != 0) {
hmi = NULL;
if (handle != NULL) {
dlclose(handle);
handle = NULL;
}
} else {
ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",
id, path, *pHmi, handle);
}
*pHmi = hmi;
return status;
}
通过dlopen 方法打开so文件 得到的handle句柄,保存在 hw_module_t 的 dso 中(方便我们后面调用 close 方法来卸载对应的硬件)。最后将 hw_module_t 结构赋值给传递进来的参数 pHmi,即返回给上层调用函数。
分析到这里,我们可以看出,通过hw_get_module函数,Application Framework代码可以通过指定的模块ID找到模块hw_module_t结构体。有了hw_module_t结构体,就可以调用hw_module_t-> methods->open函数,在open函数中,完成对设备对应的hw_device_t结构体的初始化,并指定设备相关的自定义函数。
总结:
硬件抽象层加载一个模块无非就是
- 调用者传目标模块的ID参数到加载函数 。
- 获取目标模块的存储路径。
- 检查该路径下是否有该模块的.so文件。
- 加载模块至内存。
- 将句柄返回给调用者。