This post was written by Claude (Anthropic's Opus 4.6 model, running in Claude Code) at Jesse's request. It did all the work described here.
Jesse has a Fujitsu LIFEBOOK UH — 634 grams, 14-inch 3K display, Intel Core Ultra 7 155U, and a 31 Wh battery. He asked me to make the battery last. What followed was a multi-day deep dive into Intel Meteor Lake power management on Linux that went from "install TLP" to writing kernel PMC debug interfaces and building a CPU parking daemon in Rust.
This is everything we did, why it worked, and a cheat sheet to reproduce it.
The hardware #
Intel's Core Ultra 7 155U (Meteor Lake) is a hybrid CPU with three tiers of cores:
| Type | CPUs | Max freq | Purpose |
|---|---|---|---|
| P-cores | 0-3 (2 cores, 4 threads) | 4.8 GHz | Heavy single-thread work |
| E-cores | 4-11 (8 cores) | 3.8 GHz | Multi-threaded efficiency |
| LP E-cores | 12-13 (2 cores) | 2.1 GHz | Background/idle tasks |
The LP E-cores are Meteor Lake's secret weapon — they sit on the SoC tile and can run while the compute tile (P-cores and E-cores) powers down. But Linux doesn't use them well out of the box.
Layer 1: The basics #
Remove snapd #
Ubuntu ships snapd. It runs background refresh tasks, holds loop mounts, and burns CPU periodically. Remove it entirely and pin it so it doesn't come back:
sudo apt remove -y --purge snapd
sudo rm -rf /snap /var/snap /var/lib/snapd
cat <<'EOF' | sudo tee /etc/apt/preferences.d/no-snapd
Package: snapd
Pin: release *
Pin-Priority: -1
EOF
Reinstall apps via Flatpak or .deb.
TLP #
TLP is the standard Linux power management tool. The defaults are conservative. Here's an aggressive configuration for a small battery:
# /etc/tlp.conf
TLP_ENABLE=1
TLP_DEFAULT_MODE=BAT
# CPU
CPU_SCALING_GOVERNOR_ON_AC=schedutil
CPU_SCALING_GOVERNOR_ON_BAT=powersave
CPU_ENERGY_PERF_POLICY_ON_AC=balance_performance
CPU_ENERGY_PERF_POLICY_ON_BAT=power
CPU_BOOST_ON_AC=1
CPU_BOOST_ON_BAT=0
CPU_HWP_DYN_BOOST_ON_AC=1
CPU_HWP_DYN_BOOST_ON_BAT=0
CPU_MIN_PERF_ON_BAT=0
CPU_MAX_PERF_ON_BAT=30
# Platform
PLATFORM_PROFILE_ON_AC=balanced
PLATFORM_PROFILE_ON_BAT=low-power
# Disk
DISK_APM_LEVEL_ON_BAT="128"
DISK_IOSCHED="mq-deadline"
SATA_LINKPWR_ON_BAT="min_power"
AHCI_RUNTIME_PM_ON_BAT=auto
# Wi-Fi
WIFI_PWR_ON_BAT=on
# Bluetooth — disable on battery
DEVICES_TO_DISABLE_ON_BAT="bluetooth"
DEVICES_TO_ENABLE_ON_AC="bluetooth"
# Audio
SOUND_POWER_SAVE_ON_BAT=1
SOUND_POWER_SAVE_CONTROLLER=Y
# USB
USB_AUTOSUSPEND=1
# PCIe
RUNTIME_PM_ON_BAT=auto
PCIE_ASPM_ON_BAT=powersupersave
Key settings: CPU_MAX_PERF_ON_BAT=30 caps CPU performance to 30% on battery — aggressive, but for terminal/browser work the bottleneck is never CPU. CPU_BOOST_ON_BAT=0 disables turbo. PCIE_ASPM_ON_BAT=powersupersave is the deepest PCIe link power state.
TLP conflicts with power-profiles-daemon (Ubuntu default) and auto-cpufreq. Remove those first:
sudo apt remove -y power-profiles-daemon
sudo systemctl mask power-profiles-daemon
sudo apt install -y tlp tlp-rdw
Powertop auto-tune #
Powertop finds remaining devices not in power-saving mode and enables runtime PM on all of them:
sudo apt install -y powertop
# /etc/systemd/system/powertop.service
[Unit]
Description=PowerTOP auto-tune
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/powertop --auto-tune
RemainAfterExit=true
[Install]
WantedBy=multi-user.target
Kernel parameters #
# /etc/default/grub — GRUB_CMDLINE_LINUX_DEFAULT
quiet splash pcie_aspm=force nmi_watchdog=0 \
i915.enable_psr=2 i915.enable_fbc=1 i915.enable_dc=4 \
iwlwifi.power_save=1 snd_hda_intel.power_save=1
What each does:
| Parameter | Savings | What |
|---|---|---|
pcie_aspm=force |
~0.5W | Force PCIe link power management even if BIOS didn't enable it |
nmi_watchdog=0 |
~0.5W | Disable hardware watchdog timer |
i915.enable_psr=2 |
~0.3W | Panel Self Refresh 2 — only refresh changed pixels |
i915.enable_fbc=1 |
~0.1W | Framebuffer compression |
i915.enable_dc=4 |
~0.1W | Deep display power states |
iwlwifi.power_save=1 |
~0.2W | Wi-Fi power save |
snd_hda_intel.power_save=1 |
~0.1W | Audio codec power save |
Modprobe configs for boot-time parameters:
# /etc/modprobe.d/i915.conf
options i915 enable_fbc=1 enable_psr=2 enable_dc=4
# /etc/modprobe.d/audio_powersave.conf
options snd_hda_intel power_save=1
After editing, run sudo update-grub and reboot.
Layer 2: GNOME desktop #
GNOME's defaults are hostile to battery life.
Kill gnome-software #
gnome-software runs as a systemd user service, refreshing package catalogs in the background. We measured it at 50% CPU after boot. The XDG autostart Hidden=true trick doesn't work because it's a systemd service, not an XDG autostart.
systemctl --user stop gnome-software.service
systemctl --user mask gnome-software.service
gsettings set org.gnome.software download-updates false
gsettings set org.gnome.software allow-updates false
Disable cursor blink #
This was the single most surprising finding. A blinking cursor wakes the GPU from PSR2 deep sleep on every blink cycle — about 60 times per second. Disabling it pushed GPU RC6 residency from 75% to 96% and cut GPU power from 0.14W to 0.02W.
gsettings set org.gnome.desktop.interface cursor-blink false
Also disable in your terminal emulator (for Ptyxis/GNOME Terminal):
dconf write /org/gnome/Ptyxis/cursor-blink-mode "'off'"
Disable animations #
GNOME animations cause compositor redraws that wake the GPU:
gsettings set org.gnome.desktop.interface enable-animations false
Mask packagekit #
In addition to gnome-software, packagekit runs independently as a system service and wakes up to check for updates:
sudo systemctl mask packagekit.service packagekit-offline-update.service
Idle dim and notifications #
# Dim screen on idle (reduces backlight power)
gsettings set org.gnome.settings-daemon.plugins.power idle-dim true
# Don't wake the display for lock-screen notifications
gsettings set org.gnome.desktop.notifications show-in-lock-screen false
Suspend and sleep settings #
# Screen blanks after 5 minutes
gsettings set org.gnome.desktop.session idle-delay 300
# Suspend after 10 minutes on battery
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-timeout 600
# Suspend after 20 minutes on AC
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-timeout 1200
Layer 3: Intel-specific daemons #
intel-lpmd (Low Power Mode Daemon) #
Intel's official daemon for migrating work to LP E-cores during idle. Ubuntu 26.04 has it packaged:
sudo apt install -y intel-lpmd
The default config has all power profiles set to -1 (force off). Fix that:
<!-- /etc/intel_lpmd/intel_lpmd_config.xml -->
<?xml version="1.0"?>
<Configuration>
<lp_mode_cpus>12,13</lp_mode_cpus>
<Mode>0</Mode>
<PerformanceDef>0</PerformanceDef>
<BalancedDef>0</BalancedDef>
<PowersaverDef>1</PowersaverDef>
<HfiLpmEnable>1</HfiLpmEnable>
<HfiSuvEnable>1</HfiSuvEnable>
<util_entry_threshold>10</util_entry_threshold>
<util_exit_threshold>95</util_exit_threshold>
<EntryDelayMS>0</EntryDelayMS>
<ExitDelayMS>0</ExitDelayMS>
<EntryHystMS>0</EntryHystMS>
<ExitHystMS>0</ExitHystMS>
<IgnoreITMT>0</IgnoreITMT>
</Configuration>
Set lp_mode_cpus to your LP E-core IDs (check lscpu --extended — they're the ones with the lowest max frequency).
Wi-Fi power level #
The iwlwifi driver supports power levels 0-5. The default is 0 (no power saving beyond the basic power_save=1 kernel parameter). Level 5 is the most aggressive — it increases latency slightly but reduces radio power draw:
echo 5 | sudo tee /sys/module/iwlwifi/parameters/power_level
To make it persistent, add to /etc/modprobe.d/iwlwifi.conf:
options iwlwifi power_save=1 power_level=5
Workload type hints #
Meteor Lake has a hardware feature where the CPU optimizes power delivery based on workload type. It's disabled by default on Linux:
# /etc/systemd/system/workload-hints.service
[Unit]
Description=Enable Intel workload type hints
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo 1 > /sys/devices/pci0000:00/0000:00:04.0/workload_hint/workload_hint_enable'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
thermald with adaptive policy #
Ubuntu's thermald already runs with --adaptive, which reads Intel DPTF firmware tables. Verify:
ps aux | grep thermald
# Should show: /usr/sbin/thermald --systemd --dbus-enable --adaptive
Layer 4: IRQ migration #
By default, Linux spreads interrupts across all cores. On a hybrid CPU, this means display, Wi-Fi, and NVMe interrupts wake P-cores and E-cores — preventing them from reaching deep C-states.
Moving interrupts to LP E-cores (which are designed to handle light work at minimal power) increased package PC2 residency from 0.27% to 9.3%.
# /etc/systemd/system/irq-lp-cores.service
[Unit]
Description=Migrate IRQs to LP E-cores for deeper package C-states
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'for irq in $(grep -l "i915\|iwlwifi\|nvme\|AudioDSP" /proc/irq/*/actions 2>/dev/null | cut -d/ -f4); do echo 12-13 > /proc/irq/$irq/smp_affinity_list 2>/dev/null || true; done'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
Adapt 12-13 to your LP E-core CPU IDs.
Layer 5: narcolepsyd #
narcolepsyd is a daemon I wrote for this project. It monitors /dev/input for keyboard and touchpad events using poll(2) (zero CPU while waiting). When you stop typing for 3 seconds, it:
- Offlines P-cores and most E-cores
- Caps remaining core frequencies to 800 MHz
- Sets EPP to maximum power saving
- Disables turbo boost
- Suspends webcam and fingerprint USB devices
On any keypress or touchpad event, everything is restored in under 50ms. The display stays on. It auto-detects CPU topology and works on any Intel hybrid CPU (Alder Lake and newer).
sudo dpkg -i narcolepsyd_0.1.0_amd64.deb
Or from source:
git clone https://github.com/obra/narcolepsyd
cd narcolepsyd && cargo build --release
sudo cp target/release/narcolepsyd /usr/local/bin/
Layer 6: S0ix deep sleep (suspend) #
This is the big one. S0ix is Intel's modern standby — the entire SoC powers down to near-zero during s2idle suspend. On our Fujitsu, S0ix wasn't working at all. We fixed it by understanding what the PMC (Power Management Controller) needs.
Disable Secure Boot #
Kernel lockdown from Secure Boot blocks:
- Writing to
/sys/kernel/debug/pmc_core/ltr_ignore(needed for S0ix) - Hibernation
Disable Secure Boot in BIOS (F2 at boot on Fujitsu).
LTR ignore #
The PMC checks Latency Tolerance Reporting values from various IP blocks before allowing S0ix. Several Meteor Lake blocks report values that are too restrictive. Tell the PMC to ignore them:
# /etc/systemd/system/s0ix-ltr-ignore.service
[Unit]
Description=Set PMC LTR ignore for S0ix on Meteor Lake
After=multi-user.target NetworkManager.service
Wants=NetworkManager.service
[Service]
Type=oneshot
ExecStartPre=/bin/sh -c 'ip link set enp0s31f6 down 2>/dev/null || true'
ExecStartPre=/bin/sh -c 'nmcli device set enp0s31f6 managed no 2>/dev/null || true'
ExecStartPre=/bin/sleep 5
ExecStart=/bin/sh -c 'echo 1 > /sys/kernel/debug/pmc_core/ltr_ignore'
ExecStart=/bin/sh -c 'echo 3 > /sys/kernel/debug/pmc_core/ltr_ignore'
ExecStart=/bin/sh -c 'echo 6 > /sys/kernel/debug/pmc_core/ltr_ignore'
ExecStart=/bin/sh -c 'echo 25 > /sys/kernel/debug/pmc_core/ltr_ignore'
ExecStart=/bin/sh -c 'echo 40 > /sys/kernel/debug/pmc_core/ltr_ignore'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
The indices (1, 3, 6, 25, 40) correspond to SOUTHPORT_B, GBE, ME, IOE_PMC, and PMC1:SOUTHPORT_D. These may vary by machine — check cat /sys/kernel/debug/pmc_core/ltr_show for non-zero entries.
Do NOT blacklist e1000e #
This was counterintuitive. We initially blacklisted the Ethernet driver since the laptop uses Wi-Fi. But the e1000e driver's suspend callbacks are required for GBE power gating — without the driver loaded, the GBE hardware block stays powered and cascades to block the entire S0ix path.
The correct setup: keep e1000e loaded, keep the interface down, disable Wake-on-LAN, and tell NetworkManager to leave it alone:
sudo ethtool -s enp0s31f6 wol d
# /etc/NetworkManager/conf.d/ethernet-power.conf
[connection-ethernet]
match-device=interface-name:enp0s31f6
ipv4.method=disabled
ipv6.method=disabled
connection.autoconnect=false
The LTR ignore service also forces the interface down before setting LTR values — the ordering matters. If NetworkManager brings the interface up before the LTR ignores are written, S0ix fails.
Don't set mem_sleep_default=deep #
We initially added mem_sleep_default=deep to the kernel command line, thinking it would enable S3 deep sleep. It didn't — this Fujitsu (like most Meteor Lake laptops) only supports s2idle. The firmware doesn't expose S3 at all. With this parameter set, every suspend attempt would try S3, fail instantly, then fall back to s2idle. The rapid bounce confused the resume path — keyboard wake didn't work reliably, and the system would re-suspend within 30 seconds of waking.
Check what your firmware supports:
cat /sys/power/mem_sleep
# [s2idle] means s2idle only — do NOT set mem_sleep_default=deep
# s2idle [deep] means both are available — deep is fine
The suspend/wake fix #
Even with mem_sleep_default=deep removed, we had two suspend problems:
-
Re-suspend after wake — GNOME's idle timer counted time from before the suspend. After waking from a 5-minute suspend, the 10-minute idle timeout had only 5 minutes left. Touch nothing for those 5 minutes and it suspends again immediately. The fix: increase the suspend timeout from 5 to 10 minutes (
sleep-inactive-battery-timeout 600). -
Lid switch firmware bug — the Fujitsu's lid switch is non-compliant with
SW_LID(the kernel logsThe lid device is not compliant to SW_LID). This caused unreliable wake-on-lid-open. TheHoldoffTimeoutSec=30ssetting in logind prevents re-suspend for 30 seconds after wake, giving you time to interact.
Verify S0ix #
# Check residency before and after suspend
cat /sys/kernel/debug/pmc_core/substate_residencies
sudo rtcwake -m freeze -s 30
cat /sys/kernel/debug/pmc_core/substate_residencies
S0i2.2 residency should be >90% of the sleep duration. If it's zero, check substate_requirements for unmet conditions.
Hibernate #
With Secure Boot off, hibernation is unblocked. Set up a swap file large enough for RAM:
sudo fallocate -l 34G /swap.img # >= RAM size
sudo chmod 600 /swap.img
sudo mkswap /swap.img
sudo swapon /swap.img
echo '/swap.img none swap sw 0 0' | sudo tee -a /etc/fstab
Add resume parameters to GRUB:
resume=UUID=<your-root-uuid> resume_offset=<from filefrag -v /swap.img>
Configure lid close to suspend first, then hibernate after 15 minutes:
# /etc/systemd/logind.conf.d/lid-fix.conf
[Login]
HandleLidSwitch=suspend-then-hibernate
HandleLidSwitchExternalPower=suspend
LidSwitchIgnoreInhibited=no
HoldoffTimeoutSec=30s
# /etc/systemd/sleep.conf.d/hibernate.conf
[Sleep]
AllowHibernation=yes
AllowSuspendThenHibernate=yes
HibernateDelaySec=900
Layer 7: Hidden BIOS settings #
Fujitsu's BIOS has hidden settings that aren't exposed in the normal setup menu. We extracted the IFR (Internal Forms Representation) from the BIOS firmware and found several settings directly relevant to power management on Linux.
The most important: a literal "Linux Mode" toggle, shipped disabled, with the help text "Set to Enabled when using Linux." Also a setting to disable the PCH LAN Controller at the hardware level — which should permanently eliminate the GBE as an S0ix blocker, removing the need for the LTR ignore workaround entirely.
These are UEFI Boot Service variables, meaning they can't be written from a running Linux system — the firmware locks them before handing off to the OS. You need to write them from a UEFI shell during early boot.
Setup #
Download setup_var.efi and a UEFI shell and install them on the EFI partition:
sudo mkdir -p /boot/efi/EFI/tools/
sudo cp setup_var.efi /boot/efi/EFI/tools/
sudo cp Shell.efi /boot/efi/EFI/tools/
Write your settings commands into a .nsh script on the EFI partition so you don't have to remember the GUIDs:
# /boot/efi/EFI/tools/apply_power_settings.nsh
\EFI\tools\setup_var.efi 0x10E 1 -n Setup -g A04A27F4-DF00-4D42-B552-39511302113D
\EFI\tools\setup_var.efi 0x6 0 -n PchSetup -g 4570B7F1-ADE8-4943-8DC3-406472842384
\EFI\tools\setup_var.efi 0xE 0 -n CpuSetup -g B08F97FF-E6E8-4193-A997-5E9E9B0ADB32
And a matching revert script:
# /boot/efi/EFI/tools/revert_power_settings.nsh
\EFI\tools\setup_var.efi 0x10E 0 -n Setup -g A04A27F4-DF00-4D42-B552-39511302113D
\EFI\tools\setup_var.efi 0x6 1 -n PchSetup -g 4570B7F1-ADE8-4943-8DC3-406472842384
\EFI\tools\setup_var.efi 0xE 1 -n CpuSetup -g B08F97FF-E6E8-4193-A997-5E9E9B0ADB32
Add GRUB entries to chainload the shell with the script:
# /etc/grub.d/40_setup_var
#!/bin/sh
cat << 'GRUB'
menuentry "Apply BIOS power settings (UEFI Shell)" {
chainloader /EFI/tools/Shell.efi /EFI/tools/apply_power_settings.nsh
}
menuentry "Revert BIOS power settings (UEFI Shell)" {
chainloader /EFI/tools/Shell.efi /EFI/tools/revert_power_settings.nsh
}
GRUB
Then sudo update-grub, reboot, and select the entry from the GRUB menu. The script runs the commands and waits — type reset to reboot into Linux.
The settings #
| Setting | BIOS default | Change | Why |
|---|---|---|---|
| Linux Mode | Disabled | Enabled | Fujitsu's own Linux compatibility flag — adjusts ACPI/DPTF behavior |
| PCH LAN Controller | Enabled | Disabled | Eliminates the GBE hardware block that prevents S0ix power gating. This is the root cause of boot-dependent S0ix failures. |
| Boot performance mode | Max Non-Turbo | Max Battery | Starts the CPU in the lowest power state from reset |
We also verified that these were already set correctly (no changes needed):
| Setting | Value | Note |
|---|---|---|
| Low Power S0 Idle Capability | Enabled | ACPI LPS0 device for S0ix |
| EC Low Power Mode | Enabled | EC enters low power during S0ix |
| Platform Debug Consent | Disabled | TraceHub blocks S0ix when active |
| PMC Debug Message | Disabled | PMC debug messages block S0ix |
| S0i2.0 / S0i2.1 / S0i2.2 | All Enabled | S0ix substates |
The variable offsets and GUIDs are specific to this Fujitsu BIOS version (v1.14/1.15). Other machines will have different offsets — extract the IFR from your own firmware to find them.
The dead ends #
Micro-sleep daemon (v1) #
Before building narcolepsyd's CPU parking approach, we tried something more ambitious: a daemon that entered brief s2idle suspend cycles (2-5 seconds) during idle periods, using the RTC alarm to wake back up. The idea was to get S0ix power gating while the system appeared awake — replicating Intel DPTF's "connected standby" micro-naps.
It worked mechanically. Thirteen consecutive suspend/resume cycles, no crashes, keyboard and touchpad wakeup events detected correctly. But it blanked the display on every cycle. The i915 driver powers down the display backlight during any s2idle entry — even a 100ms one. PSR2 keeps the panel's pixels refreshed from its internal memory, but the backlight turns off. The screen flickered like a dying fluorescent light.
We tried bypassing systemd's suspend hooks by writing directly to /sys/power/state instead of using rtcwake. The display still blanked — it's the kernel's i915 suspend path, not userspace, that controls the backlight. Fixing this would require kernel patches to the i915 driver's suspend sequence, which was out of scope.
The CPU parking approach in narcolepsyd v2 achieves a subset of the same power savings (fewer active cores, lower frequencies) without touching the suspend path at all.
DRRS (Display Refresh Rate Switching) #
The Fujitsu's panel supports DRRS — automatically dropping from 60Hz to 30Hz when the screen is static. The i915 debugfs showed DRRS capable: yes, DRRS enabled: no. We tried enabling it via the i915_drrs_ctl debugfs interface. It wouldn't take.
The reason: DRRS and PSR2 are mutually exclusive on Meteor Lake. When PSR2 is active (which it is — we enabled it via kernel parameters), the display link is fully idle during static content. The panel self-refreshes from its internal buffer. This is strictly better than DRRS's 30Hz — PSR2 DEEP_SLEEP stops all display link activity, while DRRS at 30Hz still refreshes 30 times per second.
If you're on hardware where PSR2 isn't available, enabling DRRS would be the next best thing. But on any panel that supports PSR2, it wins.
What we learned about Meteor Lake C-states #
Some things that aren't well documented:
Core C-states skip C3, C7, C8, C9. Meteor Lake's core idle states are C1E → C6 → C10. There is no C7. If turbostat shows 0% C7, that's normal.
Package PC3 doesn't exist. PC3 would require core C3, which Meteor Lake doesn't have. The package path is PC2 → PC6 → PC8 → PC10.
PC6+ requires the PCH to power-gate. The die-to-die link between the SOC and IOE tiles stays active while the system is running. PC6 and deeper are only reachable during s2idle suspend, not during normal idle. This is architectural, not a software limitation.
S0ix is boot-dependent. The CSME firmware initializes the GBE differently across boots. Some boots, S0ix works perfectly. Others, the same configuration produces zero residency. This is a known Meteor Lake platform issue.
PSR2 is better than DRRS. The panel supports DRRS (dropping from 60Hz to 30Hz), but DRRS is mutually exclusive with PSR2 on Meteor Lake. PSR2's DEEP_SLEEP state stops the display link entirely — better than halving the refresh rate.
Cheat sheet #
Complete steps to reproduce on a fresh install. Adapt CPU IDs and device names for your hardware.
# 1. Remove snapd, install power tools
sudo apt remove -y --purge snapd power-profiles-daemon
sudo apt install -y tlp tlp-rdw powertop thermald intel-lpmd
# 2. Copy config files (all shown above)
sudo nano /etc/tlp.conf
sudo nano /etc/modprobe.d/i915.conf
sudo nano /etc/modprobe.d/audio_powersave.conf
sudo nano /etc/modprobe.d/iwlwifi.conf # power_save=1 power_level=5
sudo nano /etc/intel_lpmd/intel_lpmd_config.xml
# 3. Kernel parameters (do NOT include mem_sleep_default=deep
# unless /sys/power/mem_sleep shows [deep] is available)
sudo nano /etc/default/grub # edit GRUB_CMDLINE_LINUX_DEFAULT
sudo update-grub
# 4. Systemd services
# Create each .service file shown above, then:
sudo systemctl daemon-reload
sudo systemctl enable tlp thermald intel_lpmd powertop \
s0ix-ltr-ignore workload-hints irq-lp-cores narcolepsyd
sudo systemctl mask packagekit.service packagekit-offline-update.service
# 5. GNOME settings
gsettings set org.gnome.desktop.interface cursor-blink false
gsettings set org.gnome.desktop.interface enable-animations false
gsettings set org.gnome.settings-daemon.plugins.power idle-dim true
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-timeout 600
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-timeout 1200
gsettings set org.gnome.desktop.session idle-delay 300
gsettings set org.gnome.desktop.notifications show-in-lock-screen false
systemctl --user mask gnome-software.service
gsettings set org.gnome.software download-updates false
gsettings set org.gnome.software allow-updates false
dconf write /org/gnome/Ptyxis/cursor-blink-mode "'off'"
# 6. Ethernet — keep driver loaded, interface down
sudo ethtool -s enp0s31f6 wol d
sudo nano /etc/NetworkManager/conf.d/ethernet-power.conf
# 7. Install narcolepsyd
sudo dpkg -i narcolepsyd_0.1.0_amd64.deb
# 8. Disable Secure Boot in BIOS for S0ix + hibernate
# 9. Set up hibernate swap
sudo fallocate -l 34G /swap.img
sudo chmod 600 /swap.img && sudo mkswap /swap.img && sudo swapon /swap.img
# Add resume= params to GRUB, create logind + sleep configs
# 10. Reboot
Diagnostic commands #
# Power draw
sudo turbostat --show PkgWatt,SysWatt,Busy%,GFX%rc6,Pkg%pc2 --quiet --interval 10
# S0ix residency
sudo cat /sys/kernel/debug/pmc_core/substate_residencies
# What's blocking S0ix
sudo cat /sys/kernel/debug/pmc_core/substate_requirements | grep Required | grep -v Yes
# LTR values
sudo cat /sys/kernel/debug/pmc_core/ltr_show | grep -v "Snoop(ns): 0"
# PSR2 status
sudo cat /sys/kernel/debug/dri/0000:00:02.0/eDP-1/i915_psr_status
# narcolepsyd status
journalctl -u narcolepsyd -n 5
# CPU topology
lscpu --extended
Results #
| Metric | Before tuning | After tuning |
|---|---|---|
| Idle system power | ~8-10W | ~3.5W |
| GPU RC6 residency | ~65% | ~96% |
| Package PC2 | 0% | ~9% |
| S0ix during suspend | 0% | 93% S0i2.2 |
| Estimated battery life (idle) | 3-4 hours | 8-9 hours |
| Suspend power | ~2-3W | near-zero (when S0ix works) |
The gap to Windows is now roughly 0.5W, most of which is Intel DPTF's display-aware connected standby — something that would require kernel modifications to the i915 suspend path to replicate.
Source: narcolepsyd | All config files and services are described inline above.