lenovo_fix.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. #!/usr/bin/env python2
  2. import glob
  3. import os
  4. import struct
  5. from periphery import MMIO
  6. from time import sleep
  7. config = {
  8. 'AC': {
  9. 'UPDATE_RATE_SEC': 5, # Update the registers every this many seconds
  10. 'PL1_TDP_W': 44, # Max package power for time window #1
  11. 'PL1_DURATION_S': 28, # Time window #1 duration
  12. 'PL2_TDP_W': 44, # Max package power for time window #2
  13. 'PL2_DURATION_S': 0.002, # Time window #2 duration
  14. 'TRIP_TEMP_C': 97 # Max allowed temperature before throttling
  15. },
  16. 'BATTERY': {
  17. 'UPDATE_RATE_SEC': 30, # Update the registers every this many seconds
  18. 'PL1_TDP_W': 29, # Max package power for time window #1
  19. 'PL1_DURATION_S': 28, # Time window #1 duration
  20. 'PL2_TDP_W': 44, # Max package power for time window #2
  21. 'PL2_DURATION_S': 0.002, # Time window #2 duration
  22. 'TRIP_TEMP_C': 85 # Max allowed temperature before throttling
  23. },
  24. }
  25. def writemsr(msr, val):
  26. n = glob.glob('/dev/cpu/[0-9]*/msr')
  27. for c in n:
  28. f = os.open(c, os.O_WRONLY)
  29. os.lseek(f, msr, os.SEEK_SET)
  30. os.write(f, struct.pack('Q', val))
  31. os.close(f)
  32. if not n:
  33. raise OSError("msr module not loaded (run modprobe msr)")
  34. def is_on_battery():
  35. with open('/sys/class/power_supply/AC/online') as f:
  36. return not bool(int(f.read()))
  37. def calc_time_window_vars(t):
  38. for Y in xrange(2**5):
  39. for Z in xrange(2**2):
  40. if t <= (2**Y) * (1. + Z / 4.) * 0.000977:
  41. return (Y, Z)
  42. raise Exception('Unable to find a good combination!')
  43. def check_config():
  44. for k in config:
  45. assert 0 < config[k]['UPDATE_RATE_SEC']
  46. assert 0 < config[k]['PL1_TDP_W']
  47. assert 0 < config[k]['PL2_TDP_W']
  48. assert 0 < config[k]['PL1_DURATION_S']
  49. assert 0 < config[k]['PL2_DURATION_S']
  50. assert 40 < config[k]['TRIP_TEMP_C'] < 98
  51. def calc_reg_values():
  52. for k in config:
  53. # the critical temperature for this CPU is 100 C
  54. trip_offset = int(round(100 - config[k]['TRIP_TEMP_C']))
  55. config[k]['MSR_TEMPERATURE_TARGET'] = trip_offset << 24
  56. # 0.125 is the power unit of this CPU
  57. PL1 = int(round(config[k]['PL1_TDP_W'] / 0.125))
  58. Y, Z = calc_time_window_vars(config[k]['PL1_DURATION_S'])
  59. TW1 = Y | (Z << 5)
  60. PL2 = int(round(config[k]['PL2_TDP_W'] / 0.125))
  61. Y, Z = calc_time_window_vars(config[k]['PL2_DURATION_S'])
  62. TW2 = Y | (Z << 5)
  63. config[k]['MSR_PKG_POWER_LIMIT'] = PL1 | (1 << 15) | (TW1 << 17) | (PL2 << 32) | (1 << 47) | (TW2 << 49)
  64. def main():
  65. check_config()
  66. calc_reg_values()
  67. mchbar_mmio = MMIO(0xfed159a0, 8)
  68. while True:
  69. cur_config = config['BATTERY' if is_on_battery() else 'AC']
  70. # set temperature trip point
  71. writemsr(0x1a2, cur_config['MSR_TEMPERATURE_TARGET'])
  72. # set PL1/2 on MSR
  73. writemsr(0x610, cur_config['MSR_PKG_POWER_LIMIT'])
  74. # set MCHBAR register to the same PL1/2 values
  75. mchbar_mmio.write32(0, cur_config['MSR_PKG_POWER_LIMIT'] & 0xffffffff)
  76. mchbar_mmio.write32(4, cur_config['MSR_PKG_POWER_LIMIT'] >> 32)
  77. sleep(cur_config['UPDATE_RATE_SEC'])
  78. if __name__ == '__main__':
  79. main()