gpu:显卡模式切换

一句话

gpu 是 max2 上管理「显卡归宿主还是归虚拟机」的全局命令。它只写状态文件,重启后由开机服务落实驱动绑定 —— 因为显卡驱动在开机时就被图形栈拿走了,运行期不可能热切换。

命令速查

命令作用何时生效
gpu打印帮助—
gpu list列出显示类设备(含同 IOMMU 组设备),每块卡下列出它的显示器只读
gpu pass <卡>把该卡及其同组设备标记为「直通」重启后
gpu host <卡>把该卡及其同组设备标记为「宿主」重启后
gpu apply按状态文件落实绑定(开机由服务调用,平时不用手跑)立即
gpu bpc <卡> <显示器> <值>运行时改该屏色深上限并立即生效(值=6|8|10|12;只支持 AMD 卡)立即
gpu bpc pins / gpu bpc clear <卡> <显示器>列出全部手动 pin / 清除该屏的 pin(回到自动默认 6)立即(clear 只删记录,当前流不变)

<卡> 的写法:BDF(01:00.0 或 0000:01:00.0),或 gpu list 里显示类设备的序号(0 起)。

<显示器> 的写法:X 输出名(如 DisplayPort-8,见 list 下挂行),或内核连接器名(DP-8),或 EDID 名片段(如 T24h;命中多台时必须用 X 输出名精确指定)。

gpu list 的列

#  类型  设备                         PCI 位置      IOMMU 组  当前驱动  期望  一致
0  VGA   AMD Radeon 780M · 引导显卡  0000:65:00.0  18        amdgpu    宿主  ✓
  • 序号:就是 gpu pass/host 的入参(0 起)。序号会随硬件在位情况变化 —— 没插 eGPU 时 0 号就是 780M,插上后 4060 通常排前面 ⇒ 脚本里禁止写死序号。
  • 设备:由 udev 的 pci.ids 动态派生;引导显卡加 · 引导显卡 后缀;同 IOMMU 组的音频等功能行用 └ 缩进。
  • 当前驱动 / 期望 / 一致:一致 为 ✓ 表示已经是期望状态;待重启 表示状态文件已改、但驱动还没换(要重启)。
  • pci.ids 只有代号、没有营销名时,在 /etc/gpu-mode-names.conf 按 vendor:device = 展示名 补一行即可。

list 下挂的显示器行

显示器(AMD Radeon 780M · 0000:65:00.0):
  └ DisplayPort-9[LEN T24h-20]2560x1440@59.95 bpc上限6
  └ DisplayPort-8[T24h-10]2560x1440@59.95 bpc上限6
  └ DisplayPort-6[T24h-10]2560x1440@59.95 bpc上限6
  └ eDP[GPD1001H]?@? bpc上限16
  • 每行 = X 输出名[EDID 名]分辨率@刷新率 bpc上限。bpc 列是 X 的 max bpc 属性(起流时取的就是这个上限,≈实际色深)。
  • X 输出名和内核连接器名编号对不上(DisplayPort-8 vs card1-DP-9)—— 脚本内部用 EDID 字节比对跨过去,显示给你的是 X 名(重设模式/截图时用的就是它)。
  • 内屏 eDP 未启用时显示 ?@?(无模式),这是正常的,只表示当前没点亮。
  • X 没起(纯 TTY)时降级显示内核信息:内核:DP-10[LEN T24h-20]已连接(图形会话未运行,无模式信息),不编刷新率。
  • 双胞胎同型号屏 EDID 完全一样时按顺序认领(第一台拿第一个 X 名),两台的值永远一致,不会串。

gpu bpc(运行时改色深,立即生效)

起因:三屏 2K 菊花链在 8bpc 下超 HBR2 带宽黑屏,必须 6bpc;而 output_bpc 在 6.18 内核只读、X 的 max bpc 属性范围钉死 8..16 —— 唯一能把 6 写进去的口子是 debugfs 的 max_bpc,且写完必须重设一次同分辨率模式(modeset 会重读镜像,不重设就不生效)。这条命令把「写 + 重设 + 回读确认」做成一步。

命令作用
sudo gpu bpc 0 DisplayPort-8 6把该屏色深上限改为 6 并立即生效(自动重设同分辨率模式,约闪屏 1 秒)
sudo gpu bpc clear 0 DisplayPort-8清除该屏的手动 pin(当前流不变,下次事件后回到自动默认 6)
gpu bpc pins列出全部手动 pin(不需要 sudo)

真机往返实测(2026-10-23,三屏 2K 菊花链):bpc 0 DisplayPort-8 8 写 8(仅落盘,未重设);bpc 0 DisplayPort-8 6 触发 modeset,回读 bpc 上限 8 → 6,三屏全程有画面。

  • pin 落点:/var/lib/amdgpu-bpc/<EDID稳定id>,内容就是 6 / 8 / 10 / 12 其中一个数字。id 形如 LEN_61F0_944134476(厂商_产品_序列号),跨连接器重编号稳定;双胞胎同 EDID(含序列号都一样)时共用一个 pin —— 值本来就该一样,不算问题。
  • 和自动 bpc 机制不打架:amdgpu-mst-bpc(开机/udev/唤醒三钩)改成了「有 pin 按 pin 写、无 pin 写默认 6」。udev 事件、睡眠唤醒、重启后重放的是你的 pin 值,手动值不会被吃掉;关自动机制不需要,pin 文件本身跨重启(/var/lib)。
  • 判据:命令末行回读 bpc 上限 旧值 → 新值;gpu bpc pins 见到记录;自动脚本日志 default 0x6, pins honored。
  • 边界:NVIDIA 卡带的屏报不支持(NVIDIA 无对等 knob);纯 TTY 下改不了(要重设模式,必须有 X);值只能是 6|8|10|12,其它一律拒绝;显示器 sink 上限更低时以回读为准(命令会明确提示)。

机制:为什么不能热切换

  • 不用内核参数(vfio-pci.ids=…):那会把开关焊进「配置基线」,改一次就得改配置重建。改用每设备自带的 driver_override 接口 —— 启动期按状态文件决定绑谁。
  • 时机卡在图形会话之前:gpu-mode-apply.service(oneshot,wantedBy=multi-user.target、before=display-manager.service、after=local-fs.target)。晚一步图形栈就把显卡拿走了。
  • 直通必须整组:显卡和它的音频功能同属一个 IOMMU 组,只切一半会被虚拟机拒绝。
  • 改状态不产生 nix 世代:状态写在 /var/lib/gpu-mode/,不属于声明式配置。

数据落点

/var/lib/gpu-mode/<BDF>.mode

vendor=0x10de        # 身份字段:防 BDF 漂移后误操作别的设备
device=0x2882
mode=passthrough     # host | passthrough

三个字段都要有。apply 时会比对 vendor/device:不符就跳过该设备(典型场景:没接 eGPU,那个 BDF 上换成了别的设备)。

怎么确认生效(判据)

gpu list                       # 期望列=直通、一致列=✓ 即已落实
basename $(readlink -f /sys/bus/pci/devices/0000:01:00.0/driver)   # 期望 vfio-pci
nvidia-smi                     # 直通后应报 Failed to initialize NVML(预期,不是故障)
ls /sys/class/drm/             # 4060 的 card 消失,只剩 780M
journalctl -u gpu-mode-apply -b | tail

apply 的日志会逐设备打印 <BDF> <旧驱动> → vfio-pci(直通)或交回内核探测(宿主)。

本机现状(2026-10-19,办公室没接 eGPU)

项实况
gpu list只有一行:0 VGA AMD Radeon 780M · 引导显卡 0000:65:00.0 组18 amdgpu 宿主 ✓,结论行「全部设备已处于期望模式」
状态文件仍是 4060 的 mode=passthrough(vendor=0x10de device=0x2882)—— 留着不动,回家插上 eGPU 重启即按直通生效
本次开机 apply⚠ 状态文件身份不符(0000:01:00.0 现在是 0x1c5c:0x174a,记录是 0x10de:0x2882)—— 跳过;0000:01:00.1 不在总线上 —— 跳过;服务状态 Finished

2026-10-19 修掉的坑:身份不符原本会 rc=1,导致没接 eGPU 时每次 nixos-rebuild 都被 gpu-mode-apply 的失败中断。现在改为「跳过、不置错」—— 设备不在位属预期状态,不是失败。真正的写失败(driver_override / bind 失败)仍会让服务失败。

坑与注意

  • BDF 会漂移:eGPU 接上时是 0000:01:00.0+01:00.1;没接时同一 BDF 可能是别的设备 ⇒ 靠状态文件里的 vendor/device 兜住,永远别只看数字。
  • 直通后宿主会失去这张卡的输出:显示布局可能变化(尤其那台显示器还在用 4060 那根线时)。
  • 回宿主必须两步:sudo gpu host 0 + 重启。没有「关掉虚拟机就自动回宿主」这回事(详见服务笔记 wK8ClezMHQJh)。
  • 回宿主那条 drivers_probe 分支在本机还没实跑过一次(项目台账记的待办)—— 第一次真回宿主时留意日志里是否出现「交回内核探测失败」。
  • 直通切换后记得重挂 USB 鼠标:kvm usb 5(USB 设备同样不写死在 XML 里)。
  • bpc:output_bpc 在 6.18 内核是只读(写啥都是 EINVAL),别把它当 knob;X 的 max bpc 属性范围钉死 8..16,xrandr --set 写不进 6 —— 必须走 debugfs max_bpc + 重设模式,gpu bpc 已经包好了,手写容易漏第二步。
  • bpc:sudo 下 DISPLAY 会丢 —— 脚本从会话进程的 environ 里找回 DISPLAY/XAUTHORITY;如果报「找不到可用的 X 会话」,先确认图形会话在,再贴 loginctl show-session 查。

出处

  • 实现:/etc/nixos/modules/hardware/gpu-mode.nix + gpu-mode.sh + gpu-mode-names.conf;bpc 自动侧:gpu-amd-780m-mst-bpc.nix(三钩 + pin 优先,pin 目录 /var/lib/amdgpu-bpc/ 由 tmpfiles 建)
  • 台账:hermes-nixos-workspace/docs/gpu-passthrough-mode-switch.md §4(list 改版)/ §5.1(bind 分支首次真机成功)/ §6;docs/egpu-4060-weekend-runbook.md §10.7;bpc 子命令与 pin 机制见 docs/gpu-bpc-subcommand.md