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.
411 lines
14 KiB
Bash
Executable File
411 lines
14 KiB
Bash
Executable File
#!/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
|