#!/bin/bash # # setPL.sh - Sets Intel Power Limit registers, allowing you to control the maximum # power consumption of the chip (ie, "overclock" for higher performance) # # Useage: setPL.sh # Example: ./setPL.sh 25 25 # # This script sets the power limits of modern Intel processors. PL1 defines # the lower limit (watts), which will apply when the CPU isn't boosting. PL2 # is the higher limit (watts), which will apply when the CPU is under load. # The processor returns from PL2 to PL1 after a configured amount of time to # avoid overheating, even if it's still under load. The purpose of this script # is to defeat that, typically by setting PL1 and PL2 both to high values, # including the same high value. # # There are two configuration registers which define the PL1/PL2 limits applied. # One is the MSR register (Model-specific register), which is accessible via # special CPU instructions. The other is the MMIO register (Memory-Mapped I/O # register), which is accessible as a memory address within the PCI memory bar # region assigned to the processor's PCIe root complex. The processor will use # the lower PL1/PL2 limits between the two registers, so for example if one is # set to PL1/PL2 of 10/15 and the other set to 20/25, the processor will use 10/15. # Complicating matters further is the fact that many vendors have system microcode # that will dynamically change the MMIO version of PL1/PL2 based on thermals, which # can defeat our ability to set higher limits. To prevent this we disable the PL1/PL2 # thresholds in the MMIO register, then set the "lock" bit within that register # to prevent the system's microcode from changing it. This locked status persists # for the duration of the power-on session. # # Many thanks and credit to the author of the Windows ThrottleStop application for # finding and applying these techniques - I used the knowledge gained from that app # to write this script # set -u # # general constants # TRUE=1 FALSE=0 APP_NAME="setPL" # # intel constants # INTEL_MSR_PKG_POWER_LIMIT=0x610 INTEL_PACKAGE_RAPL_LIMIT_0_0_0_MCHBAR_PCU=0x59a0 INTEL_PL1_ENABLE_BITS_LOW=0x00008000 INTEL_PL2_ENABLE_BITS_HIGH=0x00008000 INTEL_PL1_PL2_ENABLE_BITS=$((INTEL_PL1_ENABLE_BITS_LOW | (INTEL_PL2_ENABLE_BITS_HIGH<<32))) # # operational flags # F_DISABLE_MMIO_PL1_PL2=$TRUE # if TRUE, MMIO reg is cleared (all bits set to zero, including PL1/PL2 enable bits) # if FALSE, MMIO reg PL1/PL2 is enabled, with PL1/PL2 set to same value as MSR # # functions # # # Parameters: $1 - Hex address # Return Value: Value read (hex ASCII) # Terminates script on error # readPhysMemWord() { # # sample devmem2 output that we need to parse: # Value at address 0xFEDC0000 (0x7efd68cf4000): 0xEF32C519 # printf -v addrHex "0x%x" $1 output=$(devmem2 $addrHex w 2>&1) if [ $? -eq 0 ]; then retVal=$(echo "$output" | sed -nE 's/Value at address.*: (0x[0-9A-E]+)/\1/p') if [ -z "$retVal" ]; then echo "Error parsing devmem2 read output" exit 1 fi # else successful - fall through to return from function else echo "Error reading from physical memory via devmem2. If due to permissions you may need to disable Secure Boot." echo "devmem2 output: ${output}" exit 1 fi } # # Parameters: $1 - Hex address # Return Value: None # Terminates script on error # writePhysMemWord() { printf -v addrHex "0x%x" $1 printf -v val "0x%x" $2 output=$(devmem2 $addrHex w $val 2>&1) if [ $? -ne 0 ]; then echo "Error writing to physical memory via devmem2. If due to permissions you may need to disable Secure Boot." echo "devmem2 output: ${output}" exit 1 fi } # # Parameters: $1 - Hex address # Return Value: Value read (hex ASCII) # Terminates script on error # readMsr() { printf -v addrHex "0x%x" $1 output=$(rdmsr --hexadecimal --zero-pad --c-language $addrHex 2>&1) if [ $? -ne 0 ]; then echo "Error reading MSR. If due to permissions you may need to disable Secure Boot." echo "rdmsr output: ${output}" exit 1 fi retVal=$output } # # Parameters: $1 - Hex address # $2 - Hex value to write # Return Value: None # Terminates script on error # writeMsr() { printf -v addrHex "0x%x" $1 printf -v val "0x%x" $2 output=$(wrmsr $addrHex $val 2>&1) if [ $? -ne 0 ]; then echo "Error writing MSR. If due to permissions you may need to disable Secure Boot." echo "wrmsr output: ${output}" exit 1 fi } printTurboStat_PL1_PL2() { turbostat sleep 0 2>&1 | grep MSR_PKG_POWER_LIMIT -A 2 } verifyAppInstalled() { toolName=$1 if ! command -v "$toolName" &> /dev/null; then # app not installed retVal=1 else retVal=0 fi } verifyAppsInstalled() { local appsList local appsMissingList local appName appsList=("$@") appsMissingList=() for appName in "${appsList[@]}"; do verifyAppInstalled "$appName" if [ $retVal -eq 1 ]; then appsMissingList+=("$appName") fi done if [ ${#appsMissingList[@]} -gt 0 ]; then echo "The following apps must be installed to run ${APP_NAME}: ${appsMissingList[@]}" exit 1 fi } verifyTurbostatInstallation() { # sample warning when turbostat not properly installed for running kernel: # WARNING: turbostat not found for kernel 5.15.0-30 output=$(turbostat 2>&1 --help | grep "WARNING: turbostat not found for kernel"); if [ -n "$output" ]; then kernelVer=$(uname -r) echo "turbostat is installed but not the package needed for current kernel" echo "Maybe try sudo apt install linux-tools-${kernelVer}" exit 1 fi } # # script entry point # arguments: # if [ "$#" != "2" ]; then echo "Usage: ${APP_NAME} " exit 1 fi # convert values to micro-watts PL1=$(($1 * 1000000)) PL2=$(($2 * 1000000)) # make sure script is running with root privilege if [ $(id -u) -ne 0 ]; then echo "This script must be run with root privileges (root user or with 'sudo')" exit 1 fi # make sure the necessary apps are installed requiredApps=('devmem2' 'rdmsr' 'wrmsr' 'turbostat' 'setpci') verifyAppsInstalled "${requiredApps[@]}" verifyTurbostatInstallation # print current values echo "**** Current PL values from 'turbostat'" printTurboStat_PL1_PL2 # set new PL1/PL2 values echo "**** Setting PL1=$PL1 and PL2=$PL2 in /sys/class/powercap/intel-rapl/intel-rapl:0/constraint_*_power_limit_uw" echo "$PL1" > /sys/class/powercap/intel-rapl/intel-rapl:0/constraint_0_power_limit_uw echo "$PL2" > /sys/class/powercap/intel-rapl/intel-rapl:0/constraint_1_power_limit_uw # enable both PL1 and PL2 (bit 15 and 47) if not already enabled readMsr $INTEL_MSR_PKG_POWER_LIMIT; msr=$retVal is_PL1_PL2_Enabled=$(((msr & INTEL_PL1_PL2_ENABLE_BITS) == INTEL_PL1_PL2_ENABLE_BITS)) if [ $is_PL1_PL2_Enabled -ne $TRUE ]; then echo "**** Enabling PL1 and PL2 in MSR_PKG_POWER_LIMIT" msr=$((msr | INTEL_PL1_PL2_ENABLE_BITS)) writeMsr $INTEL_MSR_PKG_POWER_LIMIT $msr else echo "**** PL1 and PL2 already enabled in MSR_PKG_POWER_LIMIT" fi echo "**** New PL values from 'turbostat'" printTurboStat_PL1_PL2 # # now handle the MMIO version of the power-limit register (PACKAGE_RAPL_LIMIT_0_0_0_MCHBAR_PCU) # # get the MCHBAR address, which is at config offset 0x48 in config space for the processor mchbar="0x"$(setpci -s 00:00.0 48.l) printf "**** MCHBAR is 0x%x\n" $mchbar isMchbarEnabled=$(((mchbar & 0x1) != 0)) if [ $isMchbarEnabled -ne $TRUE ]; then echo "MCHBAR is not enabled!!!" exit 1 fi mchbar=$((mchbar & ~1)) # clear off enable bit so value represents valid physical address # calculate address of PACKAGE_RAPL_LIMIT_0_0_0_MCHBAR_PCU MMIO register raplLimitAddr=$((mchbar + INTEL_PACKAGE_RAPL_LIMIT_0_0_0_MCHBAR_PCU)) # get the current value of PACKAGE_RAPL_LIMIT_0_0_0_MCHBAR_PCU readPhysMemWord $((raplLimitAddr+0)); low=$retVal readPhysMemWord $((raplLimitAddr+4)); high=$retVal printf "**** Current value of PACKAGE_RAPL_LIMIT_0_0_0_MCHBAR_PCU = 0x%08x:0x%08x\n" $high $low # set the new value for PACKAGE_RAPL_LIMIT_0_0_0_MCHBAR_PCU isMMIOLocked=$(((high & 0x80000000) == 0x80000000)) if [ $isMMIOLocked -eq $TRUE ]; then if ((low & INTEL_PL1_ENABLE_BITS_LOW || high & INTEL_PL2_ENABLE_BITS_HIGH)); then # MMIO is locked but either PL1 and/or PL2 are enabled, meaning we can't disable PL1+PL2 this power-on session echo "**** Warning: MMIO limit reg already locked but with PL1 and/or PL2 enabled, can't change" else # MMIO is locked with PL1/PL2 disabled, likely from our script doing so on previous invocation this power-on session if [ $F_DISABLE_MMIO_PL1_PL2 -eq $TRUE ]; then echo "**** MMIO limit reg locked with PL1/PL2 disabled on previous invocation (expected)" else echo "**** Warning: MMIO limit already locked so can't set PL1/PL2 values in it" fi fi else if [ $F_DISABLE_MMIO_PL1_PL2 -eq $TRUE ]; then # set MMIO to zero, which will also set the PL1/PL2 enable bits for the MMIO reg to FALSE lowNew=0x00000000; highNew=0x00000000; else # set MMIO to the same PL1/PL2 values as the MSR reg lowNew=$((msr & 0x00000000FFFFFFFF)) highNew=$(((msr & 0xFFFFFFFF00000000)>>32)) fi highNew=$((highNew | 0x80000000)) # set lock bit printf "**** Setting PACKAGE_RAPL_LIMIT_0_0_0_MCHBAR_PCU = 0x%08x:0x%08x\n" $highNew $lowNew writePhysMemWord $((raplLimitAddr+0)) $lowNew writePhysMemWord $((raplLimitAddr+4)) $highNew fi