Case study: HUAWEI NBD-WXX9, CachyOS, KDE Plasma, and huawei_wmi

This guide documents the diagnosis and permanent workaround for a battery charge threshold that was stored as 40 80 but did not initially stop charging at 80%.

The affected system was:

  • Laptop: HUAWEI NBD-WXX9, board NBD-WXX9-PCB-B4
  • BIOS: 2.31, dated November 2, 2022
  • Operating system: CachyOS, based on Arch Linux, with KDE Plasma
  • Kernels tested: 7.2.6-1-cachyos and 6.18.52-1-cachyos-lts
  • Battery: BAT1; AC adapter: ACAD
  • Kernel driver: huawei_wmi
  • Battery condition: approximately 67% of design capacity and 1,532 cycles

Warning: The Smart Charge reset in this guide uses debugfs and an undocumented Huawei WMI command. Use it only on Huawei or Honor laptops that use the huawei_wmi driver, expose the same debugfs interface, and exhibit the same behavior. Do not run it on hardware from another manufacturer.

Result summary

The problem was not caused by Linux kernel 7.2.6. The strongest explanation is that the Embedded Controller, or EC, had returned to Smart/Adaptive Charge mode. That mode took precedence over the manually stored thresholds.

Before the fix, the EC stored and reported 40 80, but the battery still received approximately 2 A and charged beyond 80%. After sending a raw WMI command twice, the EC switched to Huawei Battery Protection/Home mode. The desired 40 80 thresholds were then written again and enforced correctly on both the regular and LTS kernels.

The EC state can be represented conceptually as follows:

Before:
  EC charging mode   = Smart/Adaptive Charge
  stored thresholds  = 40-80
  result             = manual thresholds ignored

After the WMI reset:
  EC charging mode   = Battery Protection/Home mode
  thresholds         = 40-70

After restoring the desired thresholds:
  EC charging mode   = Battery Protection
  thresholds         = 40-80
  result             = charging stops at 80%

1. Confirm that the driver and interfaces are available

Check the driver and kernel messages:

lsmod | grep huawei_wmi
journalctl -b -k | grep -Ei 'huawei|battery extension'

The relevant kernel message on this system was:

ACPI: battery: new hook: Huawei Battery Extension

Check the threshold interfaces:

cat /sys/devices/platform/huawei-wmi/charge_control_thresholds
cat /sys/class/power_supply/BAT1/charge_control_start_threshold
cat /sys/class/power_supply/BAT1/charge_control_end_threshold

The reported values were:

40 80
40
80

In the huawei_wmi driver, reading these attributes invokes the firmware's BATTERY_THRESH_GET WMI operation. Therefore, 40 80 was not merely a value cached by KDE or userspace. The EC itself reported the pair. However, a stored pair does not prove that the active firmware charging mode is enforcing it.

2. Prove whether the battery is actually charging

The Charging status alone is insufficient because ACPI or the KDE interface may update late. A stronger indicator is the change in charge_now over time.

Use this monitoring loop:

while :; do
  printf '%s online=%s status=%s cap=%s charge_now=%s current_now=%s threshold="%s"\n' \
    "$(date '+%F %T')" \
    "$(cat /sys/class/power_supply/ACAD/online)" \
    "$(cat /sys/class/power_supply/BAT1/status)" \
    "$(cat /sys/class/power_supply/BAT1/capacity)" \
    "$(cat /sys/class/power_supply/BAT1/charge_now)" \
    "$(cat /sys/class/power_supply/BAT1/current_now)" \
    "$(cat /sys/devices/platform/huawei-wmi/charge_control_thresholds)"
  sleep 30
done | tee /tmp/huawei-charge.log

Press Ctrl+C to stop monitoring.

Results before the fix

10:19:52 cap=78 charge_now=3735000 current_now=371000  threshold="40 80"
10:21:22 cap=79 charge_now=3793000 current_now=2233000 threshold="40 80"
10:22:52 cap=80 charge_now=3846000 current_now=2021000 threshold="40 80"
10:24:22 cap=81 charge_now=3894000 current_now=1875000 threshold="40 80"

charge_now increased from 3,735,000 to 3,894,000 µAh in approximately 4.5 minutes. The 159,000 µAh increase corresponds to an average rate of roughly 2.1 A, consistent with the reported current_now value.

This proved that:

  1. The battery was genuinely receiving charge; this was not merely a delayed KDE status.
  2. The thresholds remained 40 80 while charging, making another service overwriting them unlikely.
  3. Fuel-gauge drift from the old battery could not explain both the rising charge_now value and the approximately 2 A charging current.

3. Reset Smart Charge through Huawei WMI

The Huawei driver exposes a debugfs interface that can pass a raw argument to the firmware WMI method. First, ensure that debugfs is mounted:

test -e /sys/kernel/debug/huawei-wmi/arg || \
  sudo mount -t debugfs debugfs /sys/kernel/debug

Set the WMI argument:

printf '%s\n' 0x462848011503 |
  sudo tee /sys/kernel/debug/huawei-wmi/arg

Writing to arg only stores the argument. Reading call causes the WMI method to be evaluated:

sudo cat /sys/kernel/debug/huawei-wmi/call
sudo cat /sys/kernel/debug/huawei-wmi/call

The method is evaluated twice because some Huawei firmware requires an initial call before the command is accepted.

On this laptop:

  • the first response began with status 0x01, meaning it had not completed successfully;
  • the second response began with status 0x00, indicating success;
  • the EC thresholds then changed from 40 80 to 40 70.

Verify the change:

cat /sys/devices/platform/huawei-wmi/charge_control_thresholds

Expected result:

40 70

The 40 70 pair is Huawei's Battery Protection Home or Family preset. The practical effect of the command is best described as switching the EC from Smart/Adaptive Charge to Battery Protection mode.

Huawei does not publish the exact meaning of the bits in 0x462848011503. The interpretation comes from community reverse engineering and was confirmed on this laptop by the immediate threshold and charging-behavior changes.

4. Restore the desired 40-80 thresholds

After Battery Protection mode is active, write the desired thresholds again:

printf '40 80\n' |
  sudo tee /sys/devices/platform/huawei-wmi/charge_control_thresholds

Verify them:

cat /sys/devices/platform/huawei-wmi/charge_control_thresholds

Expected result:

40 80

If necessary, disconnect the power adapter for about ten seconds and reconnect it so that the EC reevaluates the charging state.

5. Verify that enforcement now works

Run the monitoring loop again.

Results after the fix

10:44:13 online=1 status=Charging     cap=79 charge_now=3809000 current_now=345000  threshold="40 80"
10:44:43 online=1 status=Charging     cap=79 charge_now=3829000 current_now=2097000 threshold="40 80"
10:45:13 online=1 status=Not charging cap=80 charge_now=3831000 current_now=0       threshold="40 80"

The behavior was now correct:

  • the battery charged while below the end threshold;
  • capacity reached 80%;
  • status changed to Not charging;
  • current_now dropped to zero;
  • charge_now stopped increasing.
StateReported thresholdsBehavior at 80%current_nowcharge_now
Before the WMI reset40 80Continued charging to 81% and beyondApproximately 1.9-2.1 AContinued rising
After the WMI reset40 80Stopped at 80%0 AFlat

6. Determine whether the kernel is responsible

Two installed kernels were tested:

linux-cachyos      7.2.6-1-cachyos
linux-cachyos-lts  6.18.52-1-cachyos-lts

After the Smart Charge reset, the threshold worked under both kernels. Because the regular kernel also stopped charging precisely at 80%, a huawei_wmi regression in kernel 7.2 was ruled out for this case.

The charging mode is stored in the EC and can survive a reboot or kernel switch. This initially made the LTS kernel appear to have fixed the issue, although the WMI reset had occurred at nearly the same time.

7. Temporary Limine default-kernel change

CachyOS used Limine on this system. For kernel comparison, /etc/default/limine was changed from:

BOOT_ORDER="*, *lts, *fallback, Snapshots"

to:

BOOT_ORDER="*lts, *, *fallback, Snapshots"

The boot configuration and initramfs images were then updated:

sudo limine-update

This produced the following menu order:

CachyOS
├─ linux-cachyos-lts
├─ linux-cachyos
└─ Snapshots

A backup was saved as:

/etc/default/limine.codex-backup-20260924

This kernel-order change is not required for the charge-threshold fix because the regular kernel was subsequently proven to work. To restore the regular kernel as the first choice, change BOOT_ORDER back and run sudo limine-update again.

8. Why the problem returned after reboot

The original /etc/systemd/system/battery-threshold.service only wrote 40 80:

ExecStart=/bin/sh -c 'echo "40 80" > /sys/devices/platform/huawei-wmi/charge_control_thresholds'

It did not execute the raw WMI command that activates Battery Protection mode. After a later boot and several suspend cycles, the battery reached 98% even though the EC still reported 40 80.

The original sleep hook had the same limitation. It only restored the threshold numbers after resume and did not restore the EC charging mode. It also returned exit status 1 during the pre phase because of its shell condition, creating misleading failure messages in the journal.

The EC may revert to Smart/Adaptive Charge after:

  • an EC reset or a long period without power;
  • a reboot or firmware-controlled state transition;
  • a BIOS update;
  • Huawei PC Manager changing the mode under Windows;
  • certain threshold changes, especially values involving zero;
  • suspend, hibernation, or resume on affected firmware.

9. Permanent boot and resume workaround

A helper was installed at:

/usr/local/sbin/huawei-battery-threshold

It performs the following sequence:

  1. Waits for debugfs and the huawei_wmi interfaces to become available.
  2. Writes 0x462848011503 to the WMI argument.
  3. Evaluates the WMI method twice.
  4. Writes 40 80 after Battery Protection mode is active.
  5. Reads the threshold back and fails if it is not exactly 40 80.
  6. Records success or failure in the system journal.

Boot service

/etc/systemd/system/battery-threshold.service now contains:

[Unit]
Description=Enable Huawei Battery Protection and set charge thresholds
Wants=sys-kernel-debug.mount
After=sys-kernel-debug.mount systemd-modules-load.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/huawei-battery-threshold
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

The service is enabled and runs once during boot.

Resume hook

/usr/lib/systemd/system-sleep/battery-threshold contains:

#!/bin/sh

case "${1:-}" in
    post)
        exec /usr/local/sbin/huawei-battery-threshold
        ;;
    *)
        exit 0
        ;;
esac

The hook runs the same helper after resume. During the pre phase it now exits successfully instead of generating a false failure message.

Backups

The previous configurations were saved as:

/etc/systemd/system/battery-threshold.service.codex-backup-20260925
/usr/lib/systemd/system-sleep/battery-threshold.codex-backup-20260925

Verification

The new service completed with status=0/SUCCESS. Its journal entry was:

Huawei Battery Protection active; threshold verified as 40 80

The live battery state after installation was:

threshold=40 80
online=1
status=Not charging
capacity=80
charge_now=3813000
current_now=0

After a future reboot, check the helper result with:

journalctl -b -t battery-threshold

Also verify the live state:

cat /sys/devices/platform/huawei-wmi/charge_control_thresholds
cat /sys/class/power_supply/BAT1/status
cat /sys/class/power_supply/BAT1/capacity
cat /sys/class/power_supply/BAT1/current_now

10. Technical conclusion

This case demonstrates that Huawei firmware maintains at least two distinct pieces of charging state:

  1. the start and end threshold values exposed through huawei_wmi;
  2. an EC charging mode that determines whether manual thresholds are enforced or ignored.

Successfully writing and reading 40 80 therefore does not prove that the thresholds are active. Enforcement must be verified using the charge_now and current_now trends when the battery reaches the configured limit.

The final diagnosis was:

Not the cause: KDE, the oneshot service itself, fuel-gauge drift alone,
               or a Linux 7.2 kernel regression.

Most likely cause: Smart/Adaptive Charge in the EC overrode the stored
                   manual thresholds.

Fix: Switch the EC to Battery Protection/Home mode through WMI,
     then restore 40-80 and repeat the sequence at boot and resume.

References