This repository was archived by the owner on Nov 8, 2026. It is now read-only.
Repository navigation
Expand file tree
/
Copy pathmseries-power
More file actions
executable file
·114 lines (106 loc) · 4.92 KB
/
Copy pathmseries-power
File metadata and controls
executable file
·114 lines (106 loc) · 4.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#!/bin/bash
# Battery and CPU policy for Apple Silicon (macsmc-battery + apple-cpufreq).
# mseries-power status show charge limit, power source, CPU caps
# mseries-power limit 80|100 set (and persist) the battery charge limit
# mseries-power apply apply policy for the current power source (run by systemd/udev)
# mseries-power cap set only the CPU ceiling from the power source AND the active power profile
# mseries-power follow run forever: re-apply the cap whenever the power profile changes (systemd service)
# The CPU ceiling only applies on battery in the power-saver profile. Switching to balanced (e.g. a burst for local AI
# or a build) lifts it; switching back or unplugging/replugging restores the policy.
# Policy comes from /etc/mseries/power.conf. Needs root to change anything.
set -euo pipefail
shopt -s nullglob
SYS=${MSERIES_SYS:-/sys} # overridable so the policy can be tested against a fake tree
conf=${MSERIES_CONF:-/etc/mseries/power.conf}
bat=$SYS/class/power_supply/macsmc-battery
ac=$SYS/class/power_supply/macsmc-ac/online
CHARGE_LIMIT=80
CHARGE_START=75
BATTERY_PROFILE=power-saver
AC_PROFILE=balanced
BATTERY_CAP_KHZ=2400000 # performance-cluster ceiling on battery (hardware max 3036000); 0 = no cap
# shellcheck disable=SC1090
[[ -f $conf ]] && . "$conf"
write() {
if ! { printf '%s' "$2" > "$1"; } 2>/dev/null; then
echo "could not write $2 to $1" >&2
return 1
fi
}
apply_limit() {
# The start threshold must stay below the end threshold, so lower/raise them in the right order.
if (( CHARGE_LIMIT >= 100 )); then
write "$bat/charge_control_end_threshold" 100; write "$bat/charge_control_start_threshold" 95
else
write "$bat/charge_control_start_threshold" "$CHARGE_START"; write "$bat/charge_control_end_threshold" "$CHARGE_LIMIT"
fi
}
# CPU ceiling in kHz for the current state: 0 means no cap. Only on battery AND in the power-saver profile.
wanted_cap() {
local on_ac profile
on_ac=$(cat "$ac" 2>/dev/null || echo 1)
profile=$(powerprofilesctl get 2>/dev/null || echo unknown)
if [[ $on_ac == 1 || $profile != power-saver ]]; then echo 0; else echo "$BATTERY_CAP_KHZ"; fi
}
apply_cap() {
local cap p max min target
cap=$(wanted_cap)
for p in "$SYS"/devices/system/cpu/cpufreq/policy*; do
max=$(cat "$p/cpuinfo_max_freq")
min=$(cat "$p/cpuinfo_min_freq")
[[ $max =~ ^[0-9]+$ && $min =~ ^[0-9]+$ && $cap =~ ^[0-9]+$ ]] || {
echo "invalid CPU frequency policy: $p" >&2; return 1;
}
# Only the performance clusters are capped; the efficiency cores are already slow.
if (( max > 2500000 && cap > 0 )); then
target=$cap
(( target > max )) && target=$max
(( target < min )) && target=$min
else target=$max; fi
write "$p/scaling_max_freq" "$target"
done
}
apply_source() {
local on_ac profile
on_ac=$(cat "$ac" 2>/dev/null || echo 1)
if [[ $on_ac == 1 ]]; then profile=$AC_PROFILE; else profile=$BATTERY_PROFILE; fi
powerprofilesctl set "$profile"
apply_cap
}
follow() {
apply_cap
# Re-apply whenever power-profiles-daemon reports a profile change.
gdbus monitor --system --dest net.hadess.PowerProfiles --object-path /net/hadess/PowerProfiles 2>/dev/null |
while read -r line; do
if [[ $line == *ActiveProfile* || $line == *'is owned by'* ]]; then apply_cap; fi
done
# A disconnected monitor is not a successful completion of this long-running service.
return 1
}
case ${1:-status} in
apply) apply_limit; apply_source ;;
cap) apply_cap ;;
follow) follow; exit 0 ;;
limit)
[[ ${2:-} =~ ^(80|100)$ ]] || { echo 'usage: mseries-power limit 80|100' >&2; exit 1; }
CHARGE_LIMIT=$2; apply_limit
mkdir -p "$(dirname "$conf")"; touch "$conf"
if grep -q '^CHARGE_LIMIT=' "$conf"; then sed -i "s/^CHARGE_LIMIT=.*/CHARGE_LIMIT=$2/" "$conf"; else echo "CHARGE_LIMIT=$2" >> "$conf"; fi
;;
status) ;;
*) echo 'usage: mseries-power status|limit 80|100|apply|cap|follow' >&2; exit 1 ;;
esac
# Mutating commands report failures directly; status is best effort on machines missing a sensor.
[[ ${1:-status} == status ]] || exit 0
read_value() { cat "$1" 2>/dev/null || printf 'unknown'; }
full=$(read_value "$bat/energy_full"); design=$(read_value "$bat/energy_full_design")
health=unknown
if [[ $full =~ ^[0-9]+$ && $design =~ ^[0-9]+$ ]] && (( design > 0 )); then
health=$(( full * 100 / design ))
fi
echo "battery: $(read_value "$bat/capacity")% ($(read_value "$bat/status")), health $health%, $(read_value "$bat/cycle_count") cycles"
echo "charge limit: stop at $(read_value "$bat/charge_control_end_threshold")%, resume at $(read_value "$bat/charge_control_start_threshold")%"
echo "power source online: $(read_value "$ac"); profile: $(powerprofilesctl get 2>/dev/null || echo unknown)"
for p in "$SYS"/devices/system/cpu/cpufreq/policy*; do
echo "cpus $(read_value "$p/related_cpus"): max $(read_value "$p/scaling_max_freq") kHz (hardware $(read_value "$p/cpuinfo_max_freq"))"
done