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:
enne2
2026-10-25 20:42:52 +01:00
commit d4902b0b72
15 changed files with 2674 additions and 0 deletions
+410
View File
@@ -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