Skip to content

Instantly share code, notes, and snippets.

@Anexgohan
Created June 19, 2026 06:55
Show Gist options
  • Select an option

  • Save Anexgohan/9dc8ad67da8e7570db95d9da218b0def to your computer and use it in GitHub Desktop.

Select an option

Save Anexgohan/9dc8ad67da8e7570db95d9da218b0def to your computer and use it in GitHub Desktop.
PWM write test for Pankha fan-control
#!/usr/bin/env bash
#
set -u
HW="${1:-}"
N="${2:-}"
TARGET_PWM="${TARGET_PWM:-200}"
WATCH_SECS="${WATCH_SECS:-30}"
# --- Fan selection -------------------------------------------------------
# Both args supplied -> use directly. Otherwise list PWM-capable fans and let
# the user pick one (requires an interactive terminal).
if [ -z "$HW" ] || [ -z "$N" ]; then
if [ ! -t 0 ]; then
echo "No fan specified and not running interactively." >&2
echo "Usage: sudo $0 <hwmon_path> <fan_number>" >&2
exit 1
fi
echo "=== Available PWM-capable fans ==="
echo "Pick a CHASSIS fan (rpm>0, writable=yes). Avoid the CPU cooler or an AIO pump header."
echo
declare -a SEL_HW SEL_N
idx=0
for h in /sys/class/hwmon/hwmon*; do
[ -e "$h" ] || continue
name=$(cat "$h/name" 2>/dev/null)
for f in "$h"/fan[0-9]*_input; do
[ -e "$f" ] || continue
n=$(basename "$f" | sed 's/fan//; s/_input//')
pp="$h/pwm${n}"
[ -e "$pp" ] || continue
idx=$((idx + 1))
SEL_HW[$idx]="$h"
SEL_N[$idx]="$n"
printf ' [%2d] %-10s %s fan%s rpm=%-6s pwm=%-4s enable=%-3s writable=%s\n' \
"$idx" "$name" "$h" "$n" \
"$(cat "$f" 2>/dev/null)" \
"$(cat "$pp" 2>/dev/null)" \
"$(cat "$h/pwm${n}_enable" 2>/dev/null || echo -)" \
"$( [ -w "$pp" ] && echo yes || echo no )"
done
done
if [ "$idx" -eq 0 ]; then
echo "No PWM-capable fans found under /sys/class/hwmon." >&2
echo "Is the it87 (or relevant Super I/O) driver loaded? See pankha-fan-diag.sh." >&2
exit 1
fi
echo
read -rp "Select fan number [1-$idx] (or q to quit): " choice
case "$choice" in
q|Q) echo "Aborted."; exit 0 ;;
''|*[!0-9]*) echo "Invalid selection." >&2; exit 1 ;;
esac
if [ "$choice" -lt 1 ] || [ "$choice" -gt "$idx" ]; then
echo "Selection out of range." >&2
exit 1
fi
HW="${SEL_HW[$choice]}"
N="${SEL_N[$choice]}"
echo
fi
PWM="$HW/pwm$N"
EN="$HW/pwm${N}_enable"
RPM="$HW/fan${N}_input"
if [ ! -w "$PWM" ]; then
echo "ERROR: cannot write $PWM (run as root? wrong path/fan number?)" >&2
exit 1
fi
# Final confirmation when interactive.
if [ -t 0 ]; then
echo "Selected : $(cat "$HW/name" 2>/dev/null) fan$N ($PWM)"
echo "This will raise it to pwm=$TARGET_PWM for ${WATCH_SECS}s, then restore the original value."
read -rp "Proceed? [y/N]: " ok
case "$ok" in
y|Y|yes|YES) ;;
*) echo "Aborted."; exit 0 ;;
esac
echo
fi
orig_pwm="$(cat "$PWM")"
orig_en="$(cat "$EN" 2>/dev/null || echo n/a)"
echo "Chip : $(cat "$HW/name" 2>/dev/null) ($HW)"
echo "BASELINE : pwm=$orig_pwm enable=$orig_en rpm=$(cat "$RPM" 2>/dev/null)"
restore() {
[ "$orig_en" != "n/a" ] && echo "$orig_en" > "$EN" 2>/dev/null
echo "$orig_pwm" > "$PWM" 2>/dev/null
echo "RESTORED : pwm=$(cat "$PWM" 2>/dev/null) enable=$(cat "$EN" 2>/dev/null) rpm=$(cat "$RPM" 2>/dev/null)"
}
trap restore EXIT
# Enable manual PWM mode.
echo 1 > "$EN" 2>/dev/null
echo "After enable=1, reads back: $(cat "$EN" 2>/dev/null) (if this is not 1, the chip refused manual mode)"
echo "Writing pwm=$TARGET_PWM and watching ${WATCH_SECS}s:"
echo "$TARGET_PWM" > "$PWM"
for i in $(seq 0 "$WATCH_SECS"); do
printf ' t=%2ss pwm=%-3s enable=%-2s rpm=%s\n' \
"$i" "$(cat "$PWM" 2>/dev/null)" "$(cat "$EN" 2>/dev/null)" "$(cat "$RPM" 2>/dev/null)"
sleep 1
done
echo "Watch complete. Interpretation:"
echo " - pwm stayed $TARGET_PWM and rpm rose/held -> writes HOLD (control works)"
echo " - pwm/rpm dropped back on their own -> BIOS/SMM re-assert (timed override)"
echo " - enable would not stay 1, or rpm flat -> driver won't enter manual mode"
echo "Trap now restores original values."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment