Skip to content
Guide

Custom fan curves and a thermal failsafe on a Linux gaming laptop

When the firmware owns the fans, a curve is a boost on top of its table, and it needs a dead zone and a failsafe that works even when the curve is wrong. Here is the design, then how Cryo does it with its shipped numbers.

· 9 min read

What a fan curve is when the firmware owns the fans

On a desktop board a fan curve sets a duty cycle directly. On an Alienware laptop the embedded controller (EC) runs the fans from its own thermal tables, one per firmware profile, with no operating system involved. The mainline alienware-wmi driver has no duty-cycle write. It offers a fan boost per fan (fan*_boost in its hwmon device) that lifts the fan above what the active table wants: you can push a fan above the table, never pull it below. The Alienware fan control guide covers the layers, kernel versions and sysfs files.

So here a fan curve is a mapping from temperature to boost, re-evaluated on a timer from user space. It needs the alienware_wmi hwmon device; without it there is no boost to write. Because boost only adds, a curve can make the machine louder and cooler than the firmware would, never quieter. For quieter, pick a quieter profile.

Three rules for any user-space curve

Interpolate between a few points

A handful of (temperature, boost) points with straight lines between them is enough. Each fan group follows its own sensor: CPU fans the CPU temperature, GPU fans the GPU temperature. Boost should never fall as temperature rises.

Hysteresis and write suppression

Temperatures wobble, and a loop that re-evaluates every second chases every wobble: the fans ramp up and down audibly, which is called hunting. Hysteresis fixes it: remember the temperature at which you last changed the boost, and ignore readings close to it. Write suppression is the second filter: skip boost changes too small to hear, so the EC is not sent a stream of near-identical writes.

A failsafe that does not depend on the curve

Curves are sometimes wrong: one starts too late, applies in only one mode, was switched off by a manual boost, or the process running it died. The failsafe has to work regardless. It should run in every mode, trip on absolute temperatures, override the curve, hold long enough not to saw-tooth, release only after a wide margin, and give back whatever was set before.

Cryo's compatibility notes record a community report from an Alienware 16X Aurora: without Cryo, the stock EC fan response trailed a sudden all-core spike by 15 to 20 seconds with the package already past 90 °C; with the guard, the fans were ramping within the first one-second poll.

How Cryo implements it

Cryo is an open-source daemon (cryod), CLI (cryoctl) and desktop app (cryo-gui) for Alienware laptops on alienware-wmi. Verified so far: the Alienware m18 R2 and the Alienware 16X Aurora AC16251, both SUPPORTED with curves and guard; the 2011 Aurora R4 desktop runs in LIMITED mode with no fan control. At startup the daemon checks that fan*_boost exists and accepts a write; if not, curves and Thermal Guard disable themselves with a visible notice.

The curve model

SettingDefaultAccepted range
Points per fan group6 (CPU), 5 (GPU)2 to 8, temperatures rising, boosts never falling
Between pointsLinear interpolation
Hysteresis3 °C0 to 15
Minimum change before a write5 points1 to 25
Applies inCustom mode only
EvaluatedOnce a second

Every fan in a group gets the same boost; the m18 R2 has two CPU and two GPU fans. Cryo's boost scale runs 0 to 100. Below the first point the curve holds the first point's boost, above the last point the last one's, and fractions are dropped.

The dead zone is measured from the temperature at the last write, in both directions: after a write at 70 °C, readings of 68 to 72 °C change nothing (the m18 R2 reports whole degrees). Outside it, a new target is written only if it differs from the current boost by at least 5, except that 0 and 100 are always written so the curve reaches its ends.

Custom is the kernel's custom platform profile; the kernel documentation notes that on some devices manual fan control only works reliably there. In every other mode the EC's table runs the fans and Cryo writes no boost on its own except through the guard.

The default curves, worked through

Bold values are shipped points; the rest come from interpolation.

TemperatureCPU boostGPU boost
45 °C00
60 °C1515
65 °C2527
70 °C3540
75 °C4755
80 °C6070
85 °C7591
87 °C81100
88 °C85100
95 °C100100

A CPU warming from 70 to 73 °C: 35 written at 70, held at 71 and 72, 42 written at 73. Falling back to 72 holds 42; 70 writes 35 again. At the low end, 48 and 49 °C give targets of 3 and 4, both suppressed; 50 °C writes 5. The curves hand over to the guard by design: the CPU curve reaches 85 at 88 °C, the CPU trip, and the GPU curve is at 91 by 85 °C, the GPU trip.

Thermal Guard

SettingDefaultAccepted range
CPU trip88 °C60 to 105
GPU trip85 °C60 to 105
Release, below trip10 °C1 to 30
Minimum hold30 s0 to 600
Boost while engaged1000 to 100

The guard runs in every profile, Quiet included, and outranks the curves. When the CPU reaches 88 °C or the GPU 85 °C, it saves each group's boost and sets both groups to 100. It releases only once both temperatures are 10 °C below their trips (CPU 78 °C or lower, GPU 75 °C or lower) and it has held at least 30 seconds, then restores the saved boosts and lets the curves start over. The hold exists because boost-clock spikes clear within seconds of full fans; without it the guard saw-tooths under sustained load.

It is built to stay up: a broken config file is logged and replaced by defaults, a failed tick is logged and the loop continues, and systemd restarts cryod three seconds after a failure. Underneath, the EC's own tables keep running.

The guard is a fan failsafe, not a power limit. In one G-Mode session logged on the m18 R2 the CPU still touched 99 °C with the guard engaged and the CPU fans at about 4,200 and 8,900 rpm. On that machine the CPU peak is a single-core boost hotspot that fans barely move; the lever for it is the CPU cap.

Edit curves and thresholds

In the desktop app's Fan control panel, "Use Custom mode" switches the profile and "Automatic fan curves" turns curves on or off. Under "Curves & Thermal Guard", Edit opens each curve as a chart with the live temperature and the guard's trip line; points are edited in place, added (up to 8) or removed (down to 2) and applied together. Hysteresis, minimum step and the trip and release temperatures sit below.

From the shell no sudo is needed: the daemon does the write, and the installer gives your account access to its socket. The comments show a fresh install:

sh
cryoctl profile custom
cryoctl curve show
# cpu         45:0  60:15  70:35  80:60  88:85  95:100
# gpu         45:0  60:15  70:40  80:70  87:100
# hysteresis  3.0 °C
# min step    5 %
# curves      on
cryoctl curve cpu 45:0 60:15 70:35 80:60 88:85 95:100
cryoctl curve hysteresis 3
cryoctl curve step 5
cryoctl guard show
# cpu trip    88 °C
# gpu trip    85 °C
# release     10 °C below trip
# min hold    30 s
# boost       100 %
# guard       on
cryoctl guard cpu 88
cryoctl guard release 10
cryoctl guard hold 30

The daemon validates every change before it touches sysfs or the config file and rejects a bad one with a reason: cryoctl curve cpu 45:0 70:40 80:30 fails with "fan_curves.cpu boosts must not decrease as temperature rises". The minimum hold is CLI-only.

Changes land in /etc/cryo/config.json, which holds only your overrides, so keys you never touched keep tracking the shipped defaults; cryoctl config prints both. The daemon's own decisions live in /var/lib/cryo/state.json, including the curves-off latch: a boost slider or cryoctl boost switches curves off, and they stay off across restarts until you flip "Automatic fan curves" back on.

Tune a curve from data

Guessed points are how curves end up wrong. Log a real session first:

sh
cryoctl watch --log ~/cryo-session.csv

It appends one row a second until Ctrl+C: profile, temperatures, dGPU load, power and VRAM, the gaming and guard flags, and fan RPMs. A real row from a game on the m18 R2:

text
2026-09-12T16:11:33,gmode,63.0,59.0,96,56.9,2475,12246,12282,1,0,2812;5093;2805;3893,icarus-win64-shipping.exe,10970

Count seconds per 5 °C CPU band (column 3):

sh
awk -F, 'NR>1 {b=int($3/5)*5; c[b]++} END {for (k in c) print k, c[k]}' ~/cryo-session.csv | sort -n
# 50 671
# 55 363
# 60 36
# 65 62
# 70 2075
# 75 1887
# 80 99
# 85 63
# 90 299
# 95 258

That is a 5,812-row game session on the m18 R2: about two thirds at 70 to 79 °C, about nine minutes in the 90s. The slope across your busiest band decides how loud the machine is; the hot tail is the guard's job and the CPU cap's. The CSV has no boost column; cryoctl status shows each fan's boost in parentheses after its RPM.

What the desktop app shows when the guard engages

Since 0.7.0 a GUARD badge appears in the header, frost sweeps across the window in about half a second, the panel edges ice over, and a frosted border holds until the guard releases, then thaws over a couple of seconds. With reduce motion on or the window unfocused it switches without the sweep; with the frost effects off in Settings there is no frost. From a clone, uv run cryo-gui --demo --scenario guard shows it against a simulated daemon, with no hardware and no root.

Common mistakes

No hysteresis, so the fans hunt. Cryo accepts 0. At one reading a second every degree of wobble then re-evaluates the curve, and only the minimum step stands between that and audible ramping. Keep the default 3 °C unless your log gives a reason.

A curve that starts too late. Flat until 80 °C and steep after it hands the work to the guard: boost 100 for at least 30 seconds, release at 78 °C, repeat. The shipped curve climbs from 45 °C so the guard stays a backstop.

Trusting a curve with no failsafe. Curves run only in Custom mode, a manual boost latches them off, and a script that dies leaves its last boost in place. Leave the guard on with its trips at or below the defaults. The daemon accepts trips up to 105 °C, but above the defaults the firmware's own protection is the last line.

To try it on an m18 R2 or 16X Aurora, get the installer or follow the quick start; on anything else, run cryoctl doctor first.

Next steps

Frequently asked questions

Can a fan curve make an Alienware laptop quieter than the firmware?
No. The alienware-wmi driver exposes a fan boost that adds to what the active firmware table wants, so a curve can only make the fans faster. For a quieter machine, pick a quieter profile such as Quiet; Thermal Guard still watches temperatures there.
Does Thermal Guard work outside Custom mode?
Yes. Curves apply only in Custom mode, but the guard runs in every profile. At CPU 88 °C or GPU 85 °C it sets both fan groups to boost 100, and it releases once both are 10 °C below their trips and it has held at least 30 seconds.
Why do my fans not change when the temperature moves a degree or two?
That is the hysteresis. Cryo ignores readings within 3 °C of the temperature where it last wrote a boost, and skips changes smaller than 5 points, so the fans do not hunt around a curve point. Both are adjustable with cryoctl curve hysteresis and cryoctl curve step.
Why did my curves stop after I moved a boost slider?
A manual boost, from the slider or cryoctl boost, switches curves off and records that in /var/lib/cryo/state.json so a restart does not undo it. Flip the Automatic fan curves switch in the desktop app to hand the fans back to the curves.
What happens if cryod crashes or the config file is broken?
A broken /etc/cryo/config.json is logged and the daemon runs on defaults, guard included. A failed tick is logged and the loop continues, and systemd restarts cryod three seconds after a crash. While it is down, the firmware's own fan tables still run.
Which laptops does this work on?
Fan boost needs the alienware-wmi hwmon device. Verified so far: the Alienware m18 R2 and the Alienware 16X Aurora AC16251. The 2011 Aurora R4 desktop runs in LIMITED mode without fan control. On any other model, run cryoctl doctor and file a model report.