Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions profiles/cachyos-desktop/tuned.conf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ transparent_hugepages=madvise
# No-op on other hardware.
mode=frequency

# [scx]
# Activate a sched-ext scheduler via scx_loader for improved desktop
# responsiveness. Requires scx_loader (org.scx.Loader) to be installed
# and running.
# scheduler=scx_bpfland
# mode=auto

[cpu]
# Mirror PPD balanced behaviour: allow the CPU to idle down to the lowest
# non-linear frequency (best performance-per-watt point) rather than the
# absolute minimum, improving responsiveness under light load.
scaling_min_freq=amd_pstate_lowest_nonlinear_freq

[sysctl]
# Improve desktop responsiveness by grouping tasks in the same session
kernel.sched_autogroup_enabled=1
Expand Down
7 changes: 7 additions & 0 deletions profiles/cachyos-gaming/tuned.conf
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ energy_perf_bias=performance
energy_performance_preference=performance
min_perf_pct=100
boost=1
scaling_min_freq=amd_pstate_lowest_nonlinear_freq

[acpi]
platform_profile=performance
Expand Down Expand Up @@ -57,3 +58,9 @@ panel_power_savings=0
# On dual-CCD 3D V-Cache CPUs (e.g. 7950X3D, 9950X3D) prefer the X3D CCD
# to maximise the available L3 cache for games. No-op on other hardware.
mode=cache

# [scx]
# Activate a sched-ext scheduler via scx_loader for low-latency gaming.
# Requires scx_loader (org.scx.Loader) to be installed and running.
# scheduler=scx_lavd
# mode=gaming
11 changes: 11 additions & 0 deletions profiles/cachyos-laptop/tuned.conf
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ include=balanced-battery
# hardware.
mode=frequency

# [scx]
# Activate a sched-ext scheduler via scx_loader in power-saving mode.
# Requires scx_loader (org.scx.Loader) to be installed and running.
# scheduler=scx_bpfland
# mode=powersave

[cpu]
# Mirror PPD balanced behaviour on battery: keep the CPU floor at the lowest
# non-linear frequency for good responsiveness without wasting power.
scaling_min_freq=amd_pstate_lowest_nonlinear_freq

[sysctl]
# CachyOS uses zram+zstd by default; higher swappiness works better
# with compressed in-memory swap than with slow disk-based swap
Expand Down
11 changes: 11 additions & 0 deletions profiles/cachyos-powersave/tuned.conf
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ include=powersave
# No-op on other hardware.
mode=frequency

# [scx]
# Activate a sched-ext scheduler via scx_loader in power-saving mode.
# Requires scx_loader (org.scx.Loader) to be installed and running.
# scheduler=scx_bpfland
# mode=powersave

[cpu]
# Mirror PPD power-saver behaviour: allow the CPU to reach the absolute
# minimum frequency for maximum power savings.
scaling_min_freq=cpuinfo_min_freq

[sysctl]
# CachyOS uses zram+zstd by default; higher swappiness works better
# with compressed in-memory swap than with slow disk-based swap
Expand Down
48 changes: 48 additions & 0 deletions tuned/plugins/plugin_cpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ def _get_config_options(self):
"pm_qos_resume_latency_us": None,
"energy_performance_preference" : None,
"boost": None,
"scaling_min_freq" : None,
}

def _check_arch(self):
Expand Down Expand Up @@ -863,3 +864,50 @@ def _get_energy_performance_preference(self, device, instance, ignore_missing=Fa
else:
log.debug("energy_performance_available_preferences file missing, which can happen if the system is booted without a P-state driver.")
return None

@command_set("scaling_min_freq", per_device=True)
def _set_scaling_min_freq(self, scaling_min_freq, device, instance, sim, remove):
if not self._is_cpu_online(device):
log.debug("%s is not online, skipping" % device)
return None
cpu_id = device.lstrip("cpu")
policy_dir = "/sys/devices/system/cpu/cpufreq/policy%s" % cpu_id
scaling_min_freq_path = os.path.join(policy_dir, "scaling_min_freq")

if not os.path.exists(scaling_min_freq_path):
log.debug("scaling_min_freq not available for cpu '%s'" % device)
return None

# If the value is a sysfs sibling filename (e.g. "cpuinfo_min_freq" or
# "amd_pstate_lowest_nonlinear_freq"), read the numeric value from that
# file and write it to scaling_min_freq — mirroring PPD behaviour.
val = scaling_min_freq
if not scaling_min_freq.lstrip('-').isdigit():
source_path = os.path.join(policy_dir, scaling_min_freq)
if os.path.exists(source_path):
val = self._cmd.read_file(source_path).strip()
if not val:
log.error("Failed to read '%s' for cpu '%s'" % (source_path, device))
return None
else:
log.warning("scaling_min_freq source '%s' not found for cpu '%s', skipping" % (scaling_min_freq, device))
return None

if not sim:
self._cmd.write_to_file(scaling_min_freq_path, val,
no_error=[errno.ENOENT] if remove else False, ignore_same=True)
log.info("Setting scaling_min_freq to '%s' for cpu '%s'" % (val, device))
return val

@command_get("scaling_min_freq")
def _get_scaling_min_freq(self, device, instance, ignore_missing=False):
if not self._is_cpu_online(device):
log.debug("%s is not online, skipping" % device)
return None
cpu_id = device.lstrip("cpu")
path = "/sys/devices/system/cpu/cpufreq/policy%s/scaling_min_freq" % cpu_id
if os.path.exists(path):
return self._cmd.read_file(path).strip()
else:
log.debug("scaling_min_freq not available for cpu '%s'" % device)
return None
210 changes: 210 additions & 0 deletions tuned/plugins/plugin_scx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
from . import base
from .decorators import *
import tuned.logs

log = tuned.logs.get()

DBUS_SERVICE = "org.scx.Loader"
DBUS_PATH = "/org/scx/Loader"
DBUS_IFACE = "org.scx.Loader"
DBUS_PROPS_IFACE = "org.freedesktop.DBus.Properties"

MODE_MAP = {
"auto": 0,
"gaming": 1,
"powersave": 2,
"lowlatency": 3,
"server": 4,
}

MODE_REVERSE = {v: k for k, v in MODE_MAP.items()}


def _get_dbus_proxy():
"""Connect to the scx_loader D-Bus service. Returns (proxy, iface) or (None, None)."""
try:
import dbus
bus = dbus.SystemBus()
proxy = bus.get_object(DBUS_SERVICE, DBUS_PATH)
iface = dbus.Interface(proxy, DBUS_IFACE)
return proxy, iface
except Exception as e:
log.info("scx: scx_loader D-Bus service not available: %s" % e)
return None, None


def _get_property(proxy, prop_name):
"""Read a D-Bus property from the scx_loader service."""
try:
import dbus
props = dbus.Interface(proxy, DBUS_PROPS_IFACE)
return props.Get(DBUS_IFACE, prop_name)
except Exception as e:
log.warning("scx: failed to read property '%s': %s" % (prop_name, e))
return None


class ScxPlugin(base.Plugin):
"""
Controls sched-ext schedulers via the scx_loader D-Bus service
(org.scx.Loader).

CachyOS ships sched-ext schedulers managed by scx_loader. This
plug-in allows tuned profiles to select which scheduler runs and in
what mode. On systems without scx_loader the plug-in does nothing
and logs an informational message.

`scheduler`:::
Name of the sched-ext scheduler to activate (e.g. `scx_bpfland`,
`scx_lavd`). Set to `none` or leave empty to stop any running
scheduler.

`mode`:::
Scheduler mode. Accepted values: `auto`, `gaming`, `lowlatency`,
`powersave`, `server`.

.Gaming profile — low-latency scheduler
====
----
[scx]
scheduler=scx_lavd
mode=gaming
----
====

.Desktop profile — balanced scheduler
====
----
[scx]
scheduler=scx_bpfland
mode=auto
----
====
"""

@classmethod
def _get_config_options(cls):
return {
"scheduler": None,
"mode": None,
}

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

def _instance_cleanup(self, instance):
pass

def _validate_scheduler(self, proxy, name):
"""Check if the scheduler name is in supported_schedulers."""
supported = _get_property(proxy, "SupportedSchedulers")
if supported is None:
return True
supported = [str(s) for s in supported]
if name not in supported:
log.warning("scx: scheduler '%s' not in supported list: %s"
% (name, ", ".join(supported)))
return False
return True

@command_set("scheduler")
def _set_scheduler(self, value, instance, sim, remove):
if value is None:
return None

value = str(value).strip()

proxy, iface = _get_dbus_proxy()
if proxy is None:
return None

# Parse the value. Two formats are accepted:
# - Plain scheduler name from profile config (e.g. "scx_bpfland")
# Mode is read from instance.options["mode"].
# - Encoded "name:mode_num" saved by _get_scheduler and passed
# back by the framework during cleanup/restore.
# - "none" sentinel meaning no scheduler was running.
if value.lower() in ("none", ""):
if not sim:
log.info("scx: stopping scheduler")
try:
iface.StopScheduler()
except Exception as e:
log.warning("scx: failed to stop scheduler: %s" % e)
return "none"

if ":" in value:
# Encoded format from framework restore: "name:mode_num"
parts = value.split(":", 1)
sched_name = parts[0]
try:
mode_num = int(parts[1])
except (ValueError, IndexError):
mode_num = 0
else:
# Plain name from profile config
sched_name = value
mode_opt = instance.options.get("mode", "auto")
if mode_opt is None:
mode_opt = "auto"
mode_opt = self._variables.expand(str(mode_opt))
mode_str = mode_opt.strip().lower()
mode_num = MODE_MAP.get(mode_str)
if mode_num is None:
log.warning("scx: invalid mode '%s', expected one of: %s"
% (mode_str, ", ".join(sorted(MODE_MAP.keys()))))
return None

if not self._validate_scheduler(proxy, sched_name):
return None

mode_str = MODE_REVERSE.get(mode_num, str(mode_num))
if not sim:
log.info("scx: switching to scheduler '%s' in mode '%s'" % (sched_name, mode_str))
try:
iface.SwitchScheduler(sched_name, mode_num)
except Exception as e:
log.warning("scx: failed to switch scheduler: %s" % e)
return None
return "%s:%d" % (sched_name, mode_num)

@command_get("scheduler")
def _get_scheduler(self, instance):
proxy, _ = _get_dbus_proxy()
if proxy is None:
return None
current = _get_property(proxy, "CurrentScheduler")
Comment on lines +172 to +177
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On systems without scx_loader, _get_scheduler/_get_mode return None, which causes tuned-adm verify (ignore_missing=false) to fail for profiles that set [scx] options, even though the plugin is intended to be a no-op when the service is absent. Consider overriding _instance_verify_static to treat missing scx_loader as ignore_missing=True (or short-circuit verification to True) when the D-Bus proxy cannot be obtained, similar to plugins that hardcode ignore_missing for optional facilities.

Copilot uses AI. Check for mistakes.
if current and str(current):
mode_val = _get_property(proxy, "SchedulerMode")
mode_num = int(mode_val) if mode_val is not None else 0
return "%s:%d" % (str(current), mode_num)
return "none"

@command_set("mode")
def _set_mode(self, value, instance, sim, remove):
# Mode is applied together with the scheduler in _set_scheduler.
# This handler validates the value and returns None when
# scx_loader is absent so that verification is skipped.
if value is None:
return None
proxy, _ = _get_dbus_proxy()
if proxy is None:
return None
value = str(value).strip().lower()
if value not in MODE_MAP:
if not sim:
log.warning("scx: invalid mode '%s', expected one of: %s"
% (value, ", ".join(sorted(MODE_MAP.keys()))))
return None
return value

Comment on lines +184 to +201
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mode is registered as an independent @command_set/@command_get option, but _set_mode explicitly does not apply any tuning. This means profiles that set mode (with or without scheduler) will have the framework save/verify/restore a value that the plugin never enforces, leading to confusing behavior and potential verification failures. Either make mode a non-command auxiliary option (validated/used by _set_scheduler only) or implement mode changes by applying them through scx_loader (e.g., switching the currently running scheduler with the requested mode).

Copilot uses AI. Check for mistakes.
@command_get("mode")
def _get_mode(self, instance):
proxy, _ = _get_dbus_proxy()
if proxy is None:
return None
mode_val = _get_property(proxy, "SchedulerMode")
if mode_val is not None:
return MODE_REVERSE.get(int(mode_val), str(mode_val))
return None