现象
- 工位单卡(780M 集显)、合盖、Type-C 菊花链 3× 2560x1440@60:第三台点不亮(前两台正常)。
- 把第三台改成 1920x1080@60 → 能亮(最关键的旁证)。
- eGPU(4060) 未接入,只有核显这一条链路。
根因:DP 链路带宽不够(不是驱动/线材/显示器故障)
实测链路与带宽账:
| 项 | 实测值 | 说明 |
|---|---|---|
| 链路速率 | 4 lane @ 0x14 = HBR2 | 总 21.6 Gbit/s(hub 只报 HBR2,无 HBR3) |
| DSC | DSC_Sink_Support: no | sink/hub 不支持 DSC,无法压缩绕过带宽 |
| 3×1440p60 @8bpc | 21.73 Gbit/s | 101% → 超了,第三台不亮 |
| 3×1440p60 @6bpc | 16.30 Gbit/s | 75% → 三台全亮(已实测) |
| 3×1080p60 @8bpc | 13.37 Gbit/s | 62% → 能亮(所以改 1080p 就好) |
带宽算式:pixel_clock × bpc × 1.25(8b/10b 编码开销)。2560x1440@60 = 241.5 MHz;1920x1080@60 = 148.5 MHz。
定位方法(可复用)
- 看哪些口亮着:
/sys/class/drm/card*-*/status与enabled;X 侧名用xrandr --query(DRM 名≠X 名,例:DRM DP-10 = X DisplayPort-9)。 - 读链路速率:
/sys/kernel/debug/dri/<PCI>/DP-*/link_settings→Current: 4 0x14 ...(0x14 = HBR2 = 5.4G/lane;0x1e = HBR3 = 8.1G/lane)。 - 读色深:同目录
max_bpc(上限,可写)与output_bpc(sink 报的最大值,只读)。 - 判断是否菊花链节点:
is_mst_connector→no(直连/eDP) /root/branch/end(链末端的屏就是 end)。 - DSC 能力:
dp_dsc_fec_support(sink 报 no 就别指望 DSC)。 - 内核日志:
dmesg | grep -i DM_MST→DM_MST: DP12, 4-lane link detected说明链路训练正常,瓶颈在带宽分配。
修复:把 MST 下游连接器色深上限降到 6bpc
amdgpu 的 X 属性 max bpc 被内核钉在 8..16(xrandr 改不到 6),只能写 debugfs;且该 X 属性只是 debugfs 值的只读镜像(xrandr --set 无效,无需同步)。
# 手动(需 root;写入形式必须是 0x6 且带换行)
echo 0x6 > /sys/kernel/debug/dri/0000:65:00.0/DP-10/max_bpc
cat /sys/kernel/debug/dri/0000:65:00.0/DP-10/max_bpc # 读回应为 6
# 再让它重排一次(X 侧用 kscreen-doctor;xrandr 改布局会被 KScreen 撤销)
kscreen-doctor output.DisplayPort-9.mode.2560x1440@60
写入格式边界(实测):printf '0x6'(无换行)被内核拒(Invalid argument);printf '0x06' 也被拒;echo 0x6 / printf '0x6\n' 可以。
固化进 NixOS(重启与热插拔自动生效)
模块 /etc/nixos/modules/display-bpc.nix,开关写在 configuration.nix:
local.display.mstBpc = { enable = true; maxBpc = 6; };
钩子(①–③ 在 display-bpc.nix;④ 在 display-resume-wake.nix,均已实测生效):
- ①
systemd.services.amdgpu-mst-bpc:开机时(早于 display-manager)写 debugfs;带--wait 15重试(开机那一刻 MST 拓扑常常还没建起来,会静默wrote 0)。实测 gen46 开机:wrote 0x6 to 3 MST connector(s) after 2s✓ - ②
services.udev.extraRules:DRMadd|change(热插拔 / 链路变化 / 唤醒时连接器重建)时重放;原为只匹配change。实测:一次 modeset 后 5 个 MST 连接器的 max_bpc 仍全为 6 ✓ - ③
powerManagement.resumeCommands:睡前/唤醒重放,--wait 30。实测二次唤醒都是wrote 0x6 to 15 MST connector(s) after 1s✓ - ④
display-resume-wake.nix:治“链路正常但屏黑”。最终形态 = 系统级 resume 钩子display-refresh-nudge投递到用户会话的systemd.user.services.display-refresh,在会话里跑xset dpms force off → 等 gapSeconds → force on。二次迭代均已实测(gen46,见下节)。它不是 sysfs ——/sys/class/drm/*/dpms是只读属性,写不进去。
判据用 is_mst_connector 动态挑选(不写死连接器名):只处理值不为 no 的节点(root/branch/end);直连屏与 eDP 不受影响。
验证流程(实测通过):三个连接器写回 8 → systemctl restart amdgpu-mst-bpc → 全部回到 6;服务日志 wrote 0x6 to 15 MST connector(s)(15 = 5 个 MST 连接器 × 3 个 debugfs 别名目录)。
注意:RemainAfterExit=true 的 oneshot 用 systemctl start 是空操作,重放必须 restart(曾因此误判机制失效)。
结果
- 三台 2560x1440@60 全亮(
Screen 0: 7680x1440),MST 色深上限 6。 - 代价:色彩精度 8bpc → 6bpc(Win10 时代亦是此做法)。
- 可选:主屏留 8bpc、另两台 6bpc(5.43×2 + 7.24 = 18.10 < 21.6 仍可);要更高色彩可再调
maxBpc,或试force_yuv420_output(降色度采样,不推荐)。
2026-10-09 事故:睡眠唤醒后外接屏全黑 / 第三屏不亮 / 竖带错乱
现象(按发生顺序)
- 前一晚合盖 + 三台外接屏正常工作 → 按睡眠快捷键 → 三屏休眠。
- 次日键鼠唤醒:外接屏不亮 → 开盖、内屏解锁登录后仍不亮 → 拔插 Type-C 才亮。
- 再次合盖:只出 2 屏,第三屏不亮。
- 很快合盖下那 2 屏也黑了 → 开盖仍不亮 → 再拔插 Type-C → 内屏 + 2 外屏亮、第三屏仍不亮,且有个外屏内容竖带顺序错乱(此前内屏出过同类现象)。
证据(本次开机日志 + 运行时实测)
| 时刻 | 事件 |
|---|---|
| 10-08 23:49:06 | amdgpu-mst-bpc: wrote 0x6 to 0 MST connector(s) ← 开机那次一个连接器都没找到,静默通过 |
| 10-08 23:51:15 | PM: suspend entry (s2idle) ← 按下睡眠 |
| 10-09 10:02:13 | PM: suspend exit ← 唤醒;此后没有任何 bpc 钩子跑过(服务 RemainAfterExit 不重跑;udev 规则只匹配 change) |
| 10-09 10:03:03 → 10:03:27 | DM_MST: stopping TM → starting TM + DP12, 4-lane link detected ← 拔插 Type-C 后 MST 才重建 |
| 每次唤醒 | [drm] DM_MST: Differing MST start on aconnector ← 内核察觉拓扑不一致 |
| 10-09 10:05 前后 | plasmashell 一串 QML 错误 + plasma-kscreen-osd 耗 40s + WaitForName: Service was not registered within timeout ← KScreen 当时在挣扎 |
| 运行时实测 | debugfs:DP-2 / DP-8 / DP-11 / DP-12 / DP-13 全部 max_bpc=6(插拔触发的 udev 补上的,说明当时已不是带宽问题) |
三个独立故障与根因
- 唤醒后全黑、必须拔插:唤醒时 MST 链路在 6bpc 生效之前就以 8bpc 重训练 → 3×2K60@8bpc = 101% 超带宽 → 训练失败 → 全黑。唤醒路径此前没有任何 bpc 钩子(旧 udev 规则只匹配
change;而睡眠唤醒时内核是把连接器当新对象add回来的)。 - 第三屏不亮(当时状态):不是带宽,而是 X 的连接器表和内核错位——内核说 connected =
DP-8 / DP-12 / DP-13(+eDP),X 只知道DisplayPort-7 / -11 / -12→ 物理上连着的第三屏在 X 里根本不存在,而 X 里那两个输出的几何还重叠。 - 竖带顺序错乱:X 布局里
DisplayPort-11在 (0,0)、宽 2560,而DisplayPort-7在 (+1440,0) → 重叠 1120px;内屏 eDP 也与 DP-11 同在 (0,0)。即「拓扑变了、KScreen 拿旧配置套到新编号上」。
观察(未处理):当时内屏为 1440x900(mode 102),原生 2560x1600(mode 95)可选 —— 内屏分辨率不属于本故障范围,未改动。
修复(已落盘 /etc/nixos/modules/display-bpc.nix)
- udev 规则
change→add|change(唤醒时连接器是当新对象 add 回来的)。 - 新增钩子 ③
powerManagement.resumeCommands→ 唤醒后重放,--wait 30。 - 开机钩子 ① 改
--wait 15,找不到连接器时打印WARN ... NOT applied(不再静默)。 - 脚本新增
--wait N语义:最多 N 次、每次间隔 1s,直到出现 MST 连接器为止;udev 路径不带参数(RUN 会阻塞事件队列,必须快)。 - 备份:
/etc/nixos/backup/display-bpc.nix.bak-20261009-101747。
恢复与验证
- 故障 2/3 不是 nix 能修的:X 连接器表错位只能重开会话(注销重登 / 重启),因为 X 不会中途重新枚举连接器。
- 布局乱时:
kscreen-doctor -o读输出名 →kscreen-doctor output.<名>.enable output.<名>.mode.2560x1440排位(别用 xrandr,会被 KScreen 撤销)。 - 验证序列:
rebuild→ 注销重登或重启 →sudo cat /sys/kernel/debug/dri/*/DP-*/max_bpc应全为0x6→ 三屏全亮 → 合盖睡眠再唤醒(不拔插)看三屏是否自动回来。 - 复现时取证:
journalctl -b -k | grep -iE "DM_MST|link training",以及 debugfs 的max_bpc/is_mst_connector/link_settings。
后续(同日下午):第二个病根定案 + 第 4 个钩子
新现象:rebuild 重启后再做「开盖→睡眠→解锁」,显示设置里四块屏全在且布局正常,但肉眼看只有内屏亮、三块外屏全黑(这次不用拔插也能从显示设置确认输出都在)。同时:唤醒时 Lenovo TrackPoint 无线键盘无法操作解锁,按笔记本键盘才激活并输入密码。
逐层实测 —— 每一层都健康:
| 层 | 实测 | 结论 |
|---|---|---|
| 链路训练 | DP-7/8/9/10 Current: 4 lane 0x14(HBR2) | 在跑 |
| MST 拓扑 | root / branch / end 节点齐全 | 正常 |
| 色深 | 全部 max_bpc=6;唤醒日志 wrote 0x6 to 15 MST connector(s) after 1s | 钩子③ 生效 |
| 内核 DPMS | DP-7/9/10 + eDP 全 On | 正常 |
| X 侧 | Monitor is On;四个输出全 enabled;布局无重叠(Screen 7680x2340) | 正常 |
| 开机钩子 | wrote 0x6 to 3 MST connector(s) after 2s(此前是 0) | 钩子① 重试生效 |
定案(第 2 个独立病根):问题在 sink 侧,不在主机侧。s2idle 睡眠不切断 DP 链路(唤醒后链路仍是 HBR2 在跑、DPMS=On),所以显示器整个睡眠过程没看到任何一次 HPD 或 DPMS 跳变,于是自己停在 standby 不自醒 —— 同一晚「插拔 Type-C 就亮」也是同一道理(插拔制造了 HPD)。
验证:对三个输出做一次 X 侧 disable→enable 后,三屏立即全部恢复(已实测;代价是 KScreen 借机把三块屏全排到 (0,0)、Screen 掉成 1440x900,用 kscreen-doctor 按 0 / 2560 / 5120 + eDP 1727,1440 排回即可)。
后续补测:xset dpms force off → on 同样能点亮三屏,且 X 几何前后完全一致(不搬布局) → 采纳它作为第 4 钩子的动作。
第 4 个钩子:display-resume-wake.nix(两次迭代,最终走 X 路径)
第一次实现(sysfs,已否证 ✗):想在系统级 resume 钩子里写 /sys/class/drm/<connector>/dpms 做 Off→On。
- 实测
-r--r--r-- root:root—— 那是只读属性(内核没给 store 处理,连 root 都写不进去),写入必然EACCES。 - 2026-10-09 12:19 那次就是这样:脚本用
set -eu在第一个失败上夭折 → 已被写成 Off 的输出没能写回 On →sleep-actions.service被弄成 FAILURE,外屏照旧黑。 - 教训:sysfs 属性要先
stat看权限再依赖;钩子脚本必须用含失败用例的演练验证,parse/eval 通过≠能跑(那两种验证当初都过了 ✗)。
第二次实现(X 路径):
- 动作:图形会话里
xset dpms force off; sleep 3; xset dpms force on→ 三屏立即恢复、布局不动。 - 架构(resume 跑在系统级,那里看不到 X,所以分两段):①
powerManagement.resumeCommands→display-refresh-nudge遍历/etc/passwd中 uid∈[1000,30000) 的真人用户(动态推导,不写死用户名),systemctl --user -M <user>@.host start --no-block display-refresh.service;没有图形会话的用户失败 → 打印真实原因但不判死(属预期情形)。②systemd.user.services.display-refresh→ 在用户会话里跑(自带 DISPLAY/XAUTHORITY):settleSeconds落定 → 等 X 可连(≤xWaitSeconds)→force off→gapSeconds→force on。 - 开关:
local.display.resumeWake = { enable = true; gapSeconds = 3; settleSeconds = 5; };(另有xWaitSeconds=10、uid 区间[minUid,maxUid)= [1000,30000)) - 已实测通过(2026-10-09 12:37 / 12:38 两次睡眠,世代 gen46):每次唤醒都干净跑完 ——
display-refresh-nudge: queued for cat / queued=1 no-session=none(系统钩子找到会话)
→amdgpu-mst-bpc: wrote 0x6 to 15 MST connector(s) after 1s(钩子③)
→display-refresh: cycled DPMS off/on (gap=3s, waited 0s for X)(会话里的 user service)
三块外屏自动回来,全程无拔插、无人工命令;settle=5s证明合适(waited 0s= X 已就绪)。取证:journalctl -b | grep -E "display-refresh|amdgpu-mst-bpc" - 其他验证:真实配置 eval
enable=true gap=3 settle=5 xWait=10 uid=[1000,30000);user service 与 resumeCommands 同时存在;两脚本bash -n通过;nudge 演练只挑到cat(root / nixbld / systemd 网络账号都被正确跳过)。 - 备份:
display-resume-wake.nix.bak-v1-*/.bak-v2-sysfs-*、configuration.nix.bak-resumewake-v3-20261009-122800。
手动救急(已实测有效,别拔插):
xset dpms force off; sleep 3; xset dpms force on # 全局黑 3 秒,不动布局(实测三屏立即恢复)
不要用 kscreen-doctor output.X.disable/enable 当日常救急手段 —— 实测会把所有输出排到 (0,0) 打乱布局(要用它就得再按 0 / 2560 / 5120 + eDP 1727,1440 手动排回)。
同时发现的独立问题:唤醒时无线键盘不能解锁
- 键盘是 USB 设备(
17ef:60ee Lenovo TrackPoint Keyboard II,挂在usb7= PCI0000:67:00.4,即底座/Type-C 那条链),不是蓝牙。 - 唤醒瞬间内核在重置 USB:
usb 3-1.1: reset/usb 1-4: reset/usb 1-3: reset/usb 3-1.3: new full-speed USB device。 - X 侧报:
(EE) event5 - Lenovo TrackPoint Keyboard II: client bug: event processing lagging behind by 2715ms← 键盘事件被延迟 2.7 秒。 - 内屏键盘是
AT Translated Set 2(PS/2 通路,不经 USB)→ 所以它立刻可用,完全解释了现象。 - 锁屏 PAM 噪声来源:
pam_unix(kde-fingerprint:auth): unexpected response from failed conversation function+conversation failed(指纹模块在锁屏 PAM 里的会话处理不干净)。 - 已处理(同日,选①):锁屏 PAM 去掉指纹 ——
security.pam.services.kde.fprintAuth = lib.mkForce false;(改在hardware-configuration.nix,原为true;备份hardware-configuration.nix.bak-fprint-20261009-111320)。 - SDDM 登录界面的指纹保留:
security.pam.services.sddm.fprintAuth未动;pam.d/sddm走auth substack login,而pam.d/login里 fprintd 那一行仍在(开机登录仍可刷指纹,且那时没有 USB 重枚举风暴)。 - 验证(真实配置 eval):渲染出的
pam.d/kde已无 fprintd 行;pam.d/login仍有 1 行。 - 仍未动:键盘自身的 USB 重枚举 + 2.7s 事件滞后(属另一个问题,当前对策是唤醒后等 2–3 秒再敲)。
- 另一个观察(同日上午):解锁界面直接输密码是有效的,只是画面不刷新(要动一下/点一下才会看到)—— 与 sink 不自醒同一类「画面不重绘」现象;结合上条键盘滞后,判断为输入事件迟到而非焦点丢失。
结论
- 唤醒已不再需要任何人工干预(两次实测全自动恢复:拔插 ✗ 、手敲 DPMS ✗)。
- 若日后仍偶发屏黑,先看
journalctl -b | grep display-refresh有没有那一行:没有 = 投递失败(查 user manager 与 uid 区间);有 = 时机不对(调settleSeconds)或 sink 侧还有别的保持条件(再取link_settings与dpms快照比对)。
复刻到别的机器
- 拷
/etc/nixos/modules/→imports = [ ./modules ];→ 打开local.display.mstBpc与local.display.resumeWake→nixos-rebuild switch。 - 模块自带脚本(writeShellScriptBin 内联),零外部依赖;面板工具与画像/快照在
/etc/nixos/scripts/,手册见plasma-panel-cmds.txt。
自动 bpc 与手动 bpc 的协作(2026-10-23,gpu bpc)
自动机制(本篇上面的开机/udev/唤醒三个钩子)原来是“无条件全量写 6”,和手动值是互斥的:任何一次 udev 事件、睡眠唤醒、重启都会把手设的值吃掉。2026-10-23 起改为协作:gpu bpc 命令和自动脚本读同一份 pin 记录。
- 手动侧:
sudo gpu bpc <卡> <显示器> <6|8|10|12>—— 写 debugfsmax_bpc+ 自动重设同分辨率模式(modeset 会重读镜像,不重设不生效)+ 回读确认,三步一步做完,约闪屏 1 秒。gpu bpc pins查看,gpu bpc clear清除(当前流不变)。 - pin 落点:
/var/lib/amdgpu-bpc/<EDID稳定id>,内容就是6/8/10/12其中一个数字。id 形如LEN_61F0_944134476(厂商_产品_序列号),跨连接器重编号稳定;双胞胎同 EDID(含序列号都一样)时共用一个 pin —— 值本来就该一样,不算问题。 - 自动侧:amdgpu-mst-bpc 脚本按连接器查 pin —— 有 pin 按 pin 写、无 pin 写默认 6。开机、udev
add|change、睡眠唤醒后重放的都是你的 pin 值。日志判据:default 0x6, pins honored。同一个 EDID 稳定 id 算法两边同源(gpu-mode.sh与 bpc 模块各一份,改一处必须同步另一处)。 - 关系:自动机制不需要关;pin 文件本身跨重启(/var/lib),改值零 rebuild、零世代 —— 和
gpu pass/host的状态文件同一哲学。真机往返实测(2026-10-23,三屏 2K 菊花链):bpc 0 DisplayPort-8 8写 8 →bpc 0 DisplayPort-8 6触发 modeset,回读bpc 上限 8 → 6,三屏全程有画面。 - 边界:NVIDIA 卡带的屏报不支持(无对等 knob);纯 TTY 下改不了(要重设模式,必须有 X);值只能 6|8|10|12,其它一律拒绝;显示器 sink 上限更低时以回读为准。实现与判据另见 gpu 笔记(
v6LU0gqKzY4E「gpu bpc」节)与台账docs/gpu-bpc-subcommand.md。