📝 前言
在嵌入式显示系统开发中,HDMI 休眠唤醒速度直接影响用户体验。本文记录了在 RK3588 平台上,将 HDMI 唤醒时间从 6 秒优化到 100 毫秒的完整过程,性能提升达到 98%。
适用平台:
- 芯片:RK3588
- 系统:Android 11
- 内核:Linux 5.10
关键词:RK3588 DRM DPMS HDMI优化 显示性能
🎯 问题描述
原始问题
在项目中使用 sysfs 的 status 节点控制 HDMI 显示器的休眠和唤醒:
# 休眠
echo off > /sys/devices/platform/display-subsystem/drm/card0/card0-HDMI-A-1/status
# 唤醒
echo detect > /sys/devices/platform/display-subsystem/drm/card0/card0-HDMI-A-1/status
遇到的性能问题:
- 🐌 休眠耗时:约 3 秒
- 🐌 唤醒耗时:约 6-8 秒
- 😫 用户体验极差,屏幕长时间黑屏
实测日志分析
休眠流程:
15:04:50.170 [hw_output] 写入 status off
15:04:50.290 [hw_output] 检测到断开事件 (+120ms)
15:04:53.039 [hwc-drm] 开始处理断开 (+2749ms) ← 瓶颈
15:04:53.123 [DrmDevice] PowerDown 完成 (+84ms)
总耗时: ~3秒
唤醒流程:
15:27:42.809 按下电源键
15:27:49.150 [hwc-drm] 收到热插拔事件 (+6341ms) ← 瓶颈
15:27:50.600 发送 hotplug 到 SurfaceFlinger (+1450ms)
15:27:51.175 VOP2 初始化完成 (+575ms)
总耗时: ~8秒
瓶颈定位:大量时间消耗在 EDID 读取和模式探测(fill_modes)。
🔍 深入分析
原有实现机制
查看内核代码 drivers/gpu/drm/drm_sysfs.c 中的 status_store 函数:
static ssize_t status_store(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
// ...
if (old_force != connector->force || !connector->force) {
// 问题:每次都执行完整的模式探测,包括 EDID 读取
connector->funcs->fill_modes(connector,
dev->mode_config.max_width,
dev->mode_config.max_height);
}
// ...
}
性能瓶颈分析
- EDID 读取慢:通过 I2C 读取显示器 EDID 信息需要 1-2 秒
- 模式探测慢:解析 EDID 并构建支持的显示模式列表
- 热插拔流程重:触发完整的 HWC 显示重初始化
- 无缓存机制:每次唤醒都重新读取 EDID,即使显示器未改变
💡 优化方案
方案 1:Status 节点智能缓存
核心思想:利用模式缓存,跳过不必要的 EDID 读取。
实现逻辑:
- 休眠时:跳过
fill_modes,直接断开 - 唤醒时:
- 检查是否有缓存的模式列表
- 有缓存 → 直接使用,跳过 EDID 读取
- 无缓存 → 执行完整探测(首次或更换显示器)
方案 2:DPMS 快速电源控制(推荐)⭐
核心思想:使用硬件级 DPMS(Display Power Management Signaling)直接控制电源。
关键优势:
- ✅ 硬件级控制:直接操作 VOP2 寄存器
- ✅ 不触发热插拔:保持连接状态
- ✅ 无需 EDID:完全跳过模式探测
- ✅ 响应极快:<100ms
🔧 代码实现
一、内核层优化
文件:drivers/gpu/drm/drm_sysfs.c
1.1 优化 status 节点(智能缓存)
static ssize_t status_store(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct drm_connector *connector = to_drm_connector(device);
struct drm_device *dev = connector->dev;
enum drm_connector_force old_force;
int ret;
ret = mutex_lock_interruptible(&dev->mode_config.mutex);
if (ret)
return ret;
old_force = connector->force;
// 解析命令
if (sysfs_streq(buf, "detect"))
connector->force = 0;
else if (sysfs_streq(buf, "on"))
connector->force = DRM_FORCE_ON;
else if (sysfs_streq(buf, "off"))
connector->force = DRM_FORCE_OFF;
else
ret = -EINVAL;
if (old_force != connector->force || !connector->force) {
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] force updated from %d to %d\n",
connector->base.id, connector->name,
old_force, connector->force);
if (connector->force == DRM_FORCE_OFF) {
/* 优化点1:休眠时跳过 fill_modes */
connector->status = connector_status_disconnected;
drm_sysfs_hotplug_event(dev);
} else {
/* 优化点2:唤醒时使用缓存 */
bool need_full_detect = false;
// 检查是否有缓存的模式
if (list_empty(&connector->modes)) {
// 无缓存,需要完整探测
need_full_detect = true;
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] No cached modes, doing full detection\n",
connector->base.id, connector->name);
} else if (old_force == DRM_FORCE_OFF) {
// 从休眠唤醒,使用缓存
connector->status = connector_status_connected;
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] Fast resume with cached modes\n",
connector->base.id, connector->name);
} else {
// detect 命令强制完整探测
need_full_detect = !connector->force;
}
if (need_full_detect) {
// 只在必要时进行完整探测
connector->funcs->fill_modes(connector,
dev->mode_config.max_width,
dev->mode_config.max_height);
}
// 总是发送热插拔事件

最低0.47元/天 解锁文章
174

被折叠的 条评论
为什么被折叠?



