Files
throttled/lenovo_fix.py

104 lines
3.2 KiB
Python
Executable File

#!/usr/bin/env python2
import glob
import os
import struct
from periphery import MMIO
from time import sleep
config = {
'AC': {
'UPDATE_RATE_SEC': 5, # Update the registers every this many seconds
'PL1_TDP_W': 44, # Max package power for time window #1
'PL1_DURATION_S': 28, # Time window #1 duration
'PL2_TDP_W': 44, # Max package power for time window #1
'PL2_DURATION_S': 0.002, # Time window #1 duration
'TRIP_TEMP_C': 97 # Max allowed temperature before throttling
},
'BATTERY': {
'UPDATE_RATE_SEC': 30, # Update the registers every this many seconds
'PL1_TDP_W': 29, # Max package power for time window #1
'PL1_DURATION_S': 28, # Time window #1 duration
'PL2_TDP_W': 44, # Max package power for time window #1
'PL2_DURATION_S': 0.002, # Time window #1 duration
'TRIP_TEMP_C': 85 # Max allowed temperature before throttling
},
}
def writemsr(msr, val):
n = glob.glob('/dev/cpu/[0-9]*/msr')
for c in n:
f = os.open(c, os.O_WRONLY)
os.lseek(f, msr, os.SEEK_SET)
os.write(f, struct.pack('Q', val))
os.close(f)
if not n:
raise OSError("msr module not loaded (run modprobe msr)")
def is_on_battery():
with open('/sys/class/power_supply/AC/online') as f:
return not bool(int(f.read()))
def calc_time_window_vars(t):
for Y in xrange(2**5):
for Z in xrange(2**2):
if t <= (2**Y) * (1. + Z / 4.) * 0.000977:
return (Y, Z)
raise Exception('Unable to find a good combination!')
def check_config():
for k in config:
assert 0 < config[k]['UPDATE_RATE_SEC']
assert 0 < config[k]['PL1_TDP_W']
assert 0 < config[k]['PL2_TDP_W']
assert 0 < config[k]['PL1_DURATION_S']
assert 0 < config[k]['PL2_DURATION_S']
assert 40 < config[k]['TRIP_TEMP_C'] < 98
def calc_reg_values():
for k in config:
# the critical temperature for this CPU is 100 C
trip_offset = int(round(100 - config[k]['TRIP_TEMP_C']))
config[k]['MSR_TEMPERATURE_TARGET'] = trip_offset << 24
# 0.125 is the power unit of this CPU
PL1 = int(round(config[k]['PL1_TDP_W'] / 0.125))
Y, Z = calc_time_window_vars(config[k]['PL1_DURATION_S'])
TW1 = Y | (Z << 5)
PL2 = int(round(config[k]['PL2_TDP_W'] / 0.125))
Y, Z = calc_time_window_vars(config[k]['PL2_DURATION_S'])
TW2 = Y | (Z << 5)
config[k]['MSR_PKG_POWER_LIMIT'] = PL1 | (1 << 15) | (TW1 << 17) | (PL2 << 32) | (1 << 47) | (TW2 << 49)
def main():
check_config()
calc_reg_values()
mchbar_mmio = MMIO(0xfed159a0, 8)
while True:
cur_config = config['BATTERY' if is_on_battery() else 'AC']
# set temperature trip point
writemsr(0x1a2, cur_config['MSR_TEMPERATURE_TARGET'])
# set PL1/2 on MSR
writemsr(0x610, cur_config['MSR_PKG_POWER_LIMIT'])
# set MCHBAR register to the same PL1/2 values
mchbar_mmio.write32(0, cur_config['MSR_PKG_POWER_LIMIT'] & 0xffffffff)
mchbar_mmio.write32(4, cur_config['MSR_PKG_POWER_LIMIT'] >> 32)
sleep(cur_config['UPDATE_RATE_SEC'])
if __name__ == '__main__':
main()