diff --git a/lenovo_fix.py b/lenovo_fix.py index e3aee04..7ab9bbe 100755 --- a/lenovo_fix.py +++ b/lenovo_fix.py @@ -7,7 +7,24 @@ import struct from periphery import MMIO from time import sleep -UPDATE_RATE_SEC = 15 +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): @@ -21,17 +38,65 @@ def writemsr(msr, val): 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: - # set temperature trip point to 97 C - writemsr(0x1a2, 0x3000000) - # set MSR to PL1 45W, max duration - PL2 45W, 2ms - writemsr(0x610, 0x42816800fe8168) + 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, 0x00fe8168) - mchbar_mmio.write32(4, 0x00428168) - sleep(UPDATE_RATE_SEC) + 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__':