|
@@ -24,8 +24,10 @@ from time import time
|
|
|
|
|
|
DEFAULT_SYSFS_POWER_PATH = '/sys/class/power_supply/AC*/online'
|
|
|
VOLTAGE_PLANES = {'CORE': 0, 'GPU': 1, 'CACHE': 2, 'UNCORE': 3, 'ANALOGIO': 4}
|
|
|
+CURRENT_PLANES = {'CORE': 0, 'GPU': 1, 'CACHE': 2}
|
|
|
TRIP_TEMP_RANGE = [40, 97]
|
|
|
UNDERVOLT_KEYS = ('UNDERVOLT', 'UNDERVOLT.AC', 'UNDERVOLT.BATTERY')
|
|
|
+ICCMAX_KEYS = ('ICCMAX', 'ICCMAX.AC', 'ICCMAX.BATTERY')
|
|
|
power = {'source': None, 'method': 'polling'}
|
|
|
|
|
|
platform_info_bits = {
|
|
@@ -277,6 +279,17 @@ def calc_undervolt_mv(msr_value):
|
|
|
return int(round(offset / 1.024))
|
|
|
|
|
|
|
|
|
+def get_undervolt(plane=None, convert=False):
|
|
|
+ planes = [plane] if plane in VOLTAGE_PLANES else VOLTAGE_PLANES
|
|
|
+ out = {}
|
|
|
+ for plane in planes:
|
|
|
+ writemsr(0x150, 0x8000001000000000 | (VOLTAGE_PLANES[plane] << 40))
|
|
|
+ read_value = readmsr(0x150, flatten=True) & 0xFFFFFFFF
|
|
|
+ out[plane] = calc_undervolt_mv(read_value) if convert else read_value
|
|
|
+
|
|
|
+ return out
|
|
|
+
|
|
|
+
|
|
|
def undervolt(config):
|
|
|
for plane in VOLTAGE_PLANES:
|
|
|
write_offset_mv = config.getfloat(
|
|
@@ -286,8 +299,7 @@ def undervolt(config):
|
|
|
writemsr(0x150, write_value)
|
|
|
if args.debug:
|
|
|
write_value &= 0xFFFFFFFF
|
|
|
- writemsr(0x150, 0x8000001000000000 | (VOLTAGE_PLANES[plane] << 40))
|
|
|
- read_value = readmsr(0x150, flatten=True)
|
|
|
+ read_value = get_undervolt(plane)[plane]
|
|
|
read_offset_mv = calc_undervolt_mv(read_value)
|
|
|
match = OK if write_value == read_value else ERR
|
|
|
print(
|
|
@@ -297,6 +309,56 @@ def undervolt(config):
|
|
|
)
|
|
|
|
|
|
|
|
|
+def calc_icc_max_msr(plane, current):
|
|
|
+ """Return the value to be written in the MSR 150h for setting the given
|
|
|
+ IccMax (in A) to the given current plane.
|
|
|
+ """
|
|
|
+ assert 0 < current <= 0x3FF
|
|
|
+ assert plane in CURRENT_PLANES
|
|
|
+ current = int(round(current * 4))
|
|
|
+ return 0x8000001700000000 | (CURRENT_PLANES[plane] << 40) | current
|
|
|
+
|
|
|
+
|
|
|
+def calc_icc_max_amp(msr_value):
|
|
|
+ """Return the max current (in A) from the given raw MSR 150h value.
|
|
|
+ """
|
|
|
+ return (msr_value & 0x3FF) / 4.0
|
|
|
+
|
|
|
+
|
|
|
+def get_icc_max(plane=None, convert=False):
|
|
|
+ planes = [plane] if plane in CURRENT_PLANES else CURRENT_PLANES
|
|
|
+ out = {}
|
|
|
+ for plane in planes:
|
|
|
+ writemsr(0x150, 0x8000001600000000 | (CURRENT_PLANES[plane] << 40))
|
|
|
+ read_value = readmsr(0x150, flatten=True) & 0x3FF
|
|
|
+ out[plane] = calc_icc_max_amp(read_value) if convert else read_value
|
|
|
+
|
|
|
+ return out
|
|
|
+
|
|
|
+
|
|
|
+def set_icc_max(config):
|
|
|
+ for plane in CURRENT_PLANES:
|
|
|
+ try:
|
|
|
+ write_current_amp = config.getfloat(
|
|
|
+ 'ICCMAX.{:s}'.format(power['source']), plane, fallback=config.getfloat('ICCMAX', plane, fallback=-1.0)
|
|
|
+ )
|
|
|
+ if write_current_amp > 0:
|
|
|
+ write_value = calc_icc_max_msr(plane, write_current_amp)
|
|
|
+ writemsr(0x150, write_value)
|
|
|
+ if args.debug:
|
|
|
+ write_value &= 0x3FF
|
|
|
+ read_value = get_icc_max(plane)[plane]
|
|
|
+ read_current_A = calc_icc_max_amp(read_value)
|
|
|
+ match = OK if write_value == read_value else ERR
|
|
|
+ print(
|
|
|
+ '[D] IccMax plane {:s} - write {:.2f} A ({:#x}) - read {:.2f} A ({:#x}) - match {}'.format(
|
|
|
+ plane, write_current_amp, write_value, read_current_A, read_value, match
|
|
|
+ )
|
|
|
+ )
|
|
|
+ except (configparser.NoSectionError, configparser.NoOptionError):
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
def load_config():
|
|
|
config = configparser.ConfigParser()
|
|
|
config.read(args.config)
|
|
@@ -321,7 +383,7 @@ def load_config():
|
|
|
)
|
|
|
)
|
|
|
|
|
|
- # fix any invalid value (ie. < 0) in the undervolt settings
|
|
|
+ # fix any invalid value (ie. > 0) in the undervolt settings
|
|
|
for key in UNDERVOLT_KEYS:
|
|
|
for plane in VOLTAGE_PLANES:
|
|
|
if key in config:
|
|
@@ -345,6 +407,24 @@ def load_config():
|
|
|
value = config.getfloat(key, plane, fallback=0.0)
|
|
|
config.set(key, plane, str(value))
|
|
|
|
|
|
+ iccmax_enabled = False
|
|
|
+ # check for invalid values (ie. <= 0 or > 0x3FF) in the IccMax settings
|
|
|
+ for key in ICCMAX_KEYS:
|
|
|
+ for plane in CURRENT_PLANES:
|
|
|
+ if key in config:
|
|
|
+ try:
|
|
|
+ value = config.getfloat(key, plane)
|
|
|
+ if value <= 0 or value >= 0x3FF:
|
|
|
+ raise ValueError
|
|
|
+ iccmax_enabled = True
|
|
|
+ except ValueError:
|
|
|
+ warning('Invalid value for {:s} in {:s}'.format(plane, key))
|
|
|
+ config.remove_option(key, plane)
|
|
|
+ except configparser.NoOptionError:
|
|
|
+ pass
|
|
|
+ if iccmax_enabled:
|
|
|
+ warning('Warning! Raising IccMax above design limits can damage your system!')
|
|
|
+
|
|
|
return config
|
|
|
|
|
|
|
|
@@ -594,7 +674,15 @@ def monitor(exit_event, wait):
|
|
|
'DRAM': (readmsr(MSR_DRAM_ENERGY_STATUS, cpu=0) * rapl_power_unit, time()),
|
|
|
}
|
|
|
|
|
|
- print('Realtime monitoring of throttling causes:\n')
|
|
|
+ undervolt_values = get_undervolt(convert=True)
|
|
|
+ undervolt_output = ' | '.join('{:s}: {:.2f} mV'.format(plane, undervolt_values[plane]) for plane in VOLTAGE_PLANES)
|
|
|
+ print('[D] Undervolt offsets: {:s}'.format(undervolt_output))
|
|
|
+
|
|
|
+ iccmax_values = get_icc_max(convert=True)
|
|
|
+ iccmax_output = ' | '.join('{:s}: {:.2f} A'.format(plane, iccmax_values[plane]) for plane in CURRENT_PLANES)
|
|
|
+ print('[D] IccMax: {:s}'.format(iccmax_output))
|
|
|
+
|
|
|
+ print('[D] Realtime monitoring of throttling causes:\n')
|
|
|
while not exit_event.is_set():
|
|
|
value = readmsr(IA32_THERM_STATUS, from_bit=0, to_bit=15, cpu=0)
|
|
|
offsets = {'Thermal': 0, 'Power': 10, 'Current': 12, 'Cross-domain (e.g. GPU)': 14}
|
|
@@ -638,6 +726,7 @@ def main():
|
|
|
check_kernel()
|
|
|
check_cpu()
|
|
|
|
|
|
+ print('[I] Loading config file.')
|
|
|
config = load_config()
|
|
|
power['source'] = 'BATTERY' if is_on_battery(config) else 'AC'
|
|
|
|
|
@@ -656,11 +745,13 @@ def main():
|
|
|
thread.start()
|
|
|
|
|
|
undervolt(config)
|
|
|
+ set_icc_max(config)
|
|
|
|
|
|
- # handle dbus events for applying undervolt on resume from sleep/hybernate
|
|
|
+ # handle dbus events for applying undervolt/IccMax on resume from sleep/hybernate
|
|
|
def handle_sleep_callback(sleeping):
|
|
|
if not sleeping:
|
|
|
undervolt(config)
|
|
|
+ set_icc_max(config)
|
|
|
|
|
|
def handle_ac_callback(*args):
|
|
|
try:
|
|
@@ -672,8 +763,10 @@ def main():
|
|
|
DBusGMainLoop(set_as_default=True)
|
|
|
bus = dbus.SystemBus()
|
|
|
|
|
|
- # add dbus receiver only if undervolt is enabled in config
|
|
|
- if any(config.getfloat(key, plane, fallback=0) != 0 for plane in VOLTAGE_PLANES for key in UNDERVOLT_KEYS):
|
|
|
+ # add dbus receiver only if undervolt/IccMax is enabled in config
|
|
|
+ if any(
|
|
|
+ config.getfloat(key, plane, fallback=0) != 0 for plane in VOLTAGE_PLANES for key in UNDERVOLT_KEYS + ICCMAX_KEYS
|
|
|
+ ):
|
|
|
bus.add_signal_receiver(
|
|
|
handle_sleep_callback, 'PrepareForSleep', 'org.freedesktop.login1.Manager', 'org.freedesktop.login1'
|
|
|
)
|
|
@@ -684,6 +777,8 @@ def main():
|
|
|
path="/org/freedesktop/UPower/devices/line_power_AC",
|
|
|
)
|
|
|
|
|
|
+ print('[I] Starting main loop.')
|
|
|
+
|
|
|
if args.monitor is not None:
|
|
|
monitor_thread = Thread(target=monitor, args=(exit_event, args.monitor))
|
|
|
monitor_thread.daemon = True
|