3
# The Intel i915 GPU driver allows to change the minimum, maximum and boost
4
# frequencies in steps of 50 MHz via /sys/class/drm/card<n>/<freq_info>,
5
# where <n> is the DRM card index and <freq_info> one of the following:
7
# - gt_max_freq_mhz (enforced maximum freq)
8
# - gt_min_freq_mhz (enforced minimum freq)
9
# - gt_boost_freq_mhz (enforced boost freq)
11
# The hardware capabilities can be accessed via:
13
# - gt_RP0_freq_mhz (supported maximum freq)
14
# - gt_RPn_freq_mhz (supported minimum freq)
15
# - gt_RP1_freq_mhz (most efficient freq)
17
# The current frequency can be read from:
18
# - gt_act_freq_mhz (the actual GPU freq)
19
# - gt_cur_freq_mhz (the last requested freq)
21
# Copyright (C) 2022 Collabora Ltd.
22
# Author: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
24
# SPDX-License-Identifier: MIT
30
DRM_FREQ_SYSFS_PATTERN="/sys/class/drm/card%d/gt_%s_freq_mhz"
31
ENF_FREQ_INFO="max min boost"
32
CAP_FREQ_INFO="RP0 RPn RP1"
33
ACT_FREQ_INFO="act cur"
34
THROTT_DETECT_SLEEP_SEC=2
35
THROTT_DETECT_PID_FILE_PATH=/tmp/thrott-detect.pid
40
unset INTEL_DRM_CARD_INDEX
41
unset GET_ACT_FREQ GET_ENF_FREQ GET_CAP_FREQ
42
unset SET_MIN_FREQ SET_MAX_FREQ
48
# Simple printf based stderr logger.
54
printf "%s: %s: " "${msg_type}" "${0##*/}" >&2
60
# Helper to print sysfs path for the given card index and freq info.
62
# arg1: Frequency info sysfs name, one of *_FREQ_INFO constants above
63
# arg2: Video card index, defaults to INTEL_DRM_CARD_INDEX
65
print_freq_sysfs_path() {
66
printf ${DRM_FREQ_SYSFS_PATTERN} "${2:-${INTEL_DRM_CARD_INDEX}}" "$1"
70
# Helper to set INTEL_DRM_CARD_INDEX for the first identified Intel video card.
72
identify_intel_gpu() {
75
while [ ${i} -lt 16 ]; do
76
[ -c "/dev/dri/card$i" ] || {
81
path=$(print_freq_sysfs_path "" ${i})
82
path=${path%/*}/device/vendor
84
[ -r "${path}" ] && read vendor < "${path}" && \
85
[ "${vendor}" = "0x8086" ] && INTEL_DRM_CARD_INDEX=$i && return 0
94
# Read the specified freq info from sysfs.
96
# arg1: Flag (y/n) to also enable printing the freq info.
97
# arg2...: Frequency info sysfs name(s), see *_FREQ_INFO constants above
98
# return: Global variable(s) FREQ_${arg} containing the requested information
101
local var val path print=0 ret=0
103
[ "$1" = "y" ] && print=1
106
while [ $# -gt 0 ]; do
108
path=$(print_freq_sysfs_path "$1")
110
[ -r ${path} ] && read ${var} < ${path} || {
111
log ERROR "Failed to read freq info from: %s" "${path}"
117
log ERROR "Got empty freq info from: %s" "${path}"
122
[ ${print} -eq 1 ] && {
124
printf "%6s: %4s MHz\n" "$1" "${val}"
134
# Display requested info.
139
[ -n "${GET_CAP_FREQ}" ] && {
140
printf "* Hardware capabilities\n"
141
read_freq_info y ${CAP_FREQ_INFO}
145
[ -n "${GET_ENF_FREQ}" ] && {
146
printf "* Enforcements\n"
147
read_freq_info y ${ENF_FREQ_INFO}
151
[ -n "${GET_ACT_FREQ}" ] && {
153
read_freq_info y ${ACT_FREQ_INFO}
159
# Helper to print frequency value as requested by user via '-s, --set' option.
160
# arg1: user requested freq value
173
val=$((${1%?} * ${FREQ_RP0} / 100))
174
# Adjust freq to comply with 50 MHz increments
175
val=$((val / 50 * 50))
178
log ERROR "Cannot set freq to invalid value: %s" "$1"
182
log ERROR "Cannot set freq to unspecified value"
186
# Adjust freq to comply with 50 MHz increments
187
val=$(($1 / 50 * 50))
195
# Helper for set_freq().
198
log INFO "Setting GPU max freq to %s MHz" "${SET_MAX_FREQ}"
200
read_freq_info n min || return $?
202
[ ${SET_MAX_FREQ} -gt ${FREQ_RP0} ] && {
203
log ERROR "Cannot set GPU max freq (%s) to be greater than hw max freq (%s)" \
204
"${SET_MAX_FREQ}" "${FREQ_RP0}"
208
[ ${SET_MAX_FREQ} -lt ${FREQ_RPn} ] && {
209
log ERROR "Cannot set GPU max freq (%s) to be less than hw min freq (%s)" \
210
"${SET_MIN_FREQ}" "${FREQ_RPn}"
214
[ ${SET_MAX_FREQ} -lt ${FREQ_min} ] && {
215
log ERROR "Cannot set GPU max freq (%s) to be less than min freq (%s)" \
216
"${SET_MAX_FREQ}" "${FREQ_min}"
220
[ -z "${DRY_RUN}" ] || return 0
222
printf "%s" ${SET_MAX_FREQ} | tee $(print_freq_sysfs_path max) \
223
$(print_freq_sysfs_path boost) > /dev/null
225
log ERROR "Failed to set GPU max frequency"
231
# Helper for set_freq().
234
log INFO "Setting GPU min freq to %s MHz" "${SET_MIN_FREQ}"
236
read_freq_info n max || return $?
238
[ ${SET_MIN_FREQ} -gt ${FREQ_max} ] && {
239
log ERROR "Cannot set GPU min freq (%s) to be greater than max freq (%s)" \
240
"${SET_MIN_FREQ}" "${FREQ_max}"
244
[ ${SET_MIN_FREQ} -lt ${FREQ_RPn} ] && {
245
log ERROR "Cannot set GPU min freq (%s) to be less than hw min freq (%s)" \
246
"${SET_MIN_FREQ}" "${FREQ_RPn}"
250
[ -z "${DRY_RUN}" ] || return 0
252
printf "%s" ${SET_MIN_FREQ} > $(print_freq_sysfs_path min)
254
log ERROR "Failed to set GPU min frequency"
260
# Set min or max or both GPU frequencies to the user indicated values.
263
# Get hw max & min frequencies
264
read_freq_info n RP0 RPn || return $?
266
[ -z "${SET_MAX_FREQ}" ] || {
267
SET_MAX_FREQ=$(compute_freq_set "${SET_MAX_FREQ}")
268
[ -z "${SET_MAX_FREQ}" ] && return 1
271
[ -z "${SET_MIN_FREQ}" ] || {
272
SET_MIN_FREQ=$(compute_freq_set "${SET_MIN_FREQ}")
273
[ -z "${SET_MIN_FREQ}" ] && return 1
277
# Ensure correct operation order, to avoid setting min freq
278
# to a value which is larger than max freq.
281
# crt_min=crt_max=600; new_min=new_max=700
282
# > operation order: max=700; min=700
284
# crt_min=crt_max=600; new_min=new_max=500
285
# > operation order: min=500; max=500
287
if [ -n "${SET_MAX_FREQ}" ] && [ -n "${SET_MIN_FREQ}" ]; then
288
[ ${SET_MAX_FREQ} -lt ${SET_MIN_FREQ} ] && {
289
log ERROR "Cannot set GPU max freq to be less than min freq"
293
read_freq_info n min || return $?
295
if [ ${SET_MAX_FREQ} -lt ${FREQ_min} ]; then
296
set_freq_min || return $?
299
set_freq_max || return $?
302
elif [ -n "${SET_MAX_FREQ}" ]; then
304
elif [ -n "${SET_MIN_FREQ}" ]; then
307
log "Unexpected call to set_freq()"
313
# Helper for detect_throttling().
315
get_thrott_detect_pid() {
316
[ -e ${THROTT_DETECT_PID_FILE_PATH} ] || return 0
319
read pid < ${THROTT_DETECT_PID_FILE_PATH} || {
320
log ERROR "Failed to read pid from: %s" "${THROTT_DETECT_PID_FILE_PATH}"
324
local proc_path=/proc/${pid:-invalid}/cmdline
325
[ -r ${proc_path} ] && grep -qs "${0##*/}" ${proc_path} && {
330
# Remove orphaned PID file
331
rm -rf ${THROTT_DETECT_PID_FILE_PATH}
336
# Control detection and reporting of GPU throttling events.
337
# arg1: start - run throttle detector in background
338
# stop - stop throttle detector process, if any
339
# status - verify if throttle detector is running
341
detect_throttling() {
343
pid=$(get_thrott_detect_pid)
347
printf "Throttling detector is "
348
[ -z "${pid}" ] && printf "not running\n" && return 0
349
printf "running (pid=%s)\n" ${pid}
353
[ -z "${pid}" ] && return 0
355
log INFO "Stopping throttling detector (pid=%s)" "${pid}"
356
kill ${pid}; sleep 1; kill -0 ${pid} 2>/dev/null && kill -9 ${pid}
357
rm -rf ${THROTT_DETECT_PID_FILE_PATH}
362
log WARN "Throttling detector is already running (pid=%s)" ${pid}
367
read_freq_info n RPn || exit $?
370
sleep ${THROTT_DETECT_SLEEP_SEC}
371
read_freq_info n act min cur || exit $?
374
# The throttling seems to occur when act freq goes below min.
375
# However, it's necessary to exclude the idle states, where
376
# act freq normally reaches RPn and cur goes below min.
378
[ ${FREQ_act} -lt ${FREQ_min} ] && \
379
[ ${FREQ_act} -gt ${FREQ_RPn} ] && \
380
[ ${FREQ_cur} -ge ${FREQ_min} ] && \
381
printf "GPU throttling detected: act=%s min=%s cur=%s RPn=%s\n" \
382
${FREQ_act} ${FREQ_min} ${FREQ_cur} ${FREQ_RPn}
387
log INFO "Started GPU throttling detector (pid=%s)" ${pid}
389
printf "%s\n" ${pid} > ${THROTT_DETECT_PID_FILE_PATH} || \
390
log WARN "Failed to write throttle detector PID file"
400
Usage: ${0##*/} [OPTION]...
402
A script to manage Intel GPU frequencies. Can be used for debugging performance
403
problems or trying to obtain a stable frequency while benchmarking.
405
Note Intel GPUs only accept specific frequencies, usually multiples of 50 MHz.
408
-g, --get [act|enf|cap|all]
409
Get frequency information: active (default), enforced,
410
hardware capabilities or all of them.
412
-s, --set [{min|max}=]{FREQUENCY[%]|+|-}
413
Set min or max frequency to the given value (MHz).
414
Append '%' to interpret FREQUENCY as % of hw max.
415
Use '+' or '-' to set frequency to hardware max or min.
416
Omit min/max prefix to set both frequencies.
418
-r, --reset Reset frequencies to hardware defaults.
420
-m, --monitor [act|enf|cap|all]
421
Monitor the indicated frequencies via 'watch' utility.
422
See '-g, --get' option for more details.
424
-d|--detect-thrott [start|stop|status]
425
Start (default operation) the throttling detector
426
as a background process. Use 'stop' or 'status' to
427
terminate the detector process or verify its status.
429
--dry-run See what the script will do without applying any
432
-h, --help Display this help text and exit.
437
# Parse user input for '-g, --get' option.
438
# Returns 0 if a value has been provided, otherwise 1.
444
act) GET_ACT_FREQ=1;;
445
enf) GET_ENF_FREQ=1;;
446
cap) GET_CAP_FREQ=1;;
447
all) GET_ACT_FREQ=1; GET_ENF_FREQ=1; GET_CAP_FREQ=1;;
449
# No value provided, using default.
463
# Validate user input for '-s, --set' option.
465
validate_option_set() {
467
+|-|[0-9]%|[0-9][0-9]%)
478
# Parse script arguments.
480
[ $# -eq 0 ] && { print_usage; exit 1; }
482
while [ $# -gt 0 ]; do
485
parse_option_get "$2" && shift
492
SET_MIN_FREQ=${1#min=}
493
validate_option_set "${SET_MIN_FREQ}"
496
SET_MAX_FREQ=${1#max=}
497
validate_option_set "${SET_MAX_FREQ}"
501
validate_option_set "${SET_MIN_FREQ}"
502
SET_MAX_FREQ=${SET_MIN_FREQ}
515
parse_option_get "$2" && MONITOR_FREQ=$2 && shift
551
identify_intel_gpu || {
552
log INFO "No Intel GPU detected"
556
[ -n "${SET_MIN_FREQ}${SET_MAX_FREQ}" ] && { set_freq || RET=$?; }
559
[ -n "${DETECT_THROTT}" ] && detect_throttling ${DETECT_THROTT}
561
[ -n "${MONITOR_FREQ}" ] && {
562
log INFO "Entering frequency monitoring mode"
564
exec watch -d -n 1 "$0" -g "${MONITOR_FREQ}"