#!/bin/sh # halogen-fabric-clock hold | release | status: the AMD GPU's fabric clock (FCLK), held at its top level. # # On Strix Halo the GPU and the NPU working at the same time can hang the machine, and corrupt the NPU's results, # while the GPU's fabric clock changes speed; held at its top level, the two run together cleanly. halogen-flash-server # with HALOGEN_NPU_MODELS refuses to start until the clock is held. This holds it: the performance level `manual` and # only the top pp_dpm_fclk level, on every amdgpu device that has the control (the GPU and SoC clocks stay automatic). # Cost: about 1 W at idle. Run as root; halogen-fabric-clock.service runs `hold` at every boot and after a resume. # # hold hold it (waits up to 60 s for the GPU driver to come up) # release give it back to the driver (the performance level `auto`) # status print each device's state; exit 0 when every device is held set -u SYS="${HALOGEN_SYSFS:-/sys}" devices() { for d in "$SYS"/class/drm/card*/device; do [ -w "$d/pp_dpm_fclk" ] || [ -r "$d/pp_dpm_fclk" ] || continue [ -e "$d/power_dpm_force_performance_level" ] || continue echo "$d" done | sort -u } held() { # held DEVICE_DIR: the performance level high, or manual with only the top fclk level selected perf=$(cat "$1/power_dpm_force_performance_level" 2>/dev/null) [ "$perf" = high ] && return 0 [ "$perf" = manual ] || return 1 top=$(grep . "$1/pp_dpm_fclk" | tail -n 1) [ "$(grep -c '\*' "$1/pp_dpm_fclk")" = 1 ] && [ "${top%\*}" != "$top" ] } cmd="${1:-status}" case "$cmd" in hold) n=0 while [ -z "$(devices)" ] && [ "$n" -lt 60 ]; do sleep 1; n=$((n + 1)); done [ -n "$(devices)" ] || { echo "halogen-fabric-clock: no amdgpu device with a fabric clock control (pp_dpm_fclk) under $SYS/class/drm" >&2; exit 1; } rc=0 for d in $(devices); do lvl=$(grep . "$d/pp_dpm_fclk" | tail -n 1 | cut -d: -f1) # the driver applies the level several seconds after the write, so the read-back waits up to 30 s ok=0 if echo manual > "$d/power_dpm_force_performance_level" && echo "$lvl" > "$d/pp_dpm_fclk"; then t=0; until held "$d" || [ "$t" -ge 300 ]; do sleep 0.1; t=$((t + 1)); done held "$d" && ok=1 fi if [ "$ok" = 1 ]; then echo "halogen-fabric-clock: $d: held at level $lvl ($(grep . "$d/pp_dpm_fclk" | tail -n 1 | awk '{print $2}'))" else echo "halogen-fabric-clock: $d: could not hold it (level $lvl)" >&2; rc=1 fi done exit "$rc" ;; release) for d in $(devices); do echo auto > "$d/power_dpm_force_performance_level" && echo "halogen-fabric-clock: $d: released (auto)"; done ;; status) [ -n "$(devices)" ] || { echo "halogen-fabric-clock: no amdgpu device with a fabric clock control"; exit 1; } rc=0 for d in $(devices); do if held "$d"; then echo "$d: held ($(cat "$d/power_dpm_force_performance_level"))" else echo "$d: NOT held ($(cat "$d/power_dpm_force_performance_level"))"; rc=1; fi done exit "$rc" ;; *) echo "usage: halogen-fabric-clock hold | release | status" >&2; exit 2 ;; esac