2. Display 设备节点发现与初始化

任务说明

本案例要求实现 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/

编译通过后,验证方法如下:

  1. 确认生成的 display.default.so 位于 /vendor/lib64/hw/ 或 /system/lib64/hw/(取决于 vendor: true 配置);
  2. 在设备上执行 ls -l /vendor/lib64/hw/display.* 检查模块文件存在;
  3. 编写测试程序调用 hw_get_module(DISPLAY_HARDWARE_MODULE_ID, &module),随后调用 module->methods->open(module, HWC_DISPLAY_PRIMARY, &device);
  4. 判定标准: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 并输出错误日志。

SurfaceFlinger hw_get_module() hw_module_t .id = DISPLAY_HARDWARE_MODULE_ID .methods = &display_module_methods .open = display_device_open display_device_open() 校验 name == "primary" calloc 分配设备结构体 填充 hw_device_t 字段 ① 获取模块 ② 调用 open display_device_t 内存布局 hw_device_t common(第一个成员) tag = HARDWARE_DEVICE_TAG version = HARDWARE_DEVICE_API_VERSION(1,0) module = 指向模块结构体 | close = display_device_close 私有数据(示例值) width = 1080 | height = 1920 | format = RGBA_8888 ③ 返回 &dev->common 类型安全转换 hw_device_t* 可安全 转换为 display_device_t*