Skip to content

DRM_DeviceController

SweerItTer edited this page Feb 21, 2026 · 4 revisions

DeviceController API 文档

概述

DeviceController 是 utilsCore DRM 模块的核心类,提供全局唯一的 DRM 设备管理功能,负责设备初始化、资源获取、热插拔处理和平面资源管理。

职责

  • 全局唯一的 DRM 设备管理器
  • 设备初始化和资源查询
  • 热插拔事件处理
  • 平面资源管理和查询

适用场景

  • DRM 显示输出的设备管理
  • HDMI 热插拔监控
  • 可用平面资源的查询
  • 显示模式的获取和配置

依赖关系

  • 依赖: drm 内核子系统, libdrm
  • 被依赖: DrmLayer, PlanesCompositor, DmaBuffer

类分析

DeviceController 类

职责与用途

DeviceController 是 DRM 设备的单例管理器,提供:

  • DRM 设备的打开和初始化
  • 资源(connector/crtc/encoder/plane)的查询
  • 热插拔事件的监听和处理
  • 平面资源的缓存和查询

设计模式

  • 单例模式: 全局只允许一个 DeviceController 实例
  • RAII: 使用 FdWrapper 自动管理设备文件描述符
  • 回调机制: 通过回调通知热插拔事件

公共 API 方法

create() - 创建设备控制器
static DrmDevicePtr create(const std::string& path = "/dev/dri/card0");

参数说明:

  • path (输入): DRM 设备路径,默认 /dev/dri/card0

返回值:

  • 成功: 返回全局唯一的 DrmDevicePtr (DeviceController 的智能指针)
  • 失败: 返回 nullptr

所有权归属:

  • 返回的 shared_ptr 持有 DeviceController 的所有权
  • 全局单例,多次调用返回相同的实例

注意事项:

  1. 单例限制: 全局只允许一个实例,多次调用返回相同指针
  2. dumb buffer 支持: 必须检查 DRM_CAP_DUMB_BUFFER
  3. 原子模式: 必须设置 DRM_CLIENT_CAP_ATOMIC
  4. 路径验证: 确保 path 指向有效的 DRM 设备

使用例程:

// 使用默认路径
auto controller = DeviceController::create();
if (!controller) {
    fprintf(stderr, "Failed to create DRM controller\n");
    return -1;
}

// 使用自定义路径
auto controller2 = DeviceController::create("/dev/dri/card1");

get() - 获取设备文件描述符
int get() const;

参数说明: 无

返回值: DRM 设备的文件描述符

所有权归属: 只读访问,不转移所有权

注意事项:

  • 返回的 fd 由 FdWrapper 管理,不要手动关闭
  • fd 可以用于直接调用 DRM ioctl

registerResourceCallback() - 注册资源回调
void registerResourceCallback(
    const ResourceCallback& preRefreshCallback, 
    const ResourceCallback& postRefreshCallback
);

参数说明:

  • preRefreshCallback (输入): 热插拔刷新前回调
  • postRefreshCallback (输入): 热插拔刷新后回调

返回值: 无

所有权归属: 回调函数由 DeviceController 持有,调用者不负责释放

注意事项:

  1. 回调时机:
    • preRefreshCallback: 热插拔事件发生前,通知释放资源
    • postRefreshCallback: 热插拔事件处理后,通知重新获取资源
  2. 延迟处理: 热插拔事件有 600ms 延迟等待系统稳定
  3. 线程安全: 回调在热插拔线程中调用,注意线程安全

使用例程:

auto controller = DeviceController::create();

controller->registerResourceCallback(
    []() {
        // 释放资源
        printf("Pre-refresh: Release resources\n");
    },
    []() {
        // 重新获取资源
        printf("Post-refresh: Reacquire resources\n");
    }
);

getPossiblePlane() - 获取可用平面
void getPossiblePlane(
    int plane_type, 
    uint32_t format, 
    std::vector<uint32_t>& out_ids
);

参数说明:

  • plane_type (输入): 平面类型 (DRM_PLANE_TYPE_OVERLAY / PRIMARY / CURSOR)
  • format (输入): 像素格式 (如 DRM_FORMAT_ARGB8888)
  • out_ids (输出): 符合条件的 plane ID 列表

返回值: 无,结果写入 out_ids

所有权归属: out_ids 由调用者提供并持有

注意事项:

  1. 平面类型:
    • DRM_PLANE_TYPE_OVERLAY: 覆盖层(通常用于UI)
    • DRM_PLANE_TYPE_PRIMARY: 主层(通常用于视频)
    • DRM_PLANE_TYPE_CURSOR: 光标层
  2. 格式匹配: 只返回支持指定格式的 plane
  3. CRTC 支持: plane 必须支持当前的 CRTC

使用例程:

auto controller = DeviceController::create();

// 查询支持 ARGB8888 的 overlay planes
std::vector<uint32_t> overlay_planes;
controller->getPossiblePlane(DRM_PLANE_TYPE_OVERLAY, 
                               DRM_FORMAT_ARGB8888, 
                               overlay_planes);

for (uint32_t plane_id : overlay_planes) {
    printf("Overlay plane: %u\n", plane_id);
}

getPlaneById() - 根据 ID 获取平面
PlanesCachePtr getPlaneById(uint32_t id);

参数说明:

  • id (输入): plane ID

返回值:

  • 成功: 返回 PlanesCachePtr (平面的缓存信息)
  • 失败: 返回 nullptr

所有权归属: 返回的 shared_ptr 持有平面信息的所有权

注意事项:

  • 返回的指针指向 DeviceController 内部缓存
  • 平面信息包括格式列表、plane 详细信息等

refreshResources() - 刷新资源
std::shared_ptr<drmModeRes> refreshResources();

参数说明: 无

返回值:

  • 成功: 返回 drmModeRes 智能指针
  • 失败: 返回 nullptr

所有权归属: 返回的 shared_ptr 使用自定义 Deleter 自动释放

注意事项:

  • 重新查询所有 connector、encoder、crtc 资源
  • 热插拔后必须调用此方法更新资源
  • 使用 res_mutex 保护并发访问

refreshAllDevices() - 刷新所有设备
SharedDev& refreshAllDevices();

参数说明: 无

返回值: 设备列表的引用

所有权归属: 只读访问,不转移所有权

注意事项:

  • 查询所有 connector 并配置 connector-crtc 绑定
  • 使用 devMutex 保护并发访问
  • 热插拔后必须调用此方法

refreshPlane() - 刷新平面资源
size_t refreshPlane(uint32_t crtc_id);

参数说明:

  • crtc_id (输入): CRTC ID

返回值:

  • 成功: 返回匹配的 plane 数量
  • 失败: 返回 0

所有权归属: 只读访问,不转移所有权

注意事项:

  • 查询所有支持指定 CRTC 的 plane
  • 更新 planesCache_ 缓存
  • 使用 cacheMutex 保护并发访问

getPropertyId() - 获取属性ID
uint32_t getPropertyId(int fd, drmModeObjectPropertiesPtr props, const char *name);

参数说明:

  • fd (输入): DRM 文件描述符
  • props (输入): DRM 对象属性集合
  • name (输入): 属性名称

返回值:

  • 成功: 返回属性ID
  • 失败: 返回 0

所有权归属: 只读访问,不转移所有权

注意事项:

  • 用于查询 DRM 对象的属性ID
  • 通常用于原子提交时设置属性

getDevices() - 获取设备组合列表
SharedDev& getDevices();

参数说明: 无

返回值: 设备组合列表的引用

所有权归属: 只读访问,不转移所有权

注意事项:

  • 返回当前所有可用的设备组合 (connector-crtc 绑定)
  • 使用 devMutex 保护并发访问
  • 热插拔后需要调用 refreshAllDevices() 更新列表

getResources() - 获取资源
std::shared_ptr<drmModeRes> getResources() const;

参数说明: 无

返回值:

  • 成功: 返回 drmModeRes 智能指针
  • 失败: 返回 nullptr

所有权归属: 返回的 shared_ptr 使用自定义 Deleter 自动释放

注意事项:

  • 返回 DRM 资源信息 (connector/encoder/crtc)
  • 使用 resMutex 保护并发访问
  • 热插拔后需要调用 refreshResources() 更新资源

getPlaneResources() - 获取平面资源
std::shared_ptr<drmModePlaneRes> getPlaneResources() const;

参数说明: 无

返回值:

  • 成功: 返回 drmModePlaneRes 智能指针
  • 失败: 返回 nullptr

所有权归属: 返回的 shared_ptr 使用自定义 Deleter 自动释放

注意事项:

  • 返回 DRM 平面资源信息
  • 使用 resMutex 保护并发访问
  • 热插拔后需要调用 refreshResources() 更新资源

所有权规则总结

资源 拥有者 释放方式 线程保护
DRM FD FdWrapper ~FdWrapper()close() 内部同步
resources_ DeviceController drmModeFreeResources() res_mutex
planeResources_ DeviceController drmModeFreePlaneResources() res_mutex
devices_ DeviceController 析构时自动释放 devMutex
planesCache_ DeviceController 析构时自动释放 cacheMutex
回调函数 DeviceController 析构时自动释放 -

所有权传递规则

创建流:
DeviceController::create() → 全局单例 (DrmDev::fd_ptr)

回调流:
注册回调 → DeviceController 持有回调 → 热插拔时调用

线程安全说明

同步机制

  1. fd_mutex: 保护 DRM 文件描述符访问
  2. res_mutex: 保护 resources_ 和 planeResources_
  3. dev_mutex: 保护 devices_ 列表
  4. cache_mutex: 保护 planesCache_ 缓存

线程安全建议

  • refreshResources(), refreshAllDevices(), refreshPlane() 是线程安全的
  • 回调函数在热插拔线程中调用,注意线程安全
  • 不要在回调中执行耗时操作

模板化使用场景

场景 1: 初始化 DRM 设备

// 创建设备控制器
DrmDev::fd_ptr = DeviceController::create("/dev/dri/card0");
// 获取设备组合
std::vector<DevPtr>& devices = DrmDev::fd_ptr->getDevices(); 
if (devices.empty()){
    std::cout << "[DrmDev] Get no devices." << std::endl;
    return;
}
// 取出第一个可用设备组合
DevPtr dev = devices[0];
if (!dev) {
    std::cout << "Failed to get usable device." << std::endl;
    return;
}
std::cout << "Connector ID: " << dev->connector_id << ", CRTC ID: " << dev->crtc_id
    << ", Resolution: " << dev->width << "x" << dev->height << "\n";

// 获取所有在指定CRTC上的Plane
DrmDev::fd_ptr->refreshPlane(dev->crtc_id);
// 初始化 id 列表
std::vector<uint32_t> usablePrimaryPlaneIds;
std::vector<uint32_t> usableOverlayPlaneIds;
// 获取指定类型并且支持目标格式的 Plane
DrmDev::fd_ptr->getPossiblePlane(DRM_PLANE_TYPE_PRIMARY, DRM_FORMAT_BGRA8888, usablePrimaryPlaneIds);
DrmDev::fd_ptr->getPossiblePlane(DRM_PLANE_TYPE_OVERLAY, DRM_FORMAT_NV12, usableOverlayPlaneIds);
// 检查是否有可以ID
if (usablePrimaryPlaneIds.empty() || usableOverlayPlaneIds.empty()) {
    std::cout << "Some plane do not matched.\n"; 
    return; 
}
// 初始化 layer
std::unique_ptr<DrmLayer> primaryLayer = std::make_unique(std::vector<DmaBufferPtr>(), 2);
std::unique_ptr<DrmLayer> overLayer = std::make_unique(std::vector<DmaBufferPtr>(), 2);
// 配置属性
DrmLayer::LayerProperties OverLayerProps{
    // NV12
    .plane_id_   = usableOverlayPlaneIds[0],  
    .crtc_id_    = dev->crtc_id,

    // 源图像区域
    // src_* 使用左移 16
    .srcX_       = fx(0),
    .srcY_       = fx(0),
    .srcwidth_   = fx(autoWidth),
    .srcheight_  = fx(autoHeight),
    // 显示图像区域
    // crtc_* 不使用左移
    .crtcX_      = 0,
    .crtcY_      = 0,
    // 自动缩放
    .crtcwidth_  = dev->width,
    .crtcheight_ = dev->height,
    .zOrder_	 = 0
};
// 配置属性
DrmLayer::LayerProperties PrimaryLayerProps = OverLayerProps;
PrimaryLayerProps.plane_id_ = usablePrimaryPlaneIds[0];
PrimaryLayerProps.zOrder_   = 1;
// 初始化layer
primaryLayer->setProperty(PrimaryLayerProps);
overLayer->setProperty(OverLayerProps);

// 将layer添加到合成器
std::unique_ptr<PlanesCompositor> compositor = std::move(PlanesCompositor::create());
compositor->addLayer(primaryLayer);
compositor->addLayer(overLayer);
std::cout << "Layer initialized.\n"; 

场景 2: 处理热插拔事件

DrmDev::fd_ptr = DeviceController::create("/dev/dri/card0");

// 热插拔回调会自动触发
// 在回调中:
// 1. 释放旧的 plane 和 framebuffer
// 2. 刷新资源
DrmDev::fd_ptr->registerResourceCallback(
    []() {
        // 释放资源
        printf("Pre-refresh: Release resources\n");
    },
    []() {
        // 重新获取资源
        printf("Post-refresh: Reacquire resources\n");
    }
);

// 3. 重新创建 plane 和 framebuffer
// 4. 重新绑定回调

注意事项

  1. 单例模式: 全局只允许一个 DeviceController 实例
  2. 热插拔延迟: 热插拔事件处理有 600ms 延迟
  3. 回调线程: 回调在热插拔线程中调用,注意线程安全
  4. CRTC 管理: 防止重复分配 CRTC,使用 crtcStatu 跟踪
  5. 资源刷新: 热插拔后必须调用 refreshAllDevices()refreshPlane()
  6. 平面查询: getPossiblePlane() 会检查格式和 CRTC 支持
  7. 线程安全: 使用多个 mutex 保护不同资源

相关文档

主页

API 文档

DMA 模块

DRM 模块

NET 模块

V4L2 模块

V4L2Param 模块

RGA 模块

MPP 模块

Sys 模块

Mouse 模块

Utils 模块

Clone this wiki locally