硬件:780M集显MST菊花链3台2K显示器带宽不足

现象

  • 工位单卡(780M 集显)、合盖、Type-C 菊花链 3× 2560x1440@60:第三台点不亮(前两台正常)。
  • 把第三台改成 1920x1080@60 → 能亮(最关键的旁证)。
  • eGPU(4060) 未接入,只有核显这一条链路。

根因:DP 链路带宽不够(不是驱动/线材/显示器故障)

实测链路与带宽账:

项实测值说明
链路速率4 lane @ 0x14 = HBR2总 21.6 Gbit/s(hub 只报 HBR2,无 HBR3)
DSCDSC_Sink_Support: nosink/hub 不支持 DSC,无法压缩绕过带宽
3×1440p60 @8bpc21.73 Gbit/s101% → 超了,第三台不亮
3×1440p60 @6bpc16.30 Gbit/s75% → 三台全亮(已实测)
3×1080p60 @8bpc13.37 Gbit/s62% → 能亮(所以改 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:DRM add|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:06amdgpu-mst-bpc: wrote 0x6 to 0 MST connector(s) ← 开机那次一个连接器都没找到,静默通过
10-08 23:51:15PM: suspend entry (s2idle) ← 按下睡眠
10-09 10:02:13PM: suspend exit ← 唤醒;此后没有任何 bpc 钩子跑过(服务 RemainAfterExit 不重跑;udev 规则只匹配 change)
10-09 10:03:03 → 10:03:27DM_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 补上的,说明当时已不是带宽问题)

三个独立故障与根因

  1. 唤醒后全黑、必须拔插:唤醒时 MST 链路在 6bpc 生效之前就以 8bpc 重训练 → 3×2K60@8bpc = 101% 超带宽 → 训练失败 → 全黑。唤醒路径此前没有任何 bpc 钩子(旧 udev 规则只匹配 change;而睡眠唤醒时内核是把连接器当新对象 add 回来的)。
  2. 第三屏不亮(当时状态):不是带宽,而是 X 的连接器表和内核错位——内核说 connected = DP-8 / DP-12 / DP-13(+eDP),X 只知道 DisplayPort-7 / -11 / -12 → 物理上连着的第三屏在 X 里根本不存在,而 X 里那两个输出的几何还重叠。
  3. 竖带顺序错乱: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钩子③ 生效
内核 DPMSDP-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 = PCI 0000: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> —— 写 debugfs max_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。