2026 · Panther Lake · B9406CAA

ASUS ExpertBook Ultra on Linux,
without the broken bits.

Thirteen focused, versioned modules that turn the 2026 ASUS ExpertBook Ultra (B9406CAA) into a working Linux laptop — without manually rebuilding the kernel. Configuration changes are reversible; camera firmware is separately confirmed before staging.

site MIT Linux 6.18+ B9406CAA No kernel patches

Is this repo for me?

Run this one-liner. It's read-only, no sudo, no system changes — just probes the hardware and tells you which modules apply.

curl -fsSL https://raw.githubusercontent.com/burakgon/asus-expertbook-linux/main/scripts/check-hardware.sh | bash

Expected output on this exact laptop:

asus-expertbook-linux — hardware compatibility probe

Laptop model
  OK   ASUS EXPERTBOOK B9406CAA (full match — every module fits)

CPU
  OK   Intel(R) Core(TM) Ultra X7 358H (Panther Lake / Core Ultra Series 3)

Touchpad
  OK   PixArt 093A:4F05 (ACPI ASCP1D80) — touchpad-fix applies directly

Audio (Cirrus CS42L43 + 2× CS35L56)
  OK   subsystem 1043:15e4 — audio-fix firmware blobs apply directly
  OK   cs35l56 / cs42l43 driver loaded

Wi-Fi
  OK   Intel Wi-Fi 7 BE211 (8086:e440) — wifi-fix applies directly
  OK   iwlmld op_mode loaded (Wi-Fi 7 driver)

Fingerprint
  OK   FocalTech FT9349 (2808:a97a) — supported by upstream libfprint 1.94.100+
  OK   libfprint 1.94.100 installed — no custom Git package required

Camera firmware
  OK   ASUS 3009 / 10.1.2.3009 baseline satisfied (ESRT raw 479569)

Distro
  OK   CachyOS Linux — pacman; every module is supported here
  package manager: pacman    boot parameters: limine

Result: install-all is appropriate for this hardware.

Before / after

Each row is one symptom you'll hit on a fresh Linux install of this laptop, and what changes after the corresponding module is installed. No band-aids in the bad sense — every fix uses the same upstream- recognised mechanism distros already use for other laptops.

PixArt I²C-HID haptic touchpad 093A:4F05 (ACPI ASCP1D80)
Before — touchpad Cursor doesn't move when you touch the pad. Kernel log spams
kernel bug: Touch jump detected and discarded.
→
After — touchpad-fix Light touch responds normally. libinput quirks list /dev/input/event9 confirms the pressure-axis quirk is loaded.

A second failure freezes the whole desktop. i2c_designware.0 logs controller timed out, then timeout in disabling adapter, then timeout waiting for bus ready about twenty times a second, and the ASCP1D80 IRQ thread and i915_flip workers sit in D-state. It was logged on Linux 7.2.3 at the end of long sessions, once within a minute of a resume. While the session still accepts input, rebind the touchpad's i2c_hid_acpi device (omarchy restart trackpad on Omarchy); if that hangs, reboot. Do not poll acpitz or ASUS hwmon fan nodes from a status bar. The pressure quirk above does not prevent this wedge. Commands and the logged incidents are in docs/b9406-desktop-freeze.md.

PixArt haptic touchpad 093A:4F05 (Windows Precision pressure pad)
Before — click force & haptics No click-force or haptic-strength setting. MyASUS sets both on Windows; Linux has no control, and the firmware forgets them at power-off.
→
After — touchpad-haptics (optional) touchpad-haptics set --click-force light --intensity 30; saved values are restored whenever the pad appears. Standard HID feature reports only, verified against the pad's descriptor.
Cirrus CS42L43 codec + 2× CS35L56 speaker amps (PCI subsystem 1043:15e4)
Before — speakers “Dummy Output” or silence. The ghost RT722 can make sof_sdw fail with -12; older firmware/UCM packages also leave the speaker amps unrouted.
→
After — audio-fix The accepted in-kernel B9406CAA quirk is used when present; older kernels get a narrowly scoped DKMS compatibility overlay. The real HiFi profile and CS35L56 tuning firmware are active.
Intel Wi-Fi 7 BE211 Panther Lake CNVi (8086:e440)
Before — Wi-Fi Wi-Fi 7 (802.11be / EHT) unstable: EHT RX can collapse to MCS0/NSS1 and MLO sessions tear down. Linux 7.2's C106 firmware may separately flood missed beacons warnings even while data flows.
→
After — wifi-fix EHT disabled (disable_11be=Y) → fast Wi-Fi 6 / HE (160 MHz, ~2.1 Gbit/s). Status reports the loaded firmware and the warning count without hiding logs or forcing a firmware downgrade.
Samsung Display Corp eDP panel + Intel xe driver (Xe3 Panther Lake iGPU)
Before — internal panel Linux 7.2's Panel Replay default brings PSR idle timeouts with on-screen corruption, flicker, VRR smearing, stale frames, and HDR washed out after every HDR modeset; 7.0/7.1 went black outright (kwin_wayland: Pageflip timed out!).
→
After — display-fix Self-refresh pinned to PSR1: owners report no flicker, stale frames or VRR smearing, and HDR stays vivid across toggles; xe.enable_dpcd_backlight=2 makes panel brightness follow the sysfs/KDE setting.
Samsung OLED HDR (EDID HDR metadata inside DisplayID 2.0)
Before — no HDR toggle KDE offers no HDR toggle. libdisplay-info 0.3.0 never looks inside the DisplayID 2.0 extension where this panel declares PQ, 1600 cd/m² and BT.2020, so KWin sees an SDR panel.
→
After — hdr-fix (optional) The upstream commits that read DisplayID 2.0 CTA blocks, plus two parser fixes of our own, backported onto 0.3.0 with the .so.3 ABI intact and preferred through ld.so.conf.d; HDR appears in Display & Monitor. Removed again once the distro ships 0.4.0.
Intel Core Ultra X7 / X9 Panther Lake hybrid (P + E + LP-E cores)
Before — thermal policy No userspace thermal policy: the OEM's adaptive thermal tables (PL1/PL2 limits, passive trips) are not applied; only the kernel's int340x sensors and limits are exposed.
→
After — intel-perf-fix thermald runs the OEM tables in adaptive mode. intel-lpmd is opt-in: on Panther Lake it showed no significant idle gain and slowed app launches.
Platform profiles (Intel SoC Power Slider + asus-wmi)
Before — Power Save Power Save only reaches balanced, and from Performance it snaps back to Balanced. The legacy profile file lists only the choices both handlers share, so the SoC slider never goes low-power and the fans never go quiet.
→
After — power-profile-bridge Power Save sets the SoC slider to low-power and asus-wmi to quiet and stays there; Balanced and Performance map one-to-one. power-profiles-daemon stays in charge of the profile and CPU EPP.
Intel Panther Lake NPU (8086:b03e) + USB UVC webcam
Before — AI camera effects No Linux equivalent of Windows Studio Effects. NPU sits idle, no virtual cam, no background blur / smart framing / voice focus shipped out of the box.
→
After — webcam-ai-fix OBS pipeline + obs-backgroundremoval plugin + virtual cam (/dev/video10 "AI Camera"). CPU background blur — the OBS plugin has no NPU path on Linux.
Shinetech USB camera + UEFI ESRT target
Before — camera firmware ASUS distributes camera firmware 3009 only inside a Windows EXE.
→
After — camera-firmware Local ESRT version is compared with the fixed, hash-pinned 3009 baseline. If older, the signed capsule is offered and staged via fwupd; the Windows executable is never run.
Intel Sensor Hub (8086:e445, carries the ambient light sensor)
Before — no ambient light sensor The kernel's generic ish_ptl.bin is rejected (ISH loader: cmd 2 failed 10); linux-firmware has no ASUS image, so /sys/bus/iio never gets an als device.
→
After — ish-firmware The ASUS-signed image from ASUS's own Sensor Hub driver package is verified and installed under the per-OEM name the kernel requests; iio:device1 = als appears and keyboard-backlight-auto has a sensor to read.
Ambient light sensor (iio als) + keyboard backlight
Before — keyboard backlight never adapts KDE PowerDevil reads the sensor for screen brightness only; there is no keyboard equivalent. The backlight stays wherever the Fn keys left it, and comes up dark after every boot.
→
After — keyboard-backlight-auto (optional) The backlight follows the room using Windows 11's documented ALR curve: dim in the dark, brightest around 40–100 lux, off above 200–300 lux. Forced off with the lid shut; the Fn keys still take over on demand.
ASUS BIOS SLKB ACPI method (BIOS B9406CAA.312)
Before — brightness reads back as zero /sys/class/leds/asus::kbd_backlight/brightness, UPower and brightnessctl all report 0 whatever the real level is, so systemd-backlight saves and restores a dark keyboard at every boot. Writes themselves reach the EC fine.
→
After — keyboard-backlight-fix (superseded) Nothing to fix on the write path: the v1.x asusd workaround targeted an ACPI branch mainline asus-wmi never reaches. The module now skips its own install and documents the correction; keyboard-backlight-auto works around the boot-time reset.

Install

Three idiomatic ways to use the patcher. Configuration installs are idempotent and reversible; an older camera firmware is only offered after a separate confirmation.

Option 1 — install everything

git clone https://github.com/burakgon/asus-expertbook-linux.git
cd asus-expertbook-linux
./patch.sh install-all
sudo reboot

Option 2 — pick à la carte

./patch.sh list                       # see what's available + state
./patch.sh install touchpad-fix audio-fix
./patch.sh diff display-fix           # preview changes before applying
./patch.sh uninstall wifi-fix         # back out anytime

Option 3 — interactive menu

./patch.sh    # auto-elevates with sudo

=== asus_expertboot_linux patcher ===

  #   Module                    Version    Installed  State          Description
  --------------------------------------------------------------------------------------
  1   audio-fix                 3.2.0      3.2.0      up to date     Adaptive ghost-RT722 fix + HiFi audio
  2   camera-firmware           3009       3009       up to date     Verified camera UEFI capsule
  3   display-fix               1.4.0      1.4.0      up to date     PSR1 self-refresh + DPCD brightness
  4   hdr-fix                   1.0.0      1.0.0      up to date     DisplayID 2.0 HDR metadata for KWin
  5   intel-perf-fix            1.3.0      1.3.0      up to date     thermald (+ opt-in intel-lpmd)
  6   ish-firmware              5.8.1.7783 5.8.1.7783 up to date     ASUS Sensor Hub image (ambient light)
  7   keyboard-backlight-auto   1.3.0      1.3.0      up to date     Ambient-light keyboard backlight
  8   keyboard-backlight-fix    2.0.0      -          not installed  (superseded) asusd workaround
  9   power-profile-bridge      1.0.0      1.0.0      up to date     Power-saver → SoC low-power + quiet fans
  10  touchpad-fix              1.2.1      1.2.1      up to date     PixArt 093A:4F05 pressure quirk
  11  touchpad-haptics          1.0.0      1.0.0      up to date     Click force + haptic intensity
  12  webcam-ai-fix             1.1.0      1.1.0      up to date     OBS CPU background blur
  13  wifi-fix                  2.1.1      2.1.1      up to date     BE211: EHT fallback + beacon diagnostics

Actions
  i <num>    install / update module
  u <num>    uninstall module
  d <num>    diff source vs installed
  s <num>    detailed status
  I          install all modules
  up         update all currently-installed
  U          uninstall all modules
  r          refresh
  q          quit

>

After install, verify any time with:

./patch.sh status

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.

ModuleVersionFixes
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
  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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-daemonSoC Power Sliderasus-wmi
power-saverlow-powerquiet
balancedbalancedbalanced
performanceperformanceperformance

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

The patcher

patch.sh is a small bash module manager (~500 lines). Each subfolder containing a module.sh manifest is a discoverable module. The patcher records the installed version under /var/lib/asus_expertboot_patcher/ so subsequent operations know whether each module needs an update.

Distros. Arch and its derivatives (CachyOS, EndeavourOS, Manjaro) are the reference platform and run every module. Debian, Ubuntu and Pop!_OS are supported incrementally: lib/distro.sh abstracts the package manager, kernel-header discovery, initramfs regeneration, kernel-cmdline backend (Limine / kernelstub / GRUB) and service enablement, and modules are being moved onto it one at a time. Working there today: touchpad-fix, wifi-fix (config files only) and intel-perf-fix. Progress is tracked in issue #4.

Commands

./patch.sh                # interactive menu
./patch.sh list           # discover + state
./patch.sh status         # detailed
./patch.sh diff           # source vs installed
./patch.sh install <mod>  # idempotent (also "update")
./patch.sh update-all     # only re-installs out-of-date
./patch.sh uninstall <mod>

State table

FilesState fileReported as
none—not installed
all—untracked
allmatchesup to date
allolderupdate available
someanypartial

Upstream submissions

The repo separates accepted, pending and retired upstream work, plus drafts for other trackers. The B9406CAA SoundWire fix is accepted in Linus' tree; the display and libinput quirks are written but not sent yet; the former sidecar-amplifier proposal was invalid and has been removed.

#TreeReplaces
0001 drm-intel-next drivers/gpu/drm/i915/display/intel_quirks.c display-fix: pins this laptop to PSR1 through the existing Panel Replay (subsystem + sink OUI) and PSR2 (PCI ID) quirk tables. Not sent: tested only through the equivalent module parameters; still needs one built-kernel test
former 0002 Linux sound Removed: B9406CAA is not a sidecar-amplifier design
0003 libinput/quirks/30-vendor-pixart.quirks touchpad-fix's libinput override: marks 093A:4F05 as a pressure pad (AttrInputProp=+INPUT_PROP_PRESSUREPAD), as upstream did for the sister pad 4811. Not sent: pending an on-device test with a libinput record attached
0004 drivers/soundwire/dmi-quirks.c Accepted upstream as 90af3209742d (first in Linux 7.3); exact patch retained for backports. A stable request for 7.2.y is drafted
0005 power-profiles-daemon src/ppd-driver-platform-profile.c power-profile-bridge's drop-in: an emulated power-saver no longer reads its own balanced write back as a switch to balanced. Not sent: its new integration test fails on main every time and the full suite passes with the fix (129/129)
0006 drivers/platform/x86/asus-wmi.c Keyboard backlight read-back quirk from #12: kbd_led_get() returns the cached level instead of the constant 0 this firmware reports. Not sent
drafts drm/xe issue; libdisplay-info; Arch packaging The drm/xe issue text that accompanies 0001, a libdisplay-info report for the DisplayID v2 overread fixed in hdr-fix, and a request for Arch to ship libdisplay-info 0.4.0, which would make hdr-fix unnecessary. All are for the maintainer to send

See the live tracking notes, backport status and retired-patch rationale in upstream-patches/README.md (status rechecked on 2026-09-28 against 7.3-rc5, the 7.2.8 and 6.18.54 stable releases, and drm-intel-next).

FAQ

Does this work on similar 2026 ExpertBook Ultra models?

Most of it transfers. The audio-fix firmware blobs are matched on PCI subsystem 1043:15e4 (this exact laptop). Sibling subsystems 104315d4 and 104315f4 ship different per-OEM tuning files in upstream linux-firmware. The camera capsule is strictly B9406CAA-only, and so is ish-firmware: the Sensor Hub image is signed for this board, so the module refuses to install on other models. The touchpad-fix, wifi-fix, display-fix, intel-perf-fix, webcam-ai-fix, keyboard-backlight-auto and keyboard-backlight-fix modules are hardware-agnostic or match by family-level identifiers and apply more broadly. PRs adding sibling-model entries are welcome.

Does the fingerprint reader work?

Yes. Upstream libfprint 1.94.100 and later supports the FocalTech FT9349 (2808:a97a). Current Arch/CachyOS users should install the normal libfprint and fprintd packages, then run fprintd-enroll; no custom Git package or pacman pin is needed.

Does this break Bluetooth?

No. The Wi-Fi module deliberately leaves iwlwifi.bt_coex_active=Y alone. Bluetooth audio, HID, and file transfer keep working as usual.

Does this downgrade Wi-Fi 7?

Yes — on purpose. 802.11be / EHT is broken on the BE211 (RX collapses to MCS0/NSS1, MLO tears down), so wifi-fix disables it (disable_11be=Y) and the link runs as stable Wi-Fi 6 / HE instead — ~2.1 Gbit/s over 160 MHz 6 GHz here, faster in practice than the flaky EHT link. Drop the module (or set disable_11be=N) once Intel fixes the iwlwifi EHT path upstream.

What about the F1 mute LED?

The HiFi UCM (active since audio-fix v2.0.0, and upstream in alsa-ucm-conf 1.2.16) drives the mic-mute LED (platform::micmute) correctly. The speaker-mute LED (F1) stays in its EC default state before Linux 7.4, because until then this laptop exposes no speaker-mute LED device — there's nothing for the UCM SetLED hook to bind to. Linux 7.4's asus-wmi adds platform::mute (WMI device 0x0004001C) with an audio-mute trigger. It's a missing-device limitation, not a profile issue.

Why not just upstream all of this and skip the repo?

That's the goal — see upstream-patches/. Two of the audio pieces already landed: alsa-ucm-conf 1.2.16 ships the cs42l43-spk+cs35l56 codec dir and linux-firmware-cirrus >= 20260519 ships the OEM blobs, and the B9406CAA ghost-RT722 kernel quirk is accepted as 90af3209742d. It landed after Linux 7.2, so audio-fix detects the marker in each installed kernel and keeps its DKMS compatibility overlay only where a release or distro backport still lacks it.

Is this safe to install on a working setup?

The configuration modules are reversible: ./patch.sh uninstall <mod> removes their files and reverts service enables. All file targets are well-known system paths (/etc/modprobe.d, /etc/udev/hwdb.d, /etc/udev/rules.d, /etc/libinput, /etc/systemd/system, /etc/ld.so.conf.d, /lib/firmware, /usr/share/alsa, /usr/local, /etc/wireplumber/wireplumber.conf.d) — no packaged kernel replacement or pacman lock manipulation. display-fix regenerates boot entries from a removable Limine drop-in; hdr-fix keeps its patched libdisplay-info under /usr/local/lib without touching pacman-owned files; ish-firmware puts its image under /lib/firmware/updates, outside the directories linux-firmware owns, and flashes nothing. camera-firmware is the deliberate exception: it asks separately before staging a signed capsule, and applied device firmware is not downgraded by uninstall.

How do I test changes before installing?
./patch.sh diff <module>
# shows source vs installed for every file the module manages,
# marked unchanged / would update / would create
Where's the project licensed?

MIT for code (scripts, configs, patches). Firmware blobs under audio-fix/ are redistributed verbatim from upstream linux-firmware under their original Cirrus Logic redistribution license; see NOTICE.