The thirteen modules
Each is a self-contained folder with a manifest, a payload, and a
README. Click any module to open the bug story and the file-level
details.
| Module | Version | Fixes |
audio-fix |
3.2.0 |
Adaptive B9406CAA ghost-RT722 compatibility + HiFi UCM; DKMS only on kernels lacking the upstream quirk, bundled CS35L56 firmware only on linux-firmware-cirrus < 20260519 |
camera-firmware |
3009 |
Compare ESRT locally and offer the fixed, hash-pinned ASUS 3009 capsule via fwupd — no Windows or latest-version query |
display-fix |
1.4.0 |
PSR1 self-refresh plus forced VESA DPCD backlight for working brightness |
hdr-fix |
1.0.0 |
(optional) HDR toggle for the internal OLED — libdisplay-info 0.3.0 with the upstream DisplayID 2.0 commits and two parser fixes, preferred via ld.so.conf.d; a pacman hook retires it when the package changes |
intel-perf-fix |
1.3.0 |
Panther Lake thermal policy — thermald runs the OEM's adaptive tables; intel-lpmd opt-in |
ish-firmware |
5.8.1.7783 |
Verified ASUS-signed Intel Sensor Hub image under the per-OEM name the kernel requests, so the ambient light sensor (als) exists |
keyboard-backlight-auto |
1.3.0 |
(optional) Keyboard backlight follows the ambient light sensor, using Windows 11's documented ALR curve; off with the lid shut |
keyboard-backlight-fix |
2.0.0 |
(superseded) The v1.x asusd workaround and why it was wrong — skips its own install |
power-profile-bridge |
1.0.0 |
Power Save reaches the SoC slider's low-power and asus-wmi's quiet fans; power-profiles-daemon stays in charge |
touchpad-fix |
1.2.1 |
PixArt 093A:4F05 haptic pad — pressure-axis clamp + libinput quirk, written as a managed block in local-overrides.quirks |
touchpad-haptics |
1.0.0 |
(optional) Click force and haptic intensity through the pad's standard HID feature reports, restored whenever the pad appears |
webcam-ai-fix |
1.1.0 |
AI camera stack — OBS + obs-backgroundremoval + v4l2loopback (CPU-only on Linux) |
wifi-fix |
2.1.1 |
BE211: disable broken 802.11be/EHT and diagnose Linux 7.2 missed beacons warnings without broad power/performance overrides |
Module deep dives
touchpad-fix — the pad's descriptor gives pressure the Y axis's maximum
The pad's own HID report descriptor gives Tip Pressure no Logical
Maximum of its own, so under HID's global-item rules it inherits the
Y field's 2601, and the kernel reports that as the
ABS_MT_PRESSURE maximum. It is not a kernel parser bug.
Real presses sit far below it (peaks around 1000), so libinput's
pressure thresholds never see a proper touch and every motion is
rejected as a "kernel bug: Touch jump." The sister pad
093A:4811 was fixed upstream in libinput 1.32 with
AttrInputProp=+INPUT_PROP_PRESSUREPAD; 4F05
has no upstream entry yet.
$ journalctl -b | grep -c "Touch jump" # libinput logs through the compositor
1873 ← without the module
0 ← with the module
Files installed (v1.2.0):
/etc/udev/hwdb.d/61-pixart-4f05-pressure-fix.hwdb — clamps ABS_PRESSURE / ABS_MT_PRESSURE (EVDEV_ABS_18 / EVDEV_ABS_3A) to 0:100 so libinput's pressure heuristics see sane values; optional belt-and-suspenders
/etc/libinput/local-overrides.quirks — a marked block telling libinput to ignore pressure entirely (AttrEventCode=-ABS_MT_PRESSURE;-ABS_PRESSURE), the same shape as the shipped Asus UX302LA quirk. This is the load-bearing fix.
libinput reads admin overrides only from
/etc/libinput/local-overrides.quirks, and that file is
shared with every other override on the machine. Since 1.2.0 the
module therefore writes its own managed block instead of replacing
the file: install drops earlier B9406 touchpad sections (the 1.1.x
copy and Omarchy-derived ones) and keeps every other section;
uninstall removes only the block. The rewritten file has to pass
libinput quirks validate before it replaces the old
one (kept as local-overrides.quirks.asus-expertbook-linux.bak),
and unpaired markers stop the module without touching the file.
After a reboot,
sudo libinput quirks list /dev/input/event9 should print
AttrEventCode=-ABS_MT_PRESSURE;-ABS_PRESSURE;.
touchpad-haptics — (optional) click force and haptic strength
Windows sets click force and haptic intensity through MyASUS; Linux
has no control for either. The PixArt 093A:4F05 pad is a
Windows Precision Touchpad pressure pad, and its own HID report
descriptor (964 bytes) declares two standard feature reports:
Button Press Threshold (report 8, 1–3: light,
medium, firm) and Haptic Intensity (report 9,
0–100). The firmware does not answer GET_FEATURE, so the
current values cannot be read back; it keeps them across suspend and
forgets them at power-off.
Files installed:
/usr/local/bin/touchpad-haptics — CLI that sends validated SET_FEATURE requests; it refuses any device that is not HID_ID 0018:0000093A:00004F05 with exactly this report descriptor
/etc/udev/rules.d/71-asus-b9406-touchpad-haptics.rules — tags the pad's hidraw node uaccess, so the logged-in user can change the settings without root, and starts the restore service when the node appears
/etc/systemd/system/touchpad-haptics-restore.service — oneshot that applies /etc/touchpad-haptics.conf at boot and after a driver rebind
/etc/touchpad-haptics.conf — saved settings, seeded from /usr/share/touchpad-haptics/; all commented out at first, so the firmware defaults stay
touchpad-haptics set --click-force light --intensity 30 # try, not saved
sudo touchpad-haptics set --click-force medium --intensity 60 --save
touchpad-haptics status
The firmware cannot report its values back, so test the two reports
separately. touchpad-haptics set --intensity 0 should
take the vibration out of a click, which proves report 9 (intensity)
reaches the pad and nothing about report 8. For click force, keep a
clear intensity and compare --click-force light with
--click-force firm, pressing slowly: with
firm a press that clicked under light
should need noticeably more force. If set cannot open the
device, log out and back in once. The report layout was first worked
out in
bramvera/omarchy-asus-expertbook-haptic-touchpad;
this module is an independent, desktop-agnostic implementation. See
touchpad-haptics/README.md.
audio-fix — adaptive upstream/DKMS ghost fix plus HiFi audio
- B9406CAA firmware advertises an unfitted RT722. When the SoundWire core marks it
UNATTACHED, the generic SOF machine driver can still create a duplicate SimpleJack link, fail with -12, and register no ALSA card.
- Cirrus CS35L56 amps boot in
FIRMWARE_MISSING when linux-firmware-cirrus < 20260519 lacks the per-OEM blobs for PCI subsystem 1043:15e4. This unit has no CirrusSmartAmpCalibrationData EFI variable, so the kernel silently skips per-unit calibration and logs no calibration line; the tuning still loads.
- The combined speaker-codec string
cs35l56+cs42l43-spk has no UCM in alsa-ucm-conf < 1.2.16, and its old SpeakerCodec regex drops the -spk suffix. Version 1.2.16 fixes this upstream.
- On kernels before 7.1 the generic topology also exposes an unused SSP2-BT offload PCM whose failed probes flood the kernel log. The 7.1+ function topologies no longer expose it.
- SOF firmware and topologies are the separate
sof-firmware package. A minimal install can lack it, and then no card appears even with the ghost-RT722 fix (SOF firmware and/or topology file not found).
$ sudo dmesg | grep cs35l56
cs35l56 sdw:0:2:01fa:3556:01:0: FIRMWARE_MISSING ← without
cs35l56 sdw:0:2:01fa:3556:01:1: FIRMWARE_MISSING ← without
──────────────────────────────────────────────────────────────────────────────
cs35l56 sdw:0:2:01fa:3556:01:0: DSP1: Firmware: 1a00d6 vendor: 0x2 v3.13.4, 41 algorithms ← with
cs35l56 sdw:0:2:01fa:3556:01:0: Tuning PID: 0x23134, SID: 0x470200, TID: 0x84b06 ← with
On current packages most of this is upstream. The
HiFi UCM ships in alsa-ucm-conf 1.2.16 and the speaker
firmware in linux-firmware-cirrus 20260519, so there the
module installs only the SSP2-BT drop-in and, where needed, the DKMS
overlay; the UCM and firmware files are fallbacks for older
packages. Install also pulls in sof-firmware when it is
missing.
Version 3.2 stopped installing the bundled firmware next to a
current linux-firmware-cirrus. The copies were
byte-identical to the package's, but as uncompressed files they
shadowed it, and their per-amp -l2uN.wmfw names, which
upstream never uses, are looked up before the package's generic
104315e4.wmfw. A newer Cirrus firmware would never have
loaded. Updating removes copies identical to the bundled ones and
leaves any other file alone.
Only “Dummy Output”? That means the kernel did not
register an ALSA card. Since version 3.1 the module installs a
narrowly scoped snd-soc-sof-sdw DKMS overlay only on
kernels lacking the accepted upstream quirk; it removes only the
B9406CAA's UNATTACHED RT722 before DAI-link creation. Run
./patch.sh status audio-fix; the known
SDW3-Playback-SimpleJack / sof_sdw ... error -12
signature should disappear after one reboot. DKMS rebuilds the
overlay automatically whenever Arch/CachyOS installs a kernel that
still needs it, and removes it once upstream commit
90af3209742d is present in every installed kernel. That
commit first ships in Linux 7.3 and carries no
Cc: stable, so no 7.2.y or 6.18.y release has it yet and
7.2 kernels still need the overlay; a
stable request is drafted.
If audio vanishes right after a kernel update, run
dkms status asus-expertbook-sof-sdw: added
instead of installed means the rebuild failed. Overlay
3.0.0 failed that way on 7.2.y kernels whose stable update backported
the 7.3 asoc_sdw_parse_sdw_endpoints(dev, ctx, ...)
signature; overlay 3.0.1 probes the kernel headers for it instead of
trusting the version. Overlay 3.0.2 also limits DKMS to 6.x–7.2
kernels, so a 7.3+ kernel update keeps the kernel's own, newer
sof_sdw; an overlay upgrade compiles for every kernel
first and takes the old overlay off a kernel only once its
replacement exists.
Files installed (v3.2.0):
/usr/src/asus-expertbook-sof-sdw-3.0.2/ and /lib/modules/*/updates/dkms/ — board-scoped compatibility filter, built only for kernels lacking upstream commit 90af3209742d (never by DKMS for 7.3+); install also regenerates initramfs images
/etc/wireplumber/wireplumber.conf.d/52-disable-bt-sco-offload.conf — disables the dead SSP2-BT offload PCM so its probe stops spamming the log on pre-7.1 kernels (inert on 7.1+, where the PCM no longer exists); Bluetooth audio still works through PipeWire
- Only on
linux-firmware-cirrus < 20260519 (newer packages ship identical files, and the bundled copies are removed there): /lib/firmware/cirrus/cs35l56-b0-dsp1-misc-104315e4-l2u{0,1}.bin per-OEM tuning blobs and the matching .wmfw files, the generic CS35L56 Rev 3.13.4 firmware patch (chip ROM upgrades 3.4.4 → 3.13.4)
- HiFi UCM — only on
alsa-ucm-conf < 1.2.16 (1.2.16+ ships it in the package): sof-soundwire.conf (fixed -spk regex), the cs35l56+cs42l43-spk / cs42l43-spk+cs35l56 speaker confs, and codecs/cs42l43-spk+cs35l56/init.conf
sof-firmware package — pulled in when it is missing
The F1 speaker-mute LED needs asus-wmi's
platform::mute (WMI device 0x0004001C), which
lands in Linux 7.4. Until then only platform::micmute
exists; the kernel's audio-micmute trigger drives it.
wifi-fix — disable broken EHT and diagnose missed-beacon warnings
The core problem is 802.11be (EHT / Wi-Fi 7) itself on
the Intel BE211 (8086:e440) under
iwlwifi/iwlmld: the EHT RX path can collapse to
MCS0 / NSS1 and MLO sessions tear down, so the "Wi-Fi 7"
link is slower and flakier than plain Wi-Fi 6 on this machine.
$ journalctl -k -b | grep -c "missed beacons exceeds"
7228 # possible with Linux 7.2 C106 even on a strong, connected HE link
Core fix — drop the broken 802.11be layer so the radio
runs as Wi-Fi 6 (HE). Same approach Omarchy ships; verified at about
2.1 Gbit/s over 160 MHz HE here.
Files installed:
/etc/modprobe.d/iwlwifi-disable-eht.conf — options iwlwifi disable_11be=Y; disables EHT / Wi-Fi 7 and falls back to Wi-Fi 6 / HE
Linux 7.2 raises Panther Lake firmware support from C102 to C106, and
the driver can log thousands of
missed beacons ... but receiving data warnings while the
link remains strong, fast and connected. On the reference machine the
bursts followed a single access point every 30–37 minutes and
vanished on others, which points at an AP pausing its beacons
(off-channel scanning, for example) more than at C106. Status reports
the loaded firmware and warning count; it does not hide logs or force
a firmware downgrade. Version 2.1 removes the former power-scheme,
global ASPM-performance and offload overrides because they did not
stop the warnings and were broader than the demonstrated bug. Linux
7.3 loads C107 and 7.4 is set to take C108; EHT stays off until a
retest there shows the RX collapse is gone. Bluetooth coexistence
remains enabled.
display-fix — PSR1 self-refresh plus DPCD brightness
The Samsung Display Corp panel reports IEEE OUI 00:aa:01
in DPCD register 0x300 and supports Panel Replay Selective Update
(Early Transport), the self-refresh mode Linux 7.2's xe driver picks
by default on Panther Lake. In that mode the panel comes up visibly
desaturated after every HDR-enabling modeset (KWin's HDR toggle, a
fresh login with HDR on) and stale content lingers on screen. The
colors return once anything streams frames with the SDPs again (a
KWin color-accuracy toggle, a write to i915_edp_psr_debug),
so the panel appears to miss the new BT.2020/PQ signaling when Panel
Replay stops the stream on a static desktop. Dropping only Early
Transport keeps the washed out colors and slows the cursor to ~20 fps,
so Panel Replay itself is at fault.
Disabling Panel Replay alone is worse: xe falls back to PSR2 selective
update over the panel's DSC link and every screen update paints
red/green speckle garbage, most plausibly because the driver gates
PSR2 + DSC on platform generation only while this panel advertises DSC
selective update for Panel Replay alone. Disabling only selective
fetch (xe.enable_psr2_sel_fetch=0) keeps Panel Replay
without selective update, which freezes the panel on the boot console
text. PSR1 passes everything: vivid colors across HDR toggles, a
smooth cursor, no stale frames, and self-refresh still saves power on
a static screen. Linux 7.0/7.1 also wedged the display engine outright
(Timed out waiting PSR idle state,
DSB 0 timed out waiting for idle, KWin's
Pageflip timed out!) with a selective-fetch DSB deadlock
that could take the whole kernel down. 7.2 has not retired that
signature: a 7.2.0 boot with only the backlight parameter still logged
DSB poll errors, a FIFO underrun and PSR idle timeouts, with
corruption visible on screen.
Version 1.4 therefore pins self-refresh to PSR1 with
xe.enable_panel_replay=0 xe.enable_psr=1. Both are needed:
enable_psr=1 alone leaves Panel Replay on, and
enable_panel_replay=0 alone lands in the PSR2 garbage.
The older xe.enable_panel_replay=0 xe.enable_psr2_sel_fetch=0
pair reaches PSR1 too, because Panther Lake has no PSR2 hardware
tracking to fall back on without selective fetch.
The mismatch in vsc dp vsc sdp error 7.2 logs on every
HDR modeset is a separate readout false positive: the driver's VSC
SDP readout rejects the revision 7 packet it emits itself for Panel
Replay with colorimetry. The upstream fix, "drm/i915/dp: Handle VSC
SDP revision 7 in unpack" (fd2e337ba66f), is in
drm-intel-next and should reach Linux 7.4. Issue
#7
records the older reproducer and the Linux 7.2 retest.
Files installed:
/etc/modprobe.d/xe-dpcd-backlight.conf — enable_dpcd_backlight=2 enable_panel_replay=0 enable_psr=1 for any late module load
/etc/limine-entry-tool.d/90-asus-expertbook-linux-display.conf — appends xe.enable_dpcd_backlight=2 xe.enable_panel_replay=0 xe.enable_psr=1; the post-install hook calls limine-update
Install removes the old xe-disable-psr.conf, which
turned self-refresh off entirely. It leaves Omarchy's own
asus-expertbook-b9406-display.conf in place, since that
file sets only xe.enable_panel_replay=0, and puts it back
where version 1.3 archived it. Only a hand-edited copy that sets
xe.enable_psr or xe.enable_panel_replay to
another value is archived, because it would override the module on
the cmdline. ./patch.sh status display-fix warns when
Panel Replay stays on, when xe.enable_panel_replay=0
appears without a PSR2 disable, and when the panel's debugfs status
reports anything but PSR1. Upstream, patch 0001 now pins
this laptop to PSR1 through the two quirk tables drm-intel-next
already has (Panel Replay off for subsystem 1043:15e4
with sink OUI 00:aa:01, PSR2 off for PCI
0xb080 / 1043:15e4). It is not sent yet and
still needs one built-kernel test.
Known issues PSR1 does not cover (Linux 7.2, Panther Lake):
Selective fetch area calculation failed in pipe A once per boot is an informational fallback to a full-frame update. It is harmless and goes away with PSR1, which does not use selective fetch.
- Stock 7.2.y kernels (Arch, Omarchy) carry only half of the fix for the trace-less hard freeze from display page tables in stolen memory (drm/xe #7513). The second half,
0687ec06f51b ("Do not allocate into stolen for new framebuffers"), is in 7.3; CachyOS kernels carry it since 7.2.1 (CachyOS#986).
DSB 0 timed out / flip_done timed out with VRR's short vblank is fixed by b201029ca695 ("Ensure a non-zero safe window from PTL onwards") in 7.3, with no stable tag.
- Fullscreen switches on a Panther Lake Samsung OLED under KDE have been reported to cause plane faults and DSB poll errors on 7.2.7 (drm/xe #9385);
KWIN_DRM_NO_DIRECT_SCANOUT=1 avoids them.
- With PSR1 active, KWin may be unable to turn VRR on at runtime, because the change needs a full modeset (drm/xe #9359).
- A 5K Apple Studio Display on this CPU can stay black after a cold boot even though the link trains; a physical replug brings it up (drm/xe #9153, open).
- Poweroff or reboot can hang in
nhi_pci_remove on 7.2.y with a Thunderbolt/USB4 dock driving DisplayPort monitors; reproduced on a B9406CAA with a CalDigit TS5. Fixes are queued for 7.3 with Cc: stable (CachyOS#1047). Unplug the dock before shutting down, or boot the 6.18 LTS kernel.
hdr-fix — (optional) HDR toggle for the internal OLED
KDE's Display & Monitor settings show no HDR option for the
internal panel, and forcing it with
KWIN_FORCE_ASSUME_HDR_SUPPORT=1 gives washed-out colors.
The panel does advertise HDR (PQ, max 1600 cd/m², BT.2020 RGB), but
inside a CTA-861 block embedded in a DisplayID 2.0
extension, not in a classic CTA extension. libdisplay-info 0.3.0,
which KWin reads those capabilities through, does not look there.
0.4.0 does (73ec53d8 "info: search CTA blocks in
DisplayID v2 extensions"), but it changes the soname to
.so.4, so KWin has to be rebuilt against it, and the
distributions still ship 0.3.0.
The module backports the seven upstream commits that add DisplayID
2.0 data-block and CTA-861 support onto 0.3.0, plus three upstream
hardening fixes and two fixes of our own: those upstream commits,
still unchanged in upstream main, let a checksum-valid EDID with an
oversized DisplayID data block read past the end of the buffer, and
leaked data blocks when parsing failed. Both fixes are offered
upstream. The public API only gains three functions and an enum, so
the result keeps the .so.3 ABI: all 86 symbols of the
packaged library are still exported. Upstream's 64 tests pass, also
under AddressSanitizer, and 300,000 randomized DisplayID 2.0 EDIDs
parse without a sanitizer error. Install only proceeds on package
releases known to be plain upstream 0.3.0 (Arch
0.3.0-1, CachyOS 0.3.0-1.1), installs
meson, ninja, gcc,
patch and hwdata if missing, downloads the
0.3.0 release tarball, checks its SHA-256 (the same pin as Arch's
PKGBUILD), applies the patches, builds the library and runs its own
64-test EDID suite, stopping on any failure before anything is
installed.
Files installed:
/usr/local/lib/asus-expertbook-hdr/ — the patched libdisplay-info.so.3
/etc/ld.so.conf.d/asus-expertbook-hdr.conf — lists that directory; the dynamic linker consults ld.so.conf directories before /usr/lib, so libdisplay-info.so.3 resolves to the patched copy. No pacman-owned file is touched.
/etc/pacman.d/hooks/asus-expertbook-hdr.hook + /usr/local/lib/asus-expertbook-hdr-guard — remove the override as soon as the libdisplay-info package is upgraded, rebuilt, downgraded or removed, so a distribution fix is never shadowed
./patch.sh install hdr-fix
# log out and back in: KWin loads the library when it starts
./patch.sh status hdr-fix
system: libdisplay-info 0.3.0-1.1
linker: libdisplay-info.so.3 -> /usr/local/lib/asus-expertbook-hdr/libdisplay-info.so.3 (built for 0.3.0-1.1)
panel: PQ yes, BT.2020 yes, max 1600 cd/m² (as KWin reads it)
Then turn HDR on in System Settings > Display & Monitor.
display-fix's PSR1 pin keeps the HDR colors correct
across toggles. When the distribution ships 0.4.0, KWin links
.so.4, which has the fix, and the hook has already
removed the override. See
hdr-fix/README.md.
intel-perf-fix — Panther Lake thermal policy: thermald by default, intel-lpmd opt-in
Panther Lake is an "adaptive" thermal platform: the firmware carries
the OEM's own thermal tables (Intel DTT: PL1/PL2 limits, passive trip
points, a power-slider condition). The kernel's int340x drivers
expose the sensors and limits, but nothing in the kernel runs that
policy. Scheduling is not the gap it looks like: this 16-core CPU has
no SMT, so the kernel places work by core capacity ("Hybrid CPU
capacity scaling enabled", P 1024 / E 701 / LP-E 625) instead of
ITMT.
Packages installed (no payload files):
thermald (Arch extra; Debian/Ubuntu main) — thermald.service enabled. Runs the OEM's adaptive thermal tables (supported on Panther Lake since 2.5.9). It can lower limits if a BIOS table is bad; since 2.5.12 it restores them when stopped, so an A/B test is easy.
intel-lpmd (Arch extra/cachyos; Ubuntu universe, not in Debian) — opt-in: sudo INTEL_PERF_LPMD=1 ./patch.sh install intel-perf-fix. In low-power mode it confines system/user/machine.slice to the four LP-E CPUs, forces intel_pstate to active mode and moves the SoC power slider.
Version 1.2 stopped enabling intel-lpmd by default.
Intel labels 0.1.1 (the CachyOS build) a test release not meant for
distributions, and upstream main no longer changes cpusets by
default. A public Panther Lake A/B test (XPS 16, battery, balanced)
found no significant idle-power gain and roughly doubled app launch
time with it; apps started while confined also size their thread
pools to four CPUs. Updating from 1.1 disables the service unless
INTEL_PERF_LPMD=1 is set. The earlier LP-E-parking and
idle-wattage claims were never measured on this laptop.
This is the first module ported off pacman: package and
service calls go through lib/distro.sh, so the same hook
runs on apt systems too. On Debian and Ubuntu only the
thermald half is useful for now: Debian has no
intel-lpmd package, and Ubuntu 24.04 ships 0.0.3
(February 2024), which predates Panther Lake and exits within
milliseconds of starting. With INTEL_PERF_LPMD=1 the
module warns when the enabled unit is not running and points at its
journal.
power-profiles-daemon stays in charge of profiles.
thermald reads its slider state; intel-lpmd,
when enabled, touches the same intel_pstate and
SoC-slider settings, so the three are not fully independent layers.
The patcher tracks this package-only module the same way it tracks
file-based ones (versioned, idempotent, status-checked).
power-profile-bridge — Power Save that actually saves
Panther Lake registers two platform-profile handlers on this laptop:
Intel's SoC Power Slider
(low-power balanced performance) and
asus-wmi (quiet balanced performance). The
legacy /sys/firmware/acpi/platform_profile that
power-profiles-daemon drives lists only the choices both share, so
its Power Save writes balanced: the slider never reaches
low-power and the fans never reach quiet
(asusctl#387;
no kernel change is pending).
$ cat /sys/firmware/acpi/platform_profile_choices
balanced performance
A small root service follows power-profiles-daemon's
ActiveProfile over D-Bus and writes each handler's own
/sys/class/platform-profile/*/profile:
| power-profiles-daemon | SoC Power Slider | asus-wmi |
| power-saver | low-power | quiet |
| balanced | balanced | balanced |
| performance | performance | performance |
Going from Performance straight to Power Save is worse: the profile
snaps back to Balanced within a second. power-profiles-daemon
emulates power-saver by writing balanced, blocks its
file-monitor handler only while it writes, then receives the event
for its own write and takes it as a switch to balanced (0.30 and
main;
power-profiles-daemon#187
was closed by a fix for hp-wmi's cool choice only).
power-profiles-daemon keeps managing the CPU energy-performance
preference and stays the only thing KDE talks to, but a drop-in
starts it with --block-driver=platform_profile, so the
bridge alone writes platform profiles and nothing snaps back. The
module restores your profile after restarting the daemon. The bridge
runs as part of the daemon (it starts with it, including on D-Bus
activation, which is how KDE usually starts it), sets every profile
and re-applies after resume, whenever the daemon (re)appears on the
bus, and once a minute. A single
handler can be overridden in /etc/power-profile-bridge.conf
(for example power-saver.asus-wmi=balanced).
Files installed:
/usr/local/bin/power-profile-bridge and /etc/systemd/system/power-profile-bridge.service — the bridge and its root service
/etc/systemd/system/power-profiles-daemon.service.d/50-asus-expertbook-power-profile-bridge.conf — turns off the daemon's platform_profile driver; removed on uninstall
/etc/power-profile-bridge.conf — per-handler overrides, seeded from /usr/share/power-profile-bridge/ and left in place on uninstall
$ power-profile-bridge --status # changes nothing
power-profiles-daemon: power-saver
legacy platform_profile: custom
SoC Power Slider: low-power (choices: low-power balanced performance; wants low-power)
asus-wmi: quiet (choices: quiet balanced performance; wants quiet)
It needs power-profiles-daemon (KDE's default), running: install
refuses while TLP, tuned, auto-cpufreq or system76-power is active,
and the service never starts the daemon itself (its unit conflicts
with those). The bridge also re-applies once a minute. Hotkeys that
change the platform profile behind the daemon's back no longer show
in the applet. The xe GPU's own
power_profile is left untouched. See
power-profile-bridge/README.md.
webcam-ai-fix — Linux equivalent of Windows Studio Effects
Windows Studio Effects on Copilot+ PCs runs background blur, smart
framing, eye-contact correction, and voice focus on the NPU. None
of these are shipped on Linux out of the box, even though the
Intel Panther Lake NPU itself is fully supported by the kernel
(intel_vpu driver, /dev/accel/accel0 exposed)
and the userspace stack (OpenVINO 2026, level-zero) is available via
the AUR.
This module bundles the userspace plumbing: a virtual camera
target via v4l2loopback, OBS Studio as the capture +
filter pipeline, and the obs-backgroundremoval
plugin which runs ONNX models. On Linux that plugin is
CPU-only — its execution providers are
CUDA / ROCm / MIGraphX, and there is no OpenVINO/NPU path
in the OBS plugin (installing openvino does not add an
"NPU" device). CPU is fine for 720p30 background blur.
Files installed:
/etc/modules-load.d/v4l2loopback.conf — auto-load at boot
/etc/modprobe.d/v4l2loopback-options.conf — devices=1 video_nr=10 card_label='AI Camera' exclusive_caps=1
Packages installed:
v4l2loopback-dkms (extra) — kernel module
obs-studio (extra) — orchestrator
obs-backgroundremoval (AUR) — ML segmentation plugin (ONNX); CPU-only on Linux
Use: open OBS, add a Video Capture Device source pointing at the
real webcam, attach the Background Removal filter, and start
the virtual camera (Tools → Start Virtual Camera). Apps then see
"AI Camera" as a webcam choice.
Reality check — no NPU offload in OBS on Linux. For an
actual NPU route see
ericjchang/linux-studio-effects
(OpenVINO + v4l2loopback), but it's validated on Arrow Lake,
not yet Panther Lake, and installs via git + pip.
camera-firmware — verified ASUS 3009 capsule without Windows
The module reads the B9406CAA camera's UEFI ESRT entry and compares
it only with the known ASUS 3009 / 10.1.2.3009
baseline (raw 479569). It does not query ASUS for a
“latest” release. Equal or newer firmware is reported as current and
never reflashed.
When the installed value is older or unusable, install shows both
versions and asks [y/N]. Only after confirmation does it
use the matching local EXE or download that one fixed ASUS artifact.
The outer EXE and extracted capsule have independent pinned SHA-256
values; a mismatch aborts before fwupd is called. The
Windows program itself is never executed.
AC power and a reboot are required to apply a staged capsule. Unlike
configuration modules, an applied device-firmware update is not
undone by ./patch.sh uninstall.
ish-firmware — the ambient light sensor's missing firmware
The ambient light sensor sits behind the Intel Integrated Sensor Hub
(00:12.0, 8086:e445), which only runs after
the kernel uploads an OEM-signed image into its RAM at every boot.
Before the generic ish_ptl.bin the loader asks for
per-OEM names built from CRC-32s of the DMI strings, such as
intel/ish/ish_ptl_<crc32(sys_vendor)>_<crc32(product_name)>.bin
— a rule that reproduces linux-firmware's Lenovo/Dell entries exactly
— but linux-firmware ships no ASUS image, and this board rejects the
generic one. Result: no als device, ever;
/sys/bus/ishtp/devices/ stays empty and
keyboard-backlight-auto has nothing to read.
$ journalctl -k -b | grep 'ISH loader'
intel_ish_ipc 0000:00:12.0: ISH loader: load firmware: intel/ish/ish_ptl.bin
intel_ish_ipc 0000:00:12.0: ISH loader: cmd 2 failed 10
Same approach as camera-firmware: the ASUS-owned file is
not redistributed and the Windows program is never run. The module
refuses to run unless board_name is
B9406CAA, uses a verified local copy of ASUS's fixed
Intel Sensor Hub V5.8.62.0 package or downloads that
one artifact (no "latest version" query), checks the pinned SHA-256
of the EXE and of the embedded
AsusSign_ishS_SI_B9406CAA_5.8.1.7783.bin, and reloads
intel_ish_ipc so the image is uploaded without a reboot.
Files installed:
/lib/firmware/updates/intel/ish/ish_ptl_59b8d9f2_6f5619d0.bin — the ASUS-signed 5.8.1.7783 image under the vendor + product-name name every Panther Lake kernel from 6.14 on tries. /lib/firmware/updates is searched first, so nothing lands in directories linux-firmware owns and no NoExtract pin is needed.
/etc/udev/rules.d/95-asus-expertbook-ish-keep-on.rules — keeps the ISH out of runtime suspend, which intel_ish_ipc does not support; otherwise a power tool's auto-tune suspends it and als disappears
$ ./patch.sh status ish-firmware
expected: /lib/firmware/updates/intel/ish/ish_ptl_59b8d9f2_6f5619d0.bin
image: verified ASUS-signed 5.8.1.7783
loader: intel/ish/ish_ptl_59b8d9f2_6f5619d0.bin → FW 5.8.1.7783
runtime PM: kept on
ishtp: 5 client devices enumerated
als: iio:device1 reading 24.8 lux
Nothing is flashed: the image lives in ISH RAM for the duration of
the boot, and uninstall deletes the file. Install it
before keyboard-backlight-auto. The
durable fix is for ASUS to submit the image to linux-firmware the
way Lenovo, Dell and HP did. Evidence, hashes and the naming rule are
in
ish-firmware/README.md.
keyboard-backlight-auto — (optional) keyboard backlight follows the ambient light sensor
Version 1.3.0 closes disconnected input devices and discovers
replacements every five seconds, fixing excessive CPU usage after a
virtual keyboard disappears. Updates restart the daemon and status
reports its CPU average. See the
runtime health checks.
KDE PowerDevil reads the ambient light sensor for screen
brightness only — there is no keyboard equivalent. Without this module
the backlight only ever changes when you press the Fn keys, and
systemd-backlight restores a dark keyboard at every boot.
The sensor itself (iio:device1, als) only
exists once the Sensor Hub runs ASUS's signed image, so install
ish-firmware first.
The rules are borrowed, not invented. The bucketized
ambient light response (ALR) curve is Microsoft's documented Windows 11
default for Keyboard Backlight Autobrightness, reproduced
verbatim down to the registry string format:
Bucket Min lux Max lux Percentage Level here (max 3)
1 0 6 35% 1
2 5 14 52% 2
3 12 32 70% 2
4 30 45 88% 3
5 40 100 100% 3
6 95 110 88% 3
7 105 160 70% 2
8 155 205 52% 2
9 200 300 0% 0
The curve is deliberately not monotonic, which is the
part that surprises people: the keyboard is dimmest-but-on in
the dark, brightest between 40 and 100 lux, and off above 200–300 lux.
In true darkness a keyboard at full power is glare against a
dark-adapted eye; in a bright room the keycap legends are already
readable by ambient light, where backlighting only washes out their
contrast. Apple's Computer light adjustment patents
(US 7,839,379 and family) describe the simpler inverse relationship
instead — we follow Microsoft's because it is an exact, numeric,
currently-maintained table rather than a prose description.
The rest of the machinery:
- Smoothing — GNOME's
gsd-power-manager.c:
alpha = 1 / (1 + τ/dt),
acc = alpha·reading + (1−alpha)·acc, with
τ = 1/(2π × 0.1 Hz) ≈ 1.6 s. Driven by the
measured dt, so a missed sample doesn't distort it.
- Hysteresis — free, from Microsoft's
overlapping buckets (1 is 0–6 lux, 2 is 5–14, …). The
daemon stays in the bucket it is in while the reading remains inside
that bucket's range, so a reading hovering at 5.5 lux cannot flap
between 35% and 52%.
- Manual override — Microsoft's lookup table: an Fn
keypress creates a window around the current reading (at 120 lux,
40:150:0.60:0.60 gives 48–192 lux) and
autobrightness resumes once the reading leaves it. The override
means "stop writing" rather than "hold percentage X" — the
level you picked is yours until the reading leaves the window.
Detected through
brightness_hw_changed, not the Fn key
itself; see below.
- Lid — ours, not from either spec. Lid shut →
backlight forced to 0, and any active override is dropped so the
curve, not a level chosen in another room, decides on reopen. State
comes from the
Lid Switch evdev device, with
/proc/acpi/button/lid/*/state as the initial reading and
fallback.
Watch it decide without letting it touch the backlight:
sudo kbd-backlight-auto --probe -v
17.3 lux | bucket 3 (12-32 lux, 70%) | would set level 2/3
20.4 lux | bucket 3 (12-32 lux, 70%) | level 2/3 (unchanged)
Files installed:
/usr/local/bin/kbd-backlight-auto — the daemon, dependency-free Python 3
/etc/systemd/system/kbd-backlight-auto.service — ordered After= systemd-backlight@leds:asus::kbd_backlight so the curve gets the last word at boot
/etc/kbd-backlight-auto.conf — every knob, shipped fully commented out; the curve and override table use Microsoft's own string format
The one knob worth knowing about is calibration: the
curve's thresholds are absolute lux, so a mis-scaled
sensor puts the keyboard in the wrong bucket.
Finding the Fn keypress took some digging. Those keys
emit no input event at all — verified on 2026-09-02
by listening on all fifteen /dev/input/event* devices
while they were pressed, which captured nothing but touchpad traffic.
The Asus WMI hotkeys device advertises
KEY_KBDILLUMUP/KEY_KBDILLUMDOWN only because
asus-nb-wmi's sparse keymap declares them.
The OS is not blind to them though. The kernel reports EC-initiated
changes through the LED class's
brightness_hw_changed attribute
(POLLPRI) — which is how UPower notices and
relays
BrightnessChangedWithSource(level, "internal"), and in
turn what raises KDE's on-screen display. Observed on the system bus
while the keys were pressed:
member=BrightnessChangedWithSource int32 1 string "internal"
member=BrightnessChangedWithSource int32 2 string "internal"
member=BrightnessChangedWithSource int32 3 string "internal"
That attribute carries the real level, unlike the
plain brightness node stuck at
0, so the daemon both detects the press and
learns what you chose:
EC set level 3/3 | manual override active for 0.0-18.5 lux (reading 9.2)
EC set level 0/3 | manual override active for 0.0-18.4 lux (reading 9.2)
A value matching what the daemon itself last wrote is treated as its
own change echoing back, so it never starts a spurious override. To
stop the daemon touching the backlight at all, set
enabled = no or run
sudo systemctl stop kbd-backlight-auto.
keyboard-backlight-fix — (superseded) the v1.x asusd workaround, and why it was wrong
This module skips its own install. On BIOS
B9406CAA.312 with mainline asus-wmi, keyboard
brightness already reaches the EC without it. It is kept for older
firmware and for the record.
Versions 1.x claimed the BIOS's SLKB ACPI method clamped
OS-initiated brightness writes to zero, and shipped asusd
to translate them into the OEM-tested
0x100..0x103 range. Re-measured on
2026-09-02 with asusctl not
installed, /etc/asusd absent and
asusd inactive: writing 0/1/2/3 to
/sys/class/leds/asus::kbd_backlight/brightness
works — the keyboard visibly steps through all four
levels, and the KDE slider works for the same reason. But reading that
node back returns 0 every time; UPower and
brightnessctl agree, because all three read the same
attribute.
The SLKB disassembly was accurate; the claim about which
branch Linux reaches was not. Mainline asus-wmi never
writes the bare 0..3 range that the buggy branch clamps:
static void kbd_led_update(struct asus_wmi *asus)
{
int ctrl_param = 0;
scoped_guard(spinlock_irqsave, &asus_ref.lock)
ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL);
}
0x80 | level lands in
0x80..0x83 — SLKB's
second branch, the one v1.x itself documented as
working. asusd's range translation had nothing to fix.
The real defect is kbd_led_read(): the firmware's query
returns nothing usable, so the level always masks down to
0. Note this is the query path
only — the LED's brightness_hw_changed attribute does
report real levels when the EC changes the backlight itself, which is
how UPower and KDE learn about an Fn keypress. asusd does not fix that
either — it is a firmware read path, not a range problem. The visible
cost is that systemd-backlight@leds:asus::kbd_backlight
saves 0 at every shutdown and restores a dark
keyboard at every boot; keyboard-backlight-auto is ordered
After= it and overrides it within a second.
The v1.x status check was a false negative by
construction — it wrote a level and read it back, and the read
is always 0, so it reported
FAILED on a perfectly working backlight. That check is
gone.
What stays unknown is whether software control was genuinely broken on
BIOS B9406CAA.304, the firmware v1.x was written against;
the reference machine has since moved to 312
and 304 is no longer testable. What can be said is that the
mechanism v1.x blamed cannot have been the cause. That
uncertainty is why the module is kept rather than deleted:
sudo KBF_FORCE=1 ./patch.sh install keyboard-backlight-fix