Skip to content

Commit

Permalink
Add a plugin for amdgpu tuning of the panel_power_savings attribute
Browse files Browse the repository at this point in the history
This attribute accepts a range from 0 through 4 where larger values
will also have larger panel power savings.

Using this has a trade off for color accuracy, and it is only applied
when the system is currently operating on battery.

The plugin uses upower to get a signal when battery changes so that
it will react instantly.

Intentionally the plugin will check what values are already programmed
to the sysfs file to avoid unnecessary writes.  Writing the sysfs file
will cause a modeset which isn't necessary if writing the same value twice.

The default values are applied to the profiles that are used in
power-profiles-daemon compatbility.  They also match the values used in
that software.

Signed-off-by: Mario Limonciello <[email protected]>
  • Loading branch information
superm1 committed Mar 18, 2024
1 parent ad314ee commit 6a7ff4a
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 0 deletions.
3 changes: 3 additions & 0 deletions profiles/accelerator-performance/tuned.conf
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,6 @@ sched_min_granularity_ns = 10000000
# and reduces their over-scheduling. Synchronous workloads will still
# have immediate wakeup/sleep latencies.
sched_wakeup_granularity_ns = 15000000

[amdgpu]
panel_power_savings=0
3 changes: 3 additions & 0 deletions profiles/balanced/tuned.conf
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ radeon_powersave=dpm-balanced, auto

[scsi_host]
alpm=medium_power

[amdgpu]
panel_power_savings=3
3 changes: 3 additions & 0 deletions profiles/latency-performance/tuned.conf
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ vm.dirty_background_ratio=3
# 100 tells the kernel to aggressively swap processes out of physical memory
# and move them to swap cache
vm.swappiness=10

[amdgpu]
panel_power_savings=0
3 changes: 3 additions & 0 deletions profiles/powersave/tuned.conf
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ kernel.nmi_watchdog=0

[script]
script=${i:PROFILE_DIR}/script.sh

[amdgpu]
panel_power_savings=4
3 changes: 3 additions & 0 deletions profiles/throughput-performance/tuned.conf
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,6 @@ type=sysctl
uname_regex=aarch64
cpuinfo_regex=${thunderx_cpuinfo_regex}
kernel.numa_balancing=0

[amdgpu]
panel_power_savings=0
120 changes: 120 additions & 0 deletions tuned/plugins/plugin_amdgpu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
from . import base
from .decorators import *
import errno
import tuned.logs
import dbus
from tuned.utils.commands import commands

log = tuned.logs.get()
cmd = commands()

UPOWER_DBUS_NAME = "org.freedesktop.UPower"
UPOWER_DBUS_PATH = "/org/freedesktop/UPower"
UPOWER_DBUS_INTERFACE = "org.freedesktop.UPower"


class AmdgpuPlugin(base.Plugin):
"""
`amdgpu`::
Configures panel power savings on laptops with amdgpu driven eDP panels.
This accepts a value range from 0 to 4, where 4 is the highest power savings
but will trade off color accuracy.
Settings will only be applied when the system is on battery.
"""

def __init__(self, *args, **kwargs):
super(AmdgpuPlugin, self).__init__(*args, **kwargs)
self.proxy = None

def upower_changed(self, interface, changed, invalidated):
properties = dbus.Interface(self.proxy, "org.freedesktop.DBus.Properties")
self._on_battery = bool(properties.Get(UPOWER_DBUS_INTERFACE, "OnBattery"))
log.debug(f"🔋: {self._on_battery}, 🎯: {self.target_value}")
for device in self._assigned_devices:
self.apply_target(device)

def setup_battery_signaling(self):
bus = dbus.SystemBus()
self.proxy = bus.get_object(UPOWER_DBUS_NAME, UPOWER_DBUS_PATH)
self.proxy.connect_to_signal("PropertiesChanged", self.upower_changed)
self.upower_changed(None, None, None)

def _init_devices(self):
self._assigned_devices = set()
self._free_devices = set()

for device in (
self._hardware_inventory.get_devices("drm")
.match_sys_name("card*eDP*")
.match_sys_name("amdgpu")
.match_sys_name("panel_power_savings")
):
self._free_devices.add(device.sys_name)
self._devices_supported = len(self._free_devices) > 0

def _get_device_objects(self, devices):
return [self._hardware_inventory.get_device("drm", x) for x in devices]

@classmethod
def _get_config_options(cls):
return {"panel_power_savings": None}

def _instance_init(self, instance):
instance._has_static_tuning = True
instance._has_dynamic_tuning = False

def _amdgpu_files(self, device):
return {
"panel_power_savings": f"/sys/class/drm/{device}/amdgpu/panel_power_savings"
}

def _instance_cleanup(self, instance):
pass

def apply_target(self, device, sim=False):
"""Apply the target value to the panel_power_savings file if it doesn't already have it"""

# if we don't have a proxy, we can't tell if we're on battery
if not self.proxy:
self.setup_battery_signaling()
return None

# determine value to use (only apply if on battery, otherwise set to 0)
if self._on_battery:
target = self.target_value
else:
target = 0

# make sure the value is different (avoids unnecessary kernel modeset)
current = int(self._get_panel_power_savings(device))
if current == target:
log.info(f"panel_power_savings already {target} [🔋: {self._on_battery}]")
return target

# flush it out
log.info(f"panel_power_savings -> {target} [🔋: {self._on_battery}]")
if sim or cmd.write_to_file(self._amdgpu_files(device)["panel_power_savings"], target):
return target
return None

@command_set("panel_power_savings", per_device=True)
def _set_panel_power_savings(self, value, device, sim, remove):
"""Set the panel_power_savings value"""
try:
value = int(value, 10)
except ValueError:
log.warn(f"Invalid value {value} for panel_power_savings")
return None
if value in range(0, 5):
self.target_value = value
return self.apply_target(device, sim)
log.warn(f"Invalid value {value} for panel_power_savings")
return None

@command_get("panel_power_savings")
def _get_panel_power_savings(self, device, ignore_missing=False):
"""Get the current panel_power_savings value"""
fname = self._amdgpu_files(device)["panel_power_savings"]
return cmd.read_file(fname, no_error=ignore_missing).strip()

0 comments on commit 6a7ff4a

Please sign in to comment.