diff --git a/profiles/accelerator-performance/tuned.conf b/profiles/accelerator-performance/tuned.conf index 696a1ddd5..a0f3da453 100644 --- a/profiles/accelerator-performance/tuned.conf +++ b/profiles/accelerator-performance/tuned.conf @@ -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 + +[video] +panel_power_savings=0 diff --git a/profiles/balanced/tuned.conf b/profiles/balanced/tuned.conf index 1dda2f32e..78f28d424 100644 --- a/profiles/balanced/tuned.conf +++ b/profiles/balanced/tuned.conf @@ -22,6 +22,7 @@ timeout=10 [video] radeon_powersave=dpm-balanced, auto +panel_power_savings=0 [disk] # Comma separated list of devices, all devices if commented out. diff --git a/profiles/latency-performance/tuned.conf b/profiles/latency-performance/tuned.conf index 1dec6905c..c78060237 100644 --- a/profiles/latency-performance/tuned.conf +++ b/profiles/latency-performance/tuned.conf @@ -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 + +[video] +panel_power_savings=0 diff --git a/profiles/powersave/tuned.conf b/profiles/powersave/tuned.conf index c593a800e..17dff1f7e 100644 --- a/profiles/powersave/tuned.conf +++ b/profiles/powersave/tuned.conf @@ -22,6 +22,7 @@ timeout=10 [video] radeon_powersave=dpm-battery, auto +panel_power_savings=3 [disk] # Comma separated list of devices, all devices if commented out. diff --git a/profiles/throughput-performance/tuned.conf b/profiles/throughput-performance/tuned.conf index e4e832fa6..ff80219f9 100644 --- a/profiles/throughput-performance/tuned.conf +++ b/profiles/throughput-performance/tuned.conf @@ -73,3 +73,6 @@ type=sysctl uname_regex=aarch64 cpuinfo_regex=${thunderx_cpuinfo_regex} kernel.numa_balancing=0 + +[video] +panel_power_savings=0 diff --git a/tuned/plugins/plugin_video.py b/tuned/plugins/plugin_video.py index a93d60991..4bd76ddd5 100644 --- a/tuned/plugins/plugin_video.py +++ b/tuned/plugins/plugin_video.py @@ -11,9 +11,10 @@ class VideoPlugin(base.Plugin): """ `video`:: - - Sets various powersave levels on video cards. Currently, only the - Radeon cards are supported. The powersave level can be specified + + Sets various power saving features on video cards. + Radeon cards are supported. + The powersave level can be specified by using the [option]`radeon_powersave` option. Supported values are: + -- @@ -40,17 +41,27 @@ class VideoPlugin(base.Plugin): radeon_powersave=high ---- ==== + + Mobile hardware with amdgpu driven eDP panels can be configured + with the [option]`panel_power_savings` option. + This accepts a value range from 0 to 4, where 4 is the highest power savings + but will trade off color accuracy. """ + def __init__(self, *args, **kwargs): + super(VideoPlugin, self).__init__(*args, **kwargs) + def _init_devices(self): self._devices_supported = True self._free_devices = set() self._assigned_devices = set() - # FIXME: this is a blind shot, needs testing - for device in self._hardware_inventory.get_devices("drm").match_sys_name("card*").match_property("DEVTYPE", "drm_minor"): - self._free_devices.add(device.sys_name) - + # Add any radeon and amdgpu hardware with /any/ supported attributes present + for device in self._hardware_inventory.get_devices("drm").match_sys_name("card*-*"): + attrs = self._files(device.sys_name) + for attr in attrs: + if os.path.exists(attrs[attr]): + self._free_devices.add(device.sys_name) self._cmd = commands() def _get_device_objects(self, devices): @@ -60,6 +71,7 @@ def _get_device_objects(self, devices): def _get_config_options(self): return { "radeon_powersave" : None, + "panel_power_savings": None, } def _instance_init(self, instance): @@ -69,20 +81,42 @@ def _instance_init(self, instance): def _instance_cleanup(self, instance): pass - def _radeon_powersave_files(self, device): + def _files(self, device): return { "method" : "/sys/class/drm/%s/device/power_method" % device, "profile": "/sys/class/drm/%s/device/power_profile" % device, - "dpm_state": "/sys/class/drm/%s/device/power_dpm_state" % device + "dpm_state": "/sys/class/drm/%s/device/power_dpm_state" % device, + "panel_power_savings": "/sys/class/drm/%s/amdgpu/panel_power_savings" % device, } + def apply_panel_power_saving_target(self, device, target, sim=False): + """Apply the target value to the panel_power_savings file if it doesn't already have it""" + + # if we don't have the file, we might be radeon not amdgpu + if not os.path.exists(self._files(device)["panel_power_savings"]): + return None + + # make sure the value is different (avoids unnecessary kernel modeset) + current = int(self._get_panel_power_savings(device)) + if current == target: + log.info( + "panel_power_savings for %s already %s" % (device, target) + ) + return target + + # flush it out + log.info("%s panel_power_savings -> %s" % (device, target)) + if sim or self._cmd.write_to_file(self._files(device)["panel_power_savings"], target): + return target + return None + @command_set("radeon_powersave", per_device=True) def _set_radeon_powersave(self, value, device, sim, remove): - sys_files = self._radeon_powersave_files(device) + sys_files = self._files(device) va = str(re.sub(r"(\s*:\s*)|(\s+)|(\s*;\s*)|(\s*,\s*)", " ", value)).split() if not os.path.exists(sys_files["method"]): if not sim: - log.warn("radeon_powersave is not supported on '%s'" % device) + log.debug("radeon_powersave is not supported on '%s'" % device) return None for v in va: if v in ["default", "auto", "low", "mid", "high"]: @@ -114,7 +148,10 @@ def _set_radeon_powersave(self, value, device, sim, remove): @command_get("radeon_powersave") def _get_radeon_powersave(self, device, ignore_missing = False): - sys_files = self._radeon_powersave_files(device) + sys_files = self._files(device) + if not os.path.exists(sys_files["method"]): + log.debug("radeon_powersave is not supported on '%s'" % device) + return None method = self._cmd.read_file(sys_files["method"], no_error=ignore_missing).strip() if method == "profile": return self._cmd.read_file(sys_files["profile"]).strip() @@ -124,3 +161,26 @@ def _get_radeon_powersave(self, device, ignore_missing = False): return "dpm-" + self._cmd.read_file(sys_files["dpm_state"]).strip() else: 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("Invalid value %s for panel_power_savings" % value) + return None + if value in range(0, 5): + return self.apply_panel_power_saving_target(device, value, sim) + else: + log.warn("Invalid value %s for panel_power_savings" % value) + return None + + @command_get("panel_power_savings") + def _get_panel_power_savings(self, device, ignore_missing=False): + """Get the current panel_power_savings value""" + if not os.path.exists(self._files(device)["panel_power_savings"]): + log.debug("panel_power_savings is not supported on '%s'" % device) + return None + fname = self._files(device)["panel_power_savings"] + return self._cmd.read_file(fname, no_error=ignore_missing).strip()