Merge pull request #17 from Eraden/prepare-for-voltage
[WIP] Prepare code for adding voltage
This commit is contained in:
commit
5f5b3bc69b
4
.github/workflows/rust.yml
vendored
4
.github/workflows/rust.yml
vendored
@ -21,6 +21,10 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Add target
|
||||
run: rustup target install x86_64-unknown-linux-musl
|
||||
- name: Run clippy
|
||||
run: cargo clippy -- -D warnings
|
||||
- name: Run fmt check
|
||||
run: cargo fmt -- --check
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
||||
- name: Install binary compressor
|
||||
|
@ -26,7 +26,7 @@ impl FromStr for Card {
|
||||
value[4..]
|
||||
.parse::<u32>()
|
||||
.map_err(|e| AmdFanError::InvalidSuffix(format!("{:?}", e)))
|
||||
.map(|n| Card(n))
|
||||
.map(Card)
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ impl<'de> Deserialize<'de> for Card {
|
||||
}
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_str(CardVisitor).map(|v| Card(v))
|
||||
deserializer.deserialize_str(CardVisitor).map(Card)
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,7 +115,7 @@ pub enum LogLevel {
|
||||
}
|
||||
|
||||
impl LogLevel {
|
||||
pub fn to_str(&self) -> &str {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
LogLevel::Off => "OFF",
|
||||
LogLevel::Error => "ERROR",
|
||||
@ -149,7 +149,7 @@ impl Config {
|
||||
return self.max_speed();
|
||||
}
|
||||
|
||||
crate::linear_map(
|
||||
crate::utils::linear_map(
|
||||
temp,
|
||||
self.speed_matrix[idx].temp,
|
||||
self.speed_matrix[idx + 1].temp,
|
||||
|
51
src/fan/change_mode.rs
Normal file
51
src/fan/change_mode.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use gumdrop::Options;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::io_err::not_found;
|
||||
use crate::FanMode;
|
||||
|
||||
/// Change card fan mode to either automatic or manual
|
||||
pub fn run(switcher: Switcher, mode: FanMode, config: Config) -> std::io::Result<()> {
|
||||
let mut controllers = crate::utils::controllers(&config, true)?;
|
||||
|
||||
let cards = match switcher.card {
|
||||
Some(card_id) => match controllers
|
||||
.iter()
|
||||
.position(|hw_mon| *hw_mon.card == card_id)
|
||||
{
|
||||
Some(card) => vec![controllers.remove(card)],
|
||||
None => {
|
||||
eprintln!("Card does not exists. Available cards: ");
|
||||
for hw_mon in controllers {
|
||||
eprintln!(" * {}", *hw_mon.card);
|
||||
}
|
||||
return Err(not_found());
|
||||
}
|
||||
},
|
||||
None => controllers,
|
||||
};
|
||||
|
||||
for hw_mon in cards {
|
||||
match mode {
|
||||
FanMode::Automatic => {
|
||||
if let Err(e) = hw_mon.set_automatic() {
|
||||
log::error!("{:?}", e);
|
||||
}
|
||||
}
|
||||
FanMode::Manual => {
|
||||
if let Err(e) = hw_mon.set_manual() {
|
||||
log::error!("{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
pub struct Switcher {
|
||||
#[options(help = "Print help message")]
|
||||
help: bool,
|
||||
#[options(help = "GPU Card number")]
|
||||
card: Option<u32>,
|
||||
}
|
25
src/fan/mod.rs
Normal file
25
src/fan/mod.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use gumdrop::Options;
|
||||
|
||||
pub mod change_mode;
|
||||
pub mod monitor;
|
||||
pub mod service;
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
pub struct AvailableCards {
|
||||
#[options(help = "Help message")]
|
||||
help: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
pub enum FanCommand {
|
||||
#[options(help = "Print current temp and fan speed")]
|
||||
Monitor(monitor::Monitor),
|
||||
#[options(help = "Set fan speed depends on GPU temperature")]
|
||||
Service(service::Service),
|
||||
#[options(help = "Switch to GPU automatic fan speed control")]
|
||||
SetAutomatic(change_mode::Switcher),
|
||||
#[options(help = "Switch to GPU manual fan speed control")]
|
||||
SetManual(change_mode::Switcher),
|
||||
#[options(help = "Print available cards")]
|
||||
Available(AvailableCards),
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
use crate::config::Config;
|
||||
use crate::{controllers, AmdFanError};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::utils::controllers;
|
||||
use crate::AmdFanError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MonitorFormat {
|
||||
Short,
|
||||
@ -34,6 +36,7 @@ pub struct Monitor {
|
||||
format: MonitorFormat,
|
||||
}
|
||||
|
||||
/// Start print cards temperature and fan speed
|
||||
pub fn run(monitor: Monitor, config: Config) -> std::io::Result<()> {
|
||||
match monitor.format {
|
||||
MonitorFormat::Short => short(config),
|
||||
@ -57,7 +60,14 @@ pub fn verbose(config: Config) -> std::io::Result<()> {
|
||||
hw_mon
|
||||
.pwm()
|
||||
.map_or_else(|_e| String::from("FAILED"), |f| f.to_string()),
|
||||
(crate::linear_map(hw_mon.pwm().unwrap_or_default() as f64, min as f64, max as f64, 0f64, 100f64)).round(),
|
||||
(crate::utils::linear_map(
|
||||
hw_mon.pwm().unwrap_or_default() as f64,
|
||||
min as f64,
|
||||
max as f64,
|
||||
0f64,
|
||||
100f64
|
||||
))
|
||||
.round(),
|
||||
);
|
||||
|
||||
println!();
|
||||
@ -93,7 +103,14 @@ pub fn short(config: Config) -> std::io::Result<()> {
|
||||
min,
|
||||
max,
|
||||
hw_mon.pwm().unwrap_or_default(),
|
||||
crate::linear_map(hw_mon.pwm().unwrap_or_default() as f64, min as f64, max as f64, 0f64, 100f64).round(),
|
||||
crate::utils::linear_map(
|
||||
hw_mon.pwm().unwrap_or_default() as f64,
|
||||
min as f64,
|
||||
max as f64,
|
||||
0f64,
|
||||
100f64
|
||||
)
|
||||
.round(),
|
||||
);
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_secs(4));
|
39
src/fan/service.rs
Normal file
39
src/fan/service.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use gumdrop::Options;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::io_err::not_found;
|
||||
|
||||
/// Start service which will change fan speed according to config and GPU temperature
|
||||
pub fn run(config: Config) -> std::io::Result<()> {
|
||||
let mut controllers = crate::utils::controllers(&config, true)?;
|
||||
if controllers.is_empty() {
|
||||
return Err(not_found());
|
||||
}
|
||||
let mut cache = std::collections::HashMap::new();
|
||||
loop {
|
||||
for hw_mon in controllers.iter_mut() {
|
||||
let gpu_temp = hw_mon.max_gpu_temp().unwrap_or_default();
|
||||
log::debug!("Current {} temperature: {}", hw_mon.card, gpu_temp);
|
||||
let last = *cache.entry(*hw_mon.card).or_insert(1_000f64);
|
||||
|
||||
if (last - gpu_temp).abs() < 0.001f64 {
|
||||
log::debug!("Temperature didn't change");
|
||||
continue;
|
||||
};
|
||||
let speed = config.speed_for_temp(gpu_temp);
|
||||
log::debug!("Resolved speed {:.2}", speed);
|
||||
|
||||
if let Err(e) = hw_mon.set_speed(speed) {
|
||||
log::error!("Failed to change speed to {}. {:?}", speed, e);
|
||||
}
|
||||
cache.insert(*hw_mon.card, gpu_temp);
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_secs(4));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
pub struct Service {
|
||||
#[options(help = "Help message")]
|
||||
help: bool,
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
use crate::config::Card;
|
||||
use crate::io_err::{invalid_input, not_found};
|
||||
use crate::{linear_map, AmdFanError, HwMon, HW_MON_DIR, ROOT_DIR};
|
||||
use crate::utils::linear_map;
|
||||
use crate::{AmdFanError, HwMon, HW_MON_DIR, ROOT_DIR};
|
||||
|
||||
/// pulse width modulation fan control minimum level (0)
|
||||
const PULSE_WIDTH_MODULATION_MIN: &str = "pwm1_min";
|
||||
@ -21,11 +22,11 @@ const PULSE_WIDTH_MODULATION_MANUAL: &str = "1";
|
||||
impl HwMon {
|
||||
pub fn new(card: &Card, name: &str) -> Self {
|
||||
Self {
|
||||
card: card.clone(),
|
||||
card: *card,
|
||||
name: String::from(name),
|
||||
pwm_min: None,
|
||||
pwm_max: None,
|
||||
temp_inputs: load_temp_inputs(&card, name),
|
||||
temp_inputs: load_temp_inputs(card, name),
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +35,7 @@ impl HwMon {
|
||||
for name in self.temp_inputs.iter() {
|
||||
results.push(self.read_gpu_temp(name).unwrap_or(0));
|
||||
}
|
||||
results.sort();
|
||||
results.sort_unstable();
|
||||
results
|
||||
.last()
|
||||
.copied()
|
||||
@ -186,7 +187,7 @@ fn hw_mon_dirs_path(card: &Card) -> std::path::PathBuf {
|
||||
|
||||
#[inline]
|
||||
fn hw_mon_dir_path(card: &Card, name: &str) -> std::path::PathBuf {
|
||||
hw_mon_dirs_path(&card).join(name)
|
||||
hw_mon_dirs_path(card).join(name)
|
||||
}
|
||||
|
||||
pub(crate) fn open_hw_mon(card: Card) -> std::io::Result<HwMon> {
|
||||
|
@ -1,14 +1,12 @@
|
||||
use std::io::Error as IoErr;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
pub fn invalid_data() -> IoErr {
|
||||
IoErr::from(ErrorKind::InvalidData)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn not_found() -> IoErr {
|
||||
IoErr::from(ErrorKind::NotFound)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn invalid_input() -> IoErr {
|
||||
IoErr::from(ErrorKind::NotFound)
|
||||
}
|
||||
|
188
src/main.rs
188
src/main.rs
@ -1,16 +1,18 @@
|
||||
mod config;
|
||||
mod hw_mon;
|
||||
mod io_err;
|
||||
mod monitor;
|
||||
|
||||
extern crate log;
|
||||
|
||||
use std::fmt::Formatter;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
use crate::config::{load_config, Card, Config};
|
||||
use crate::io_err::{invalid_data, not_found};
|
||||
use gumdrop::Options;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use crate::config::{load_config, Card};
|
||||
|
||||
mod config;
|
||||
mod fan;
|
||||
mod hw_mon;
|
||||
mod io_err;
|
||||
mod utils;
|
||||
mod voltage;
|
||||
|
||||
extern crate log;
|
||||
|
||||
static CONFIG_DIR: &str = "/etc/amdfand";
|
||||
static CONFIG_PATH: &str = "/etc/amdfand/config.toml";
|
||||
@ -45,12 +47,6 @@ impl std::fmt::Display for AmdFanError {
|
||||
}
|
||||
}
|
||||
|
||||
// linear mapping from the xrange to the yrange
|
||||
fn linear_map(x: f64, x1: f64, x2: f64, y1: f64, y2: f64) -> f64 {
|
||||
let m = (y2 - y1) / (x2 - x1);
|
||||
m * (x - x1) + y1
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HwMon {
|
||||
card: Card,
|
||||
@ -65,38 +61,12 @@ pub enum FanMode {
|
||||
Automatic,
|
||||
}
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
pub struct Service {
|
||||
#[options(help = "Help message")]
|
||||
help: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
pub struct Switcher {
|
||||
#[options(help = "Print help message")]
|
||||
help: bool,
|
||||
#[options(help = "GPU Card number")]
|
||||
card: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
pub struct AvailableCards {
|
||||
#[options(help = "Help message")]
|
||||
help: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
#[derive(Options)]
|
||||
pub enum Command {
|
||||
#[options(help = "Print current temp and fan speed")]
|
||||
Monitor(monitor::Monitor),
|
||||
#[options(help = "Set fan speed depends on GPU temperature")]
|
||||
Service(Service),
|
||||
#[options(help = "Switch to GPU automatic fan speed control")]
|
||||
SetAutomatic(Switcher),
|
||||
#[options(help = "Switch to GPU manual fan speed control")]
|
||||
SetManual(Switcher),
|
||||
#[options(help = "Print available cards")]
|
||||
Available(AvailableCards),
|
||||
#[options(help = "GPU card fan control")]
|
||||
Fan(fan::FanCommand),
|
||||
#[options(help = "Overclock GPU card")]
|
||||
Voltage(voltage::VoltageCommand),
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
@ -109,105 +79,6 @@ pub struct Opts {
|
||||
command: Option<Command>,
|
||||
}
|
||||
|
||||
fn read_cards() -> std::io::Result<Vec<Card>> {
|
||||
let mut cards = vec![];
|
||||
let entries = std::fs::read_dir(ROOT_DIR)?;
|
||||
for entry in entries {
|
||||
match entry
|
||||
.and_then(|entry| {
|
||||
entry
|
||||
.file_name()
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.map(String::from)
|
||||
.ok_or_else(invalid_data)
|
||||
})
|
||||
.and_then(|file_name| file_name.parse::<Card>().map_err(|_| invalid_data()))
|
||||
{
|
||||
Ok(card) => {
|
||||
cards.push(card);
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
Ok(cards)
|
||||
}
|
||||
|
||||
fn controllers(config: &Config, filter: bool) -> std::io::Result<Vec<HwMon>> {
|
||||
Ok(read_cards()?
|
||||
.into_iter()
|
||||
.filter(|card| !filter || config.cards().iter().find(|name| **name == *card).is_some())
|
||||
.filter_map(|card| hw_mon::open_hw_mon(card).ok())
|
||||
.filter(|hw_mon| !filter || { hw_mon.is_amd() })
|
||||
.filter(|hw_mon| !filter || hw_mon.name_is_amd())
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn service(config: Config) -> std::io::Result<()> {
|
||||
let mut controllers = controllers(&config, true)?;
|
||||
if controllers.is_empty() {
|
||||
return Err(not_found());
|
||||
}
|
||||
let mut cache = std::collections::HashMap::new();
|
||||
loop {
|
||||
for hw_mon in controllers.iter_mut() {
|
||||
let gpu_temp = hw_mon.max_gpu_temp().unwrap_or_default();
|
||||
log::debug!("Current {} temperature: {}", hw_mon.card, gpu_temp);
|
||||
let last = *cache.entry(*hw_mon.card).or_insert(1_000f64);
|
||||
|
||||
if (last - gpu_temp).abs() < 0.001f64 {
|
||||
log::debug!("Temperature didn't change");
|
||||
continue;
|
||||
};
|
||||
let speed = config.speed_for_temp(gpu_temp);
|
||||
log::debug!("Resolved speed {:.2}", speed);
|
||||
|
||||
if let Err(e) = hw_mon.set_speed(speed) {
|
||||
log::error!("Failed to change speed to {}. {:?}", speed, e);
|
||||
}
|
||||
cache.insert(*hw_mon.card, gpu_temp);
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_secs(4));
|
||||
}
|
||||
}
|
||||
|
||||
fn change_mode(switcher: Switcher, mode: FanMode, config: Config) -> std::io::Result<()> {
|
||||
let mut controllers = controllers(&config, true)?;
|
||||
|
||||
let cards = match switcher.card {
|
||||
Some(card_id) => match controllers
|
||||
.iter()
|
||||
.position(|hw_mon| *hw_mon.card == card_id)
|
||||
{
|
||||
Some(card) => vec![controllers.remove(card)],
|
||||
None => {
|
||||
eprintln!("Card does not exists. Available cards: ");
|
||||
for hw_mon in controllers {
|
||||
eprintln!(" * {}", *hw_mon.card);
|
||||
}
|
||||
return Err(not_found());
|
||||
}
|
||||
},
|
||||
None => controllers,
|
||||
};
|
||||
|
||||
for hw_mon in cards {
|
||||
match mode {
|
||||
FanMode::Automatic => {
|
||||
if let Err(e) = hw_mon.set_automatic() {
|
||||
log::error!("{:?}", e);
|
||||
}
|
||||
}
|
||||
FanMode::Manual => {
|
||||
if let Err(e) = hw_mon.set_manual() {
|
||||
log::error!("{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
if std::fs::read(CONFIG_DIR).map_err(|e| e.kind() == ErrorKind::NotFound) == Err(true) {
|
||||
std::fs::create_dir_all(CONFIG_DIR)?;
|
||||
@ -215,7 +86,7 @@ fn main() -> std::io::Result<()> {
|
||||
|
||||
let config = load_config()?;
|
||||
|
||||
std::env::set_var("RUST_LOG", config.log_level().to_str());
|
||||
std::env::set_var("RUST_LOG", config.log_level().as_str());
|
||||
pretty_env_logger::init();
|
||||
|
||||
let opts: Opts = Opts::parse_args_default_or_exit();
|
||||
@ -226,14 +97,20 @@ fn main() -> std::io::Result<()> {
|
||||
}
|
||||
|
||||
match opts.command {
|
||||
None => service(config),
|
||||
Some(Command::Monitor(monitor)) => monitor::run(monitor, config),
|
||||
Some(Command::Service(_)) => service(config),
|
||||
Some(Command::SetAutomatic(switcher)) => change_mode(switcher, FanMode::Automatic, config),
|
||||
Some(Command::SetManual(switcher)) => change_mode(switcher, FanMode::Manual, config),
|
||||
Some(Command::Available(_)) => {
|
||||
None => fan::service::run(config),
|
||||
Some(Command::Fan(fan::FanCommand::Monitor(monitor))) => fan::monitor::run(monitor, config),
|
||||
Some(Command::Fan(fan::FanCommand::Service(_))) => fan::service::run(config),
|
||||
Some(Command::Fan(fan::FanCommand::SetAutomatic(switcher))) => {
|
||||
fan::change_mode::run(switcher, FanMode::Automatic, config)
|
||||
}
|
||||
Some(Command::Fan(fan::FanCommand::SetManual(switcher))) => {
|
||||
fan::change_mode::run(switcher, FanMode::Manual, config)
|
||||
}
|
||||
Some(Command::Fan(fan::FanCommand::Available(_))) => {
|
||||
println!("Available cards");
|
||||
controllers(&config, false)?.into_iter().for_each(|hw_mon| {
|
||||
utils::controllers(&config, false)?
|
||||
.into_iter()
|
||||
.for_each(|hw_mon| {
|
||||
println!(
|
||||
" * {:6>} - {}",
|
||||
hw_mon.card,
|
||||
@ -242,5 +119,8 @@ fn main() -> std::io::Result<()> {
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
Some(Command::Voltage(voltage::VoltageCommand::Placeholder(_))) => {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
src/utils.rs
Normal file
30
src/utils.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use crate::config::{Card, Config};
|
||||
use crate::{hw_mon, HwMon, ROOT_DIR};
|
||||
|
||||
/// linear mapping from the xrange to the yrange
|
||||
pub fn linear_map(x: f64, x1: f64, x2: f64, y1: f64, y2: f64) -> f64 {
|
||||
let m = (y2 - y1) / (x2 - x1);
|
||||
m * (x - x1) + y1
|
||||
}
|
||||
|
||||
/// Read all available graphic cards from direct rendering manager
|
||||
pub fn read_cards() -> std::io::Result<Vec<Card>> {
|
||||
Ok(std::fs::read_dir(ROOT_DIR)?
|
||||
.into_iter()
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter_map(|entry| entry.file_name().as_os_str().to_str().map(String::from))
|
||||
.filter_map(|file_name| file_name.parse::<Card>().ok())
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Wrap cards in HW Mon manipulator and
|
||||
/// filter cards so only amd and listed in config cards are accessible
|
||||
pub fn controllers(config: &Config, filter: bool) -> std::io::Result<Vec<HwMon>> {
|
||||
Ok(read_cards()?
|
||||
.into_iter()
|
||||
.filter(|card| !filter || config.cards().iter().any(|name| **name == **card))
|
||||
.filter_map(|card| hw_mon::open_hw_mon(card).ok())
|
||||
.filter(|hw_mon| !filter || { hw_mon.is_amd() })
|
||||
.filter(|hw_mon| !filter || hw_mon.name_is_amd())
|
||||
.collect())
|
||||
}
|
9
src/voltage/mod.rs
Normal file
9
src/voltage/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
#[derive(Debug, gumdrop::Options)]
|
||||
pub enum VoltageCommand {
|
||||
Placeholder(Placeholder),
|
||||
}
|
||||
|
||||
#[derive(Debug, gumdrop::Options)]
|
||||
pub struct Placeholder {
|
||||
help: bool,
|
||||
}
|
Loading…
Reference in New Issue
Block a user