lenovo_fix.py 3.2 KB

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