mirror of
https://gitlab.steamos.cloud/holo/steamos-manager.git
synced 2025-07-07 07:00:27 -04:00
Add CPU Scheduler Governors.
Add a new enumeration for cpu scheduler governors. Adds a property to get the available governors as a map of value to strings similar to how we expose GPU Power Profiles. Adds another property to get and set the current governor which is applied to all cpus when set.
This commit is contained in:
parent
71827ac7ee
commit
7f9d25074e
4 changed files with 223 additions and 4 deletions
|
@ -306,6 +306,27 @@
|
||||||
Version available: 9
|
Version available: 9
|
||||||
-->
|
-->
|
||||||
<method name="ReloadConfig"/>
|
<method name="ReloadConfig"/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
CpuGovernors:
|
||||||
|
|
||||||
|
Enumerate the supported cpu governors on the system.
|
||||||
|
|
||||||
|
A list of supported governors (a dictionary of values to names)
|
||||||
|
Version available: 9
|
||||||
|
-->
|
||||||
|
<property name="CpuGovernors" type="a{us}" access="read"/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
CpuGovernor:
|
||||||
|
|
||||||
|
The current governor used for the system's CPUs.
|
||||||
|
|
||||||
|
A value from the CpuGovernors list
|
||||||
|
|
||||||
|
Version available: 9
|
||||||
|
-->
|
||||||
|
<property name="CpuGovernor" type="u" access="readwrite"/>
|
||||||
|
|
||||||
</interface>
|
</interface>
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,8 @@ use crate::daemon::DaemonCommand;
|
||||||
use crate::error::{to_zbus_error, to_zbus_fdo_error};
|
use crate::error::{to_zbus_error, to_zbus_fdo_error};
|
||||||
use crate::hardware::{variant, FanControl, FanControlState, HardwareVariant};
|
use crate::hardware::{variant, FanControl, FanControlState, HardwareVariant};
|
||||||
use crate::power::{
|
use crate::power::{
|
||||||
set_gpu_clocks, set_gpu_performance_level, set_gpu_power_profile, set_tdp_limit,
|
set_cpu_governor, set_gpu_clocks, set_gpu_performance_level, set_gpu_power_profile,
|
||||||
GPUPerformanceLevel, GPUPowerProfile,
|
set_tdp_limit, CPUGovernor, GPUPerformanceLevel, GPUPowerProfile,
|
||||||
};
|
};
|
||||||
use crate::process::{run_script, script_output, ProcessManager};
|
use crate::process::{run_script, script_output, ProcessManager};
|
||||||
use crate::wifi::{
|
use crate::wifi::{
|
||||||
|
@ -191,6 +191,14 @@ impl SteamOSManager {
|
||||||
.map_err(to_zbus_fdo_error)
|
.map_err(to_zbus_fdo_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn set_cpu_governor(&self, governor: u32) -> fdo::Result<()> {
|
||||||
|
let governor = CPUGovernor::try_from(governor).map_err(to_zbus_fdo_error)?;
|
||||||
|
set_cpu_governor(governor)
|
||||||
|
.await
|
||||||
|
.inspect_err(|message| error!("Error setting CPU governor: {message}"))
|
||||||
|
.map_err(to_zbus_fdo_error)
|
||||||
|
}
|
||||||
|
|
||||||
async fn set_gpu_performance_level(&self, level: u32) -> fdo::Result<()> {
|
async fn set_gpu_performance_level(&self, level: u32) -> fdo::Result<()> {
|
||||||
let level = match GPUPerformanceLevel::try_from(level) {
|
let level = match GPUPerformanceLevel::try_from(level) {
|
||||||
Ok(level) => level,
|
Ok(level) => level,
|
||||||
|
|
|
@ -20,8 +20,8 @@ use crate::daemon::DaemonCommand;
|
||||||
use crate::error::{to_zbus_error, to_zbus_fdo_error, zbus_to_zbus_fdo};
|
use crate::error::{to_zbus_error, to_zbus_fdo_error, zbus_to_zbus_fdo};
|
||||||
use crate::hardware::check_support;
|
use crate::hardware::check_support;
|
||||||
use crate::power::{
|
use crate::power::{
|
||||||
get_gpu_clocks, get_gpu_performance_level, get_gpu_power_profile, get_gpu_power_profiles,
|
get_cpu_governor, get_cpu_governors, get_gpu_clocks, get_gpu_performance_level,
|
||||||
get_tdp_limit,
|
get_gpu_power_profile, get_gpu_power_profiles, get_tdp_limit,
|
||||||
};
|
};
|
||||||
use crate::wifi::{get_wifi_backend, get_wifi_power_management_state};
|
use crate::wifi::{get_wifi_backend, get_wifi_power_management_state};
|
||||||
use crate::API_VERSION;
|
use crate::API_VERSION;
|
||||||
|
@ -191,6 +191,21 @@ impl SteamOSManager {
|
||||||
method!(self, "FormatDevice", device, label, validate)
|
method!(self, "FormatDevice", device, label, validate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[zbus(property(emits_changed_signal = "false"))]
|
||||||
|
async fn cpu_governors(&self) -> fdo::Result<HashMap<u32, String>> {
|
||||||
|
get_cpu_governors().await.map_err(to_zbus_fdo_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property(emits_changed_signal = "false"))]
|
||||||
|
async fn cpu_governor(&self) -> fdo::Result<u32> {
|
||||||
|
get_cpu_governor().await.map_err(to_zbus_fdo_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property)]
|
||||||
|
async fn set_cpu_governor(&self, governor: u32) -> zbus::Result<()> {
|
||||||
|
setter!(self, "SetCpuGovernor", &(governor))
|
||||||
|
}
|
||||||
|
|
||||||
#[zbus(property(emits_changed_signal = "false"))]
|
#[zbus(property(emits_changed_signal = "false"))]
|
||||||
async fn gpu_power_profiles(&self) -> fdo::Result<HashMap<u32, String>> {
|
async fn gpu_power_profiles(&self) -> fdo::Result<HashMap<u32, String>> {
|
||||||
get_gpu_power_profiles().await.map_err(to_zbus_fdo_error)
|
get_gpu_power_profiles().await.map_err(to_zbus_fdo_error)
|
||||||
|
|
175
src/power.rs
175
src/power.rs
|
@ -21,10 +21,16 @@ const GPU_HWMON_PREFIX: &str = "/sys/class/hwmon";
|
||||||
const GPU_HWMON_NAME: &str = "amdgpu";
|
const GPU_HWMON_NAME: &str = "amdgpu";
|
||||||
const GPU_DRM_PREFIX: &str = "/sys/class/drm";
|
const GPU_DRM_PREFIX: &str = "/sys/class/drm";
|
||||||
const GPU_VENDOR: &str = "0x1002";
|
const GPU_VENDOR: &str = "0x1002";
|
||||||
|
const CPU_GOVERNOR_PREFIX: &str = "/sys/devices/system/cpu/cpufreq";
|
||||||
|
|
||||||
|
const CPU0_NAME: &str = "policy0";
|
||||||
|
const CPU_POLICY_NAME: &str = "policy";
|
||||||
|
|
||||||
const GPU_POWER_PROFILE_SUFFIX: &str = "device/pp_power_profile_mode";
|
const GPU_POWER_PROFILE_SUFFIX: &str = "device/pp_power_profile_mode";
|
||||||
const GPU_PERFORMANCE_LEVEL_SUFFIX: &str = "device/power_dpm_force_performance_level";
|
const GPU_PERFORMANCE_LEVEL_SUFFIX: &str = "device/power_dpm_force_performance_level";
|
||||||
const GPU_CLOCKS_SUFFIX: &str = "device/pp_od_clk_voltage";
|
const GPU_CLOCKS_SUFFIX: &str = "device/pp_od_clk_voltage";
|
||||||
|
const CPU_GOVERNOR_SUFFIX: &str = "scaling_governor";
|
||||||
|
const CPU_GOVERNORS_SUFFIX: &str = "scaling_available_governors";
|
||||||
|
|
||||||
const TDP_LIMIT1: &str = "power1_cap";
|
const TDP_LIMIT1: &str = "power1_cap";
|
||||||
const TDP_LIMIT2: &str = "power2_cap";
|
const TDP_LIMIT2: &str = "power2_cap";
|
||||||
|
@ -143,6 +149,60 @@ impl fmt::Display for GPUPerformanceLevel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Hash, Eq, PartialEq, Debug, Copy, Clone)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum CPUGovernor {
|
||||||
|
Conservative,
|
||||||
|
OnDemand,
|
||||||
|
UserSpace,
|
||||||
|
PowerSave,
|
||||||
|
Performance,
|
||||||
|
SchedUtil,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for CPUGovernor {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(v: u32) -> Result<Self, Self::Error> {
|
||||||
|
match v {
|
||||||
|
x if x == CPUGovernor::Conservative as u32 => Ok(CPUGovernor::Conservative),
|
||||||
|
x if x == CPUGovernor::OnDemand as u32 => Ok(CPUGovernor::OnDemand),
|
||||||
|
x if x == CPUGovernor::UserSpace as u32 => Ok(CPUGovernor::UserSpace),
|
||||||
|
x if x == CPUGovernor::PowerSave as u32 => Ok(CPUGovernor::PowerSave),
|
||||||
|
x if x == CPUGovernor::Performance as u32 => Ok(CPUGovernor::Performance),
|
||||||
|
x if x == CPUGovernor::SchedUtil as u32 => Ok(CPUGovernor::SchedUtil),
|
||||||
|
_ => Err("No enum match for value {v}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for CPUGovernor {
|
||||||
|
type Err = Error;
|
||||||
|
fn from_str(input: &str) -> Result<CPUGovernor, Self::Err> {
|
||||||
|
Ok(match input {
|
||||||
|
"conservative" => CPUGovernor::Conservative,
|
||||||
|
"ondemand" => CPUGovernor::OnDemand,
|
||||||
|
"userspace" => CPUGovernor::UserSpace,
|
||||||
|
"powersave" => CPUGovernor::PowerSave,
|
||||||
|
"performance" => CPUGovernor::Performance,
|
||||||
|
"schedutil" => CPUGovernor::SchedUtil,
|
||||||
|
v => bail!("No enum match for value {v}"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for CPUGovernor {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
CPUGovernor::Conservative => write!(f, "conservative"),
|
||||||
|
CPUGovernor::OnDemand => write!(f, "ondemand"),
|
||||||
|
CPUGovernor::UserSpace => write!(f, "userspace"),
|
||||||
|
CPUGovernor::PowerSave => write!(f, "powersave"),
|
||||||
|
CPUGovernor::Performance => write!(f, "performance"),
|
||||||
|
CPUGovernor::SchedUtil => write!(f, "schedutil"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn read_gpu_sysfs_contents() -> Result<String> {
|
async fn read_gpu_sysfs_contents() -> Result<String> {
|
||||||
// check which profile is current and return if possible
|
// check which profile is current and return if possible
|
||||||
let base = find_gpu_prefix().await?;
|
let base = find_gpu_prefix().await?;
|
||||||
|
@ -151,6 +211,64 @@ async fn read_gpu_sysfs_contents() -> Result<String> {
|
||||||
.map_err(|message| anyhow!("Error opening sysfs file for reading {message}"))
|
.map_err(|message| anyhow!("Error opening sysfs file for reading {message}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn read_cpu_governor_sysfs_available_contents() -> Result<String> {
|
||||||
|
let base = path(CPU_GOVERNOR_PREFIX);
|
||||||
|
fs::read_to_string(base.join(CPU0_NAME).join(CPU_GOVERNORS_SUFFIX))
|
||||||
|
.await
|
||||||
|
.map_err(|message| anyhow!("Error opening sysfs file for reading {message}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_cpu_governor_sysfs_contents() -> Result<String> {
|
||||||
|
// Read contents of policy0 path
|
||||||
|
let base = path(CPU_GOVERNOR_PREFIX);
|
||||||
|
let full_path = base.join(CPU0_NAME).join(CPU_GOVERNOR_SUFFIX);
|
||||||
|
fs::read_to_string(full_path)
|
||||||
|
.await
|
||||||
|
.map_err(|message| anyhow!("Error opening sysfs file for reading {message}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write_cpu_governor_sysfs_contents(contents: String) -> Result<()> {
|
||||||
|
// Iterate over all cpuX paths
|
||||||
|
let mut dir = fs::read_dir(path(CPU_GOVERNOR_PREFIX)).await?;
|
||||||
|
let mut wrote_stuff = false;
|
||||||
|
loop {
|
||||||
|
let base = match dir.next_entry().await? {
|
||||||
|
Some(entry) => {
|
||||||
|
let file_name = entry
|
||||||
|
.file_name()
|
||||||
|
.into_string()
|
||||||
|
.map_err(|_| anyhow!("Unable to convert path to string"))?;
|
||||||
|
if file_name.starts_with(CPU_POLICY_NAME) {
|
||||||
|
entry.path()
|
||||||
|
} else {
|
||||||
|
// Not a policy path, so move on
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if wrote_stuff {
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
bail!("No data written, unable to find any policyX sysfs paths")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let file_name = base.join(CPU_GOVERNOR_SUFFIX);
|
||||||
|
println!(
|
||||||
|
"Trying to write to file at path: {:?}",
|
||||||
|
file_name.as_os_str()
|
||||||
|
);
|
||||||
|
let mut myfile = File::create(file_name)
|
||||||
|
.await
|
||||||
|
.inspect_err(|message| error!("Error opening sysfs file for writing: {message}"))?;
|
||||||
|
|
||||||
|
// Write contents to each one
|
||||||
|
wrote_stuff = true;
|
||||||
|
myfile.write_all(contents.as_bytes()).await?;
|
||||||
|
myfile.sync_data().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn get_gpu_power_profile() -> Result<GPUPowerProfile> {
|
pub(crate) async fn get_gpu_power_profile() -> Result<GPUPowerProfile> {
|
||||||
// check which profile is current and return if possible
|
// check which profile is current and return if possible
|
||||||
let contents = read_gpu_sysfs_contents().await?;
|
let contents = read_gpu_sysfs_contents().await?;
|
||||||
|
@ -242,6 +360,43 @@ pub(crate) async fn set_gpu_performance_level(level: GPUPerformanceLevel) -> Res
|
||||||
.inspect_err(|message| error!("Error writing to sysfs file: {message}"))
|
.inspect_err(|message| error!("Error writing to sysfs file: {message}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn get_cpu_governors() -> Result<HashMap<u32, String>> {
|
||||||
|
let contents = read_cpu_governor_sysfs_available_contents().await?;
|
||||||
|
// Get the list of supported governors from cpu0
|
||||||
|
let mut result = HashMap::new();
|
||||||
|
|
||||||
|
let words = contents.split_whitespace();
|
||||||
|
for word in words {
|
||||||
|
match CPUGovernor::from_str(word) {
|
||||||
|
Ok(v) => {
|
||||||
|
result.insert(v as u32, word.to_string());
|
||||||
|
}
|
||||||
|
Err(message) => bail!("Error parsing governor {message}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn get_cpu_governor() -> Result<u32> {
|
||||||
|
// get the current governor from cpu0 (assume all others are the same)
|
||||||
|
let contents = read_cpu_governor_sysfs_contents().await?;
|
||||||
|
|
||||||
|
let governor = match CPUGovernor::from_str(contents.trim()) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => bail!("Error converting CPU governor sysfs file contents to enumeration"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(governor as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn set_cpu_governor(governor: CPUGovernor) -> Result<()> {
|
||||||
|
// Set the given governor on all cpus
|
||||||
|
let name = governor.to_string();
|
||||||
|
write_cpu_governor_sysfs_contents(name).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn set_gpu_clocks(clocks: u32) -> Result<()> {
|
pub(crate) async fn set_gpu_clocks(clocks: u32) -> Result<()> {
|
||||||
// Set GPU clocks to given value valid between 200 - 1600
|
// Set GPU clocks to given value valid between 200 - 1600
|
||||||
// Only used when GPU Performance Level is manual, but write whenever called.
|
// Only used when GPU Performance Level is manual, but write whenever called.
|
||||||
|
@ -646,6 +801,26 @@ CCLK_RANGE in Core0:
|
||||||
assert!(GPUPowerProfile::from_str("fullscreen").is_err());
|
assert!(GPUPowerProfile::from_str("fullscreen").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cpu_governor_roundtrip() {
|
||||||
|
enum_roundtrip!(CPUGovernor {
|
||||||
|
0: u32 = Conservative,
|
||||||
|
1: u32 = OnDemand,
|
||||||
|
2: u32 = UserSpace,
|
||||||
|
3: u32 = PowerSave,
|
||||||
|
4: u32 = Performance,
|
||||||
|
5: u32 = SchedUtil,
|
||||||
|
"conservative": str = Conservative,
|
||||||
|
"ondemand": str = OnDemand,
|
||||||
|
"userspace": str = UserSpace,
|
||||||
|
"powersave": str = PowerSave,
|
||||||
|
"performance": str = Performance,
|
||||||
|
"schedutil": str = SchedUtil,
|
||||||
|
});
|
||||||
|
assert!(CPUGovernor::try_from(6).is_err());
|
||||||
|
assert!(CPUGovernor::from_str("usersave").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gpu_performance_level_roundtrip() {
|
fn gpu_performance_level_roundtrip() {
|
||||||
enum_roundtrip!(GPUPerformanceLevel {
|
enum_roundtrip!(GPUPerformanceLevel {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue