소스 검색

Merge pull request #30 from DEvil0000/improvement/configure_cTDP

Improvement/configure cTDP
Francesco Palmarini 6 년 전
부모
커밋
d6f8d995cd
3개의 변경된 파일59개의 추가작업 그리고 4개의 파일을 삭제
  1. 3 0
      README.md
  2. 4 0
      etc/lenovo_fix.conf
  3. 52 4
      lenovo_fix.py

+ 3 - 0
README.md

@@ -26,6 +26,9 @@ I have found that under load my CPU was not always hitting max turbo frequency,
 
 I have run **[Geekbench 4](https://browser.geekbench.com/v4/cpu/8656840)** and now I can get a score of 5391/17265! On balance_performance I can reach only 4672/16129, so **15% improvement** in single core and 7% in multicore, not bad ;)
 
+### setting cTDP (EXPERIMENTAL)
+On a lot of modern CPUs from Intel one can configure the TDP up or down based on predefined profiles. This is what this option does. For a i7-8650U normal would be 15W, up profile is setting it to 25W and down to 10W. You can lookup the values of your CPU at the Intel product website.
+
 ## Requirements
 A stripped down version of the python module `python-periphery` is now built-in and it is used for accessing the MCHBAR register by memory mapped I/O. You also need `dbus` and `gobject` python bindings for listening to dbus signals on resume from sleep/hibernate.
 

+ 4 - 0
etc/lenovo_fix.conf

@@ -15,6 +15,8 @@ PL2_Tdp_W: 44
 PL2_Duration_S: 0.002
 # Max allowed temperature before throttling
 Trip_Temp_C: 85
+# Set cTDP to normal=0, down=1 or up=2 (EXPERIMENTAL)
+cTDP: 0
 
 ## Settings to apply while connected to AC power
 [AC]
@@ -32,6 +34,8 @@ PL2_Duration_S: 0.002
 Trip_Temp_C: 95
 # Set HWP energy performance hints to 'performance' on high load (EXPERIMENTAL)
 HWP_Mode: False
+# Set cTDP to normal=0, down=1 or up=2 (EXPERIMENTAL)
+cTDP: 0
 
 [UNDERVOLT]
 # CPU core voltage offset (mV)

+ 52 - 4
lenovo_fix.py

@@ -33,6 +33,7 @@ VOLTAGE_PLANES = {
 }
 
 TRIP_TEMP_RANGE = (40, 97)
+C_TDP_RANGE = (0, 2)
 
 power = {'source': None, 'method': 'polling'}
 
@@ -58,6 +59,33 @@ def writemsr(msr, val):
         else:
             raise e
 
+# returns the value between from_bit and to_bit as unsigned long
+def readmsr(msr, from_bit = 0, to_bit = 63):
+    if from_bit > to_bit:
+        print('wrong readmsr bit params')
+        sys.exit(1)
+    n = ['/dev/cpu/{:d}/msr'.format(x) for x in range(cpu_count())]
+    if not os.path.exists(n[0]):
+        try:
+            subprocess.check_call(('modprobe', 'msr'))
+        except subprocess.CalledProcessError:
+            print('[E] Unable to load the msr module.')
+            sys.exit(1)
+    try:
+        for c in n:
+            f = os.open(c, os.O_RDONLY)
+            os.lseek(f, msr, os.SEEK_SET)
+            val = struct.unpack('Q', os.read(f, 8))[0]
+            os.close(f)
+            extractor = int(''.join(["0"]*(63-to_bit) + ["1"]*(to_bit+1-from_bit) + ["0"]*from_bit), 2)
+            return (val & extractor) >> from_bit
+    except (IOError, OSError) as e:
+        if e.errno == EPERM or e.errno == EACCES:
+            print('[E] Unable to read from MSR. Try to disable Secure Boot.')
+            sys.exit(1)
+        else:
+            raise e
+
 
 def is_on_battery():
     with open(SYSFS_POWER_PATH) as f:
@@ -65,9 +93,11 @@ def is_on_battery():
 
 
 def calc_time_window_vars(t):
+    # 0.000977 is the time unit of this CPU
+    time_unit = 1.0/2**readmsr(0x606, 16, 19)
     for Y in range(2**5):
         for Z in range(2**2):
-            if t <= (2**Y) * (1. + Z / 4.) * 0.000977:
+            if t <= (2**Y) * (1. + Z / 4.) * time_unit:
                 return (Y, Z)
     raise ValueError('Unable to find a good combination!')
 
@@ -126,17 +156,26 @@ def calc_reg_values(config):
         regs[power_source]['MSR_TEMPERATURE_TARGET'] = trip_offset << 24
 
         # 0.125 is the power unit of this CPU
-        PL1 = int(round(config.getfloat(power_source, 'PL1_Tdp_W') / 0.125))
+        power_unit = 1.0/2**readmsr(0x606, 0, 3)
+        PL1 = int(round(config.getfloat(power_source, 'PL1_Tdp_W') / power_unit))
         Y, Z = calc_time_window_vars(config.getfloat(power_source, 'PL1_Duration_s'))
         TW1 = Y | (Z << 5)
 
-        PL2 = int(round(config.getfloat(power_source, 'PL2_Tdp_W') / 0.125))
+        PL2 = int(round(config.getfloat(power_source, 'PL2_Tdp_W') / power_unit))
         Y, Z = calc_time_window_vars(config.getfloat(power_source, 'PL2_Duration_s'))
         TW2 = Y | (Z << 5)
 
         regs[power_source]['MSR_PKG_POWER_LIMIT'] = PL1 | (1 << 15) | (TW1 << 17) | (PL2 << 32) | (1 << 47) | (
             TW2 << 49)
 
+        # cTDP
+        try:
+            c_tdp_target_value = int(config.getfloat(power_source, 'cTDP'))
+            valid_c_tdp_target_value = min(C_TDP_RANGE[1], max(C_TDP_RANGE[0], c_tdp_target_value))
+            regs[power_source]['MSR_CONFIG_TDP_CONTROL'] = valid_c_tdp_target_value
+        except configparser.NoOptionError:
+            pass
+
     return regs
 
 
@@ -162,7 +201,16 @@ def power_thread(config, regs, exit_event):
             power['source'] = 'BATTERY' if is_on_battery() else 'AC'
 
         # set temperature trip point
-        writemsr(0x1a2, regs[power['source']]['MSR_TEMPERATURE_TARGET'])
+        if readmsr(0xce, 30, 30) != 1:
+            print("setting temperature target is not supported by this CPU")
+        else:
+            writemsr(0x1a2, regs[power['source']]['MSR_TEMPERATURE_TARGET'])
+
+        # set cTDP
+        if readmsr(0xce, 33, 34) < 2:
+            print("cTDP setting not supported by this cpu")
+        else:
+            writemsr(0x64b, regs[power['source']]['MSR_CONFIG_TDP_CONTROL'])
 
         # set PL1/2 on MSR
         writemsr(0x610, regs[power['source']]['MSR_PKG_POWER_LIMIT'])