lenovo_fix.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. #!/usr/bin/env python2
  2. import ConfigParser
  3. import glob
  4. import os
  5. import struct
  6. from collections import defaultdict
  7. from periphery import MMIO
  8. from time import sleep
  9. SYSFS_POWER_PATH = '/sys/class/power_supply/AC/online'
  10. CONFIG_PATH = '/etc/lenovo_fix.conf'
  11. def writemsr(msr, val):
  12. n = glob.glob('/dev/cpu/[0-9]*/msr')
  13. for c in n:
  14. f = os.open(c, os.O_WRONLY)
  15. os.lseek(f, msr, os.SEEK_SET)
  16. os.write(f, struct.pack('Q', val))
  17. os.close(f)
  18. if not n:
  19. raise OSError("msr module not loaded (run modprobe msr)")
  20. def is_on_battery():
  21. with open(SYSFS_POWER_PATH) as f:
  22. return not bool(int(f.read()))
  23. def calc_time_window_vars(t):
  24. for Y in xrange(2**5):
  25. for Z in xrange(2**2):
  26. if t <= (2**Y) * (1. + Z / 4.) * 0.000977:
  27. return (Y, Z)
  28. raise Exception('Unable to find a good combination!')
  29. def load_config():
  30. config = ConfigParser.ConfigParser()
  31. config.read(CONFIG_PATH)
  32. for power_source in ('AC', 'BATTERY'):
  33. assert 0 < config.getfloat(power_source, 'Update_Rate_s')
  34. assert 0 < config.getfloat(power_source, 'PL1_Tdp_W')
  35. assert 0 < config.getfloat(power_source, 'PL1_Duration_s')
  36. assert 0 < config.getfloat(power_source, 'PL2_Tdp_W')
  37. assert 0 < config.getfloat(power_source, 'PL2_Duration_S')
  38. assert 40 < config.getfloat(power_source, 'Trip_Temp_C') < 98
  39. return config
  40. def calc_reg_values(config):
  41. regs = defaultdict(dict)
  42. for power_source in ('AC', 'BATTERY'):
  43. # the critical temperature for this CPU is 100 C
  44. trip_offset = int(round(100 - config.getfloat(power_source, 'Trip_Temp_C')))
  45. regs[power_source]['MSR_TEMPERATURE_TARGET'] = trip_offset << 24
  46. # 0.125 is the power unit of this CPU
  47. PL1 = int(round(config.getfloat(power_source, 'PL1_Tdp_W') / 0.125))
  48. Y, Z = calc_time_window_vars(config.getfloat(power_source, 'PL1_Duration_s'))
  49. TW1 = Y | (Z << 5)
  50. PL2 = int(round(config.getfloat(power_source, 'PL2_Tdp_W') / 0.125))
  51. Y, Z = calc_time_window_vars(config.getfloat(power_source, 'PL2_Duration_s'))
  52. TW2 = Y | (Z << 5)
  53. regs[power_source]['MSR_PKG_POWER_LIMIT'] = PL1 | (1 << 15) | (TW1 << 17) | (PL2 << 32) | (1 << 47) | (
  54. TW2 << 49)
  55. return regs
  56. def main():
  57. config = load_config()
  58. regs = calc_reg_values(config)
  59. if not config.getboolean('GENERAL', 'Enabled'):
  60. return
  61. mchbar_mmio = MMIO(0xfed159a0, 8)
  62. while True:
  63. power_source = 'BATTERY' if is_on_battery() else 'AC'
  64. # set temperature trip point
  65. writemsr(0x1a2, regs[power_source]['MSR_TEMPERATURE_TARGET'])
  66. # set PL1/2 on MSR
  67. writemsr(0x610, regs[power_source]['MSR_PKG_POWER_LIMIT'])
  68. # set MCHBAR register to the same PL1/2 values
  69. mchbar_mmio.write32(0, regs[power_source]['MSR_PKG_POWER_LIMIT'] & 0xffffffff)
  70. mchbar_mmio.write32(4, regs[power_source]['MSR_PKG_POWER_LIMIT'] >> 32)
  71. sleep(config.getfloat(power_source, 'Update_Rate_s'))
  72. if __name__ == '__main__':
  73. main()