Add IccMax override feature
This commit is contained in:
109
lenovo_fix.py
109
lenovo_fix.py
@@ -24,8 +24,10 @@ from time import time
|
|||||||
|
|
||||||
DEFAULT_SYSFS_POWER_PATH = '/sys/class/power_supply/AC*/online'
|
DEFAULT_SYSFS_POWER_PATH = '/sys/class/power_supply/AC*/online'
|
||||||
VOLTAGE_PLANES = {'CORE': 0, 'GPU': 1, 'CACHE': 2, 'UNCORE': 3, 'ANALOGIO': 4}
|
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]
|
TRIP_TEMP_RANGE = [40, 97]
|
||||||
UNDERVOLT_KEYS = ('UNDERVOLT', 'UNDERVOLT.AC', 'UNDERVOLT.BATTERY')
|
UNDERVOLT_KEYS = ('UNDERVOLT', 'UNDERVOLT.AC', 'UNDERVOLT.BATTERY')
|
||||||
|
ICCMAX_KEYS = ('ICCMAX', 'ICCMAX.AC', 'ICCMAX.BATTERY')
|
||||||
power = {'source': None, 'method': 'polling'}
|
power = {'source': None, 'method': 'polling'}
|
||||||
|
|
||||||
platform_info_bits = {
|
platform_info_bits = {
|
||||||
@@ -277,6 +279,17 @@ def calc_undervolt_mv(msr_value):
|
|||||||
return int(round(offset / 1.024))
|
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):
|
def undervolt(config):
|
||||||
for plane in VOLTAGE_PLANES:
|
for plane in VOLTAGE_PLANES:
|
||||||
write_offset_mv = config.getfloat(
|
write_offset_mv = config.getfloat(
|
||||||
@@ -286,8 +299,7 @@ def undervolt(config):
|
|||||||
writemsr(0x150, write_value)
|
writemsr(0x150, write_value)
|
||||||
if args.debug:
|
if args.debug:
|
||||||
write_value &= 0xFFFFFFFF
|
write_value &= 0xFFFFFFFF
|
||||||
writemsr(0x150, 0x8000001000000000 | (VOLTAGE_PLANES[plane] << 40))
|
read_value = get_undervolt(plane)[plane]
|
||||||
read_value = readmsr(0x150, flatten=True)
|
|
||||||
read_offset_mv = calc_undervolt_mv(read_value)
|
read_offset_mv = calc_undervolt_mv(read_value)
|
||||||
match = OK if write_value == read_value else ERR
|
match = OK if write_value == read_value else ERR
|
||||||
print(
|
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():
|
def load_config():
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read(args.config)
|
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 key in UNDERVOLT_KEYS:
|
||||||
for plane in VOLTAGE_PLANES:
|
for plane in VOLTAGE_PLANES:
|
||||||
if key in config:
|
if key in config:
|
||||||
@@ -345,6 +407,24 @@ def load_config():
|
|||||||
value = config.getfloat(key, plane, fallback=0.0)
|
value = config.getfloat(key, plane, fallback=0.0)
|
||||||
config.set(key, plane, str(value))
|
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
|
return config
|
||||||
|
|
||||||
|
|
||||||
@@ -594,7 +674,15 @@ def monitor(exit_event, wait):
|
|||||||
'DRAM': (readmsr(MSR_DRAM_ENERGY_STATUS, cpu=0) * rapl_power_unit, time()),
|
'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():
|
while not exit_event.is_set():
|
||||||
value = readmsr(IA32_THERM_STATUS, from_bit=0, to_bit=15, cpu=0)
|
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}
|
offsets = {'Thermal': 0, 'Power': 10, 'Current': 12, 'Cross-domain (e.g. GPU)': 14}
|
||||||
@@ -638,6 +726,7 @@ def main():
|
|||||||
check_kernel()
|
check_kernel()
|
||||||
check_cpu()
|
check_cpu()
|
||||||
|
|
||||||
|
print('[I] Loading config file.')
|
||||||
config = load_config()
|
config = load_config()
|
||||||
power['source'] = 'BATTERY' if is_on_battery(config) else 'AC'
|
power['source'] = 'BATTERY' if is_on_battery(config) else 'AC'
|
||||||
|
|
||||||
@@ -656,11 +745,13 @@ def main():
|
|||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
undervolt(config)
|
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):
|
def handle_sleep_callback(sleeping):
|
||||||
if not sleeping:
|
if not sleeping:
|
||||||
undervolt(config)
|
undervolt(config)
|
||||||
|
set_icc_max(config)
|
||||||
|
|
||||||
def handle_ac_callback(*args):
|
def handle_ac_callback(*args):
|
||||||
try:
|
try:
|
||||||
@@ -672,8 +763,10 @@ def main():
|
|||||||
DBusGMainLoop(set_as_default=True)
|
DBusGMainLoop(set_as_default=True)
|
||||||
bus = dbus.SystemBus()
|
bus = dbus.SystemBus()
|
||||||
|
|
||||||
# add dbus receiver only if undervolt is enabled in config
|
# 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):
|
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(
|
bus.add_signal_receiver(
|
||||||
handle_sleep_callback, 'PrepareForSleep', 'org.freedesktop.login1.Manager', 'org.freedesktop.login1'
|
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",
|
path="/org/freedesktop/UPower/devices/line_power_AC",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
print('[I] Starting main loop.')
|
||||||
|
|
||||||
if args.monitor is not None:
|
if args.monitor is not None:
|
||||||
monitor_thread = Thread(target=monitor, args=(exit_event, args.monitor))
|
monitor_thread = Thread(target=monitor, args=(exit_event, args.monitor))
|
||||||
monitor_thread.daemon = True
|
monitor_thread.daemon = True
|
||||||
|
|||||||
Reference in New Issue
Block a user