在部分 Intel MacBook 上运行 Linux 时,内置 eDP 液晶面板可能存在一个较隐蔽的兼容性问题:系统关屏、休眠、重新点亮屏幕或重新初始化显示链路时,面板的断电—重新上电周期过短,可能导致白屏、绿屏、显示异常,甚至需要重新关机才能恢复。

在特定硬件上,通过延长 Intel i915 驱动中的 Panel Power Cycle Delay 可以显著改善这一问题。

本文记录一次 Arch Linux LTS 内核从旧版本升级到新版本后,重新为新内核构建自定义 i915 模块,并将 eDP 面板最低电源周期固定为 2000 ms 的完整维护过程。

重点不在于重新编译整个 Linux 内核,而是:

在保持 Arch 官方内核不变的情况下,仅重新编译带有补丁的 i915 模块,并让它优先于官方 i915 被加载。


一、为什么每次内核升级后都需要重新构建 i915

假设系统原本运行:

6.18.45-2-lts

系统更新后安装:

6.18.49-1-lts

虽然两者都属于 Linux 6.18 LTS,但对于内核模块而言,它们是两个不同的 Kernel Release。

自定义编译的:

i915.ko

并不能简单地从:

6.18.45-2-lts

复制到:

6.18.49-1-lts

原因包括:

  • Kernel ABI 可能发生变化
  • 导出符号可能变化
  • CONFIG_MODVERSIONS 相关 CRC 可能变化
  • Module.symvers 不同
  • vermagic 不同
  • i915 源码本身可能变化

因此,只要目标 Kernel Release 改变,自定义 i915 原则上就应该重新构建。


二、先不要重启

这是整个维护过程最重要的操作原则之一。

完成:

sudo pacman -Syu

以后,如果发现:

linux-lts
linux-lts-headers

已经升级到了新的版本,应当暂时保持当前旧内核继续运行。

例如:

uname -r

仍然显示:

6.18.45-2-lts

而:

pacman -Q linux-lts linux-lts-headers

已经显示:

linux-lts 6.18.49-1
linux-lts-headers 6.18.49-1

这正是理想状态。

此时:

  • 旧内核仍在内存中运行
  • 旧 i915 已经加载
  • 新内核文件已经写入磁盘
  • 可以安全地为新内核构建新的自定义 i915
  • 完成全部准备之后再重启

这样可以避免启动进入尚未修补的新内核。


三、确认目标 Kernel Release

首先确定目标版本:

NEWREL="6.18.49-1-lts"

检查新内核:

pacman -Q linux-lts linux-lts-headers

然后检查:

cat /usr/lib/modules/$NEWREL/build/include/config/kernel.release

输出应精确为:

6.18.49-1-lts

再确认新内核官方 i915:

modinfo -k "$NEWREL" -n i915

通常会得到:

/lib/modules/6.18.49-1-lts/kernel/drivers/gpu/drm/i915/i915.ko.zst

以及:

modinfo -k "$NEWREL" i915 | grep '^vermagic:'

例如:

vermagic: 6.18.49-1-lts SMP preempt mod_unload

四、获取与 Arch 内核包完全对应的源码

不建议仅仅从 kernel.org 下载一个同版本的 vanilla Linux 源码然后直接编译。

更稳妥的方法是使用 Arch Linux 官方的 linux-lts packaging repository。

例如:

git clone https://gitlab.archlinux.org/archlinux/packaging/packages/linux-lts.git
cd linux-lts

拉取 tags:

git fetch --tags --force

检查目标包版本:

git tag -l "6.18.49-1"

切换到精确 tag:

git checkout --detach "6.18.49-1"

检查:

grep -E '^(pkgbase|pkgver|pkgrel)=' PKGBUILD

应类似:

pkgbase=linux-lts
pkgver=6.18.49
pkgrel=1

这样可以保证使用的不是“差不多的 Linux 6.18.49”,而是 Arch 实际用于构建:

linux-lts 6.18.49-1

的源码与补丁集合。


五、让 makepkg 准备完整源码树

执行:

makepkg --nobuild --nodeps

这一步会:

  1. 下载 Linux 源码
  2. 验证 SHA256 / BLAKE2
  3. 验证内核签名
  4. 解压源码
  5. 应用 Arch 自己的补丁
  6. 设置 Arch 的 kernel config
  7. 设置 local version
  8. 执行 PKGBUILD 的 prepare()

完成后通常会看到:

Prepared linux-lts version 6.18.49-1-lts
Sources are ready.

源码目录类似:

src/linux-6.18.49/

进入:

cd src/linux-6.18.49

然后确认:

make -s kernelrelease

必须得到:

6.18.49-1-lts

六、确认 i915 是模块

检查:

grep '^CONFIG_DRM_I915=' .config

应该为:

CONFIG_DRM_I915=m

这意味着 i915 会作为:

i915.ko

构建,而不是静态编入内核。

这也是能够单独重新编译 i915 的前提。


七、定位 eDP Panel Power Cycle Delay

目标文件:

drivers/gpu/drm/i915/display/intel_pps.c

搜索:

grep -n -B8 -A12 'panel_power_cycle_delay' \
    drivers/gpu/drm/i915/display/intel_pps.c

其中关键代码类似:

intel_dp->pps.panel_power_up_delay =
        pps_units_to_msecs(final->power_up);

intel_dp->pps.backlight_on_delay =
        pps_units_to_msecs(final->backlight_on);

intel_dp->pps.backlight_off_delay =
        pps_units_to_msecs(final->backlight_off);

intel_dp->pps.panel_power_down_delay =
        pps_units_to_msecs(final->power_down);

intel_dp->pps.panel_power_cycle_delay =
        pps_units_to_msecs(final->power_cycle);

最后这一行就是需要处理的位置。


八、加入最低 2000 ms 限制

原始代码:

intel_dp->pps.panel_power_cycle_delay =
        pps_units_to_msecs(final->power_cycle);

修改为:

intel_dp->pps.panel_power_cycle_delay =
        pps_units_to_msecs(final->power_cycle);

intel_dp->pps.panel_power_cycle_delay =
        max(intel_dp->pps.panel_power_cycle_delay, 2000);

这里没有把:

panel_power_cycle_delay

简单强制设为 2000。

使用的是:

max(original_value, 2000)

因此逻辑是:

如果原始值 < 2000 ms:
    使用 2000 ms

如果原始值 >= 2000 ms:
    保留原始值

这比:

intel_dp->pps.panel_power_cycle_delay = 2000;

更加稳妥。


九、为什么修改的是最终计算结果

i915 的 Panel Power Sequencer 参数可能来自:

  • VBT
  • BIOS
  • 硬件寄存器
  • 默认值
  • 驱动内部推导结果

在最终值已经计算完成之后再加入:

max(..., 2000)

相当于增加一个最终安全下限。

因此既不需要改变前面的硬件检测逻辑,也不需要重构 i915 的 PPS 代码。


十、重新确认 Kernel Release

补丁完成后再次执行:

make -s kernelrelease

仍然必须是:

6.18.49-1-lts

不要在这一阶段随意修改:

EXTRAVERSION
LOCALVERSION
CONFIG_LOCALVERSION

否则最终模块可能得到错误的 vermagic


十一、准备模块构建环境

执行:

make -j"$(nproc)" modules_prepare

然后使用当前已安装 Arch 新内核自带的:

Module.symvers

例如:

KBUILD="/usr/lib/modules/6.18.49-1-lts/build"

cp "$KBUILD/Module.symvers" ./Module.symvers

检查两者:

cmp -s Module.symvers "$KBUILD/Module.symvers" &&
    echo "Module.symvers: MATCH"

这里非常关键。

因为目标不是自己重新构建一个完整的新内核,而是给 Arch 已安装好的:

6.18.49-1-lts

构建一个兼容的额外模块。

因此应该使用该内核对应的官方 Module.symvers


十二、只编译 i915

执行:

make -j"$(nproc)" M=drivers/gpu/drm/i915 modules

这会编译整个 i915 模块子树,而不是整个 Linux Kernel。

过程中会看到大量:

CC [M]

最终应出现:

LD [M] i915.ko

生成:

drivers/gpu/drm/i915/i915.ko

有些配置还可能同时生成:

kvmgt.ko

这属于正常现象。


十三、BTF 警告并不一定意味着失败

单独构建模块时可能出现:

Skipping BTF generation for i915.ko due to unavailability of vmlinux

这通常只是因为源码目录中没有完整内核构建产生的:

vmlinux

如果:

i915.ko

已经成功生成,而且后面的 modinfo 正常,则这不是构建失败。


十四、验证 vermagic

执行:

modinfo drivers/gpu/drm/i915/i915.ko |
    grep '^vermagic:'

应与目标内核一致:

vermagic: 6.18.49-1-lts SMP preempt mod_unload

如果这里仍然出现旧内核版本,则绝对不要安装。


十五、剥离调试符号

刚刚编译出的 i915 可能非常大,例如超过 100 MB。

复制一份作为安装版本:

cp drivers/gpu/drm/i915/i915.ko /tmp/i915.ko

然后:

strip -g /tmp/i915.ko

体积通常会显著下降。

再次确认:

modinfo /tmp/i915.ko |
    grep -E '^(name|vermagic):'

十六、压缩成 Arch 使用的 Zstandard 格式

Arch 内核模块通常使用:

.ko.zst

执行:

zstd -T0 -19 -f \
    /tmp/i915.ko \
    -o /tmp/i915.ko.zst

验证:

zstd -t /tmp/i915.ko.zst

十七、不要覆盖 Arch 官方 i915

官方模块位于:

/usr/lib/modules/6.18.49-1-lts/kernel/drivers/gpu/drm/i915/i915.ko.zst

不要覆盖它。

创建:

/usr/lib/modules/6.18.49-1-lts/updates/

安装自定义版本:

sudo install -d -m755 \
    /usr/lib/modules/6.18.49-1-lts/updates

sudo install -m644 \
    /tmp/i915.ko.zst \
    /usr/lib/modules/6.18.49-1-lts/updates/i915.ko.zst

现在系统中同时存在:

官方版本:
kernel/drivers/gpu/drm/i915/i915.ko.zst

自定义版本:
updates/i915.ko.zst

这是一种非常适合维护自定义模块的结构。


十八、更新 depmod

执行:

sudo depmod -a 6.18.49-1-lts

然后检查:

modinfo -k 6.18.49-1-lts -n i915

理想结果:

/lib/modules/6.18.49-1-lts/updates/i915.ko.zst

这意味着:

updates/

中的版本已经成为 i915 的优先模块。


十九、进一步验证 modprobe 实际加载路径

执行:

modprobe -S 6.18.49-1-lts \
    --show-depends i915

最后应该出现:

insmod /lib/modules/6.18.49-1-lts/updates/i915.ko.zst

这比仅检查文件是否存在更可靠。

它验证的是:

如果当前真的启动到这个内核并请求加载 i915,modprobe 会选哪个模块。


二十、重新生成 initramfs

执行:

sudo mkinitcpio -p linux-lts

如果配置中:

MODULES=()

没有强制加入 i915,同时:

HOOKS

中也没有让 i915 被提前打包进去,那么:

initramfs-linux-lts.img

里可能根本没有 i915。

这并不一定是问题。

此时 i915 会在根文件系统挂载后,由正常 module loader 从:

/usr/lib/modules/<kernelrelease>/

加载。

只要:

modinfo -k <kernelrelease> -n i915

以及:

modprobe -S <kernelrelease> --show-depends i915

都选择:

updates/i915.ko.zst

即可。


二十一、一个容易造成误判的权限问题

检查 initramfs 时,可能执行:

lsinitcpio /boot/initramfs-linux-lts.img

却得到:

ERROR: Unable to read file

这并不一定表示文件不存在。

很多系统生成的 initramfs 权限可能是:

-rw------- root root

因此普通用户无法读取。

应使用:

sudo lsinitcpio /boot/initramfs-linux-lts.img

先确认权限,再判断文件状态。


二十二、警惕 set -e

自动化维护脚本中经常会写:

set -e

它的含义是:

任意未被特殊处理的命令返回非零状态时,立即退出 shell。

例如:

grep pattern file

如果没有匹配结果,grep 返回:

1

这通常只是代表“没有找到”,不是严重错误。

但在:

set -e

环境下,它可能直接结束整个 shell。

如果终端模拟器设置成:

shell 结束后关闭窗口

表现就会像是:

终端突然关闭。

对于纯检查操作,可以使用:

grep ... || true

或者先:

set +e

避免这种情况。


二十三、重启前最终状态

在重启之前至少确认:

modinfo -k 6.18.49-1-lts -n i915

返回:

/lib/modules/6.18.49-1-lts/updates/i915.ko.zst

检查:

modinfo -k 6.18.49-1-lts i915 |
    grep '^vermagic:'

正确。

检查:

modprobe -S 6.18.49-1-lts \
    --show-depends i915

最终选择自定义模块。

然后才重启。


二十四、重启后的最终验证

启动后首先确认:

uname -r

例如:

6.18.49-1-lts

然后:

modinfo -n i915

应该是:

/lib/modules/6.18.49-1-lts/updates/i915.ko.zst

检查:

lsmod | grep '^i915'

确认模块已经正常加载。


二十五、最关键的验证:Panel Power Cycle Delay

找到:

i915_panel_timings

例如:

sudo find /sys/kernel/debug/dri \
    -type f \
    -name 'i915_panel_timings'

然后读取:

sudo cat \
/sys/kernel/debug/dri/<PCI-ID>/eDP-1/i915_panel_timings

成功情况下可以看到:

Panel power up delay: 200
Panel power down delay: 60
Panel power cycle delay: 2000
Backlight on delay: 1
Backlight off delay: 200

其中决定补丁是否真正生效的是:

Panel power cycle delay: 2000

只要这个值正确,就说明:

源码补丁
→ i915 编译
→ 模块安装
→ depmod
→ 模块选择
→ 系统启动
→ 驱动初始化

整条链路都已经真正生效。


二十六、自定义模块的签名警告

启动日志中可能出现:

i915: module verification failed:
signature and/or required key missing - tainting kernel

这是因为手工构建的:

i915.ko

没有 Arch 官方内核模块签名。

如果系统没有启用强制模块签名策略,而:

lsmod | grep i915

已经确认模块成功加载,则这通常只是:

kernel taint

标记,而不是 i915 功能异常。

对于本地维护的自定义内核模块,这是预期现象。


二十七、为什么 updates/ 方案值得保留

相比直接覆盖:

kernel/drivers/gpu/drm/i915/i915.ko.zst

将模块放入:

updates/i915.ko.zst

有几个明显优势。

第一,Arch 官方模块仍然完整保留。

第二,排障时可以快速删除:

updates/i915.ko.zst

然后:

sudo depmod -a <kernelrelease>

即可恢复官方模块。

第三,系统目录结构能够明确区分:

发行版模块

和:

本地维护模块

第四,验证非常简单:

modinfo -n i915

一眼就可以判断当前到底使用哪个版本。


二十八、每次 LTS 内核升级后的维护模板

今后如果:

6.18.x-y-lts

升级到新的:

6.18.x-z-lts

可以重复下面的流程:

pacman -Syu
        │
        ▼
暂时不要重启
        │
        ▼
确认新的 kernelrelease
        │
        ▼
取得 Arch linux-lts 对应精确 tag
        │
        ▼
makepkg --nobuild
        │
        ▼
确认 make kernelrelease
        │
        ▼
修改 intel_pps.c
        │
        ▼
增加 max(panel_power_cycle_delay, 2000)
        │
        ▼
modules_prepare
        │
        ▼
复制目标内核 Module.symvers
        │
        ▼
M=drivers/gpu/drm/i915 modules
        │
        ▼
检查 i915.ko vermagic
        │
        ▼
strip
        │
        ▼
zstd 压缩
        │
        ▼
安装到 modules/<release>/updates/
        │
        ▼
depmod
        │
        ▼
modinfo / modprobe 验证
        │
        ▼
mkinitcpio
        │
        ▼
重启
        │
        ▼
确认实际加载自定义 i915
        │
        ▼
读取 i915_panel_timings
        │
        ▼
Panel power cycle delay = 2000

结论

Intel MacBook 上某些 eDP 面板异常并不一定是 LCD 本身损坏,也不一定是 GPU 故障。

如果问题与面板重新上电间隔有关,可以从 Intel i915 的:

Panel Power Sequencer

入手分析。

通过在:

drivers/gpu/drm/i915/display/intel_pps.c

中加入:

intel_dp->pps.panel_power_cycle_delay =
        max(intel_dp->pps.panel_power_cycle_delay, 2000);

可以为面板重新上电周期设置一个最低 2000 ms 的安全限制。

更重要的是,维护方式不需要长期运行完整自定义内核。

只要:

  • 使用 Arch 精确对应的内核源码
  • 匹配正确的 Kernel Release
  • 使用目标内核的 Module.symvers
  • 单独重新编译 i915
  • 验证 vermagic
  • 将模块安装到 updates/
  • 更新 depmod
  • 在重启后检查实际模块路径
  • 最终通过 i915_panel_timings 确认 2000 ms

就可以在最大程度保留发行版内核维护便利性的同时,长期维护一个非常小而明确的硬件兼容性补丁。

对于需要依赖特定 i915 workaround 的 Intel MacBook,这种方案比长期维护整套自定义 Kernel 更轻量、更透明,也更容易回退。

Leave a Reply

Your email address will not be published. Required fields are marked *