[Tooling] (DeviceBench): verify the two campaign devices' pin profiles and add a pin helper that works on them

- bench.sh's pin_freqs writes /proc/ppm and /proc/gpufreq, which exist on neither 35d0befa
  (SM8750) nor 3B159D009VZ00000 (MT6993 dropped both for /proc/gpufreqv2); the guard the
  PROFILE_VERIFIED key exists for was about to be defeated on the device its own comment
  guessed was safe
- pin_device.sh pins big/little/GPU through the nodes each device actually has (Xiaomi:
  policy6 1958400 / policy0 1555200 / kgsl pwrlevel 0; Oppo: policy4 2000000 + policy7
  2000000 / policy0 1600000 / gpufreqv2 fix_target_opp_index 0), checks only against its own
  pins because ColorOS moves stock maxima by itself, and refuses to call a zero-key read a verdict
- both profiles are PROFILE_VERIFIED=1 on 30 s pinned windows with zero drift; the evidence and
  the two things not verified (Oppo GPU held at DVFS-request level only, no full bench window)
  are in docs/Disaggregated/devices/pin-verification-2026-09-07.md
This commit is contained in:
2026-09-07 23:43:11 -04:00
parent 7a2e256133
commit 2d690754dd
5 changed files with 1003 additions and 72 deletions
+125 -35
View File
@@ -1,24 +1,134 @@
# Device profile: Oppo / ColorOS, Mali GPU, adb serial 3B159D009VZ00000.
# Device profile: Oppo PLG110 / ColorOS, MediaTek MT6993 (Dimensity 9500),
# Mali (gpufreqv2 + ged), adb serial 3B159D009VZ00000.
#
# The second of the two devices the disaggregation campaign is measured on (the other is
# devices/xiaomi-adreno830.env). Same purpose: keep the pinning and thermal protocol in the
# repository rather than in one operator's shell history.
#
# ============================ NOT YET DEVICE-VERIFIED ============================
# PROFILE_VERIFIED=0, and bench.sh / session.sh refuse to run against it - or against a profile
# that omits the key - unless --allow-unverified-profile is passed. (profile.sh only records a
# simpleperf profile and pins nothing, so it carries no such guard.) This part is a MediaTek SoC, so unlike the Adreno
# profile the harness's existing /proc/ppm + /proc/gpufreq pin path is probably the right one -
# but "probably" is exactly the state a measurement profile must not ship in. The cluster
# indices, the available OPPs, the top GPU OPP and the thermal zone TYPE all differ between
# MediaTek generations, and odinlite.env's values are for an MT6877, not for this device.
# ============================== DEVICE-VERIFIED ==============================
# Read off the device on 2026-09-07 and confirmed against one pinned window: pins written,
# 8 concurrent busy loops as load, scaling_cur_freq for all three policies and the ged GPU
# frequency sampled 7 times over 30 s, every sample equal to the pin, then unpinned and every
# node confirmed back at its stock value. Evidence is in ../REPORT.md.
#
# To promote it: read the four TODO fields off the device
# (`cat /sys/devices/system/cpu/cpufreq/policy*/scaling_available_frequencies`,
# `cat /proc/gpufreq/gpufreq_opp_dump`, `for tz in /sys/class/thermal/thermal_zone*; do
# echo "$tz $(cat $tz/type)"; done`), run one pinned window, check big_cur/little_cur/gpu_cur_khz
# in the result JSON against the pins, then set PROFILE_VERIFIED=1.
# READ THIS BEFORE USING IT WITH bench.sh / session.sh
# ----------------------------------------------------
# The old draft of this profile said PIN_STYLE=ppm on the reasoning that "this is a MediaTek
# SoC, so the harness's existing /proc/ppm + /proc/gpufreq pin path is probably the right one".
# It is not. On MT6993:
# /proc/ppm/ does not exist
# /proc/gpufreq/ does not exist (superseded by /proc/gpufreqv2/)
# So bench.sh's pin_freqs() writes four ppm lines and one gpufreq line into nothing, exits 0,
# and reports a run it believes was pinned - the failure the PROFILE_VERIFIED guard describes,
# on the device the guard's own comment guessed was safe. PROFILE_VERIFIED=1 below certifies
# THE NODES AND THE PINS IN THIS FILE, not that bench.sh can drive them.
#
# Until bench.sh grows a PIN_STYLE switch, drive the pin out of band:
# tools/device_bench/pin_device.sh 3B159D009VZ00000 pin
# tools/device_bench/bench.sh --device <this> --backend X --no-pin
# tools/device_bench/pin_device.sh 3B159D009VZ00000 check # non-zero on drift
# tools/device_bench/pin_device.sh 3B159D009VZ00000 unpin
# =============================================================================
PROFILE_VERIFIED=1
PIN_STYLE=mtk-gpufreqv2
DEVICE_SERIAL=3B159D009VZ00000
# --- CPU -------------------------------------------------------------------------------------
# MT6993 is a 4+3+1 part, THREE policies - which is the trap on this device:
# policy0 = cpus 0-3, 300000..2700000 kHz <- the protocol's "little"
# policy4 = cpus 4-6, 300000..3500000 kHz <- the protocol's "big"
# policy7 = cpu 7, 300000..4210000 kHz <- must ALSO be pinned, see below
# policy4 is chosen as "big" over the single-core policy7 because it is the structural analogue
# of the Xiaomi's 2-core policy6: a multi-core cluster the game's render and worker threads can
# actually share. But policy7 cannot simply be left alone - an unpinned 4.21 GHz core silently
# defeats the whole pin the moment the scheduler puts a hot thread on it, so the protocol pins
# it to the SAME target as policy4 and the device then behaves as 4 little + 4 big.
#
# Neither campaign target is an exact OPP here; both are the NEAREST available step:
# big 1958400 -> policy4 has 2000000 (D +41600, +2.1%) vs 1900000 (D -58400) -> 2000000
# little 1555200 -> policy0 has 1600000 (D +44800, +2.9%) vs 1500000 (D -55200) -> 1600000
# policy7 offers 2000000 exactly, so the big pin transfers to it unrounded.
# Record this 2-3% offset when comparing absolute per-thread CPU cost against the Xiaomi.
#
# Pin method: scaling_min_freq = scaling_max_freq = target, stock `sugov_ext` governor left in
# place. Verified to hold for 30 s under load on all three policies with no drift; the ColorOS
# performance daemons did not contend for the cpufreq nodes during the window. The nodes are
# 0660 system:system, so every read AND write has to go through su - a plain `adb shell cat
# scaling_governor` returns "Permission denied" and an unwary script reads that as an empty
# governor.
CPU_BIG_POLICY=policy4
CPU_BIG_FREQ=2000000
CPU_LITTLE_POLICY=policy0
CPU_LITTLE_FREQ=1600000
# The third policy. Pinned to CPU_BIG_FREQ; not reported as big_cur, but if it is not pinned
# the run is not pinned.
CPU_EXTRA_POLICY=policy7
CPU_EXTRA_FREQ=2000000
# Stock values sampled 2026-09-07 (screen on, idle), for the restore path. Note that policy0's
# and policy7's stock maxima (2100000 / 3200000) are BELOW cpuinfo_max_freq (2700000 / 4210000):
# ColorOS is already holding a limit there, so restore to these, not to cpuinfo_max_freq.
# Governor is `sugov_ext` on all three and is never written.
#
# THESE ARE NOT CONSTANTS. Observed directly: an unpin restored policy4 to 300000-3500000 and
# within seconds a ColorOS daemon had lowered its max to 3200000 by itself. So they are good
# enough to hand the range back to the governor with (which is all the restore path needs), but
# a verification that asserts "the device is released" by exact-matching them will report a
# false failure. pin_device.sh therefore asserts only against OUR pins, never against these.
CPU_BIG_STOCK_MIN=300000
CPU_BIG_STOCK_MAX=3500000
CPU_LITTLE_STOCK_MIN=300000
CPU_LITTLE_STOCK_MAX=2100000
CPU_EXTRA_STOCK_MIN=300000
CPU_EXTRA_STOCK_MAX=3200000
# --- GPU -------------------------------------------------------------------------------------
# 57 working OPPs (indices 0..56), index 0 fastest:
# [00] 1716000 kHz ... [56] 390000 kHz
# Pin at the top OPP with the gpufreqv2 hard override:
# echo 0 > /proc/gpufreqv2/fix_target_opp_index # pin (fixes GPU and STACK to index 0)
# echo -1 > /proc/gpufreqv2/fix_target_opp_index # restore DVFS
# Confirmed: after the write the node reads "fix GPU/STACK OPP index: 0/0" and
# /sys/kernel/ged/hal/current_freqency reads "0 1716000"; after `echo -1` it reads
# "fix GPU/STACK OPP index is disabled".
#
# Deliberately NOT the ged custom_boost_gpu_freq / custom_upbound_gpu_freq pair, even though it
# is the usual MTK route. Two reasons, both visible in the node: (a) on this kernel they take an
# OPP INDEX, not a kHz value (stock reads boost=56, upbound=0 - a floor at the slowest OPP and a
# ceiling at the fastest), so an operator porting odinlite's kHz-valued GPU_PIN would write
# garbage; (b) `cat` on them prints a request log showing /odm/bin/hw/vendor.oplus.hardware.urcc-
# service writing the same nodes, so a pin there is one voter among several and can be revised
# by the vendor daemon. fix_target_opp_index has no such contention.
GPU_PIN_OPP_INDEX=0
GPU_PIN_KHZ=1716000
GPU_CURFREQ_UNIT=khz
GPU_STOCK_BOOST_INDEX=56
GPU_STOCK_UPBOUND_INDEX=0
# ged nodes are unchanged from odinlite and parse the same way: gpu_utilization prints
# "7 0 100" ($1 = busy%), current_freqency prints "<opp_index> <freq_khz>" ($NF = kHz).
GPU_UTIL_NODE=/sys/kernel/ged/hal/gpu_utilization
GPU_CURFREQ_NODE=/sys/kernel/ged/hal/current_freqency
# CAVEAT on the GPU verification: the pinned window was a CPU load, so the GPU was power-
# collapsed throughout (gpufreq_status showed PowerCount: 0). The pin was therefore confirmed at
# the DVFS-request level - ged reported OPP index 0 / 1716000 kHz for the whole window - and not
# by observing the GPU actually clocking 1716 MHz under graphics load. Note also that while
# powered down, /proc/gpufreqv2/gpufreq_status keeps printing "Freq: 26000" for the fixed OPP
# entry; that is a parked-rail readout, not the pin failing. current_freqency is the node to
# believe, and it is the one bench.sh already samples.
# --- Thermal ---------------------------------------------------------------------------------
# 87 thermal zones. `soc_max` (thermal_zone14) is the SoC-wide max aggregate - the right meaning
# for a "SoC temperature" gate, and the strictest of the candidates. Type strings are unique, so
# bench.sh's match-first-zone-by-type loop resolves it unambiguously.
# 40 C is reachable but only just: it settles to 36-37 C at rest with the screen awake, and it
# was still reading 42.4 C several minutes after ordinary use. Expect the gate to actually wait.
# Under the load test it went 52.8 -> 62.0 C in 30 s.
THERMAL_ZONE_TYPE=soc_max
THERMAL_START_MAX_MC=40000
# ColorOS traps that belong with this device, and cost a run each when forgotten:
# * the first install of a not-yet-installed package blocks on
# com.oplus.appdetail InstallGuideActivity until "continue install" is tapped
@@ -26,24 +136,4 @@
# * a foreign-signed APK has to be uninstalled before a rebuild will install;
# * pass MSYS_NO_PATHCONV=1 on every adb invocation from Git Bash, or a /data/... argument is
# rewritten into a Windows path.
# =================================================================================
PROFILE_VERIFIED=0
PIN_STYLE=ppm
DEVICE_SERIAL=3B159D009VZ00000
# Campaign protocol constants (perf-test-protocol): big 1.96 GHz, little 1.55 GHz, GPU at its
# top OPP, 40 C start gate. As above, the kHz values are the protocol's targets and the nearest
# actual OPP has to be confirmed on the device.
CPU_BIG_POLICY=TODO_VERIFY_ON_DEVICE
CPU_BIG_FREQ=1958000
CPU_LITTLE_POLICY=TODO_VERIFY_ON_DEVICE
CPU_LITTLE_FREQ=1550000
# MediaTek legacy gpufreq, same node family as odinlite. The top OPP is device-specific.
GPU_PIN_KHZ=
GPU_UTIL_NODE=/sys/kernel/ged/hal/gpu_utilization
GPU_CURFREQ_NODE=/sys/kernel/ged/hal/current_freqency
THERMAL_ZONE_TYPE=TODO_VERIFY_ON_DEVICE
THERMAL_START_MAX_MC=40000
# * every cpufreq read needs su (0660 system:system), unlike the Xiaomi where reads are open.
+85 -37
View File
@@ -1,55 +1,103 @@
# Device profile: Xiaomi, Snapdragon 8 Elite (Adreno 830), adb serial 35d0befa.
# Device profile: Xiaomi 24129PN74C, Snapdragon 8 Elite (SM8750, "sun"),
# Adreno 830v2, adb serial 35d0befa.
#
# One of the two devices the disaggregation campaign is measured on (the other is
# devices/oppo-mali.env). It exists so that the pinning and thermal protocol the campaign
# actually runs is written down in the repository instead of living in one operator's shell
# history, and so that a `--device` argument names something reviewable.
#
# ============================ NOT YET DEVICE-VERIFIED ============================
# PROFILE_VERIFIED=0 below, and bench.sh / session.sh REFUSE to run against a profile that says
# so - or that omits the key - unless --allow-unverified-profile is passed. (profile.sh is not in
# that list: it records a simpleperf profile and pins nothing, so it has nothing to pin wrongly.) Two of the values here are
# protocol constants that are known (the campaign pins big 1.96 GHz / little 1.55 GHz and gates
# at 40 C), but the sysfs node names and the exact available OPPs are NOT: this is a Qualcomm
# part and the harness was written against MediaTek, where the pin goes through
# /proc/ppm/policy/hard_userlimit_* and the GPU through /proc/gpufreq/gpufreq_opp_freq. Neither
# path exists on this SoC - Adreno pins through /sys/class/kgsl/kgsl-3d0/devfreq/{min,max}_freq
# and its cpufreq policies are not policy6/policy0.
# ============================== DEVICE-VERIFIED ==============================
# Read off the device on 2026-09-07 and confirmed against one pinned window: pins written,
# 8 concurrent busy loops as load, scaling_cur_freq / gpuclk sampled 7 times over 30 s, every
# sample equal to the pin, then unpinned and every node confirmed back at its stock value.
# Evidence is in ../REPORT.md.
#
# A profile that quietly wrote MediaTek paths on this device would be the worst outcome
# available: `su -c 'echo ... > /proc/ppm/...'` fails silently, bench.sh would report a run it
# believes was pinned, and the pin-integrity fields it samples at window end would be the only
# clue. So the unknown fields are left EMPTY and marked, rather than guessed, and the refusal is
# the mechanism that keeps them from being used before somebody has read them off the device.
# READ THIS BEFORE USING IT WITH bench.sh / session.sh
# ----------------------------------------------------
# bench.sh's pin_freqs()/unpin_freqs() are MediaTek-only: they write /proc/ppm/policy/* and
# /proc/gpufreq/gpufreq_opp_freq. NEITHER PATH EXISTS ON THIS DEVICE, and `su -c 'echo ... >
# /proc/...'` against a missing path fails without a non-zero exit - which is the exact silent
# failure the PROFILE_VERIFIED guard was built to prevent. PROFILE_VERIFIED=1 below certifies
# THE NODES AND THE PINS IN THIS FILE, not that bench.sh can drive them.
#
# To promote this profile: fill in the four TODO fields from the device
# (`cat /sys/devices/system/cpu/cpufreq/policy*/scaling_available_frequencies`,
# `ls /sys/class/kgsl/kgsl-3d0/devfreq/`, `for tz in /sys/class/thermal/thermal_zone*; do
# echo "$tz $(cat $tz/type)"; done`), teach bench.sh the Qualcomm pin path, run one pinned
# window, check big_cur/little_cur/gpu_cur_khz in the result JSON against the pins, and only
# then set PROFILE_VERIFIED=1 in the same commit as the bench.sh change.
# =================================================================================
PROFILE_VERIFIED=0
# So until bench.sh grows a PIN_STYLE switch, drive the pin out of band:
# tools/device_bench/pin_device.sh 35d0befa pin # before the run
# tools/device_bench/bench.sh --device <this> --backend X --no-pin
# tools/device_bench/pin_device.sh 35d0befa check # AFTER, exits non-zero on drift
# tools/device_bench/pin_device.sh 35d0befa unpin
# `--no-pin` is what keeps bench.sh from writing MediaTek paths into the void and labelling the
# result pinned; pin_device.sh check is what replaces the pin-integrity fields it would have
# sampled. (bench.sh's read_temp and its GPU-busy sampling ARE portable - they only read
# /sys/class/thermal and $GPU_UTIL_NODE - so those fields stay meaningful.)
# =============================================================================
PROFILE_VERIFIED=1
PIN_STYLE=qualcomm-kgsl
DEVICE_SERIAL=35d0befa
# Campaign protocol constants (perf-test-protocol): big 1.96 GHz, little 1.55 GHz, GPU at its
# top OPP, and a 40 C start gate. The kHz values are the protocol's targets; the nearest actual
# OPP has to be read off the device before they are used, because a cpufreq write that names a
# frequency the policy does not offer is rounded silently.
CPU_BIG_POLICY=TODO_VERIFY_ON_DEVICE
# --- CPU -------------------------------------------------------------------------------------
# SM8750 is a 2+6 part with NO true little cluster:
# policy0 = cpus 0-5, 384000..3532800 kHz (6x performance) <- the protocol's "little"
# policy6 = cpus 6-7, 1017600..4320000 kHz (2x prime) <- the protocol's "big"
# The campaign's targets (big 1.96 GHz / little 1.55 GHz) are both EXACT members of
# scaling_available_frequencies here, so no rounding is involved:
# policy6 avail: ... 1689600 [1958400] 2246400 ...
# policy0 avail: ... 1363200 [1555200] 1785600 ...
# Pin method: scaling_min_freq = scaling_max_freq = target, leaving the stock `walt` governor
# in place. Verified to hold: 30 s under load, zero drift, and the walt governor has no say
# once min == max. A `performance` governor is offered but is not needed and would have to be
# restored by name, so the min/max clamp is the lighter touch.
# Restore: write the stock min/max back (see CPU_*_STOCK_* below). Order matters on policy0 -
# its stock min (556800) is BELOW its pinned value, so lower the max first, then the min, or
# the min write is clamped against the still-pinned max.
CPU_BIG_POLICY=policy6
CPU_BIG_FREQ=1958400
CPU_LITTLE_POLICY=TODO_VERIFY_ON_DEVICE
CPU_LITTLE_POLICY=policy0
CPU_LITTLE_FREQ=1555200
# Adreno pins through the kgsl devfreq knobs, not /proc/gpufreq. Left empty deliberately: see
# the block above.
GPU_PIN_KHZ=
GPU_UTIL_NODE=/sys/class/kgsl/kgsl-3d0/gpubusy
# Stock values sampled 2026-09-07, for the restore path. Governor is `walt` on both policies
# and is never written, so it needs no restore.
CPU_BIG_STOCK_MIN=1017600
CPU_BIG_STOCK_MAX=2841600
CPU_LITTLE_STOCK_MIN=556800
CPU_LITTLE_STOCK_MAX=2745600
# --- GPU -------------------------------------------------------------------------------------
# Adreno pins through the kgsl pwrlevel knobs, not /proc/gpufreq. 14 pwrlevels, index 0 fastest:
# [0] 1100 MHz ... [12] 222 MHz [13] 160 MHz
# Stock range is min_pwrlevel=12 / max_pwrlevel=0, and the devfreq governor (msm-adreno-tz)
# holds its own max_freq at 1050000000 - i.e. stock DVFS never reaches level 0. Writing
# echo 0 > /sys/class/kgsl/kgsl-3d0/min_pwrlevel
# echo 0 > /sys/class/kgsl/kgsl-3d0/max_pwrlevel
# collapses the range onto level 0 and DOES unlock the 1100 MHz step: gpuclk read 1100000000
# immediately and for the whole 30 s window. Prefer this over the devfreq min_freq/max_freq
# pair, which cannot express the top step.
GPU_PIN_PWRLEVEL=0
GPU_PIN_KHZ=1100000
# UNIT TRAP, do not delete: bench.sh reports `gpu_cur_khz` from $GPU_CURFREQ_NODE via
# `awk '{print $NF}'`. On MediaTek that node yields kHz; kgsl's gpuclk yields Hz. So on this
# device the field bench.sh calls gpu_cur_khz is actually Hz, and the value to compare it
# against is GPU_PIN_HZ, not GPU_PIN_KHZ. GPU_PIN_KHZ is carried only to keep the schema
# identical across profiles.
GPU_PIN_HZ=1100000000
GPU_CURFREQ_UNIT=hz
GPU_STOCK_MIN_PWRLEVEL=12
GPU_STOCK_MAX_PWRLEVEL=0
# NOT gpubusy. `cat gpubusy` prints "<busy_cycles> <total_cycles>" and bench.sh takes $1, which
# would record raw busy cycles as a percentage. gpu_busy_percentage prints "26 %", whose $1 is
# the percentage bench.sh means.
GPU_UTIL_NODE=/sys/class/kgsl/kgsl-3d0/gpu_busy_percentage
GPU_CURFREQ_NODE=/sys/class/kgsl/kgsl-3d0/gpuclk
# Thermal gate: 40 C, the campaign's threshold. The zone TYPE differs per SoC and bench.sh
# matches on it by name, so it has to be read off the device.
THERMAL_ZONE_TYPE=TODO_VERIFY_ON_DEVICE
# --- Thermal ---------------------------------------------------------------------------------
# 84 thermal zones; `cpuss-0-0` (thermal_zone13) is the CPU-subsystem sensor for the 6-core
# cluster that carries most of the load, and was the hottest of the candidates under the load
# test (47.1 -> 52.1 C while gpuss/quiet_therm stayed near 30 C). Type strings are unique on
# this device, so bench.sh's match-first-zone-by-type loop resolves it unambiguously.
# 40 C is reachable at rest: it idles at 34.7 C.
THERMAL_ZONE_TYPE=cpuss-0-0
THERMAL_START_MAX_MC=40000
# Xiaomi has no fan; bench.sh's `settings put global fan_mode 3` is an Odin Lite line and is a
# harmless no-op here.