#!/usr/bin/env bash # # Basic idea stolen from here # https://www.phoronix.com/forums/forum/linux-graphics-x-org-drivers/amd-linux/918649-underclocking-undervolting-the-rx-470-with-amdgpu-pro-success # # After this I came to my own downclock idea. # It working with a little mess in pp_dpm_sclk output, # but unfortunately it seems that compiling downclock ratio # gives much better power saving. # # This tool modify amd gpu kernel module. # It allows you to undervolt and underclock your AMD RXxx GPUs under linux with amdgpu latest driver. # # COMPATIBILE with 17.20.* 17.30.* and 17.40.* amdgpu drivers # 17.40.* drivers require HWE kernel for Ubuntu 16.04 # # BE CAREFULL! # I DO NOT FULLY UDERSTAND HOW DOES IT WORK AND WFT I'M DOING # colors CRED='\033[0;31m'; CYELL='\033[1;33m'; CGREE='\033[0;32m'; CBLUE='\033[0;34m'; NC='\033[0m'; function logo { echo -e "${CBLUE} ____________________________________________________ | _ __ _ _ _ | | /\ |\/| | \ /__ |_) | | |\/| / \ | \ | | /--\ | | |_/ \_| | |_| | | \_/ |_/ | |__________________________________________________|${NC} " } function info { echo -e "${CGREE}${1}${NC}"; } function warn { echo -e "${CYELL}${1}${NC}"; } function error { echo -e "${CRED}${1}${NC}"; } FILE_DAG="/amd/amdgpu/amdgpu_vm.c" FILE_P10="/amd/powerplay/smumgr/polaris10_smumgr.c" FILE_HW_SMU7="/amd/powerplay/hwmgr/smu7_hwmgr.c" FILE_HW_VEGA="/amd/powerplay/hwmgr/vega10_hwmgr.c" function backup_src_file { if [ ! -f "$1" ]; then error "Source not found $1"; exit 1; fi if [ -f "$2" ]; then info "SKIP bakup, file aready exist $2"; else mkdir -p "$(dirname "$2")"; cp -f "$1" "$2"; if [ $? -ne 0 ]; then error "Can not backup $1 -> $2"; exit 1; fi fi } function backup_src { backup_src_file "${1}$FILE_DAG" "${2}$FILE_DAG" backup_src_file "${1}$FILE_P10" "${2}$FILE_P10" backup_src_file "${1}$FILE_HW_SMU7" "${2}$FILE_HW_SMU7" backup_src_file "${1}$FILE_HW_VEGA" "${2}$FILE_HW_VEGA" } # $1-backup file # $2-amd source loc # $3-patch function patch_restore_file { cp -f "$1" "$2" if [ $RESTORE -eq 0 ]; then info "Patch file $2" echo "$3" | patch "$2" if [ $? -ne 0 ]; then error "Patching failed with error code: $?"; echo "$3"; exit 1; fi info "File patched: $2" else info "Source restored $2" fi } PATCH_SMU7=$(cat <backend); struct smu7_single_dpm_table *golden_sclk_table = &(data->golden_dpm_table.sclk_table); + struct smu7_single_dpm_table *sclk_table = + &(data->dpm_table.sclk_table); struct pp_power_state *ps; struct smu7_power_state *smu7_ps; - if (value > 20) - value = 20; - ps = hwmgr->request_ps; if (ps == NULL) return -EINVAL; smu7_ps = cast_phw_smu7_power_state(&ps->hardware); - - smu7_ps->performance_levels[smu7_ps->performance_level_count - 1].engine_clock = - golden_sclk_table->dpm_levels[golden_sclk_table->count - 1].value * - value / 100 + - golden_sclk_table->dpm_levels[golden_sclk_table->count - 1].value; + + bool up = true; + if (value >= 50) { + if (value > 99) value = 99; + value = 100 - value; + up = false; + } else if (value > 20) + value = 20; + + int i; + for (i = 1; i <= smu7_ps->performance_level_count; i++) { + uint32_t clock = golden_sclk_table->dpm_levels[golden_sclk_table->count - i].value; + if (up) + clock += golden_sclk_table->dpm_levels[golden_sclk_table->count - i].value * value / 100; + else + clock -= golden_sclk_table->dpm_levels[golden_sclk_table->count - i].value * value / 100; + smu7_ps->performance_levels[smu7_ps->performance_level_count - i].engine_clock = clock; + } + + // Only downclock or reset to normal (looks like it does not work properly) + if (value <= 20) + value = 0; + + uint32_t min_clock = sclk_table->dpm_levels[0].value; + for (i = 1; i < sclk_table->count; i++) { + uint32_t clock = golden_sclk_table->dpm_levels[i].value; + clock -= golden_sclk_table->dpm_levels[i].value * value / 100; + + if (clock < min_clock) + sclk_table->dpm_levels[i].value = min_clock; + else + sclk_table->dpm_levels[i].value = clock; + } return 0; } EOF ) PATCH_VEGA=$(cat <entries[i].mvdd) *mvdd = (uint32_t) dep_table->entries[i].mvdd * VOLTAGE_SCALE; *voltage |= 1 << PHASES_SHIFT; + //MOD UNDERVOLT + //UVT_V*voltage = (*voltage & 0xFFFF0000) + (({uvolt}*VOLTAGE_SCALE) & 0xFFFF); + //END UNDRVOLT return 0; } } /* sclk is bigger than max sclk in the dependence table */ @@ -134,10 +139,13 @@ if (SMU7_VOLTAGE_CONTROL_NONE == data->mvdd_control) *mvdd = data->vbios_boot_state.mvdd_bootup_value * VOLTAGE_SCALE; else if (dep_table->entries[i].mvdd) *mvdd = (uint32_t) dep_table->entries[i - 1].mvdd * VOLTAGE_SCALE; + //MOD UNDERVOLT + //UVT_V*voltage = (*voltage & 0xFFFF0000) + (({uvolt}*VOLTAGE_SCALE) & 0xFFFF); + //END UNDRVOLT return 0; } static uint16_t scale_fan_gain_settings(uint16_t raw_setting) { @@ -770,10 +780,14 @@ polaris10_get_sclk_range_table(hwmgr, &(smu_data->smc_state_table)); for (i = 0; i < dpm_table->sclk_table.count; i++) { + //MOD UNDERCLOCK + int clk = dpm_table->sclk_table.dpm_levels[i].value; + dpm_table->sclk_table.dpm_levels[i].value -= (clk * {uclock}) / 100; + //END UNDERCLOCK result = polaris10_populate_single_graphic_level(hwmgr, dpm_table->sclk_table.dpm_levels[i].value, (uint16_t)smu_data->activity_target[i], &(smu_data->smc_state_table.GraphicsLevel[i])); if (result) EOF ) PATCH=${TPL//"{uvolt}"/$1} PATCH=${PATCH//"//UVT_V"/""} echo "${PATCH//"{uclock}"/$2}" } function show_help { echo -e "\nUSAGE:" echo -e "Patch: $0 -d /usr/src/amdgpu-pro-YOURVERSION -v 800 -c 13 # voltage at 818mV underclock 13%" echo -e "Restore: $0 -d /usr/src/amdgpu-pro-YOURVERSION -r" echo -e "\nARGUMENTS:" echo " -d DIRECTORY : Path to directory with your amdgpu driver files" echo " -v VOLTAGE : Base voltage in mV as int" echo " -c PERCENT : Underclock value in percents" } # START PROGRAM HELP=1 LOGO=1 RESTORE=0 AMDGPUDIR="" UVOLT=0 UCLOCK=0 while getopts "h?rd:v:c:" opt; do case "$opt" in h|\?) logo; show_help; exit 0 ;; d) AMDGPUDIR=$OPTARG; HELP=0 ;; r) RESTORE=1; HELP=0 ;; v) UVOLT=$((${OPTARG//[!0-9]/})) ;; c) UCLOCK=$((${OPTARG//[!0-9]/})) ;; esac done SNAME=$(basename "$0") APPDIR="$(dirname "$0")/.${SNAME%.*}" KERNEL=`uname -r` if [ $LOGO -eq 1 ]; then logo; fi # VALIDATE INPUT if [ ! -d "$AMDGPUDIR" ]; then error "Provided AMD GPU dir does not exist: ${AMDGPUDIR}" show_help; exit 1; else SRCDIR="${APPDIR}/$(basename "$AMDGPUDIR")" fi if [ $RESTORE -eq 0 ]; then if [ $UVOLT -gt 1200 ]; then warn "Undervolt value ${UVOLT}mV does not look like undervolt at all!"; fi if [ $UVOLT -lt 800 ]; then warn "Are you shure that it whould work with voltage near ${UVOLT}mV ?"; fi if [ $UVOLT -lt 700 ]; then error "Definitely wrong undervolt value ${UVOLT}mV"; HELP=1; fi if [ $UCLOCK -lt 0 ] || [ $UCLOCK -gt 50 ]; then error "Can not allow you set underclock to ${UCLOCK}% !"; HELP=1; fi info "Undervolt value ${UVOLT}mV underclock is ${UCLOCK}%" else info "Restore configuration requested" fi #SHOW HELP on FUCKUP if [ $HELP -eq 1 ]; then show_help; exit 0; fi # BEGIN TO WORK if [ $EUID -ne 0 ]; then error "No way dude, you have to be a root to do this!" exit 1; fi if [ ! -d "$SRCDIR" ]; then mkdir -p "$SRCDIR"; fi #This also creates APPDIR # STARTING backup_src "$AMDGPUDIR" "$SRCDIR" info "We are ready to start" read -p "Continue (y/n)?" yn echo if [ $yn != "Y" ] && [ $yn != "y" ]; then [ "$0" = "$BASH_SOURCE" ] && exit 1 || return 1 # handle exits from shell or function but don't exit interactive shell fi #PATCH MODE KOFILE_PTCH="${APPDIR}/${KERNEL}/amdgpu.ko_${KERNEL}_${UVOLT}_${UCLOCK}" if [ $RESTORE -eq 1 ]; then KOFILE_PTCH="${APPDIR}/${KERNEL}/amdgpu.ko_${KERNEL}_orig" fi if [ ! -f $KOFILE_PTCH ]; then info "BUILDING new amdgpu.ko file" mkdir -p "$(dirname "$KOFILE_PTCH")" PATCH_UVC=`produce_patch $UVOLT $UCLOCK` patch_restore_file "${SRCDIR}${FILE_P10}" "${AMDGPUDIR}${FILE_P10}" "$PATCH_UVC" patch_restore_file "${SRCDIR}${FILE_HW_SMU7}" "${AMDGPUDIR}${FILE_HW_SMU7}" "$PATCH_SMU7" cd ${AMDGPUDIR} ${AMDGPUDIR}/pre-build.sh "$KERNEL" make KERNELRELEASE="$KERNEL" -C "/lib/modules/$KERNEL/build" M="$AMDGPUDIR" res=$? cd - if [ $res -ne 0 ]; then error "Can not build! Error code: $?"; exit 1; fi cp -f "${AMDGPUDIR}/amd/amdgpu/amdgpu.ko" "$KOFILE_PTCH" else info "amdgpu.ko file already precompiled reusing it $KOFILE_PTCH" fi find "/lib/modules/$KERNEL" -name amdgpu.ko -exec rm -v {} \; cp -f "$KOFILE_PTCH" "/lib/modules/${KERNEL}/kernel/drivers/gpu/drm/amd/amdgpu/amdgpu.ko" depmod -a update-initramfs -u info "SUCCESS" echo "Looks like everything is ok. Please reboot now." info "DOWNCLOCK USAGE MANUAL" echo "By default you can write into hwmon file device/pp_sclk_od value from 0 to 20." echo "This will increase your clock up to 20%." echo "With my patch you can also decrease clock down from 99% to 50%." echo "Just write value from 50 to 99 to device/pp_sclk_od" echo "It may cause mess while reading pp_sclk_od and pp_dmp_sclk values."