Files
enne2 d4902b0b72 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.
2026-10-25 20:42:52 +01:00

146 lines
4.5 KiB
Bash
Executable File

# shellcheck shell=sh
# Profili di frequenza condivisi fra helper privilegiato, lettore di stato e plasmoide.
# Sorgente unica di verità: NON scrive nulla su sysfs, contiene solo la tabella dei
# profili e l'aritmetica dei limiti (kHz). Copiato da install.sh accanto all'helper
# in /usr/local/libexec/enne2-cpufreq/.
#
# Ogni profilo agisce su: governor, energy_performance_preference (EPP), boost e
# tetto di frequenza espresso come percentuale dello span utile
# (floor_khz .. cpuinfo_max_freq).
# Lettura minimale (il file è già stato validato dal chiamante dove serve).
cf_read() {
_cfout=
[ -r "$1" ] || return 1
IFS= read -r _cfout < "$1" 2>/dev/null || [ -n "$_cfout" ] || return 1
printf '%s\n' "$_cfout"
}
# Tetto hardware reale (kHz), indipendente dallo stato del boost:
# con boost disattivato il kernel riporta in cpuinfo_max_freq la frequenza
# nominale (es. 3.80 GHz invece di 4.97 GHz su amd-pstate). amd_pstate_max_freq
# conserva il massimo effettivo; su altri driver cpuinfo_max_freq è già corretto.
cf_hw_max_khz() {
_cfp0=$1
_cfmax=$(cf_read "$_cfp0/cpuinfo_max_freq" || echo 0)
case "$_cfmax" in ''|*[!0-9]*) _cfmax=0 ;; esac
_cfamd=$(cf_read "$_cfp0/amd_pstate_max_freq" || echo 0)
case "$_cfamd" in ''|*[!0-9]*) _cfamd=0 ;; esac
[ "$_cfamd" -gt "$_cfmax" ] && _cfmax=$_cfamd
printf '%s\n' "$_cfmax"
}
cf_profiles() {
printf '%s\n' quiet powersave balanced performance
}
cf_profile_known() {
case "$1" in
quiet|powersave|balanced|performance) return 0 ;;
*) return 1 ;;
esac
}
cf_profile_gov() {
case "$1" in
quiet|powersave|balanced) echo powersave ;;
performance) echo performance ;;
*) return 1 ;;
esac
}
cf_profile_epp() {
case "$1" in
quiet) echo power ;;
powersave) echo balance_power ;;
balanced) echo balance_performance ;;
performance) echo performance ;;
*) return 1 ;;
esac
}
cf_profile_boost() {
case "$1" in
quiet|powersave) echo 0 ;;
balanced|performance) echo 1 ;;
*) return 1 ;;
esac
}
cf_profile_max_pct() {
case "$1" in
quiet) echo 35 ;;
powersave) echo 60 ;;
balanced|performance) echo 100 ;;
*) return 1 ;;
esac
}
cf_profile_label() {
case "$1" in
quiet) echo "Silenzioso" ;;
powersave) echo "Risparmio" ;;
balanced) echo "Bilanciato" ;;
performance) echo "Performance" ;;
custom) echo "Personalizzato" ;;
*) echo "$1" ;;
esac
}
cf_profile_desc() {
case "$1" in
quiet) echo "EPP power, boost off, tetto 35% dello span" ;;
powersave) echo "EPP balance_power, boost off, tetto 60%" ;;
balanced) echo "EPP balance_performance, boost on, nessun tetto" ;;
performance) echo "governor performance, EPP performance, boost on" ;;
*) echo "" ;;
esac
}
# cf_profile_max_khz <profilo> <floor_khz> <hw_max_khz>
cf_profile_max_khz() {
_cfp=$(cf_profile_max_pct "$1") || return 1
_cfl=$2
_cfh=$3
[ "$_cfh" -gt "$_cfl" ] || { echo "$_cfh"; return 0; }
# un profilo al 100% non deve avere tetto: nessun arrotondamento
[ "$_cfp" -ge 100 ] && { echo "$_cfh"; return 0; }
cf_round50 $(( _cfl + (_cfh - _cfl) * _cfp / 100 ))
}
# Arrotonda a 50 MHz: valori leggibili e stabili fra una lettura e l'altra.
cf_round50() {
echo $(( ($1 + 25000) / 50000 * 50000 ))
}
# cf_detect_profile <governor> <epp> <boost> <max_khz> <floor_khz> <hw_max_khz>
# Stampa il nome del profilo se lo stato coincide (tolleranza sul tetto) oppure "custom".
cf_detect_profile() {
_dg=$1
_de=$2
_db=$3
_dmax=$4
_dfloor=$5
_dhwmax=$6
_span=$(( _dhwmax - _dfloor ))
[ "$_span" -gt 0 ] || _span=1
_tol=$(( _span / 40 )) # 2.5% dello span
[ "$_tol" -lt 80000 ] && _tol=80000
for _dp in $(cf_profiles); do
[ "$_dg" = "$(cf_profile_gov "$_dp")" ] || continue
[ "$_db" = "$(cf_profile_boost "$_dp")" ] || continue
_dp_max=$(cf_profile_max_khz "$_dp" "$_dfloor" "$_dhwmax")
_diff=$(( _dmax - _dp_max ))
[ "$_diff" -lt 0 ] && _diff=$(( -_diff ))
[ "$_diff" -le "$_tol" ] || continue
# L'EPP può non essere scrivibile con governor "performance": in quel caso
# accettiamo il profilo anche se l'EPP letto non coincide.
[ "$_de" = "$(cf_profile_epp "$_dp")" ] && { echo "$_dp"; return 0; }
if [ "$_dg" = "performance" ] && [ "$_dp" = "performance" ]; then
echo "$_dp"
return 0
fi
done
echo custom
}