2. 实现 Display HAL 的 open 函数
任务说明
本案例要求实现 Android Display HAL 模块的 open 函数。该函数是 HAL 模块与系统框架之间的核心入口:当 SurfaceFlinger 通过 hw_get_module 加载显示模块后,会调用模块的 open 方法,传入设备名称并期望获得一个填充完毕的 hw_device_t 结构体指针。本案例将实现一个最小但完整的 open 函数,支持打开主显示设备(HW_DISPLAY_PRIMARY),并正确初始化设备结构体。
核心实现
// display_hal.cpp
#include <cutils/log.h>
#include <hardware/hardware.h>
#include <hardware/display.h>
#include <errno.h>
#include <string.h>
// 设备私有数据结构
struct display_device_t {
hw_device_t common; // 必须为第一个成员
uint32_t width;
uint32_t height;
int32_t format;
};
// 设备关闭回调
static int display_device_close(hw_device_t* dev) {
if (dev) {
free(dev);
}
return 0;
}
// 模块 open 函数
static int display_device_open(const hw_module_t* module,
const char* name,
hw_device_t** device) {
if (module == NULL || device == NULL) {
ALOGE("display_device_open: invalid argument");
return -EINVAL;
}
// 仅支持主显示设备
if (strcmp(name, HWC_DISPLAY_PRIMARY) != 0) {
ALOGE("display_device_open: unsupported display %s", name);
return -ENODEV;
}
// 分配设备结构体
display_device_t* dev = (display_device_t*)calloc(1, sizeof(display_device_t));
if (dev == NULL) {
ALOGE("display_device_open: failed to allocate device");
return -ENOMEM;
}
// 填充 hw_device_t 公共字段
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = HARDWARE_DEVICE_API_VERSION(1, 0);
dev->common.module = (hw_module_t*)module;
dev->common.close = display_device_close;
// 初始化私有数据(示例值,实际应从硬件或配置获取)
dev->width = 1080;
dev->height = 1920;
dev->format = HAL_PIXEL_FORMAT_RGBA_8888;
*device = &dev->common;
ALOGI("display_device_open: opened %s successfully", name);
return 0;
}
// 模块结构体定义
static hw_module_methods_t display_module_methods = {
.open = display_device_open
};
hw_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = DISPLAY_HARDWARE_MODULE_ID,
.name = "Display HAL Module",
.author = "Android HAL Course",
.methods = &display_module_methods,
};
关键设计说明
上述实现遵循 Android HAL 规范:display_device_t 结构体将 hw_device_t 作为第一个成员,使得指向 common 的指针可以安全地转换为指向整个结构体的指针。模块的 open 方法通过 strcmp 校验设备名称,仅接受 HWC_DISPLAY_PRIMARY(即字符串 "primary"),对其他名称返回 -ENODEV。设备结构体使用 calloc 分配并清零,确保所有字段初始状态确定。公共字段 tag、version、module 和 close 均被正确赋值,其中 close 回调负责释放内存。私有字段(分辨率、像素格式)在真实场景中应从硬件能力或系统属性读取,此处使用示例值以保持代码可独立编译运行。
编译与验证
将上述代码保存为 display_hal.cpp,在 Android 源码树中创建 display/ 目录并添加以下 Android.bp 文件:
// Android.bp
cc_library_shared {
name: "display.default",
relative_install_path: "hw",
srcs: ["display_hal.cpp"],
shared_libs: [
"liblog",
"libcutils",
"libhardware",
],
vendor: true,
proprietary: true,
}
编译命令:
source build/envsetup.sh
lunch <your-target>
mmm display/
编译通过后,验证方法如下:
- 确认生成的
display.default.so位于/vendor/lib64/hw/或/system/lib64/hw/(取决于vendor: true配置); - 在设备上执行
ls -l /vendor/lib64/hw/display.*检查模块文件存在; - 编写测试程序调用
hw_get_module(DISPLAY_HARDWARE_MODULE_ID, &module),随后调用module->methods->open(module, HWC_DISPLAY_PRIMARY, &device); - 判定标准:
open返回 0,且device->tag == HARDWARE_DEVICE_TAG、device->version非零、device->close函数指针非空;调用device->close(device)后无内存泄漏(可用valgrind或address sanitizer检查)。
预期输出
当测试程序成功打开设备后,日志输出应包含:
display_device_open: opened primary successfully
若传入非法设备名称(如 "external"),则返回 -ENODEV 并输出错误日志。