Merge pull request #17 from Eraden/prepare-for-voltage

[WIP] Prepare code for adding voltage
This commit is contained in:
Adrian Woźniak 2021-08-04 21:36:59 +02:00 committed by GitHub
commit 5f5b3bc69b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 231 additions and 177 deletions

View File

@ -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

View File

@ -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
View 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
View 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),
}

View File

@ -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
View 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,
}

View File

@ -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> {

View File

@ -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)
}

View File

@ -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
View 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
View File

@ -0,0 +1,9 @@
#[derive(Debug, gumdrop::Options)]
pub enum VoltageCommand {
Placeholder(Placeholder),
}
#[derive(Debug, gumdrop::Options)]
pub struct Placeholder {
help: bool,
}