Plasmoide CPU Frequency Control: governor, limiti, EPP, boost e profili per task
Widget KDE Plasma 6 (org.enne2.cpufreq) per pilotare il profilo energetico della CPU: governor, limiti min/max, energy_performance_preference, boost/turbo, quattro profili pronti (Silenzioso, Risparmio, Bilanciato, Performance) e task con profilo dedicato che ripristina lo stato precedente alla fine del comando. Privilegi separati: il widget resta non privilegiato e l'unica operazione root passa da un helper a interfaccia ristretta in /usr/local/libexec/enne2-cpufreq/ (comandi a elenco chiuso, argomenti validati su sysfs, rilettura di verifica), invocato via pkexec con l'azione polkit dedicata org.enne2.cpufreq.set. La regola in /etc/polkit-1/rules.d/ concede il consenso senza prompt a utenti locali in sessione attiva nel gruppo wheel; senza la regola la policy chiede la password. Dettagli verificati su frigate (Plasma 6.7.5, kernel 7.2, amd-pstate-epp): - con boost disattivato cpuinfo_max_freq scende alla frequenza nominale, quindi il tetto hardware si legge da amd_pstate_max_freq e i profili applicano il boost prima dei limiti; - scrittura dei limiti in ordine sicuro (mai min > max intermedio); - baseline e contatore di riferimento per i task paralleli; - syncControls protetto da uiReady e try/catch: un ReferenceError su un id della rappresentazione non deve interrompere l'aggiornamento dello stato; - implicitWidth/Height costanti, altrimenti il containment riduce il widget a 68x68. Include install.sh/uninstall.sh (widget, backend, dry-run), README con modello di sicurezza e diagnostica, screenshot docs/widget.png. Licenza MIT.
This commit is contained in:
Executable
+410
@@ -0,0 +1,410 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: MIT
|
||||
# enne2-cpufreq — helper privilegiato a interfaccia ristretta.
|
||||
#
|
||||
# Installato come /usr/local/libexec/enne2-cpufreq/cpufreq-helper (root:root 0755),
|
||||
# invocato dal plasmoide org.enne2.cpufreq tramite pkexec e una azione polkit
|
||||
# dedicata (org.enne2.cpufreq.set). Non accetta comandi arbitrari: solo i
|
||||
# sottocomandi elencati in usage() e con argomenti validati contro sysfs.
|
||||
#
|
||||
# Uscita: righe key=value; ok=1 in caso di successo, ok=0 + error=<msg> + exit 2
|
||||
# in caso di rifiuto. Ogni scrittura viene riletta per verifica.
|
||||
set -u
|
||||
|
||||
PATH=/usr/sbin:/usr/bin:/sbin:/bin
|
||||
export PATH
|
||||
umask 022
|
||||
|
||||
CMD=${1:-status}
|
||||
[ "$#" -gt 0 ] && shift
|
||||
|
||||
SELF_DIR=$(dirname "$0")
|
||||
if [ -r "$SELF_DIR/profiles.sh" ]; then
|
||||
# shellcheck source=../../contents/scripts/profiles.sh
|
||||
. "$SELF_DIR/profiles.sh"
|
||||
else
|
||||
printf 'ok=0\nerror=profiles.sh mancante accanto all helper\n'
|
||||
exit 2
|
||||
fi
|
||||
|
||||
CPUFREQ=/sys/devices/system/cpu/cpufreq
|
||||
INTEL_PSTATE=/sys/devices/system/cpu/intel_pstate
|
||||
STATE_DIR=/var/lib/enne2-cpufreq
|
||||
BASELINE=$STATE_DIR/baseline
|
||||
REFCOUNT=$STATE_DIR/refcount
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
uso: cpufreq-helper <comando> [argomenti]
|
||||
status stato corrente (leggibile anche senza root)
|
||||
set-governor <governor> powersave | performance | ...
|
||||
set-epp <preferenza> default | performance | balance_performance | balance_power | power
|
||||
set-limits <min_khz> <max_khz>
|
||||
set-boost <0|1>
|
||||
apply <profilo> quiet | powersave | balanced | performance
|
||||
reset ripristina i default hardware (boost on, nessun tetto)
|
||||
save-state salva lo stato corrente come baseline
|
||||
restore-state ripristina la baseline salvata
|
||||
task-begin <profilo> salva la baseline (se serve) e applica il profilo
|
||||
task-end [--all] decrementa il contatore e ripristina la baseline a zero
|
||||
EOF
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf 'ok=0\nerror=%s\n' "$*"
|
||||
exit 2
|
||||
}
|
||||
|
||||
readf() { # readf <file> -> valore o exit 1
|
||||
[ -r "$1" ] || return 1
|
||||
_val=
|
||||
IFS= read -r _val < "$1" 2>/dev/null || [ -n "$_val" ] || return 1
|
||||
printf '%s\n' "$_val"
|
||||
}
|
||||
|
||||
policies() {
|
||||
for _p in "$CPUFREQ"/policy[0-9]*; do
|
||||
[ -d "$_p" ] && printf '%s\n' "$_p"
|
||||
done
|
||||
}
|
||||
|
||||
write_one() { # write_one <file> <valore>
|
||||
[ -w "$1" ] || return 1
|
||||
printf '%s\n' "$2" > "$1" 2>/dev/null
|
||||
}
|
||||
|
||||
# Scrive un attributo su TUTTE le policy. Imposta WROTE e FAILED.
|
||||
write_all() {
|
||||
_attr=$1
|
||||
_val=$2
|
||||
WROTE=0
|
||||
FAILED=0
|
||||
for _p in $(policies); do
|
||||
if write_one "$_p/$_attr" "$_val"; then
|
||||
WROTE=$((WROTE + 1))
|
||||
else
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
done
|
||||
[ "$FAILED" -eq 0 ] && [ "$WROTE" -gt 0 ]
|
||||
}
|
||||
|
||||
# Valori distinti letti per un attributo, spazio-separati.
|
||||
attr_values() {
|
||||
for _p in $(policies); do
|
||||
readf "$_p/$1"
|
||||
done | sort -u | tr '\n' ' ' | sed 's/ *$//'
|
||||
}
|
||||
|
||||
p0=$(policies | head -n 1)
|
||||
[ -n "$p0" ] || fail "nessuna policy cpufreq in $CPUFREQ"
|
||||
|
||||
driver=$(readf "$p0/scaling_driver" || echo unknown)
|
||||
hw_min=$(readf "$p0/cpuinfo_min_freq" || echo 0)
|
||||
hw_max=$(cf_hw_max_khz "$p0") # indipendente dallo stato del boost
|
||||
floor=$(readf "$p0/amd_pstate_lowest_nonlinear_freq" || echo "")
|
||||
[ -n "$floor" ] && [ "$floor" -gt "$hw_min" ] || floor=$hw_min
|
||||
|
||||
boost_global=$CPUFREQ/boost
|
||||
[ -w "$boost_global" ] || [ -r "$boost_global" ] || boost_global=""
|
||||
|
||||
boost_read() {
|
||||
for _p in $(policies); do
|
||||
readf "$_p/boost" && return 0
|
||||
done
|
||||
[ -n "$boost_global" ] && readf "$boost_global" && return 0
|
||||
readf "$INTEL_PSTATE/no_turbo" >/dev/null 2>&1 || return 1
|
||||
_nt=$(readf "$INTEL_PSTATE/no_turbo") || return 1
|
||||
[ "$_nt" = "1" ] && echo 0 || echo 1
|
||||
}
|
||||
|
||||
print_state() {
|
||||
printf 'driver=%s\n' "$driver"
|
||||
printf 'mode=%s\n' "$(readf /sys/devices/system/cpu/amd_pstate/status || echo n/a)"
|
||||
printf 'governor=%s\n' "$(attr_values scaling_governor)"
|
||||
printf 'epp=%s\n' "$(attr_values energy_performance_preference)"
|
||||
printf 'min_khz=%s\n' "$(readf "$p0/scaling_min_freq")"
|
||||
printf 'max_khz=%s\n' "$(readf "$p0/scaling_max_freq")"
|
||||
printf 'hw_min_khz=%s\n' "$hw_min"
|
||||
printf 'hw_max_khz=%s\n' "$hw_max"
|
||||
printf 'floor_khz=%s\n' "$floor"
|
||||
printf 'boost=%s\n' "$(boost_read || echo n/a)"
|
||||
printf 'policies=%s\n' "$(policies | wc -l | tr -d ' ')"
|
||||
printf 'baseline=%s\n' "$([ -r "$BASELINE" ] && echo present || echo absent)"
|
||||
printf 'refcount=%s\n' "$([ -r "$REFCOUNT" ] && readf "$REFCOUNT" || echo 0)"
|
||||
}
|
||||
|
||||
set_governor() {
|
||||
_g=${1:-}
|
||||
[ -n "$_g" ] || fail "governor mancante"
|
||||
case "$_g" in *[!a-z_-]*) fail "governor non valido: $_g" ;; esac
|
||||
_avail=$(readf "$p0/scaling_available_governors") || fail "governor non leggibili"
|
||||
case " $_avail " in
|
||||
*" $_g "*) ;;
|
||||
*) fail "governor '$_g' non disponibile ($_avail)" ;;
|
||||
esac
|
||||
write_all scaling_governor "$_g" || fail "scrittura scaling_governor fallita su $FAILED policy"
|
||||
_now=$(attr_values scaling_governor)
|
||||
[ "$_now" = "$_g" ] || fail "verifica governor fallita: letto '$_now'"
|
||||
printf 'governor=%s\n' "$_g"
|
||||
}
|
||||
|
||||
set_epp() {
|
||||
_e=${1:-}
|
||||
[ -n "$_e" ] || fail "preferenza EPP mancante"
|
||||
case "$_e" in *[!a-z_]*) fail "preferenza EPP non valida: $_e" ;; esac
|
||||
_avail=$(readf "$p0/energy_performance_available_preferences") \
|
||||
|| fail "questo driver non espone energy_performance_preference"
|
||||
case " $_avail " in
|
||||
*" $_e "*) ;;
|
||||
*) fail "EPP '$_e' non disponibile ($_avail)" ;;
|
||||
esac
|
||||
if write_all energy_performance_preference "$_e"; then
|
||||
printf 'epp=%s\n' "$_e"
|
||||
return 0
|
||||
fi
|
||||
# Con governor "performance" il driver EPP può ignorare la preferenza: non è
|
||||
# un errore fatale, viene segnalato come warning.
|
||||
if [ "$(attr_values scaling_governor)" = "performance" ]; then
|
||||
printf 'warning=EPP non applicato con governor performance\n'
|
||||
return 0
|
||||
fi
|
||||
fail "scrittura EPP fallita su $FAILED policy"
|
||||
}
|
||||
|
||||
set_boost() {
|
||||
_b=${1:-}
|
||||
case "$_b" in 0|1) ;; *) fail "boost deve essere 0 o 1" ;; esac
|
||||
_count=0
|
||||
for _p in $(policies); do
|
||||
if [ -w "$_p/boost" ] && write_one "$_p/boost" "$_b"; then
|
||||
_count=$((_count + 1))
|
||||
fi
|
||||
done
|
||||
if [ -n "$boost_global" ] && write_one "$boost_global" "$_b"; then
|
||||
_count=$((_count + 1))
|
||||
fi
|
||||
if [ -w "$INTEL_PSTATE/no_turbo" ] && write_one "$INTEL_PSTATE/no_turbo" $((1 - _b)); then
|
||||
_count=$((_count + 1))
|
||||
fi
|
||||
[ "$_count" -gt 0 ] || fail "nessun controllo boost scrivibile su questo sistema"
|
||||
_now=$(boost_read || echo "")
|
||||
[ "$_now" = "$_b" ] || printf 'warning=lettura boost dopo la scrittura: %s\n' "${_now:-n/d}"
|
||||
printf 'boost=%s\n' "$_b"
|
||||
}
|
||||
|
||||
set_limits() {
|
||||
_min=${1:-}
|
||||
_max=${2:-}
|
||||
case "$_min$_max" in *[!0-9]*) fail "limiti non numerici (kHz interi)" ;; esac
|
||||
[ -n "$_min" ] && [ -n "$_max" ] || fail "servono min_khz e max_khz"
|
||||
[ "$_min" -ge "$hw_min" ] || fail "minimo $_min sotto il limite hardware $hw_min"
|
||||
[ "$_max" -le "$hw_max" ] || fail "massimo $_max sopra il limite hardware $hw_max"
|
||||
[ "$_min" -le "$_max" ] || fail "minimo $_min maggiore del massimo $_max"
|
||||
_cur_min=$(readf "$p0/scaling_min_freq" || echo "$hw_min")
|
||||
_cur_max=$(readf "$p0/scaling_max_freq" || echo "$hw_max")
|
||||
# Ordine di scrittura sicuro: mai uno stato intermedio con min > max.
|
||||
if [ "$_min" -gt "$_cur_max" ]; then
|
||||
write_all scaling_max_freq "$_max" || fail "pre-allargamento del tetto fallito"
|
||||
fi
|
||||
if [ "$_max" -lt "$_cur_min" ]; then
|
||||
write_all scaling_min_freq "$_min" || fail "pre-abbassamento del minimo fallito"
|
||||
fi
|
||||
write_all scaling_min_freq "$_min" || fail "scrittura scaling_min_freq fallita su $FAILED policy"
|
||||
write_all scaling_max_freq "$_max" || fail "scrittura scaling_max_freq fallita su $FAILED policy"
|
||||
printf 'min_khz=%s\n' "$(readf "$p0/scaling_min_freq")"
|
||||
printf 'max_khz=%s\n' "$(readf "$p0/scaling_max_freq")"
|
||||
}
|
||||
|
||||
apply_profile() {
|
||||
_pr=${1:-}
|
||||
cf_profile_known "$_pr" || fail "profilo '$_pr' sconosciuto ($(cf_profiles | tr '\n' ' '))"
|
||||
_gov=$(cf_profile_gov "$_pr")
|
||||
_epp=$(cf_profile_epp "$_pr")
|
||||
_bst=$(cf_profile_boost "$_pr")
|
||||
_maxf=$(cf_profile_max_khz "$_pr" "$floor" "$hw_max")
|
||||
[ "$_maxf" -gt "$hw_max" ] && _maxf=$hw_max
|
||||
[ "$_maxf" -lt "$floor" ] && _maxf=$floor
|
||||
printf 'profile=%s\n' "$_pr"
|
||||
# Ordine: 1) boost, 2) governor, 3) limiti, 4) EPP.
|
||||
# Il boost viene per primo perché con boost disattivato il kernel riduce il
|
||||
# tetto nominale e una scrittura di scaling_max_freq oltre quel valore
|
||||
# verrebbe rifiutata o limitata.
|
||||
set_boost "$_bst" || return 1
|
||||
if readf "$p0/scaling_available_governors" | grep -qw -- "$_gov"; then
|
||||
set_governor "$_gov" || return 1
|
||||
else
|
||||
printf 'warning=governor %s non disponibile, mantenuto %s\n' \
|
||||
"$_gov" "$(attr_values scaling_governor)"
|
||||
fi
|
||||
set_limits "$floor" "$_maxf" || return 1
|
||||
if readf "$p0/energy_performance_available_preferences" >/dev/null 2>&1; then
|
||||
set_epp "$_epp" || return 1
|
||||
fi
|
||||
printf 'profile_label=%s\n' "$(cf_profile_label "$_pr")"
|
||||
}
|
||||
|
||||
ensure_state_dir() {
|
||||
if [ ! -d "$STATE_DIR" ]; then
|
||||
mkdir -p "$STATE_DIR" || fail "impossibile creare $STATE_DIR"
|
||||
chmod 755 "$STATE_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
save_state() {
|
||||
ensure_state_dir
|
||||
{
|
||||
printf 'governor=%s\n' "$(readf "$p0/scaling_governor")"
|
||||
printf 'epp=%s\n' "$(readf "$p0/energy_performance_preference" || echo "")"
|
||||
printf 'min_khz=%s\n' "$(readf "$p0/scaling_min_freq")"
|
||||
printf 'max_khz=%s\n' "$(readf "$p0/scaling_max_freq")"
|
||||
printf 'boost=%s\n' "$(boost_read || echo "")"
|
||||
printf 'saved_at=%s\n' "$(date +%s)"
|
||||
} > "$BASELINE" || fail "scrittura baseline fallita"
|
||||
chmod 644 "$BASELINE"
|
||||
printf 'baseline=saved\n'
|
||||
cat "$BASELINE"
|
||||
}
|
||||
|
||||
restore_state() {
|
||||
[ -r "$BASELINE" ] || fail "nessuna baseline salvata"
|
||||
_gov=$(sed -n 's/^governor=//p' "$BASELINE")
|
||||
_epp=$(sed -n 's/^epp=//p' "$BASELINE")
|
||||
_min=$(sed -n 's/^min_khz=//p' "$BASELINE")
|
||||
_max=$(sed -n 's/^max_khz=//p' "$BASELINE")
|
||||
_bst=$(sed -n 's/^boost=//p' "$BASELINE")
|
||||
if [ -n "$_gov" ]; then
|
||||
if readf "$p0/scaling_available_governors" | grep -qw -- "$_gov"; then
|
||||
write_all scaling_governor "$_gov" || printf 'warning=governor non ripristinato\n'
|
||||
fi
|
||||
fi
|
||||
if [ -n "$_min" ] && [ -n "$_max" ]; then
|
||||
case "$_min$_max" in
|
||||
*[!0-9]*) ;;
|
||||
*) set_limits "$_min" "$_max" || printf 'warning=limiti non ripristinati\n' ;;
|
||||
esac
|
||||
fi
|
||||
if [ -n "$_epp" ] && readf "$p0/energy_performance_available_preferences" >/dev/null 2>&1; then
|
||||
write_all energy_performance_preference "$_epp" || printf 'warning=EPP non ripristinato\n'
|
||||
fi
|
||||
if [ -n "$_bst" ]; then
|
||||
case "$_bst" in
|
||||
0|1) set_boost "$_bst" >/dev/null || printf 'warning=boost non ripristinato\n' ;;
|
||||
esac
|
||||
fi
|
||||
printf 'restored=1\n'
|
||||
print_state
|
||||
}
|
||||
|
||||
reset_cmd() {
|
||||
_gov=powersave
|
||||
if ! readf "$p0/scaling_available_governors" | grep -qw -- "$_gov"; then
|
||||
_gov=$(readf "$p0/scaling_available_governors" | awk '{print $1}')
|
||||
fi
|
||||
printf 'profile=reset\n'
|
||||
[ -n "$_gov" ] && set_governor "$_gov"
|
||||
set_limits "$floor" "$hw_max" || return 1
|
||||
if readf "$p0/energy_performance_available_preferences" >/dev/null 2>&1; then
|
||||
_epp=balance_performance
|
||||
if ! readf "$p0/energy_performance_available_preferences" | grep -qw -- "$_epp"; then
|
||||
_epp=default
|
||||
fi
|
||||
set_epp "$_epp" || return 1
|
||||
fi
|
||||
set_boost 1 || return 1
|
||||
}
|
||||
|
||||
task_begin() {
|
||||
_pr=${1:-}
|
||||
cf_profile_known "$_pr" || fail "profilo '$_pr' sconosciuto"
|
||||
ensure_state_dir
|
||||
_rc=0
|
||||
[ -r "$REFCOUNT" ] && _rc=$(readf "$REFCOUNT")
|
||||
case "${_rc:-0}" in *[!0-9]*|"") _rc=0 ;; esac
|
||||
if [ "$_rc" -eq 0 ] || [ ! -r "$BASELINE" ]; then
|
||||
save_state >/dev/null || fail "salvataggio dello stato corrente fallito"
|
||||
fi
|
||||
apply_profile "$_pr" || fail "applicazione del profilo '$_pr' fallita"
|
||||
_rc=$((_rc + 1))
|
||||
printf '%s\n' "$_rc" > "$REFCOUNT"
|
||||
chmod 644 "$REFCOUNT"
|
||||
printf 'refcount=%s\n' "$_rc"
|
||||
}
|
||||
|
||||
task_end() {
|
||||
ensure_state_dir
|
||||
_rc=0
|
||||
[ -r "$REFCOUNT" ] && _rc=$(readf "$REFCOUNT")
|
||||
case "${_rc:-0}" in *[!0-9]*|"") _rc=0 ;; esac
|
||||
if [ "${1:-}" = "--all" ]; then
|
||||
_rc=0
|
||||
else
|
||||
_rc=$((_rc - 1))
|
||||
[ "$_rc" -lt 0 ] && _rc=0
|
||||
fi
|
||||
printf '%s\n' "$_rc" > "$REFCOUNT"
|
||||
chmod 644 "$REFCOUNT"
|
||||
if [ "$_rc" -eq 0 ] && [ -r "$BASELINE" ]; then
|
||||
restore_state >/dev/null || printf 'warning=ripristino della baseline fallito\n'
|
||||
rm -f "$BASELINE"
|
||||
printf 'restored=1\n'
|
||||
fi
|
||||
printf 'refcount=%s\n' "$_rc"
|
||||
}
|
||||
|
||||
case "$CMD" in
|
||||
status)
|
||||
print_state
|
||||
;;
|
||||
usage|-h|--help|help)
|
||||
usage
|
||||
;;
|
||||
set-governor)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
set_governor "${1:-}"
|
||||
;;
|
||||
set-epp)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
set_epp "${1:-}"
|
||||
;;
|
||||
set-limits)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
set_limits "${1:-}" "${2:-}"
|
||||
;;
|
||||
set-boost)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
set_boost "${1:-}"
|
||||
;;
|
||||
apply)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
apply_profile "${1:-}"
|
||||
;;
|
||||
reset)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
reset_cmd
|
||||
;;
|
||||
save-state)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
save_state
|
||||
;;
|
||||
restore-state)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
restore_state
|
||||
;;
|
||||
task-begin)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
task_begin "${1:-}"
|
||||
;;
|
||||
task-end)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
task_end "${1:-}"
|
||||
;;
|
||||
*)
|
||||
printf 'ok=0\nerror=comando sconosciuto: %s\n' "$CMD"
|
||||
usage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
printf 'ok=1\n'
|
||||
exit 0
|
||||
Reference in New Issue
Block a user