#!/bin/sh
# tlp-func-cpu - Processor Functions
#
# Copyright (c) 2019 Thomas Koch <linrunner at gmx.net> and others.
# This software is licensed under the GPL v2 or later.

# Needs: tlp-func-base

# shellcheck disable=SC2086

# ----------------------------------------------------------------------------
# Constants

readonly ENERGYPERF=x86_energy_perf_policy

readonly CPU_BOOST_ALL_CTRL=/sys/devices/system/cpu/cpufreq/boost
readonly INTEL_PSTATED=/sys/devices/system/cpu/intel_pstate
readonly CPU_MIN_PERF_PCT=$INTEL_PSTATED/min_perf_pct
readonly CPU_MAX_PERF_PCT=$INTEL_PSTATED/max_perf_pct
readonly CPU_TURBO_PSTATE=$INTEL_PSTATED/no_turbo

# ----------------------------------------------------------------------------
# Functions

# --- Scaling Governor

check_intel_pstate () { # detect intel_pstate driver -- rc: 0=present/1=absent
    #  Note: intel_pstate requires Linux 3.9 or higher
    [ -d $INTEL_PSTATED ]
}

set_scaling_governor () { # set scaling governor -- $1: 0=ac mode, 1=battery mode
    local gov cpu ec

    if [ "$1" = "1" ]; then
        gov=${CPU_SCALING_GOVERNOR_ON_BAT:-}
    else
        gov=${CPU_SCALING_GOVERNOR_ON_AC:-}
    fi

    if [ -n "$gov" ]; then
        ec=0
        for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
            if [ -f "$cpu" ] && ! write_sysf "$gov" $cpu; then
                echo_debug "pm" "set_scaling_governor($1).write_error: $cpu $gov; rc=$?"
                ec=$((ec+1))
            fi
        done
        echo_debug "pm" "set_scaling_governor($1): $gov; ec=$?"
    else
        echo_debug "pm" "set_scaling_governor($1).not_configured"
    fi

    return 0
}

set_scaling_min_max_freq () { # set scaling limits -- $1: 0=ac mode, 1=battery mode
    local minfreq maxfreq cpu ec
    local conf=0

    if [ "$1" = "1" ]; then
        minfreq=${CPU_SCALING_MIN_FREQ_ON_BAT:-}
        maxfreq=${CPU_SCALING_MAX_FREQ_ON_BAT:-}
    else
        minfreq=${CPU_SCALING_MIN_FREQ_ON_AC:-}
        maxfreq=${CPU_SCALING_MAX_FREQ_ON_AC:-}
    fi

    if [ -n "$minfreq" ] && [ "$minfreq" != "0" ]; then
        conf=1
        ec=0
        for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_min_freq; do
            if [ -f "$cpu" ] && ! write_sysf "$minfreq" $cpu; then
                echo_debug "pm" "set_scaling_min_max_freq($1).min.write_error: $cpu $minfreq; rc=$?"
                ec=$((ec+1))
            fi
        done
        echo_debug "pm" "set_scaling_min_max_freq($1).min: $minfreq; ec=$ec"
    fi

    if [ -n "$maxfreq" ] && [ "$maxfreq" != "0" ]; then
        conf=1
        ec=0
        for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq; do
            if [ -f "$cpu" ] && ! write_sysf "$maxfreq" $cpu; then
                echo_debug "pm" "set_scaling_min_max_freq($1).max.write_error: $cpu $maxfreq; rc=$?"
                ec=$((ec+1))
            fi
            echo_debug "pm" "set_scaling_min_max_freq($1).max: $maxfreq; ec=$ec"
        done
    fi

    [ $conf -eq 1 ] || echo_debug "pm" "set_scaling_min_max_freq($1).not_configured"
    return 0
}

# --- Performance Policies

set_cpu_hwp_pref () { # set HWP energy performance hints -- $1: 0=ac mode, 1=battery mode
    local hwpp avail cpu ec

    if ! check_intel_pstate; then
        echo_debug "pm" "set_cpu_perf_pct($1).no_intel_pstate"
        return 0
    fi

    if [ "$1" = "1" ]; then
        hwpp=${CPU_HWP_ON_BAT:-}
    else
        hwpp=${CPU_HWP_ON_AC:-}
    fi

    if [ -n "$hwpp" ]; then
        avail=0
        ec=0
        for cpu in /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference; do
            if [ -f "$cpu" ]; then
                avail=1
                if  ! write_sysf "$hwpp" $cpu; then
                    echo_debug "pm" "set_cpu_hwp_pref($1).write_error: $cpu $hwpp; rc=$"
                    ec=$((ec+1))
                fi
            fi
        done
        if [ "$avail" = "1" ]; then
            echo_debug "pm" "set_cpu_hwp_pref($1): $hwpp; ec=$ec"
        else
            echo_debug "pm" "set_cpu_hwp_pref($1).no_hwp"
        fi
    else
        echo_debug "pm" "set_cpu_hwp_pref($1).not_configured"
    fi

    return 0
}

set_cpu_perf_pct () { # set Intel P-state performance
                      # $1: 0=ac mode, 1=battery mode
    local min max


    if ! check_intel_pstate; then
        echo_debug "pm" "set_cpu_perf_pct($1).no_intel_pstate"
        return 0
    fi

    if [ "$1" = "1" ]; then
        min=${CPU_MIN_PERF_ON_BAT:-}
        max=${CPU_MAX_PERF_ON_BAT:-}
    else
        min=${CPU_MIN_PERF_ON_AC:-}
        max=${CPU_MAX_PERF_ON_AC:-}
    fi

    if [ ! -f $CPU_MIN_PERF_PCT ]; then
        echo_debug "pm" "set_cpu_perf_pct($1).min.not_supported"
    elif [ -n "$min" ]; then
        write_sysf "$min" $CPU_MIN_PERF_PCT
        echo_debug "pm" "set_cpu_perf_pct($1).min: $min; rc=$?"
    else
        echo_debug "pm" "set_cpu_perf_pct($1).min.not_configured"
    fi

    if [ ! -f $CPU_MAX_PERF_PCT ]; then
        echo_debug "pm" "set_cpu_perf_pct($1).max.not_supported"
    elif [ -n "$max" ]; then
        write_sysf "$max" $CPU_MAX_PERF_PCT
        echo_debug "pm" "set_cpu_perf_pct($1).max: $max; rc=$?"
    else
        echo_debug "pm" "set_cpu_perf_pct($1).max.not_configured"
    fi

    return 0
}

set_cpu_boost_all () { # $1: 0=ac mode, 1=battery mode
    # global cpu boost behavior control based on the current power mode
    #
    # Relevant config option(s): CPU_BOOST_ON_{AC,BAT} with values {'',0,1}
    #
    # Note:
    #  * needs commit #615b7300717b9ad5c23d1f391843484fe30f6c12
    #     (linux-2.6 tree), "Add support for disabling dynamic overclocking",
    #    => requires Linux 3.7 or later

    local val

    if [ "$1" = "1" ]; then
        val=${CPU_BOOST_ON_BAT:-}
    else
        val=${CPU_BOOST_ON_AC:-}
    fi

    if [ -z "$val" ]; then
        # do nothing if unconfigured
        echo_debug "pm" "set_cpu_boost_all($1).not_configured"
        return 0
    fi

    if check_intel_pstate; then
        # use intel_pstate sysfiles, invert value
        if write_sysf "$((val ^ 1))" $CPU_TURBO_PSTATE; then
            echo_debug "pm" "set_cpu_boost_all($1).intel_pstate: $val"
        else
            echo_debug "pm" "set_cpu_boost_all($1).intel_pstate.cpu_not_supported"
        fi
    elif [ -f $CPU_BOOST_ALL_CTRL ]; then
        # use acpi_cpufreq sysfiles
        # simple test for attribute "w" doesn't work, so actually write
        if write_sysf "$val" $CPU_BOOST_ALL_CTRL; then
            echo_debug "pm" "set_cpu_boost_all($1).acpi_cpufreq: $val"
        else
            echo_debug "pm" "set_cpu_boost_all($1).acpi_cpufreq.cpu_not_supported"
        fi
    else
        echo_debug "pm" "set_cpu_boost_all($1).not_available"
    fi

    return 0
}

set_sched_powersave () { # set multi-core/-thread powersave policy
    # $1: 0=ac mode, 1=battery mode

    local pwr pool sdev
    local avail=0

    if [ "$1" = "1" ]; then
        pwr=${SCHED_POWERSAVE_ON_BAT:-}
    else
        pwr=${SCHED_POWERSAVE_ON_AC:-}
    fi

    if [ -z "$pwr" ]; then
        # do nothing if unconfigured
        echo_debug "pm" "set_sched_powersave($1).not_configured"
        return 0
    fi

    for pool in mc smp smt; do
        sdev="/sys/devices/system/cpu/sched_${pool}_power_savings"
        if [ -f "$sdev" ]; then
            write_sysf "$pwr" $sdev
            echo_debug "pm" "set_sched_powersave($1): ${sdev##/*/} $pwr; rc=$?"
            avail=1
        fi
    done

    [ "$avail" = "1" ] || echo_debug "pm" "set_sched_powersave($1).not_available"

    return 0
}

set_energy_perf_policy () { # set performance versus energy savings policy
    # $1: 0=ac mode, 1=battery mode

    local perf pnum rc

    if [ "$1" = "1" ]; then
        perf=${ENERGY_PERF_POLICY_ON_BAT:-}
    else
        perf=${ENERGY_PERF_POLICY_ON_AC:-}
    fi
    # translate alphanumeric to numeric values for backward compatibility
    pnum=$(echo $perf | sed -r 's/^performance$/0/;
                                s/^balance-performance$/4/;
                                s/^(default|normal)$/6/;
                                s/^balance-power?$/8/;
                                s/^power(save)?$/15/')

    if [ -z "$pnum" ]; then
        echo_debug "pm" "set_energy_perf_policy($1).not_configured"
    elif ! cmd_exists $ENERGYPERF; then
        # x86_energy_perf_policy not installed
        echo_debug "pm" "set_energy_perf_policy($1).not_available"
    else
        # x86_energy_perf_policy needs kernel module 'msr'
        load_modules $MOD_MSR
        $ENERGYPERF $pnum > /dev/null 2>&1
        rc=$?
        case $rc in
            0) echo_debug "pm" "set_energy_perf_policy($1): $perf($pnum)" ;;
            1) echo_debug "pm" "set_energy_perf_policy($1): $perf($pnum) -- unsupported cpu" ;;
            2) echo_debug "pm" "set_energy_perf_policy($1): $perf($pnum) -- kernel specific x86_energy_perf_policy missing" ;;
            *) echo_debug "pm" "set_energy_perf_policy($1): $perf($pnum) -- unknown rc=$rc " ;;
        esac
        return $rc
    fi

    return 0
}

# --- Misc

set_nmi_watchdog () { # enable/disable nmi watchdog
    local nmiwd=${NMI_WATCHDOG:-}

    if [ -z "$nmiwd" ]; then
        # do nothing if unconfigured
        echo_debug "pm" "set_nmi_watchdog.not_configured"
        return 0
    fi

    if [ -f /proc/sys/kernel/nmi_watchdog ]; then
        if write_sysf "$nmiwd" /proc/sys/kernel/nmi_watchdog; then
            echo_debug "pm" "set_nmi_watchdog: $nmiwd; rc=$?"
        else
            echo_debug "pm" "set_nmi_watchdog.disabled_by_kernel: $nmiwd"
        fi
    else
        echo_debug "pm" "set_nmi_watchdog.not_available"
    fi

    return 0
}

set_phc_controls () { # set core voltages
    local control
    local ctrl_avail="0"

    phc_controls=${PHC_CONTROLS:-}

    if [ -z "$phc_controls" ]; then
        # do nothing if unconfigured
        echo_debug "pm" "set_phc_controls.not_configured"
        return 0
    fi

    for control in /sys/devices/system/cpu/cpu*/cpufreq/phc_controls; do
        if [ -f "$control" ]; then
            write_sysf "$phc_controls" $control
            echo_debug "pm" "set_phc_controls: $control $phc_controls; rc=$?"
            ctrl_avail="1"
        fi
    done

    [ "$ctrl_avail" = "0" ] && echo_debug "pm" "set_phc_controls.not_available"

    return 0
}
