THINKFAN.CONF(5)

πŸ“¦
version 2.0.0 Β· 3 versions
, in alpine 3.24, fedora 43, fedora 44, nixos 26.05, nixpkgs unstable, ubuntu resolute, ubuntu stonking

πŸ’– THINKFAN.CONF πŸ’– β€” the thinkfan.conf manual, part 1 of 3

ok bestie, welcome to thinkfan.conf(5), aka the YAML-formatted config file for thinkfan(1) ✨ this is a File Formats Manual page and we're gonna get you all the way through it, no skips, no skimming 🫢


🌸 NAME 🌸

thinkfan.conf β€” the YAML-formatted config for thinkfan(1) πŸ’…


πŸ¦‹ DESCRIPTION πŸ¦‹

okay so. YAML is a very powerful, yet concise notation for structured data. its full specification lives at https://yaml.org/spec/1.2/spec.html. thinkfan only uses a small subset of the full YAML syntax, so it might be helpful (but is not strictly necessary) for you to take a peek at the spec πŸ‘€

⚠️ THE most important thing to note: indentation is syntactically relevant. like, it literally means something. in particular, tabs should NOT be mixed with spaces 😭 we recommend using two spaces for indentation, exactly like it's shown below πŸ’–

The thinkfan config has three main sections πŸ‘‘ :

  • sensors: β€” where temperatures should be read from 🌑️ All hwmon-style drivers are supported, as well as /proc/acpi/ibm/thermal, and β€” depending on the compile-time options β€” libatasmart (to read temperatures directly from hard disks) and NVML (via the proprietary nvidia driver). iykyk πŸ’…
  • fans: β€” which fans should be used (currently only one allowed πŸ™ˆ). Support for multiple fans is currently in development and planned for a future release πŸ‘€βœ¨ Both hwmon-style PWM controls and /proc/acpi/ibm/fan can be used.
  • levels: β€” maps temperatures to fan speeds πŸ”₯ A β€œsimple mapping” just specifies one temperature as the lower and upper bound (respectively) for a given fan speed. In a β€œdetailed mapping”, the upper and lower bounds are specified for each driver/sensor configured under sensors:. This mode should be used when thinkfan is monitoring multiple devices that can tolerate different amounts of heat β€” it's giving <em>customized care for every diva</em> πŸŽ€

Under each of these sections, there must be a list of key-value maps, each of which configures a sensor driver, fan driver, or fan speed mapping 🧚 periodt.


more parts coming, don't you dare close this tab bestie πŸ’œ

πŸ’– SENSOR & FAN DRIVERS πŸ’–

okay so for thinkfan to actually do its thing, it first needs to know which temperature sensor drivers and which fan drivers it's supposed to be talking to. like, it can't just vibe with your hardware blindfolded, bestie. πŸ’…

The mapping between temperature readings and fan speeds lives in its own separate config section (peep the FAN SPEEDS section below for that part).


🌑️ Sensor Syntax 🌑️

The entries under the sensors: section can specify sysfs/hwmon, lm_sensors, thinkpad_acpi, NVML or atasmart drivers. Support for lm_sensors, NVML and atasmart needs the appropriate libraries and must have been enabled at compile time β€” no library, no vibe, sorry. 🫠

You can have any number (greater than zero, obviously, zero sensors is giving nothing) and any combination of hwmon, tpacpi, nvml and atasmart entries. BUT β€” and this is the important part β€” there may be at most one instance of the tpacpi entry. just the one. periodt.

The syntax for identifying each type of sensor looks like this:

sensors:
  - hwmon: hwmon-path          # A path to a sysfs/hwmon sensor
    name: hwmon-name           # Optional entry
    model: hwmon-model         # Optional entry for nvme
    indices: index-list        # Optional entry

  - chip: chip-name            # An lm_sensors/libsensors chip...
    ids: id-list               # ... with some feature IDs

  - tpacpi: /proc/acpi/ibm/thermal  # Provided by the thinkpad_acpi kernel module
    indices: index-list        # Optional entry

  - nvml: nvml-bus-id          # Uses the proprietary nVidia driver

  - atasmart: disk-device-file # Requires libatasmart support

  - ...

and on top of that, every single sensor entry can have a lil stack of settings that tweak how it behaves:

sensors:
  - ...: ... # A sensor specification as shown above
    correction: correction-list  # Optional entry
    optional: bool-ignore-errors # Optional entry
    max_errors: num-max-errors   # Optional entry

πŸŒ€ Fan Syntax πŸŒ€

Since version 2.0, thinkfan can control multiple fans at once, which is honestly iconic of it. So you can specify any number of hwmon fan sections. ✨

heads up though: the thinkpad_acpi kernel module only supports one fan, so there can be at most one tpacpi section. that's a kernel limitation, not a thinkfan opinion.
# ...
fans:
  - tpacpi: /proc/acpi/ibm/fan

  - hwmon: hwmon-path
    name: hwmon-name
    indices: index-list

  - ...

The behavior of any fan can optionally be controlled with the optional and max_errors keywords, and by specifying a local fan speed config under the levels: keyword:

# ...
fans:
  - ...:  ... # A fan specification as shown above
    optional: bool-ignore-errors # Optional entry
    max_errors: num-max-errors   # Optional entry
    levels: levels-section       # Optional entry

πŸ“– Values πŸ“–

hwmon-path

There are three ways of specifying hwmon fans or sensors. pick your fighter:

  1. A full path of a temp*_input or pwm* file, like /sys/class/hwmon/hwmon0/pwm1 or /sys/class/hwmon/hwmon0/temp1_input. In this case, the indices: index-list and name: hwmon-name entries are unnecessary since the path uniquely identifies a specific fan or sensor.
warning bestie: this method may lead to problems when the load order of the drivers changes across bootups, because in the hwmonX folder name, the X actually corresponds to the load order. Use method 2) or 3) to avoid this problem. πŸ’€
  1. A directory that contains a specific hwmon driver, for example /sys/devices/platform/nct6775.2592. Note that this path does not contain the load-order dependent hwmonX folder. As long as it contains only a single hwmon driver/interface it is sufficient to specify the indices: index-list entry to tell thinkfan which specific sensors to use from that interface. The name: hwmon-name entry is unnecessary.
  2. A directory that contains multiple or all of the hwmon drivers, for example /sys/class/hwmon. Here, both the name: hwmon-name and indices: index-list entries are required to tell thinkfan which interface to select below that path, and which sensors or which fan to use from that interface.

hwmon-name

The name of a hwmon interface, typically found in a file called name. This has to be specified if hwmon-path is a base path that contains multiple hwmons. This way of specifying sensors is particularly useful if the full path to a particular hwmon keeps changing between bootups, e.g. due to changing load order of the driver modules. lowkey the smart move. 🌸

hwmon-model

The model of a device in a hwmon interface, usually found for NVME devices in a file under device called model. For example, you might have an NVME /sys/class/hwmon/hwmon3/device/model, and you might have an external NVME over USB or Thunderbolt that you don't want to monitor β€” or you might just have two NVME's. either way, this tells them apart. πŸ¦‹

index-list

A YAML list [ X1, X2, ... ] that specifies which sensors, resp. which fan to use from a given interface. Both /proc/acpi/ibm/thermal and also many hwmon interfaces contain multiple sensors, and not all of them may be relevant for fan control.

  • For hwmon entries, this is required if hwmon-path does not refer directly to a single tempXi_input file, but to a folder that contains one or more of them. In this case, index-list specifies the Xi for the tempXi_input files that should be used. A hwmon interface may also contain multiple PWM controls for fans, so in that case, index-list must contain exactly one entry.
  • For tpacpi sensors, this entry is optional. If it is omitted, all temperatures found in /proc/acpi/ibm/thermal will be used.

chip-name

The unique name of an lm-sensors hwmon chip. lm-sensors / libsensors is the official client library to access the sysfs hwmon subsystem, and as such it is the preferred way of specifying sensors. Available chips can be listed by running the sensors command as root. The header above each block will be the chip name. πŸ‘‘

ids

A list of lm-sensors feature IDs, for example [ SYSTIN, CPUTIN, "Sensor 2" ]. In the sensors command output, the name before the colon on each line will be the feature ID.

nvml-bus-id

NOTE: only available if thinkfan was compiled with USE_NVML enabled.

The PCI bus ID of an nVidia graphics card that is run with the proprietary nVidia driver. Can be obtained with e.g. lspci | grep -i vga. Usually, nVidia cards will use the open source nouveau driver, which should support hwmon sensors instead. πŸ’œ

disk-device-file

NOTE: only available if thinkfan was compiled with USE_ATASMART enabled.

Full path to a device file for a hard disk that supports S.M.A.R.T. See also the -d option in thinkfan(1) that prevents thinkfan from waking up sleeping (mechanical) disks to read their temperature. 😴 (let the disk nap, fr fr)

correction-list (optional, zeroes by default)

A YAML list that specifies temperature offsets for each sensor in use by the given driver. Use this if you want to use the β€œsimple” level syntax, but need to compensate for devices with a lower heat tolerance. Note however that the detailed level syntax is usually the better (i.e. more fine-grained) choice. ✨

bool-ignore-errors (optional, false by default)

A truth value (yes/no/true/false) that specifies whether thinkfan should ignore all errors when using a given sensor or fan. Normally, thinkfan will exit with an error message if it fails reading temperatures from a sensor or writing to a fan.

An optional device will not delay startup. Instead, thinkfan will commence normal operation with the remaining devices and re-try initializing unavailable optional devices in every loop until all are found. πŸ”

Marking a sensor/fan as optional may be useful for removable hardware or devices that may get switched off entirely to save power. (main character energy: shows up when it wants, but the show goes on without it)

num-max-errors (optional, 0 by default)

A positive integer that specifies how often thinkfan is allowed to re-try if it fails to initialize, read from or write to a given sensor or fan. On startup, thinkfan will attempt to initialize the device num-max-errors times before commencing normal operation. If the device cannot be initialized after the given number of attempts, thinkfan will fail. 😭

When a device with a positive num-max-errors fails during runtime, thinkfan will likewise attempt to re-initialize it the given number of times before failing.

levels-section (optional, use global levels section by default)

As of thinkfan 2.0, multiple fans can be configured. To this end, each fan can now have its own levels: section (cf. FAN SPEEDS below). The syntax of the global vs. the fan-specific levels: section are identical β€” the only difference being that in a fan-specific one, the speed value(s) refer only to the fans configured in that particular fans: entry. πŸŒ€

NOTE: Global and fan-specific levels: are mutually exclusive, i.e. there cannot be both a global one and fan-specific sections. pick one bestie, you can't have both. πŸ’…

πŸ”₯ FAN SPEEDS πŸ”₯

okay bestie this is the part where we tell thinkfan what to actually do πŸ’…

The levels: section is literally a list of fan speeds, and each one comes with a lower and an upper temperature bound attached to it. Here's the tea: if the temperature(s) drop below the lower bound, thinkfan switches back to the previous level. And if the upper bound gets hit, thinkfan jumps forward to the next level. That's it, that's the whole vibe β€” it's giving thermostat but make it dramatic. 🌑️

Since thinkfan 2.0, this section can show up in one of two places: either nested under an individual fan (peep the levels: keyword under Fan Syntax), or globally. You choose, queen. πŸ‘‘


✨ Simple Syntax ✨

The low-effort, low-key iconic version πŸŽ€

In the simplified form you only give one temperature as the upper/lower limit for a given fan speed. And in that case? The lower-bound and upper-bound get compared only to the highest temperature found among all the configured sensors. Every other temperature is just ignored. πŸ™ˆ

This mode is perfect for small systems (laptops, hiii 🫢) where there's really just one device whose temp needs controlling (the CPU, obvs), or where all your heat-generating devices are similar enough that one fan behaviour works for all of them. No cap.

# ...
levels:
  - [ fan-speed, lower-bound, upper-bound ]
  - ...

πŸ¦‹ Detailed Syntax πŸ¦‹

Now for the complex-girlies. This mode is for systems with devices that have wildly different temperature ratings. Like, many modern CPUs and GPUs are out here living their best life above 80Β°C on the daily, whereas a hard disk will literally die quickly if it ever reaches temps like that. πŸ’€ So unfair. In detailed mode, you specify upper and lower temperature limits for each sensor individually:

# ...
levels:
  - speed: [ fan1-speed, fan2-speed, ... ]
    lower_limit: [ l1, l2, ... ]
    upper_limit: [ u1, u2, ... ]
  - ...

🌸 Values 🌸

fan1-speed, fan2-speed, ...

fan-speed

When multiple fans are listed under the fans: section, the value of the speed: keyword has to be a list with exactly as many fanX-speed values as there are fans. They get applied to the fans by their order of appearance β€” the first speed value goes to the fan that was specified first, the second value to the second fan, and so on. If there's only one fan, then instead of a list with a single element, you can just give the speed value as a scalar. Easy. ✨

The possible speed values for fanX-speed depend on which fan driver you're using, so pay attention bestie:

  • For a hwmon fan, fanX-speed is a numeric value ranging from 0 to 255, corresponding to the PWM values accepted by the various kernel drivers.
  • For a tpacpi fan on Lenovo/IBM ThinkPads and some other Lenovo laptops (see SENSORS & FAN DRIVERS above πŸ‘†), you can use both numeric values and strings. The numeric values range from 0 to 7. The string values take the form "level lvl-id", where lvl-id may be a value from 0 to 7, auto, full-speed or disengaged. The numeric values 0 to 7 correspond to the regular fan speeds used by the firmware, although many firmwares don't even use level 7. πŸ™ƒ The value "level auto" gives control back to the firmware, which may be useful if the fan behavior only needs to be changed for certain specific temperature ranges (usually at the high and low end of the range). The values "level full-speed" and "level disengaged" take the fan speed control away from the firmware, causing the fan to slowly ramp up to an absolute maximum that can be achieved within electrical limits.
⚠️ heads up: "level full-speed" and "level disengaged" will run the fan out of specification and cause increased wear. It may be helpful to combat thermal throttling, but it's not free, so don't come for me. 😭

l1, l2, ...

u1, u2, ...

The lower and upper temperature limits refer to the sensors in the same order in which they were found when processing the sensors: section (see SENSOR & FAN DRIVERS above). For the first level entry, the lower_limit may be omitted, and for the last one, the upper_limit may be omitted. For all the levels in between, the lower limits must overlap with the upper limits of the previous level β€” that's how we make sure the entire temperature range is covered and that there's some hysteresis between speed levels. Don't skip this, it's important fr fr. πŸ”₯

And instead of a temperature, you can put an underscore (_). An underscore means the temperature of that sensor should be ignored at the given speed level. Bye sensor, not your moment. πŸ’…


πŸ‘€ SEE ALSO πŸ‘€

πŸ› BUGS πŸ›

Found something cursed? Report bugs on the github issue tracker: https://github.com/vmatare/thinkfan/issues


and that's the whole page, bestie!! πŸ’– This one's from thinkfan 2.0.0, dated November 2022, and it's THINKFAN.CONF(5). Go forth and configure your fans with main character energy. slay, stay cool (literally), and remember: the vibe changes, the facts do not. βœ¨πŸ¦‹πŸ’œ