#!/usr/bin/env bash # pin_device.sh pin|unpin|check # # Frequency pinning for the two disaggregation-campaign devices, standalone: pure adb + su, no # sourcing of a device profile, no dependency on bench.sh. It exists because bench.sh's # pin_freqs()/unpin_freqs() are MediaTek-legacy-only (/proc/ppm + /proc/gpufreq) and NEITHER of # these two devices has those paths - on both of them bench.sh's pin writes into nothing and # exits 0, which is precisely the silent "the run looks pinned but is not" failure the # PROFILE_VERIFIED guard was built to catch. So: pin here, run bench.sh with --no-pin, and use # `check` in place of the pin-integrity fields bench.sh would otherwise have sampled. # # ./pin_device.sh 35d0befa pin # ./bench.sh --device devices/xiaomi-adreno830.env --backend magma --no-pin # ./pin_device.sh 35d0befa check || echo "PIN DRIFTED - discard this run" # ./pin_device.sh 35d0befa unpin # # All three actions print the live big/little/GPU frequencies, the governors and the gate # temperature, so the output of `pin` and of `unpin` is itself the before/after evidence. # # check exit codes (deliberately three-valued: "no pin at all" and "a pin that slipped" are # different facts, and collapsing them to one non-zero would hide which one happened): # 0 PINNED - every pinned node is at its pin AND every live frequency equals it # 1 DRIFT - the device is partly pinned, or a live frequency has left its pin. The # dangerous state: a run overlapping this is not comparable. Discard it. # 2 UNPINNED- no node is at a pin this script set; the vendor governors have their range # back. That is the correct state to leave a device in, and it is still non-zero # so that `pin_device.sh X check && measure` cannot silently measure unpinned. # Note this is asserted against OUR pins, not against the stock range: ColorOS # moves policy4's max on its own within seconds of a release, so an exact-stock # comparison reported DRIFT on a correctly unpinned device. # # Values were read off each device on 2026-09-07 and confirmed against a pinned window; see # ../REPORT.md and the matching *.env profiles. Stock values are hardcoded rather than sampled # at pin time on purpose - a restore that reads "stock" from an already-pinned device would # make the pin permanent, which is how a device silently stays clamped across a reboot-less # week of runs. set -u -o pipefail # Git Bash: stop MSYS rewriting /sys/... and /proc/... arguments into C:/Program Files/... export MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*' SERIAL=${1:-} ACTION=${2:-} case "$ACTION" in pin|unpin|check) ;; *) echo "usage: $0 pin|unpin|check" >&2; exit 64 ;; esac CPUFREQ=/sys/devices/system/cpu/cpufreq # --------------------------------------------------------------------------------------------- # Device table. Everything device-specific lives here; the actions below are generic. # POLICIES - "label:policy:pinned_khz:stock_min:stock_max" per cpufreq policy to pin. # Every policy that can run a hot thread must be listed, not just the two # the protocol reports: on MT6993 an unpinned policy7 at 4.21 GHz defeats # the entire pin the moment the scheduler lands a thread on it. # THERMAL_TYPE - matched against /sys/class/thermal/thermal_zone*/type by name, never by # zone number: numbering is not stable across boots. # --------------------------------------------------------------------------------------------- case "$SERIAL" in 35d0befa) # Xiaomi 24129PN74C, Snapdragon 8 Elite (SM8750), Adreno 830v2 DEV_NAME="Xiaomi 24129PN74C / SM8750 / Adreno 830v2" GPU_STYLE=kgsl POLICIES="big:policy6:1958400:1017600:2841600 little:policy0:1555200:556800:2745600" THERMAL_TYPE=cpuss-0-0 KGSL=/sys/class/kgsl/kgsl-3d0 GPU_PIN_LEVEL=0 # pwrlevel 0 = 1100 MHz, above the stock devfreq ceiling of 1050 GPU_STOCK_MIN_LEVEL=12 GPU_STOCK_MAX_LEVEL=0 GPU_PINNED_FREQ=1100000000 # gpuclk is in Hz on kgsl, NOT kHz GPU_FREQ_UNIT=Hz ;; 3B159D009VZ00000) # Oppo PLG110 / ColorOS, MediaTek MT6993 (Dimensity 9500), Mali DEV_NAME="Oppo PLG110 / MT6993 / Mali (gpufreqv2)" GPU_STYLE=gpufreqv2 # 2000000 / 1600000 are the nearest available OPPs to the protocol's 1958400 / 1555200 # (+2.1% / +2.9%); neither target is an exact step on this part. POLICIES="big:policy4:2000000:300000:3500000 little:policy0:1600000:300000:2100000 extra:policy7:2000000:300000:3200000" THERMAL_TYPE=soc_max GPU_FIX_NODE=/proc/gpufreqv2/fix_target_opp_index GPU_PIN_LEVEL=0 # OPP index 0 = 1716000 kHz, the top working OPP GPU_PINNED_FREQ=1716000 GPU_FREQ_UNIT=kHz ;; *) echo "$0: unknown serial '$SERIAL'." >&2 echo "Known: 35d0befa (Xiaomi/Adreno830), 3B159D009VZ00000 (Oppo/Mali)." >&2 echo "Refusing to guess: the pin path differs per SoC and a wrong one fails silently." >&2 exit 64 ;; esac A="adb -s $SERIAL" # Quote the whole su invocation for the DEVICE shell, or the redirect runs unprivileged. su_() { $A shell "su -c '$*'" 2>&1 | tr -d '\r'; } $A get-state >/dev/null 2>&1 || { echo "$0: device $SERIAL is not connected" >&2; exit 65; } [ "$(su_ 'id -u')" = "0" ] || { echo "$0: no root on $SERIAL (su failed)" >&2; exit 65; } # --- read live state in ONE device round trip ------------------------------------------------- # Every read goes through su: on the Oppo the cpufreq nodes are 0660 system:system and a plain # `adb shell cat scaling_governor` answers "Permission denied", which a careless parser reads # as an empty governor rather than as a failure. read_state() { local script="" spec label pol for spec in $POLICIES; do IFS=: read -r label pol _ _ _ <<<"$spec" script="$script echo \"${label}_gov=\$(cat $CPUFREQ/$pol/scaling_governor)\";" script="$script echo \"${label}_min=\$(cat $CPUFREQ/$pol/scaling_min_freq)\";" script="$script echo \"${label}_max=\$(cat $CPUFREQ/$pol/scaling_max_freq)\";" script="$script echo \"${label}_cur=\$(cat $CPUFREQ/$pol/scaling_cur_freq)\";" done script="$script for tz in /sys/class/thermal/thermal_zone*; do" script="$script if [ \"\$(cat \$tz/type 2>/dev/null)\" = \"$THERMAL_TYPE\" ]; then" script="$script echo \"temp_mc=\$(cat \$tz/temp)\"; echo \"temp_zone=\$tz\"; break; fi; done;" # Emit the GPU nodes RAW and pick them apart locally. The device-side script is delivered as # `su -c '