+
+
+
+
SPEC.ID // ARTICLE LOG #20261025

Arch Linux 下 NVIDIA HDMI 2.1 睡眠唤醒后 4K 144Hz 掉到 60Hz 的排查与修复

最近被一个奇怪的问题折磨了挺久:我的 Arch Linux + KDE Plasma 6 Wayland 电脑只要进入睡眠再唤醒,外接的 4K 144Hz 显示器刷新率就会瞬间腰斩,不仅掉到 60Hz,而且在系统设置里压根找不到 120Hz/144Hz 的选项,就像它们从未存在过一样。

之前遇到这种情况,我只能重新插拔 HDMI 线或者重启系统。今天想想还是不忍了准备自己解决,经过一番排查,发现这根本不是 KDE Plasma 或 KWin 的锅,而是 NVIDIA 驱动对于 HDMI 2.1 FRL 协商的一个陈年老 Bug。本文记录完整的排查证据链,并给出一套无需重启、无需物理拔插、全自动恢复 4K 144Hz 的 DDC/CI 优雅解决方案。


1. 故障现象与硬件拓扑

诡异的故障表现

  • 系统睡眠唤醒后,外接显示器(我的设备是 SKYWORTH F27G10U,4K 144Hz)最高刷新率锁定在 60Hz。
  • 核心特征:并不是“设置里勾选了 144Hz 但实际掉帧或跑 60Hz”,而是 100Hz / 120Hz / 144Hz 这些高刷模式直接从系统的模式列表里彻底消失了。
  • 重新插拔 HDMI 线或重启电脑后恢复正常,显示器睡眠后再唤醒则几乎必现。
  • 重启显示器无法恢复正常。

硬件环境与输出拓扑

  • 设备:Lenovo 笔记本(Intel Alder Lake-S + NVIDIA RTX 4050 Laptop)
  • 系统/内核:Arch Linux 7.3.0-4-cachyos
  • 显卡驱动:nvidia-open-dkms (580.xx 系列开源内核模块)
  • 桌面环境:KDE Plasma 6.8.1 / Wayland (kwin_wayland)
  • 显示拓扑:
    • 内屏:card2-eDP-1(日常外接时禁用)
    • 外接屏:card1-HDMI-A-1 创维 F27G10U(3840x2160@144Hz)

关键线索:HDMI 接口在笔记本上是直连独显(card1-HDMI-A-1)的,走的是 NVIDIA 专有驱动的 HDMI 2.1 输出路径,排除了核显驱动的问题。


2. 深入排查:高刷模式究竟去哪儿了?

既然高刷选项直接消失了,那到底是 EDID 读崩了、KWin 的缓存出 Bug,还是驱动层把高刷给砍了?

2.1 检查 KScreen 输出与模式表

在唤醒故障发生后,使用命令行工具查看当前的输出模式:

1
kscreen-doctor -o

此时抓到的 HDMI-A-1 模式列表十分耐人寻味:

1
2
3
4
3840x2160@60.00*  3840x2160@60.00  3840x2160@59.94
3840x2160@29.97 3840x2160@25.00
2560x1440@120.00 2560x1440@59.95
1920x1080@119.88 1920x1080@60.00 ...

仔细看这个模式表:4K 下的 100/120/144 全部消失,但是 2K(1440p)@120Hz 和 1080p@120Hz 却完好无损!

结合 HDMI 协议与带宽计算,真相瞬间浮出水面:

分辨率与刷新率 所需带宽 / Pixel Clock 唤醒后状态 依赖的技术
3840x2160@144 1279.86 MHz ❌ 彻底消失 必须 HDMI 2.1 FRL 协议
3840x2160@120 1058.40 MHz ❌ 彻底消失 必须 HDMI 2.1 FRL 协议
3840x2160@100 888.80 MHz ❌ 彻底消失 必须 HDMI 2.1 FRL 协议
3840x2160@60 594.00 MHz ✅ 依然存在 传统 HDMI 2.0 TMDS 足以承载
2560x1440@120 497.76 MHz ✅ 依然存在 传统 HDMI 2.0 TMDS 足以承载
1920x1080@120 < 350 MHz ✅ 依然存在 传统 HDMI 2.0 TMDS 足以承载

👉 结论:显示链路在唤醒后从 HDMI 2.1 的 FRL(Fixed Rate Link)模式掉回了老旧的 TMDS(HDMI 2.0)模式!因此驱动按照 TMDS 的带宽上限把所有超出带宽的 4K 高刷模式全部裁决丢弃了。


2.2 确认不是 EDID 数据损坏

会不会是睡眠唤醒时 I2C 通信丢包导致 EDID 没读全?

用 edid-decode 解析一下驱动导出的原始 EDID:

1
cat /sys/class/drm/card1-HDMI-A-1/edid | edid-decode

输出解析结果非常健康:Base EDID、CTA-861 和 DisplayID 三个 Block 校验和全部一致,DisplayID 里清清楚楚写着:

  • DTD: 3840x2160 100.000 Hz
  • DTD: 3840x2160 120.000 Hz
  • DTD: 3840x2160 143.998650 Hz

这证明:显示器汇报的能力完好无损,纯粹是驱动层面没有激活高刷链路。


2.3 排除 KWin / 桌面环境层的问题

为了确认是不是 KWin 的缓存机制在作祟,直接查 DRM 底层连接器:

1
modetest -c -M nvidia-drm

内核层面汇报的模式列表同样没有 4K144。说明模式在驱动内核层就已经被过滤掉了,与上层的 Wayland 合成器无关。

同时,尝试过以下软件级重置,全部无效:

  • kscreen-doctor output.HDMI-A-1.disable && kscreen-doctor output.HDMI-A-1.enable ❌
  • kscreen-doctor --dpms off && kscreen-doctor --dpms on ❌
  • modetest -c 触发接口重探 ❌

原因在于:NVIDIA 驱动把“当前协商好的链路能力”缓存在 Connector 对象上。单纯的软件 modeset 根本不会触发底层的 HDMI 重新握手,只有收到 HPD(Hot Plug Detect,热插拔中断信号)时,驱动才会重新进行 FRL Link Training。


3. 根因确认:NVIDIA 的已知 Bug

至此,问题定性:NVIDIA Linux 驱动在 Suspend/Resume 唤醒后,未对 HDMI 2.1 FRL 链路重新进行 Link Training,而是直接粗暴退回到了 TMDS 模式。

经过检索,这在 NVIDIA 官方论坛是一个被多位用户确认的已知 Bug:

既然知道了只有 HPD 能触发重新协商,我们难道真的只能每次去拔线或者按物理电源键吗?

当然不用,Linux 下我们有 DDC/CI。


4. 优雅破局:利用 DDC/CI 模拟硬件热插拔

几乎所有现代显示器都支持 DDC/CI 协议,它允许主机通过 I2C 信号控制显示器的菜单设置和电源状态。

既然拔插线缆和按电源键本质上是切断显示器信号引发一次 HPD,我们完全可以通过软件向显示器下发 D6 05(关机) 和 D6 01(开机) 控制码,让显示器原地“重启”一次。

4.1 安装并测试 ddcutil

首先安装命令行工具:

1
sudo pacman -S ddcutil

检测当前外接显示器支持情况:

1
ddcutil detect

正常会检测到类似输出:

1
2
3
4
5
Display 1
I2C bus: /dev/i2c-19
DRM connector: card1-HDMI-A-1
Monitor: SKY:F27G10U:F27G10U09642
VCP version: 2.2

确认电源控制(VCP code D6)可用:

1
ddcutil getvcp D6

4.2 手动急救一行流

当唤醒发现刷新率掉了之后,只需在终端敲入以下指令:

1
2
3
4
5
6
# 1. 关机并等待断开握手,再重新开机触发 HPD
ddcutil setvcp D6 05; sleep 4
ddcutil setvcp D6 01; sleep 8

# 2. 模式恢复后切换回 144Hz
kscreen-doctor output.HDMI-A-1.mode.3840x2160@144

实测一秒见效:显示器息屏 4 秒后重新点亮,NVIDIA 驱动受到 HPD 刺激后重新完成了 FRL 协商,4K100/120/144 全部满血复活!并且 HDR 状态、200% 显示缩放以及窗口布局丝毫不受影响。


5. 优化体验:配置全自动唤醒修复服务

我们可以编写一个检测脚本,并将其挂载到 systemd 的睡眠唤醒 hook 中。

5.1 编写急救脚本 ~/.local/bin/fix-hdmi-refresh

这个脚本会先检查当前是不是已经在 144Hz:如果是,直接静默退出(不闪屏);如果高刷模式丢了,才执行显示器电源重启并套用 144Hz。

创建脚本 ~/.local/bin/fix-hdmi-refresh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#!/usr/bin/env bash
#
# 修复 NVIDIA 驱动已知 Bug:睡眠/显示器休眠唤醒后 HDMI 2.1 FRL 掉至 TMDS (4K60)
# 通过 DDC/CI 重启显示器电源触发 HPD 重新握手
#
set -u

CONNECTOR="${CONNECTOR:-HDMI-A-1}"
MODE="${MODE:-3840x2160@144}"
DRM_CARD="${DRM_CARD:-card1}"

export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
export DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-unix:path=$XDG_RUNTIME_DIR/bus}"

log() { printf '[fix-hdmi-refresh] %s\n' "$*"; }

mode_present() {
modetest -c -M nvidia-drm 2>/dev/null | grep -qE "3840x2160[[:space:]]+144"
}

mode_active() {
kscreen-doctor -o 2>/dev/null | grep -qE "3840x2160@144(\.[0-9]+)?\*"
}

apply_mode() {
kscreen-doctor "output.${CONNECTOR}.enable" \
"output.${CONNECTOR}.mode.${MODE}" >/dev/null 2>&1
sleep 2
kscreen-doctor "output.${CONNECTOR}.mode.${MODE}" >/dev/null 2>&1
}

# 如果已经是 144Hz,直接退出
if [ "${1:-}" != "--force" ] && mode_active; then
log "已经是 144Hz,无需操作"
exit 0
fi

# 如果模式丢失,通过 DDC/CI 制造热插拔事件
if [ "${1:-}" = "--force" ] || ! mode_present; then
disp=$(ddcutil detect --brief 2>/dev/null |
awk -v c="${DRM_CARD}-${CONNECTOR}" '
/^Display [0-9]+/ { d = $2 }
$0 ~ c { print d; exit }')

if [ -z "$disp" ]; then
log "找不到 ${CONNECTOR} 对应的 DDC 显示器,退出"
exit 1
fi

log "向显示器 (Display ${disp}) 发送断电重启信号以重新协商 HDMI 链路..."
ddcutil --display "$disp" --sleep-multiplier .5 setvcp D6 05 >/dev/null 2>&1 || {
log "无法关闭显示器"; exit 1; }

sleep 4

for _ in 1 2 3 4 5; do
ddcutil --display "$disp" --sleep-multiplier .5 setvcp D6 01 >/dev/null 2>&1 && break
sleep 2
done

# 等待驱动重新侦测到 144Hz 模式
for _ in $(seq 1 15); do
mode_present && break
sleep 1
done

if ! mode_present; then
log "显示器已重启,但驱动仍未上报 144Hz 模式"
exit 1
fi
log "4K144 模式已重新恢复!"
fi

apply_mode

if mode_active; then
log "完成:${CONNECTOR} 已成功切换到 ${MODE}"
else
log "模式已恢复,但切换至 ${MODE} 失败,请检查系统设置"
exit 1
fi

赋予执行权限:

1
chmod +x ~/.local/bin/fix-hdmi-refresh

5.2 挂载 systemd sleep hook 实现唤醒自动修复

在 /etc/systemd/system-sleep/ 目录下添加一个唤醒执行钩子。系统睡眠唤醒(post 阶段)时,会自动以当前桌面用户的身份调用上述急救脚本。

创建脚本 /etc/systemd/system-sleep/99-hdmi-frl-refresh.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env bash
#
# systemd sleep hook — 睡眠唤醒后自动修复 HDMI 2.1 高刷丢失
#
set -u

[ "${1:-}" = "post" ] || exit 0

# 请将 USER_NAME 和 USER_UID 修改为您本机的实际登录用户名与 UID
USER_NAME="gh0s7"
USER_UID="1000"
RUNTIME="/run/user/${USER_UID}"

# 等待 GPU 与合成器唤醒就绪
sleep 4

[ -S "${RUNTIME}/bus" ] || exit 0

# 动态获取当前活动 Wayland Socket
WL=""
for s in "${RUNTIME}"/wayland-*; do
case "$s" in *.lock) continue ;; esac
[ -S "$s" ] && { WL="$(basename "$s")"; break; }
done
[ -n "$WL" ] || exit 0

exec runuser -u "$USER_NAME" -- env \
XDG_RUNTIME_DIR="$RUNTIME" \
WAYLAND_DISPLAY="$WL" \
DBUS_SESSION_BUS_ADDRESS="unix:path=${RUNTIME}/bus" \
/home/${USER_NAME}/.local/bin/fix-hdmi-refresh

赋予 root 执行权限:

1
sudo chmod +x /etc/systemd/system-sleep/99-hdmi-frl-refresh.sh

自动化工作流解析

  1. 电脑唤醒后,systemd sleep hook 被触发,先等待 4 秒让驱动与 KWin 重新就绪;
  2. 检测当前是否依然维持 4K144:若显示器没有完全睡死、高刷还在,脚本直接退出,屏幕完全不闪烁;
  3. 若检测到高刷丢失(模式消失),自动发送 DDC 指令重启显示器并重新绑定 144Hz。从唤醒到完全自动搞定只需数秒,再也不用手动折腾!

6. 其他替代思路

转投 DP:如果显示器和拓展坞/显卡支持 DP 1.4,可以改用 DP 线。DisplayPort 不使用 HDMI 的 FRL 机制(通常通过 DSC 压缩上 4K144),完全不受 NVIDIA 该 Bug 的影响。

// HDMI本质一坨啊之前拒绝AMD为开源驱动提供 HDMI 2.1 支持的时候就看出来了,但是手里只有 HDMI 线一直懒得买 DP 线了:(

标签: #Arch Linux #Nvidia #HDMI 2.1 #故障排查 #桌面体验
COPIED // 链接已复制