diff --git a/README.md b/README.md index b9f3662..eb110c8 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ more than issue reports, as supporting hardware _needs_ hardware. Superfreq uses TOML configuration files. Default locations: -- `/etc/superfreq/config.toml` +- `/etc/xdg/superfreq/config.toml` - `/etc/superfreq.toml` You can also specify a custom path by setting the `SUPERFREQ_CONFIG` environment diff --git a/nix/module.nix b/nix/module.nix index 5014f21..76d298f 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -5,18 +5,21 @@ inputs: { ... }: let inherit (lib.modules) mkIf; - inherit (lib.options) mkOption mkEnableOption; + inherit (lib.options) mkOption mkEnableOption mkPackageOption; inherit (lib.types) submodule; + inherit (lib.lists) optional; inherit (lib.meta) getExe; - cfg = config.programs.superfreq; - - defaultPackage = inputs.self.packages.${pkgs.stdenv.system}.default; + cfg = config.services.superfreq; format = pkgs.formats.toml {}; + cfgFile = format.generate "superfreq-config.toml" cfg.settings; in { - options.programs.superfreq = { + options.services.superfreq = { enable = mkEnableOption "Automatic CPU speed & power optimizer for Linux"; + package = mkPackageOption inputs.self.packages.${pkgs.stdenv.system} "superfreq" { + pkgsText = "self.packages.\${pkgs.stdenv.system}"; + }; settings = mkOption { default = {}; @@ -26,23 +29,18 @@ in { }; config = mkIf cfg.enable { - environment.systemPackages = [defaultPackage]; + environment.systemPackages = [cfg.package]; - systemd = { - packages = [defaultPackage]; - services.superfreq = { - wantedBy = ["multi-user.target"]; - serviceConfig = let - cfgFile = format.generate "superfreq-config.toml" cfg.settings; - in { - Environment = ["SUPERFREQ_CONFIG=${cfgFile}"]; - WorkingDirectory = ""; - ExecStart = "${getExe defaultPackage} daemon --verbose"; - Restart = "on-failure"; + systemd.services.superfreq = { + wantedBy = ["multi-user.target"]; + serviceConfig = { + Environment = optional (cfg.settings != {}) ["SUPERFREQ_CONFIG=${cfgFile}"]; + WorkingDirectory = ""; + ExecStart = "${getExe cfg.package} daemon --verbose"; + Restart = "on-failure"; - RuntimeDirectory = "superfreq"; - RuntimeDirectoryMode = "0755"; - }; + RuntimeDirectory = "superfreq"; + RuntimeDirectoryMode = "0755"; }; }; @@ -55,9 +53,9 @@ in { ''; } { - assertion = !config.programs.auto-cpufreq.enable; + assertion = !config.services.auto-cpufreq.enable; message = '' - You have set programs.auto-cpufreq.enable = true; + You have set services.auto-cpufreq.enable = true; which conflicts with Superfreq. ''; } diff --git a/src/config/load.rs b/src/config/load.rs index 663f72e..1393a88 100644 --- a/src/config/load.rs +++ b/src/config/load.rs @@ -46,7 +46,7 @@ pub fn load_config_from_path(specific_path: Option<&str>) -> Result = std::result::Result; @@ -18,12 +19,15 @@ const VALID_EPB_STRINGS: &[&str] = &[ // Write a value to a sysfs file fn write_sysfs_value(path: impl AsRef, value: &str) -> Result<()> { let p = path.as_ref(); + fs::write(p, value).map_err(|e| { let error_msg = format!("Path: {:?}, Value: '{}', Error: {}", p.display(), value, e); - if e.kind() == io::ErrorKind::PermissionDenied { - ControlError::PermissionDenied(error_msg) - } else { - ControlError::WriteError(error_msg) + match e.kind() { + io::ErrorKind::PermissionDenied => ControlError::PermissionDenied(error_msg), + io::ErrorKind::NotFound => { + ControlError::PathMissing(format!("Path '{}' does not exist", p.display())) + } + _ => ControlError::WriteError(error_msg), } }) } @@ -136,6 +140,7 @@ fn get_available_governors() -> Result> { let path = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors"; if !Path::new(path).exists() { + return Err(ControlError::NotSupported( "Could not determine available governors".to_string(), )); @@ -487,7 +492,7 @@ const GOVERNOR_OVERRIDE_PATH: &str = "/etc/superfreq/governor_override"; /// Force a specific CPU governor or reset to automatic mode pub fn force_governor(mode: GovernorOverrideMode) -> Result<()> { // Create directory if it doesn't exist - let dir_path = Path::new("/etc/superfreq"); + let dir_path = Path::new("/etc/xdg/superfreq"); if !dir_path.exists() { fs::create_dir_all(dir_path).map_err(|e| { if e.kind() == io::ErrorKind::PermissionDenied { diff --git a/src/daemon.rs b/src/daemon.rs index e7b579a..edabb29 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -65,7 +65,7 @@ pub fn run_daemon(mut config: AppConfig, verbose: bool) -> Result<(), Box Result> { } pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { - // FIXME: Assume global settings can be read from cpu0 or are consistent. - // This might not work properly for heterogeneous systems (e.g. big.LITTLE) - let cpufreq_base_path = Path::new("/sys/devices/system/cpu/cpu0/cpufreq/"); + // Find a valid CPU to read global settings from + // Try cpu0 first, then fall back to any available CPU with cpufreq + let mut cpufreq_base_path_buf = PathBuf::from("/sys/devices/system/cpu/cpu0/cpufreq/"); + + if !cpufreq_base_path_buf.exists() { + let core_count = get_logical_core_count().unwrap_or_else(|e| { + eprintln!("Warning: {e}"); + 0 + }); + let path = (0..core_count) + .map(|i| PathBuf::from(format!("/sys/devices/system/cpu/cpu{i}/cpufreq/"))) + .find(|path| path.exists()); + if let Some(test_path_buf) = path { + cpufreq_base_path_buf = test_path_buf; + } + } + let turbo_status_path = Path::new("/sys/devices/system/cpu/intel_pstate/no_turbo"); let boost_path = Path::new("/sys/devices/system/cpu/cpufreq/boost"); - let current_governor = if cpufreq_base_path.join("scaling_governor").exists() { - read_sysfs_file_trimmed(cpufreq_base_path.join("scaling_governor")).ok() + + let current_governor = if cpufreq_base_path_buf.join("scaling_governor").exists() { + read_sysfs_file_trimmed(cpufreq_base_path_buf.join("scaling_governor")).ok() } else { None }; - let available_governors = if cpufreq_base_path + let available_governors = if cpufreq_base_path_buf .join("scaling_available_governors") .exists() { - read_sysfs_file_trimmed(cpufreq_base_path.join("scaling_available_governors")).map_or_else( - |_| vec![], - |s| s.split_whitespace().map(String::from).collect(), - ) + read_sysfs_file_trimmed(cpufreq_base_path_buf.join("scaling_available_governors")) + .map_or_else( + |_| vec![], + |s| s.split_whitespace().map(String::from).collect(), + ) } else { vec![] }; @@ -424,17 +440,16 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { } else { None }; + // EPP (Energy Performance Preference) let energy_perf_pref = - read_sysfs_file_trimmed(cpufreq_base_path.join("energy_performance_preference")).ok(); + read_sysfs_file_trimmed(cpufreq_base_path_buf.join("energy_performance_preference")).ok(); // EPB (Energy Performance Bias) let energy_perf_bias = - read_sysfs_file_trimmed(cpufreq_base_path.join("energy_performance_bias")).ok(); + read_sysfs_file_trimmed(cpufreq_base_path_buf.join("energy_performance_bias")).ok(); let platform_profile = read_sysfs_file_trimmed("/sys/firmware/acpi/platform_profile").ok(); - let _platform_profile_choices = - read_sysfs_file_trimmed("/sys/firmware/acpi/platform_profile_choices").ok(); // Calculate average CPU temperature from the core temperatures let average_temperature_celsius = if cpu_cores.is_empty() { @@ -458,6 +473,7 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { } }; + // Return the constructed CpuGlobalInfo CpuGlobalInfo { current_governor, available_governors, diff --git a/src/util/error.rs b/src/util/error.rs index 51c3162..6ba476f 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -9,6 +9,10 @@ pub enum ControlError { NotSupported(String), PermissionDenied(String), InvalidProfile(String), + InvalidGovernor(String), + ParseError(String), + ReadError(String), + PathMissing(String), } impl From for ControlError { @@ -37,6 +41,18 @@ impl std::fmt::Display for ControlError { "Invalid platform control profile {s} supplied, please provide a valid one." ) } + Self::InvalidGovernor(s) => { + write!(f, "Invalid governor: {s}") + } + Self::ParseError(s) => { + write!(f, "Failed to parse value: {s}") + } + Self::ReadError(s) => { + write!(f, "Failed to read sysfs path: {s}") + } + Self::PathMissing(s) => { + write!(f, "Path missing: {s}") + } } } }