1#
2# Copyright (C) 2018 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17# This script can be used to lock device clocks to stable levels for comparing
18# different versions of software.  Since the clock levels are not necessarily
19# indicative of real world behavior, this should **never** be used to compare
20# performance between different device models.
21
22# Fun notes for maintaining this file:
23#      $((arithmetic expressions)) can deal with ints > INT32_MAX, but if compares cannot. This is
24#      why we use MHz.
25#      $((arithmetic expressions)) can sometimes evaluate right-to-left. This is why we use parens.
26#      Everything below the initial host-check isn't bash - Android uses mksh
27#      mksh allows `\n` in an echo, bash doesn't
28#      can't use `awk`
29#      can't use `sed`
30#      can't use `cut` on < L
31#      can't use `expr` on < L
32
33ARG_CORES=${1:-big}
34
35CPU_TARGET_FREQ_PERCENT=50
36GPU_TARGET_FREQ_PERCENT=50
37MAX_RETRIES=10
38
39if [ "`command -v getprop`" == "" ]; then
40    if [ -n "`command -v adb`" ]; then
41        echo ""
42        echo "Pushing $0 and running it on device..."
43        dest=/data/local/tmp/`basename $0`
44        adb push $0 ${dest}
45        adb shell ${dest} $@
46        adb shell rm ${dest}
47        exit
48    else
49        echo "Could not find adb. Options are:"
50        echo "  1. Ensure adb is on your \$PATH"
51        echo "  2. Use './gradlew lockClocks'"
52        echo "  3. Manually adb push this script to your device, and run it there"
53        exit -1
54    fi
55fi
56
57# require root
58if [[ `id` != "uid=0"* ]]; then
59    echo "Not running as root, cannot lock clocks, aborting"
60    echo "Run 'adb root' and retry"
61    exit -1
62fi
63
64DEVICE=`getprop ro.product.device`
65MODEL=`getprop ro.product.model`
66
67if [ "$ARG_CORES" == "big" ]; then
68    CPU_IDEAL_START_FREQ_KHZ=0
69elif [ "$ARG_CORES" == "little" ]; then
70    CPU_IDEAL_START_FREQ_KHZ=100000000 ## finding min of max freqs, so start at 100M KHz (100 GHz)
71else
72    echo "Invalid argument \$1 for ARG_CORES, should be 'big' or 'little', but was $ARG_CORES"
73    exit -1
74fi
75
76function_setup_go() {
77    if [ -f /d/fpsgo/common/force_onoff ]; then
78        # Disable fpsgo
79        echo 0 > /d/fpsgo/common/force_onoff
80        fpsgoState=`cat /d/fpsgo/common/force_onoff`
81        if [ "$fpsgoState" != "0" ] && [ "$fpsgoState" != "force off" ]; then
82            echo "Failed to disable fpsgo"
83            exit -1
84        fi
85    fi
86}
87
88# Disable CPU hotpluging by killing mpdecision service via ctl.stop system property.
89# This helper checks the state and existence of the mpdecision service via init.svc.
90# Possible values from init.svc: "stopped", "stopping", "running", "restarting"
91function_stop_mpdecision() {
92    MPDECISION_STATUS=`getprop init.svc.mpdecision`
93    while [ "$MPDECISION_STATUS" == "running" ] || [ "$MPDECISION_STATUS" == "restarting" ]; do
94        setprop ctl.stop mpdecision
95        # Give initrc some time to kill the mpdecision service.
96        sleep 0.1
97        MPDECISION_STATUS=`getprop init.svc.mpdecision`
98    done
99}
100
101# Given a target frequency and list of available frequencies, selects one closest to the target
102function_find_target_mhz() {
103    targetFreq=$1
104    availFreq=$2
105
106    # Choose a frequency to lock to that's >= $CPU_TARGET_FREQ_PERCENT% of max
107    # (below, 100M = 1K for KHz->MHz * 100 for %)
108    targetFreqMhz=$(( ($targetFreq * $CPU_TARGET_FREQ_PERCENT) / 100000 ))
109    outputFreq=0
110    outputFreqDiff=100000000
111    for freq in ${availFreq}; do
112        freqMhz=$(( ${freq} / 1000 ))
113        if [ ${freqMhz} -ge ${targetFreqMhz} ]; then
114            newOutputFreqDiff=$(( $freq - $targetFreqMhz ))
115            if [ $newOutputFreqDiff -lt $outputFreqDiff ]; then
116                outputFreq=${freq}
117                outputFreqDiff=$(( $outputFreq - $targetFreqMhz ))
118            fi
119        fi
120    done
121
122    echo $outputFreq
123}
124
125# Enable and lock each cpu core to an available frequency that's >=
126# $CPU_TARGET_FREQ_PERCENT% of max.
127function_lock_cpu() {
128    CPU_BASE=/sys/devices/system/cpu
129    GOV=cpufreq/scaling_governor
130
131    # Options to make clock locking on go devices more sticky.
132    function_setup_go
133
134
135    # Stop mpdecision (CPU hotplug service) if it exists. Not available on all devices.
136    function_stop_mpdecision
137
138    # Loop through all available cores; We have to check by the parent folder
139    # "cpu#" instead of cpu#/online or cpu#/cpufreq directly, since they may
140    # not be accessible yet.
141    echo "=================================="
142    cpu=0
143    while [ -d ${CPU_BASE}/cpu${cpu} ]; do
144        # Try to enable core, so we can find its frequencies.
145        # Note: In cases where the online file is inaccessible, it represents a
146        # core which cannot be turned off, so we simply assume it is enabled if
147        # this command fails.
148        if [ -f "$CPU_BASE/cpu$cpu/online" ]; then
149            echo 1 > ${CPU_BASE}/cpu${cpu}/online || true
150        fi
151
152
153        # set userspace governor on all CPUs to ensure freq scaling is disabled
154        echo userspace > ${CPU_BASE}/cpu${cpu}/${GOV}
155
156        cpu=$(($cpu + 1))
157    done
158
159    # Set frequencies in a separate loop, after online and governor have been
160    # set for all cores - empirically, this improves consistency of the locking
161    cpu=0
162    remainingRetries=$MAX_RETRIES
163    while [ -d ${CPU_BASE}/cpu${cpu} ]; do
164        maxFreq=`cat ${CPU_BASE}/cpu$cpu/cpufreq/cpuinfo_max_freq`
165        availFreq=`cat ${CPU_BASE}/cpu$cpu/cpufreq/scaling_available_frequencies`
166        idealFreq=`function_find_target_mhz "$maxFreq" "$availFreq"`
167
168        # scaling_max_freq must be set before scaling_min_freq
169        freq=${CPU_BASE}/cpu$cpu/cpufreq
170        echo ${idealFreq} > ${freq}/scaling_max_freq
171        echo ${idealFreq} > ${freq}/scaling_min_freq
172        echo ${idealFreq} > ${freq}/scaling_setspeed
173
174        # Give system a bit of time to propagate the change to scaling_setspeed.
175        sleep 0.1
176
177        # validate setting the freq worked
178        obsCur=`cat ${freq}/scaling_cur_freq`
179        obsMin=`cat ${freq}/scaling_min_freq`
180        obsMax=`cat ${freq}/scaling_max_freq`
181        if [ "$obsCur" -ne "$idealFreq" ] || [ "$obsMin" -ne "$idealFreq" ] || [ "$obsMax" -ne "$idealFreq" ]; then
182            if [ remainingRetries -le 0 ]; then
183                echo "Failed to set CPU$cpu to $idealFreq Hz! Aborting..."
184                echo "${freq}/scaling_cur_freq = $obsCur"
185                echo "${freq}/scaling_min_freq = $obsMin"
186                echo "${freq}/scaling_max_freq = $obsMax"
187                exit -1
188            fi
189            remainingRetries=$(($remainingRetries - 1))
190            echo "Failed to set CPU$cpu to $idealFreq Hz! Retrying ($remainingRetries retries left)..."
191            sleep 0.2
192        else
193          echo "Locked CPU ${cpu} to $idealFreq / $maxFreq KHz"
194          remainingRetries=$MAX_RETRIES
195          cpu=$(($cpu + 1))
196        fi
197    done
198    echo "=================================="
199
200
201    # Lock wembley clocks using high-priority op code method.
202    # This block depends on the shell utility awk, which is only available on API 27+
203    if [ "$DEVICE" == "wembley" ]; then
204        # Get list of available frequencies to lock to by parsing the op-code list.
205        AVAIL_OP_FREQS=`cat /proc/cpufreq/MT_CPU_DVFS_LL/cpufreq_oppidx \
206            | awk '{print $2}' \
207            | tail -n +3 \
208            | while read line; do
209                echo "${line:1:${#line}-2}"
210            done`
211
212        # Compute the closest available frequency to the desired frequency, $idealFreq.
213        # This assumes the op codes listen in /proc/cpufreq/MT_CPU_DVFS_LL/cpufreq_oppidx are listed
214        # in order and 0-indexed.
215        opCode=-1
216        opFreq=0
217        currOpCode=-1
218        for currOpFreq in $AVAIL_OP_FREQS; do
219            currOpCode=$((currOpCode + 1))
220
221            prevDiff=$((idealFreq-opFreq))
222            prevDiff=`function_abs $prevDiff`
223            currDiff=$((idealFreq-currOpFreq))
224            currDiff=`function_abs $currDiff`
225            if [ $currDiff -lt $prevDiff ]; then
226                opCode="$currOpCode"
227                opFreq="$currOpFreq"
228            fi
229        done
230
231        echo "$opCode" > /proc/ppm/policy/ut_fix_freq_idx
232    fi
233}
234
235# Returns the absolute value of the first arg passed to this helper.
236function_abs() {
237    n=$1
238    if [ $n -lt 0 ]; then
239        echo "$((n * -1 ))"
240    else
241        echo "$n"
242    fi
243}
244
245# If we have a Qualcomm GPU, find its max frequency, and lock to
246# an available frequency that's >= GPU_TARGET_FREQ_PERCENT% of max.
247function_lock_gpu_kgsl() {
248    if [ ! -d /sys/class/kgsl/kgsl-3d0/ ]; then
249        # not kgsl, abort
250        echo "Currently don't support locking GPU clocks of $MODEL ($DEVICE)"
251        return -1
252    fi
253    if [ ${DEVICE} == "walleye" ] || [ ${DEVICE} == "taimen" ]; then
254        # Workaround crash
255        echo "Unable to lock GPU clocks of $MODEL ($DEVICE)"
256        return -1
257    fi
258
259    GPU_BASE=/sys/class/kgsl/kgsl-3d0
260
261    gpuMaxFreq=0
262    gpuAvailFreq=`cat $GPU_BASE/devfreq/available_frequencies`
263    for freq in ${gpuAvailFreq}; do
264        if [ ${freq} -gt ${gpuMaxFreq} ]; then
265            gpuMaxFreq=${freq}
266        fi
267    done
268
269    # (below, 100M = 1M for MHz * 100 for %)
270    TARGET_FREQ_MHZ=$(( (${gpuMaxFreq} * ${GPU_TARGET_FREQ_PERCENT}) / 100000000 ))
271
272    chosenFreq=${gpuMaxFreq}
273    index=0
274    chosenIndex=0
275    for freq in ${gpuAvailFreq}; do
276        freqMhz=$(( ${freq} / 1000000 ))
277        if [ ${freqMhz} -ge ${TARGET_FREQ_MHZ} ] && [ ${chosenFreq} -ge ${freq} ]; then
278            # note avail freq are generally in reverse order, so we don't break out of this loop
279            chosenFreq=${freq}
280            chosenIndex=${index}
281        fi
282        index=$(($index + 1))
283    done
284    lastIndex=$(($index - 1))
285
286    firstFreq=`function_cut_first_from_space_seperated_list $gpuAvailFreq`
287
288    if [ ${gpuMaxFreq} != ${firstFreq} ]; then
289        # pwrlevel is index of desired freq among available frequencies, from highest to lowest.
290        # If gpuAvailFreq appears to be in-order, reverse the index
291        chosenIndex=$(($lastIndex - $chosenIndex))
292    fi
293
294    echo 0 > ${GPU_BASE}/bus_split
295    echo 1 > ${GPU_BASE}/force_clk_on
296    echo 10000 > ${GPU_BASE}/idle_timer
297
298    echo performance > ${GPU_BASE}/devfreq/governor
299
300    # NOTE: we store in min/max twice, because we don't know if we're increasing
301    # or decreasing, and it's invalid to try and set min > max, or max < min
302    echo ${chosenFreq} > ${GPU_BASE}/devfreq/min_freq
303    echo ${chosenFreq} > ${GPU_BASE}/devfreq/max_freq
304    echo ${chosenFreq} > ${GPU_BASE}/devfreq/min_freq
305    echo ${chosenFreq} > ${GPU_BASE}/devfreq/max_freq
306    echo ${chosenIndex} > ${GPU_BASE}/min_pwrlevel
307    echo ${chosenIndex} > ${GPU_BASE}/max_pwrlevel
308    echo ${chosenIndex} > ${GPU_BASE}/min_pwrlevel
309    echo ${chosenIndex} > ${GPU_BASE}/max_pwrlevel
310
311    obsCur=`cat ${GPU_BASE}/devfreq/cur_freq`
312    obsMin=`cat ${GPU_BASE}/devfreq/min_freq`
313    obsMax=`cat ${GPU_BASE}/devfreq/max_freq`
314    if [ obsCur -ne ${chosenFreq} ] || [ obsMin -ne ${chosenFreq} ] || [ obsMax -ne ${chosenFreq} ]; then
315        echo "Failed to set GPU to $chosenFreq Hz! Aborting..."
316        echo "cur_freq = $obsCur"
317        echo "min_freq = $obsMin"
318        echo "max_freq = $obsMax"
319        echo "index = $chosenIndex"
320        exit -1
321    fi
322    echo "Locked GPU to $chosenFreq / $gpuMaxFreq Hz"
323}
324
325# cut is not available on some devices (Nexus 5 running LRX22C).
326function_cut_first_from_space_seperated_list() {
327    list=$1
328
329    for freq in $list; do
330        echo $freq
331        break
332    done
333}
334
335# kill processes that manage thermals / scaling
336stop thermal-engine || true
337stop perfd || true
338stop vendor.thermal-engine || true
339stop vendor.perfd || true
340setprop vendor.powerhal.init 0 || true
341setprop ctl.interface_restart android.hardware.power@1.0::IPower/default || true
342
343function_lock_cpu
344
345if [ ${DEVICE} != "wembley" ]; then
346    function_lock_gpu_kgsl
347else
348    echo "Unable to lock gpu clocks of $MODEL ($DEVICE)."
349fi
350
351# Memory bus - hardcoded per-device for now
352if [ ${DEVICE} == "marlin" ] || [ ${DEVICE} == "sailfish" ]; then
353    echo 13763 > /sys/class/devfreq/soc:qcom,gpubw/max_freq
354else
355    echo "Unable to lock memory bus of $MODEL ($DEVICE)."
356fi
357
358echo "$DEVICE clocks have been locked - to reset, reboot the device"
359