throttled/lenovo_fix.py

102 lines
3.1 KiB
Python
Raw Normal View History

2018-04-02 14:51:06 +02:00
#!/usr/bin/env python2
2018-04-03 11:17:18 +02:00
import ConfigParser
2018-04-02 14:51:06 +02:00
import glob
import os
import struct
2018-04-03 11:17:18 +02:00
from collections import defaultdict
2018-04-02 14:51:06 +02:00
from periphery import MMIO
from time import sleep
2018-04-03 11:17:18 +02:00
SYSFS_POWER_PATH = '/sys/class/power_supply/AC/online'
CONFIG_PATH = '/etc/lenovo_fix.conf'
2018-04-02 14:51:06 +02:00
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():
2018-04-03 11:17:18 +02:00
with open(SYSFS_POWER_PATH) 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!')
2018-04-03 11:17:18 +02:00
def load_config():
config = ConfigParser.ConfigParser()
config.read(CONFIG_PATH)
2018-04-03 11:17:18 +02:00
for power_source in ('AC', 'BATTERY'):
assert 0 < config.getfloat(power_source, 'Update_Rate_s')
assert 0 < config.getfloat(power_source, 'PL1_Tdp_W')
assert 0 < config.getfloat(power_source, 'PL1_Duration_s')
assert 0 < config.getfloat(power_source, 'PL2_Tdp_W')
assert 0 < config.getfloat(power_source, 'PL2_Duration_S')
assert 40 < config.getfloat(power_source, 'Trip_Temp_C') < 98
2018-04-03 11:17:18 +02:00
return config
def calc_reg_values(config):
regs = defaultdict(dict)
for power_source in ('AC', 'BATTERY'):
# the critical temperature for this CPU is 100 C
2018-04-03 11:17:18 +02:00
trip_offset = int(round(100 - config.getfloat(power_source, 'Trip_Temp_C')))
regs[power_source]['MSR_TEMPERATURE_TARGET'] = trip_offset << 24
# 0.125 is the power unit of this CPU
2018-04-03 11:17:18 +02:00
PL1 = int(round(config.getfloat(power_source, 'PL1_Tdp_W') / 0.125))
Y, Z = calc_time_window_vars(config.getfloat(power_source, 'PL1_Duration_s'))
TW1 = Y | (Z << 5)
2018-04-03 11:17:18 +02:00
PL2 = int(round(config.getfloat(power_source, 'PL2_Tdp_W') / 0.125))
Y, Z = calc_time_window_vars(config.getfloat(power_source, 'PL2_Duration_s'))
TW2 = Y | (Z << 5)
2018-04-03 11:17:18 +02:00
regs[power_source]['MSR_PKG_POWER_LIMIT'] = PL1 | (1 << 15) | (TW1 << 17) | (PL2 << 32) | (1 << 47) | (
TW2 << 49)
return regs
2018-04-02 14:51:06 +02:00
def main():
2018-04-03 11:17:18 +02:00
config = load_config()
regs = calc_reg_values(config)
if not config.getboolean('GENERAL', 'Enabled'):
return
2018-04-02 14:51:06 +02:00
mchbar_mmio = MMIO(0xfed159a0, 8)
while True:
2018-04-03 11:17:18 +02:00
power_source = 'BATTERY' if is_on_battery() else 'AC'
# set temperature trip point
2018-04-03 11:17:18 +02:00
writemsr(0x1a2, regs[power_source]['MSR_TEMPERATURE_TARGET'])
# set PL1/2 on MSR
2018-04-03 11:17:18 +02:00
writemsr(0x610, regs[power_source]['MSR_PKG_POWER_LIMIT'])
2018-04-02 14:51:06 +02:00
# set MCHBAR register to the same PL1/2 values
2018-04-03 11:17:18 +02:00
mchbar_mmio.write32(0, regs[power_source]['MSR_PKG_POWER_LIMIT'] & 0xffffffff)
mchbar_mmio.write32(4, regs[power_source]['MSR_PKG_POWER_LIMIT'] >> 32)
2018-04-03 11:17:18 +02:00
sleep(config.getfloat(power_source, 'Update_Rate_s'))
2018-04-02 14:51:06 +02:00
if __name__ == '__main__':
main()