Est. Street

关于我的小笔记本熄半屏自动默哀这档子事(一)

Elvin Starry
Elvin Starry

0xFF

好像有好长一段时间没写任何东西了,一年又一年当行尸走肉,脑子差不多死掉了。
尝试复健一下。也许只是一些胡言乱语。

0x00

image.png
小Linux系统真好玩🥰但是为什么只要我把亮度调到50%以下就会黑屏😨?
本着把Arch娘领回家就终身负责的原则,仔细观察一下现象,不难发现:

  1. 当使用键盘Fn键降低系统亮度时,无论操作步长多少(普通降亮度降5%,Shift+降亮度降1%),只要一跨越50%,电脑就会黑屏。
  2. 但它又不像 核已畏Kernel Panic 或者哪部分死机,在黑屏后按一下亮度加键,屏幕又亮过来了。联合国降半旗以寄哀思,好像它熄半屏也会自动默哀。
  3. 按一下亮度加把它叫起来之后,发现屏幕亮度仍然在45%,好像它确实是死了。在此之后,它就可以正常响应亮度变化指令了。
  4. 上述问题仅在使用Fn键调整亮度时触发。通过KDE调整亮度,甚至是通过sys class调整亮度均无法复现上述问题。

那怎么办呢?

0x01

TLDR:

  1. 受着
  2. 装一个brightnessctl,然后你可以有brightnessctl -d amdgpu_bl1 set 5%+,当然也可以是5%-,或者其他数值。然后在KDE里绕过Fn键绑一个其他的快捷键。比如,我的笔记本加亮度是Fn+F6,我可以改成Super+F6

正文结束,感谢观看。

0x02

我不禁反思,是什么导致了亮度控制的怪问题——尤其是仅通过Fn键才能触发的怪bug。我们要知道,对笔记本来说Fn键是不走操作系统的,Fn操作会被EC截获,然后EC直接控制硬件,或者触发一个中断去执行固件里的后续操作。
这不完蛋了吗,牵扯到固件或者ACPI的问题在操作系统上哪有那么好处理的。不过我这个笔记本是2021年的也算比较老了,R7-4800U也不是什么旗舰u,也许有古人的智慧?

还真有古人的智慧
image.png
不是哥们,disappear在哪了😨?我这没disappear啊😨?

[elvinstarry@ElvinArch ~]$ uname -a
Linux ElvinArch 7.0.10-zen1-1-zen #1 ZEN SMP PREEMPT_DYNAMIC Sat, 23 May 2026 14:45:01 +0000 x86_64 GNU/Linux

这也不是linux-zen的问题啊,我用linux也一样有这个问题啊😨?
看看他的quirk做了什么:
image.png
看上去也和我没有关系。
那这怎么整🤔?但既然那位哥都说他的问题已经disappear了,也许是linux或者kde在后续更新又引入了其他哪些问题呢?比如kde有什么动画或者处理输入事件的逻辑又出了什么问题。
image.png
不管怎样,古人说他的问题已经解决掉了,那我们面前的应该是新问题。
image.png

0x02+1 AMDGPU驱动

我的ArchLinux使用的是AMDGPU开源驱动,会不会是它自己不吃50%以下的亮度跳变导致的?

echo 32768 | sudo tee /sys/class/backlight/amdgpu_bl1/brightness
echo 29490 | sudo tee /sys/class/backlight/amdgpu_bl1/brightness
# 无事发生

0x02+2 抢座

在现代Linux内核中,大部分设备的ACPI亮度事件被转换为了标准键盘输入,由libinput传给窗口管理器。KDE识别到这个全局快捷键,然后传递给PowerDevil
image.png
当然这只是大多数情况。会不会我的电脑上有其他程序或者内核模块也在响应键盘的亮度事件?

systemctl --user stop plasma-powerdevil.service

很遗憾,停止PowerDevil后系统不再响应我的亮度增减指令。PowerDevil是屏幕亮度的主理人。

0x02+3 主理人无法主理

会不会是PowerDevil因为某种原因无法主理我的屏幕亮度了?
我们重新启用PowerDevil,看看它到底做了什么动作:

watch -n 0.02 'cat /sys/class/backlight/amdgpu_bl1/{brightness,actual_brightness,bl_power} 2>/dev/null | paste -d " " - - - | while read b a p; do echo "$(date +%s.%N) b=$b a=$a p=$p"; done'

但观察日志发现,按Fn键从50%下调亮度时,brightnessactual_brightness均在45%的正常范围,且bl_power始终为0,屏幕背光也没有关闭。PowerDevil仍然在主理着屏幕亮度。

journalctl --user -b -u plasma-powerdevil.service --no-pager
# 依旧无事发生

0x02+4 蹦蹦炸弹

一定要按下键盘才会触发黑屏吗?会不会是PowerDevil处理亮度事件同时执行了其他连锁操作?

qdbus6 org.kde.kglobalaccel /component/org_kde_powerdevil org.kde.kglobalaccel.Component.invokeShortcut "Decrease Screen Brightness"

PowerDevil直接发降低屏幕亮度快捷键,依旧无事发生。

0x03

排查到这里,显然和软件bug没有太大关系了,问题只可能与键盘的物理输入有关了,或者是内核层面的原因。
image.png

0x03+1 还是好键盘吗

sudo evtest   #测试一下输入,在里面监听Video Bus事件
Testing ... (interrupt to exit) 
Event: time 1781525473.231753, type 1 (EV_KEY), code 224 (KEY_BRIGHTNESSDOWN), value 1
Event: time 1781525473.231753, -------------- SYN_REPORT ------------ 
Event: time 1781525473.231776, type 1 (EV_KEY), code 224 (KEY_BRIGHTNESSDOWN), value 0 
Event: time 1781525473.231776, -------------- SYN_REPORT ------------ 
^C

不对啊,我按了一次亮度减和亮度加,亮度加去哪了,怎么没捕获到?
image.png
没有难道说,重启时刚上过电,uptime也不曾调光,还是个好键盘。后面再按就有亮度加事件了。
不过可以发现,黑屏后第一次亮度加确实没有被捕获到,像是Fn的亮度加信号被用来激活屏幕了。sus

0x03+2 内核抢座

注意到Linux内核acpi_video_switch_brightness()可能竞争屏幕亮度。

echo N | sudo tee /sys/module/video/parameters/brightness_switch_enabled
# 无事发生

但显然不是因为它。

0x03+3 它还在那吗

我们需要确认黑屏发生时Linux显示链路是否还在线。如果死掉了,下一步考虑是谁导致的。

# 开watch监控以下值:
/sys/class/backlight/amdgpu_bl1/brightness
/sys/class/backlight/amdgpu_bl1/actual_brightness
/sys/class/backlight/amdgpu_bl1/bl_power
/sys/class/drm/card1-eDP-1/status
/sys/class/drm/card1-eDP-1/enabled
/sys/class/drm/card1-eDP-1/dpms

注意到黑屏前:

brightness=32768
actual_brightness=27242
bl_power=0

connector=/sys/class/drm/card1-eDP-1
  status=connected
  enabled=enabled
  dpms=On

黑屏后:

brightness=29491  # 亮度!= 0
actual_brightness=23901
bl_power=0

connector=/sys/class/drm/card1-eDP-1
  status=connected  # eDP在线
  enabled=enabled   # eDP启用
  dpms=On

很遗憾,在Linux看来屏幕仍在正常显示。

0x03+4 KrashDE

是不是KWin或者Wayland画面层直接爆了?
grub里内核启动参数加一条:

systemd.unit=multi-user.target

登录TTY后,

brightnessctl -d amdgpu_bl1 set 50%

此时按下亮度加减,惊奇地发现屏幕在TTY下也会随着操作熄灭亮起。
image.png
于是又发现一条线索:不需要图形服务也会触发黑屏bug。此bug大概率只与图形驱动或其他硬件问题有关。

0x03+5 省电王

尝试超级拼装如下内核命令行,以排除图形驱动可能的省电策略造成的黑屏bug。

amdgpu.abmlevel=0  # AMD Adaptive Backlight Management
amdgpu.dcdebugmask=0x10 # Panel Self Refresh
amdgpu.dcdebugmask=0x410  # PSR + Replay
amdgpu.sg_display=0  # APU scatter/gather display

image.png
全都没有用
但目前我们还没法排除图形驱动的嫌疑,黑屏仍然可能发生在显示链路。

0x03+6 黑手

排查内核模块时注意到系统内存在ideapad_laptop模块。
挂进blacklist后问题仍复现,说明联想厂商驱动并非bug必要条件。

0x04

image.png
怎么排查一圈全部木大啊

我们要知道,对笔记本来说Fn键是不走操作系统的,Fn操作会被EC截获,然后EC直接控制硬件,或者触发一个中断去执行固件里的后续操作。
难不成真是ACPI的锅?

0x04+1 听牌

以下操作均默认ideapad_laptop模块被禁用。
直接开acpi_listen听事件。

video/brightnessdown BRTDN 00000087 00000000
video/brightnessdown BRTDN 00000087 00000000
video/brightnessdown BRTDN 00000087 00000000

0x87事件即为Linux内核中的ACPI屏幕亮度降低事件。

// linux/include/acpi/video.h:41
#define ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS    0x87

0x04+2 看牌

通过上文我们知道,Linux需要将ACPI的亮度事件上报为普通输入。
如果我不报呢?

echo 0 | sudo tee /sys/module/video/parameters/report_key_events

发现在50%降亮度仍然会导致黑屏。
这就意味着黑屏发生在input key reporting之前,或者其他旁路。也就是说,

ACPI驱动 -> input_report_key(KEY_BRIGHTNESSDOWN) -> 用户态

不导致黑屏。

0x04+3 验牌

sudo trace-cmd record -p function_graph \
  -g acpi_video_device_notify \
  -g brightness_switch_event \
  -g acpi_video_switch_brightness \
  -g acpi_video_device_lcd_get_level_current \
  -g acpi_video_device_lcd_set_level \
  -o /path/to/somewhere.dat

去trace一下调用。发现黑屏时

kworker/3:1-144 [003] ...1. 1910.484292: funcgraph_entry: | acpi_video_device_notify() { kworker/4:2-324 [004] ...1. 1912.369150: funcgraph_entry: | acpi_video_device_notify() {

Linux ACPI只是收到了0x87notify,而黑屏很可能已经由上层固件或EC或其他外围硬件触发。

0x04+4 开牌

sudo acpidump -b
iasl -d dsdt.dat

然后在DSDT里面,可以看到:

Method (_Q11, 0, NotSerialized)  // _Qxx: EC Query, xx=0x00-0xFF
{
    If ((^^^GP17.VGA.BRIL == 0x00))
    {
        BKLT = 0x01
    }

    P80H = 0x11
    Notify (^^^GP17.VGA.LCD, 0x87) // Device-Specific  // 亮度-
    Notify (VPC0, 0x80) // Status Change
}

Method (_Q12, 0, NotSerialized)  // _Qxx: EC Query, xx=0x00-0xFF
{
    If ((BKLT == 0x01))
    {
        BKLT = 0x00
    }
    Else
    {
        P80H = 0x12
        Notify (^^^GP17.VGA.LCD, 0x86) // Device-Specific  // 亮度+
        Notify (VPC0, 0x80) // Status Change
    }
}

不难发现,此处笔记本EC收到Fn键亮度减指令后将由ACPI向\_SB.PCI0.GP17.VGA.LCD发送0x87,随后通过Linux内核上报为普通输入。而正常情况下,_Q12会发送亮度加指令。但是有个条件:

If ((BKLT == 0x01))
{
    BKLT = 0x00
}
Else
{
    // ......
}

BKLT1时,_Q12只会把BKLT置零,并不发送0x86
这就解释了我们观察到的现象:黑屏后第一次Fn亮度加能恢复屏幕,但是看不到亮度加信号。AML在第一次亮度加时根本没有发送0x86,而是类似于重置面板的操作。

0x05

但是,BKLT是什么?它和BRIL有什么关系?
image.png

OperationRegion (GPUM, PCI_Config, 0x24, 0x04)
Field (GPUM, ByteAcc, NoLock, Preserve)
{
    GPUB,   32
}

Method (GBSA, 0, Serialized)
{
    Local0 = GPUB /* \_SB_.PCI0.GP17.VGA_.GPUB */
    Local0 &= 0xFFFFFF00
    Local0 += 0x0138
    Return (Local0)
}

OperationRegion (SCRA, SystemMemory, GBSA (), 0x04)
Field (SCRA, ByteAcc, NoLock, Preserve)
{
    Offset (0x01), 
    BRIL,   8
}

也就是说,

GPUB = pci_config_read32(gpu, 0x24);     // PCI BAR5
base = GPUB & 0xFFFFFF00;
addr = base + 0x0138;
BRIL = read8(addr + 0x01);
     = (read32(base + 0x0138) >> 8) & 0xff;

而它落在

// linux/drivers/gpu/drm/amd/include/asic_reg/nbio/nbio_7_0_offset.h:4079
#define mmBIF_UVD_INTR_CNTL 0x004e
#define mmBIF_UVD_INTR_CNTL_BASE_IDX 1

按理说这块区域不是背光调节应该在的地方,为什么会是这样我暂且蒙在鼓里,或者我哪里搞错了。
BKLT则是EC operation region里的一个bit,看上去像一个内部latch,表示固件认为面板处在某种特殊状态。
进一步trace AMDGPU,

acpi_video_device_notify() {
  acpi_notifier_call_chain() {
    blocking_notifier_call_chain() {
      notifier_call_chain.constprop.0() {
        acpi_ac_battery_notify(); (ret=0x1)
        amdgpu_acpi_event() {
          __drm_dev_dbg(); (ret=0x0)
        } (ret=0x0)
      } (ret=0x0)
    } (ret=0x0)
  } (ret=0x0)
} (ret=0x0)

只看到__drm_dev_dbg(),且只有2us调用时间,不像在处理什么东西,因此AMDGPU驱动也不像是把屏幕关黑的原因。怀疑固件侧在用户按下Fn时BRIL/BKLT状态机已触发面板异常。

0x06

因此,目前认为导致屏幕变黑的最可能的原因:

1. 用户按下Fn亮度减
2. EC触发ACPI Query _Q11
3. 亮度在50%时因未知原因导致BRIL=0,继而在_Q11逻辑内导致BKLT=1
4. BKLT=1导致显示屏关断,此时仍能正常对0x87响应
5. EC Query _Q12逻辑内发现BKLT=1,便将其置零,显示屏恢复显示,但是没有notify 0x86,导致按下亮度加只是点亮屏幕,没有增加亮度

以防有读者对第3步“亮度在50%时BRIL=0”抱有疑问。演示:

0x06+1 Showtime

显然我们的APU集显是一个PCI设备,找到它的BDF:

lspci -Dnn | grep -Ei 'VGA Compatible'
# 0000:03:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Renoir [Radeon Vega Series / Radeon Vega Mobile Series] [1002:1636] (rev c1)
BDF='0000:03:00.0'

观察上述BRIL表达式:

GPUB = pci_config_read32(gpu, 0x24);     // PCI BAR5
base = GPUB & 0xFFFFFF00;
addr = base + 0x0138;
BRIL = read8(addr + 0x01);
     = (read32(base + 0x0138) >> 8) & 0xff;

不难发现:

// 即
uint32_t r = readl(gpu_bar5 + 0x138);   
uint8_t BRIL = (r >> 8) & 0xff;         // bits 15:8

可以直接起Python,直接读sys class里面的resource5:

import os, mmap
bdf = '0000:03:00.0'
path = f"/sys/bus/pci/devices/{bdf}/resource5"

with open(path, "rb", buffering=0) as f:
    mm = mmap.mmap(f.fileno(), 0x1000, mmap.MAP_SHARED, mmap.PROT_READ)
    v = mm[0x139]
    print(f"resource5+0x139 = 0x{v:02x} ({v})")
    mm.close()

别忘了sudo。
image.png
image.png
image.png
多试验几次,不难发现亮度为50%时BRIL恒为0。为什么会这样?我也不知道,我也想知道。这事可能得问AMD,或者联想。

0x07

第一部分就先这么结束吧,至少是找出了问题所在。
那如何解决呢?比较无伤大雅的方式就是0x01节提到的方式,绕开Fn的触发。更疯狂一些的举动,啊,那就是搞点DSDT Override了,但是搞不好可能就真的 核已畏Kernel Panic 了,或者电脑直接起不来了。
更长期一些的计划,也许是给kernel打个quirk,又或者其实这电脑的固件设置里有什么Windows特调还是Linux特调的选项我没发现,导致我这库库一堆长篇大论纯是狗叫了?那种事情再说吧,还不知道下一篇blog什么时候能产出来,又或许这里真的要变成我的摄影展了。
image.png
good night

Copyright

原创 本文由 Elvin Starry 发布于 Est. Street

原文链接:https://www.frostleaf.dev/tech-research/why-my-embedded-screen-blacks-out-when-lowering-the-brightness-across-the-half-pt-1.html

许可协议CC BY-NC-SA 4.0署名-非商业性使用-相同方式共享 4.0 国际

已有 2 条评论

avatar
FrsChan

来看表情包来了

黑龙江 回复
avatar
Leo

嗨咯!看到你使用了 Qiwi 主题~

主题这两天更新到 v2 版本啦,修改了设计风格(虽然还是继承了老版本的设计 token)。如果你感兴趣,可以选择更新噢~

v1 版本的最后一个稳定版本是 1.5.9,如果你喜欢 v1 的设计,可以试试更新到这个版本!

新加坡 回复

发表评论

评论身份

保存后下次会自动带上,不用每次重新填写。