commit
01b42998a7
44
.github/workflows/rust.yml
vendored
44
.github/workflows/rust.yml
vendored
@ -14,28 +14,28 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-18.04, ubuntu-20.04]
|
||||
os: [ ubuntu-18.04, ubuntu-20.04 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- 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
|
||||
run: sudo apt-get update && sudo apt-get install upx-ucl
|
||||
- name: Build
|
||||
run: cargo build --release --verbose --target=x86_64-unknown-linux-musl && strip ./target/x86_64-unknown-linux-musl/release/amdfand && upx --best --lzma target/x86_64-unknown-linux-musl/release/amdfand
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v2.2.4
|
||||
with:
|
||||
# Artifact name
|
||||
name: amdfand-${{ matrix.os }}
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
path: ./target/x86_64-unknown-linux-musl/release/amdfand
|
||||
# The desired behavior if no files are found using the provided path.
|
||||
- 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
|
||||
run: sudo apt-get update && sudo apt-get install upx-ucl
|
||||
- name: Build
|
||||
run: cargo build --release --verbose --target=x86_64-unknown-linux-musl && strip ./target/x86_64-unknown-linux-musl/release/amdfand && upx --best --lzma target/x86_64-unknown-linux-musl/release/amdfand
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v2.2.4
|
||||
with:
|
||||
# Artifact name
|
||||
name: amdfand-${{ matrix.os }}
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
path: ./target/x86_64-unknown-linux-musl/release/amdfand
|
||||
# The desired behavior if no files are found using the provided path.
|
||||
|
154
Cargo.lock
generated
154
Cargo.lock
generated
@ -13,9 +13,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "amdfand"
|
||||
version = "1.0.7"
|
||||
version = "1.0.8"
|
||||
dependencies = [
|
||||
"amdgpu",
|
||||
"amdgpu-config",
|
||||
"gumdrop",
|
||||
"log",
|
||||
"pretty_env_logger",
|
||||
@ -26,7 +27,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "amdgpu"
|
||||
version = "1.0.7"
|
||||
version = "1.0.8"
|
||||
dependencies = [
|
||||
"gumdrop",
|
||||
"log",
|
||||
@ -37,10 +38,41 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "amdvold"
|
||||
version = "1.0.7"
|
||||
name = "amdgpu-config"
|
||||
version = "1.0.8"
|
||||
dependencies = [
|
||||
"amdgpu",
|
||||
"csv",
|
||||
"gumdrop",
|
||||
"log",
|
||||
"pretty_env_logger",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "amdmond"
|
||||
version = "1.0.8"
|
||||
dependencies = [
|
||||
"amdgpu",
|
||||
"amdgpu-config",
|
||||
"chrono",
|
||||
"csv",
|
||||
"gumdrop",
|
||||
"log",
|
||||
"pretty_env_logger",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "amdvold"
|
||||
version = "1.0.8"
|
||||
dependencies = [
|
||||
"amdgpu",
|
||||
"amdgpu-config",
|
||||
"gumdrop",
|
||||
"log",
|
||||
"pretty_env_logger",
|
||||
@ -60,12 +92,66 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.7.1"
|
||||
@ -117,6 +203,18 @@ dependencies = [
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.97"
|
||||
@ -138,6 +236,25 @@ version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pretty_env_logger"
|
||||
version = "0.4.0"
|
||||
@ -183,12 +300,24 @@ dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.126"
|
||||
@ -249,6 +378,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.8"
|
||||
@ -264,6 +404,12 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
@ -1,2 +1,2 @@
|
||||
[workspace]
|
||||
members = ["amdfand", "amdgpu", "amdvold"]
|
||||
members = ["amdgpu", "amdgpu-config", "amdfand", "amdvold", "amdmond"]
|
||||
|
25
LICENSE.md
25
LICENSE.md
@ -2,20 +2,15 @@ MIT License
|
||||
|
||||
Copyright (c) 2021 Adrian Woźniak
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
76
README.md
76
README.md
@ -1,82 +1,24 @@
|
||||
![GitHub](https://img.shields.io/github/license/Eraden/amdgpud)
|
||||
|
||||
# AMDGPU Fan control service
|
||||
# AMD GPU management tools
|
||||
|
||||
Available commands:
|
||||
This repository holds couple tools for AMD graphic cards
|
||||
|
||||
* `monitor` - Print current temp and fan speed
|
||||
* `service` - Set fan speed depends on GPU temperature
|
||||
* `set-automatic` - Switch to GPU automatic fan speed control
|
||||
* `set-manual` - Switch to GPU manual fan speed control
|
||||
* `available` - Print available cards
|
||||
* `amdfand` - fan speed daemon
|
||||
* `amdvold` - voltage and overclocking tool
|
||||
* `amdmond` - monitor daemon
|
||||
|
||||
#### amdfand set-automatic | set-manual [OPTIONS]
|
||||
|
||||
Optional arguments:
|
||||
|
||||
* -h, --help Help message
|
||||
* -c, --card CARD GPU Card number
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
cargo install argonfand
|
||||
|
||||
sudo argonfand monitor # print current temperature, current fan speed, min and max fan speed
|
||||
sudo argonfand service # check amdgpu temperature and adjust speed from config file
|
||||
```
|
||||
|
||||
## Config file
|
||||
|
||||
```toml
|
||||
# /etc/amdfand/config.toml
|
||||
log_level = "Error"
|
||||
temp_input = "temp1_input"
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 4.0
|
||||
speed = 4.0
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 30.0
|
||||
speed = 33.0
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 45.0
|
||||
speed = 50.0
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 60.0
|
||||
speed = 66.0
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 65.0
|
||||
speed = 69.0
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 70.0
|
||||
speed = 75.0
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 75.0
|
||||
speed = 89.0
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 80.0
|
||||
speed = 100.0
|
||||
```
|
||||
For more information please check README each of them.
|
||||
|
||||
## Roadmap
|
||||
|
||||
* [ ] Write metrics
|
||||
* [ ] Add support for multiple cards
|
||||
* Multiple services must recognize card even if there's multiple same version cards is installed
|
||||
* Support should be by using `--config` option
|
||||
* Multiple services must recognize card even if there's multiple same version cards is installed
|
||||
* Support should be by using `--config` option
|
||||
* [ ] CLI for fan config edit
|
||||
* [ ] CLI for voltage edit
|
||||
* [ ] GUI application using native Rust framework (ex. egui, druid)
|
||||
|
||||
## :bookmark: License
|
||||
|
||||
This work is dual-licensed under Apache 2.0 and MIT.
|
||||
You can choose between one of them if you use this work.
|
||||
This work is dual-licensed under Apache 2.0 and MIT. You can choose between one of them if you use this work.
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "amdfand"
|
||||
version = "1.0.7"
|
||||
version = "1.0.8"
|
||||
edition = "2018"
|
||||
description = "AMDGPU fan control service"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@ -9,7 +9,8 @@ categories = ["hardware-support"]
|
||||
repository = "https://github.com/Eraden/amdgpud"
|
||||
|
||||
[dependencies]
|
||||
amdgpu = { path = "../amdgpu", version = "1.0.7" }
|
||||
amdgpu = { path = "../amdgpu", version = "1.0.8" }
|
||||
amdgpu-config = { path = "../amdgpu-config", version = "1.0.8", features = ["fan"] }
|
||||
|
||||
serde = { version = "1.0.126", features = ["derive"] }
|
||||
toml = { version = "0.5.8" }
|
||||
@ -21,3 +22,4 @@ pretty_env_logger = { version = "0.4.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
amdgpu = { path = "../amdgpu", version = "1.0" }
|
||||
amdgpu-config = { path = "../amdgpu-config", version = "1.0", features = ["fan"] }
|
||||
|
66
amdfand/README.md
Normal file
66
amdfand/README.md
Normal file
@ -0,0 +1,66 @@
|
||||
![GitHub](https://img.shields.io/github/license/Eraden/amdgpud)
|
||||
|
||||
# AMDGPU Fan control service
|
||||
|
||||
Available commands:
|
||||
|
||||
* `service` - Set fan speed depends on GPU temperature
|
||||
* `set-automatic` - Switch to GPU automatic fan speed control
|
||||
* `set-manual` - Switch to GPU manual fan speed control
|
||||
* `available` - Print available cards
|
||||
|
||||
#### amdfand set-automatic | set-manual [OPTIONS]
|
||||
|
||||
Optional arguments:
|
||||
|
||||
* -h, --help Help message
|
||||
* -c, --card CARD GPU Card number
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
cargo install argonfand
|
||||
|
||||
sudo argonfand monitor # print current temperature, current fan speed, min and max fan speed
|
||||
sudo argonfand service # check amdgpu temperature and adjust speed from config file
|
||||
```
|
||||
|
||||
## Config file
|
||||
|
||||
```toml
|
||||
# /etc/amdfand/config.toml
|
||||
log_level = "Error"
|
||||
temp_input = "temp1_input"
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 4.0
|
||||
speed = 4.0
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 30.0
|
||||
speed = 33.0
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 45.0
|
||||
speed = 50.0
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 60.0
|
||||
speed = 66.0
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 65.0
|
||||
speed = 69.0
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 70.0
|
||||
speed = 75.0
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 75.0
|
||||
speed = 89.0
|
||||
|
||||
[[speed_matrix]]
|
||||
temp = 80.0
|
||||
speed = 100.0
|
||||
```
|
@ -1,8 +1,9 @@
|
||||
use crate::command::Fan;
|
||||
use amdgpu::utils::hw_mons;
|
||||
use gumdrop::Options;
|
||||
|
||||
use crate::config::Config;
|
||||
use amdgpu::utils::hw_mons;
|
||||
use amdgpu_config::fan::Config;
|
||||
|
||||
use crate::command::Fan;
|
||||
use crate::{AmdFanError, FanMode};
|
||||
|
||||
/// Change card fan mode to either automatic or manual
|
||||
|
@ -1,23 +1,14 @@
|
||||
use crate::{change_mode, monitor, service, Config};
|
||||
use amdgpu::hw_mon::HwMon;
|
||||
use amdgpu::utils::linear_map;
|
||||
use amdgpu::TempInput;
|
||||
use gumdrop::Options;
|
||||
|
||||
/// pulse width modulation fan control minimum level (0)
|
||||
const PULSE_WIDTH_MODULATION_MIN: &str = "pwm1_min";
|
||||
use amdgpu::hw_mon::HwMon;
|
||||
use amdgpu::utils::{linear_map, load_temp_inputs};
|
||||
use amdgpu::{
|
||||
utils, TempInput, PULSE_WIDTH_MODULATION_AUTO, PULSE_WIDTH_MODULATION_MAX,
|
||||
PULSE_WIDTH_MODULATION_MIN, PULSE_WIDTH_MODULATION_MODE,
|
||||
};
|
||||
use amdgpu_config::fan::Config;
|
||||
|
||||
/// pulse width modulation fan control maximum level (255)
|
||||
const PULSE_WIDTH_MODULATION_MAX: &str = "pwm1_max";
|
||||
|
||||
/// pulse width modulation fan level (0-255)
|
||||
const PULSE_WIDTH_MODULATION: &str = "pwm1";
|
||||
|
||||
/// pulse width modulation fan control method (0: no fan speed control, 1: manual fan speed control using pwm interface, 2: automatic fan speed control)
|
||||
const PULSE_WIDTH_MODULATION_MODE: &str = "pwm1_enable";
|
||||
|
||||
// static PULSE_WIDTH_MODULATION_DISABLED: &str = "0";
|
||||
const PULSE_WIDTH_MODULATION_AUTO: &str = "2";
|
||||
use crate::{change_mode, service};
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
pub struct AvailableCards {
|
||||
@ -27,8 +18,6 @@ pub struct AvailableCards {
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
pub enum FanCommand {
|
||||
#[options(help = "Print current temperature and fan speed")]
|
||||
Monitor(monitor::Monitor),
|
||||
#[options(help = "Check AMD GPU temperature and change fan speed depends on configuration")]
|
||||
Service(service::Service),
|
||||
#[options(help = "Switch GPU to automatic fan speed control")]
|
||||
@ -48,11 +37,14 @@ pub enum FanError {
|
||||
#[error("Failed to read AMD GPU temperatures from tempX_input. No input was found")]
|
||||
EmptyTempSet,
|
||||
#[error("Unable to change fan speed to manual mode. {0}")]
|
||||
ManualSpeedFailed(std::io::Error),
|
||||
ManualSpeedFailed(utils::AmdGpuError),
|
||||
#[error("Unable to change fan speed to automatic mode. {0}")]
|
||||
AutomaticSpeedFailed(std::io::Error),
|
||||
AutomaticSpeedFailed(utils::AmdGpuError),
|
||||
#[error("Unable to change AMD GPU modulation (a.k.a. speed) to {value}. {error}")]
|
||||
FailedToChangeSpeed { value: u64, error: std::io::Error },
|
||||
FailedToChangeSpeed {
|
||||
value: u64,
|
||||
error: utils::AmdGpuError,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct Fan {
|
||||
@ -81,6 +73,8 @@ impl std::ops::DerefMut for Fan {
|
||||
}
|
||||
}
|
||||
|
||||
const MODULATION_ENABLED_FILE: &str = "pwm1_enable";
|
||||
|
||||
impl Fan {
|
||||
pub fn wrap(hw_mon: HwMon, config: &Config) -> Self {
|
||||
Self {
|
||||
@ -105,7 +99,7 @@ impl Fan {
|
||||
}
|
||||
|
||||
pub(crate) fn write_manual(&self) -> crate::Result<()> {
|
||||
self.hw_mon_write("pwm1_enable", 1)
|
||||
self.hw_mon_write(MODULATION_ENABLED_FILE, 1)
|
||||
.map_err(FanError::ManualSpeedFailed)?;
|
||||
Ok(())
|
||||
}
|
||||
@ -124,27 +118,6 @@ impl Fan {
|
||||
.map_err(|error| FanError::FailedToChangeSpeed { value, error })?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn pwm_min(&mut self) -> u32 {
|
||||
if self.pwm_min.is_none() {
|
||||
self.pwm_min = Some(self.value_or(PULSE_WIDTH_MODULATION_MIN, 0));
|
||||
};
|
||||
self.pwm_min.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn pwm_max(&mut self) -> u32 {
|
||||
if self.pwm_max.is_none() {
|
||||
self.pwm_max = Some(self.value_or(PULSE_WIDTH_MODULATION_MAX, 255));
|
||||
};
|
||||
self.pwm_max.unwrap_or(255)
|
||||
}
|
||||
|
||||
pub fn pwm(&self) -> crate::Result<u32> {
|
||||
let value = self
|
||||
.hw_mon_read(PULSE_WIDTH_MODULATION)?
|
||||
.parse()
|
||||
.map_err(FanError::NonIntPwm)?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn is_fan_automatic(&self) -> bool {
|
||||
self.hw_mon_read(PULSE_WIDTH_MODULATION_MODE)
|
||||
@ -170,19 +143,6 @@ impl Fan {
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn gpu_temp(&self) -> Vec<(String, crate::Result<f64>)> {
|
||||
self.temp_inputs
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
let temp = self
|
||||
.read_gpu_temp(name.as_str())
|
||||
.map(|temp| temp as f64 / 1000f64);
|
||||
(name, temp)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn read_gpu_temp(&self, name: &str) -> crate::Result<u64> {
|
||||
let value = self
|
||||
.hw_mon_read(name)?
|
||||
@ -190,19 +150,18 @@ impl Fan {
|
||||
.map_err(FanError::NonIntTemp)?;
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn load_temp_inputs(hw_mon: &HwMon) -> Vec<String> {
|
||||
let dir = match std::fs::read_dir(hw_mon.mon_dir()) {
|
||||
Ok(d) => d,
|
||||
_ => return vec![],
|
||||
};
|
||||
dir.filter_map(|f| f.ok())
|
||||
.filter_map(|f| {
|
||||
f.file_name()
|
||||
.to_str()
|
||||
.filter(|s| s.starts_with("temp") && s.ends_with("_input"))
|
||||
.map(String::from)
|
||||
})
|
||||
.collect()
|
||||
pub fn pwm_min(&mut self) -> u32 {
|
||||
if self.pwm_min.is_none() {
|
||||
self.pwm_min = Some(self.value_or(PULSE_WIDTH_MODULATION_MIN, 0));
|
||||
};
|
||||
self.pwm_min.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn pwm_max(&mut self) -> u32 {
|
||||
if self.pwm_max.is_none() {
|
||||
self.pwm_max = Some(self.value_or(PULSE_WIDTH_MODULATION_MAX, 255));
|
||||
};
|
||||
self.pwm_max.unwrap_or(255)
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
use amdgpu::{utils, AmdGpuError};
|
||||
use amdgpu_config::fan::ConfigError;
|
||||
|
||||
use crate::command::FanError;
|
||||
use crate::config::ConfigError;
|
||||
use amdgpu::AmdGpuError;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AmdFanError {
|
||||
#[error("Vendor is not AMD")]
|
||||
NotAmdCard,
|
||||
#[error("Monitor format is not valid. Available values are: short, s, long l, verbose and v")]
|
||||
InvalidMonitorFormat,
|
||||
#[error("No hwmod has been found in sysfs")]
|
||||
NoHwMonFound,
|
||||
#[error("No AMD Card has been found in sysfs")]
|
||||
@ -20,4 +19,6 @@ pub enum AmdFanError {
|
||||
Config(#[from] ConfigError),
|
||||
#[error("{0:}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("{0:}")]
|
||||
AmdUtils(#[from] utils::AmdGpuError),
|
||||
}
|
||||
|
@ -1,23 +1,19 @@
|
||||
extern crate log;
|
||||
|
||||
use gumdrop::Options;
|
||||
|
||||
use amdgpu::utils::{ensure_config_dir, hw_mons};
|
||||
use amdgpu_config::fan::{load_config, Config, DEFAULT_FAN_CONFIG_PATH};
|
||||
|
||||
use crate::command::FanCommand;
|
||||
use crate::error::AmdFanError;
|
||||
use amdgpu::utils::hw_mons;
|
||||
use amdgpu::CONFIG_DIR;
|
||||
use config::{load_config, Config};
|
||||
use gumdrop::Options;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
mod change_mode;
|
||||
mod command;
|
||||
mod config;
|
||||
mod error;
|
||||
mod monitor;
|
||||
mod panic_handler;
|
||||
mod service;
|
||||
|
||||
extern crate log;
|
||||
|
||||
pub static DEFAULT_CONFIG_PATH: &str = "/etc/amdfand/config.toml";
|
||||
|
||||
pub type Result<T> = std::result::Result<T, AmdFanError>;
|
||||
|
||||
pub enum FanMode {
|
||||
@ -51,7 +47,6 @@ fn run(config: Config) -> Result<()> {
|
||||
|
||||
match opts.command {
|
||||
None => service::run(config),
|
||||
Some(FanCommand::Monitor(monitor)) => monitor::run(monitor, config),
|
||||
Some(FanCommand::Service(_)) => service::run(config),
|
||||
Some(FanCommand::SetAutomatic(switcher)) => {
|
||||
change_mode::run(switcher, FanMode::Automatic, config)
|
||||
@ -78,13 +73,11 @@ fn setup() -> Result<(String, Config)> {
|
||||
std::env::set_var("RUST_LOG", "DEBUG");
|
||||
}
|
||||
pretty_env_logger::init();
|
||||
if std::fs::read(CONFIG_DIR).map_err(|e| e.kind() == ErrorKind::NotFound) == Err(true) {
|
||||
std::fs::create_dir_all(CONFIG_DIR)?;
|
||||
}
|
||||
ensure_config_dir()?;
|
||||
|
||||
let config_path = Opts::parse_args_default_or_exit()
|
||||
.config
|
||||
.unwrap_or_else(|| DEFAULT_CONFIG_PATH.to_string());
|
||||
.unwrap_or_else(|| DEFAULT_FAN_CONFIG_PATH.to_string());
|
||||
let config = load_config(&config_path)?;
|
||||
log::info!("{:?}", config);
|
||||
log::set_max_level(config.log_level().as_str().parse().unwrap());
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::command::Fan;
|
||||
use amdgpu::utils::hw_mons;
|
||||
|
||||
use crate::command::Fan;
|
||||
|
||||
pub fn restore_automatic() {
|
||||
for hw in hw_mons(true).unwrap_or_default() {
|
||||
if let Err(error) = (Fan {
|
||||
|
@ -1,9 +1,10 @@
|
||||
use crate::command::Fan;
|
||||
use crate::AmdFanError;
|
||||
use amdgpu::utils::hw_mons;
|
||||
use gumdrop::Options;
|
||||
|
||||
use crate::config::Config;
|
||||
use amdgpu::utils::hw_mons;
|
||||
use amdgpu_config::fan::Config;
|
||||
|
||||
use crate::command::Fan;
|
||||
use crate::AmdFanError;
|
||||
|
||||
/// Start service which will change fan speed according to config and GPU temperature
|
||||
pub fn run(config: Config) -> crate::Result<()> {
|
||||
|
29
amdgpu-config/Cargo.toml
Normal file
29
amdgpu-config/Cargo.toml
Normal file
@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "amdgpu-config"
|
||||
version = "1.0.8"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "amdgpu_config"
|
||||
path = "./src/lib.rs"
|
||||
|
||||
[features]
|
||||
fan = []
|
||||
voltage = []
|
||||
monitor = []
|
||||
|
||||
[dependencies]
|
||||
amdgpu = { path = "../amdgpu", version = "1.0.8" }
|
||||
|
||||
serde = { version = "1.0.126", features = ["derive"] }
|
||||
toml = { version = "0.5.8" }
|
||||
csv = { version = "1.1.6" }
|
||||
|
||||
thiserror = "1.0.30"
|
||||
gumdrop = { version = "0.8.0" }
|
||||
|
||||
log = { version = "0.4.14" }
|
||||
pretty_env_logger = { version = "0.4.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
amdgpu = { path = "../amdgpu", version = "1.0" }
|
5
amdgpu-config/README.md
Normal file
5
amdgpu-config/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# amdgpu-config
|
||||
|
||||
This crates holds config files for `amdfand`, `amdvold` and `amdmond`.
|
||||
|
||||
For more information please check those services.
|
@ -1,6 +1,7 @@
|
||||
use amdgpu::utils::linear_map;
|
||||
use amdgpu::utils::{ensure_config, linear_map};
|
||||
use amdgpu::{LogLevel, TempInput};
|
||||
use std::io::ErrorKind;
|
||||
|
||||
pub static DEFAULT_FAN_CONFIG_PATH: &str = "/etc/amdfand/config.toml";
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||
pub struct MatrixPoint {
|
||||
@ -27,6 +28,13 @@ impl Config {
|
||||
self.cards.as_ref()
|
||||
}
|
||||
|
||||
pub fn speed_matrix_point(&self, temp: f64) -> Option<&MatrixPoint> {
|
||||
match self.speed_matrix.iter().rposition(|p| p.temp <= temp) {
|
||||
Some(idx) => self.speed_matrix.get(idx),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn speed_for_temp(&self, temp: f64) -> f64 {
|
||||
let idx = match self.speed_matrix.iter().rposition(|p| p.temp <= temp) {
|
||||
Some(idx) => idx,
|
||||
@ -108,14 +116,14 @@ impl Default for Config {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, PartialEq)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ConfigError {
|
||||
#[error("Fan speed {value:?} for config entry {index:} is too low (minimal value is 0.0)")]
|
||||
FanSpeedTooLow { value: f64, index: usize },
|
||||
#[error("Fan speed {value:?} for config entry {index:} is too high (maximal value is 100.0)")]
|
||||
FanSpeedTooHigh { value: f64, index: usize },
|
||||
#[error(
|
||||
"Fan speed {current:?} for config entry {index} is lower than previous value {last:?}. Entries must be sorted"
|
||||
"Fan speed {current:?} for config entry {index} is lower than previous value {last:?}. Entries must be sorted"
|
||||
)]
|
||||
UnsortedFanSpeed {
|
||||
current: f64,
|
||||
@ -123,28 +131,19 @@ pub enum ConfigError {
|
||||
last: f64,
|
||||
},
|
||||
#[error(
|
||||
"Fan temperature {current:?} for config entry {index} is lower than previous value {last:?}. Entries must be sorted"
|
||||
"Fan temperature {current:?} for config entry {index} is lower than previous value {last:?}. Entries must be sorted"
|
||||
)]
|
||||
UnsortedFanTemp {
|
||||
current: f64,
|
||||
index: usize,
|
||||
last: f64,
|
||||
},
|
||||
#[error("{0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
pub fn load_config(config_path: &str) -> crate::Result<Config> {
|
||||
let config = match std::fs::read_to_string(config_path) {
|
||||
Ok(s) => toml::from_str(&s).unwrap(),
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => {
|
||||
let config = Config::default();
|
||||
std::fs::write(config_path, toml::to_string(&config).unwrap())?;
|
||||
config
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{:?}", e);
|
||||
panic!();
|
||||
}
|
||||
};
|
||||
pub fn load_config(config_path: &str) -> Result<Config, ConfigError> {
|
||||
let config = ensure_config::<Config, ConfigError, _>(config_path)?;
|
||||
|
||||
let mut last_point: Option<&MatrixPoint> = None;
|
||||
|
||||
@ -154,8 +153,7 @@ pub fn load_config(config_path: &str) -> crate::Result<Config> {
|
||||
return Err(ConfigError::FanSpeedTooLow {
|
||||
value: matrix_point.speed,
|
||||
index,
|
||||
}
|
||||
.into());
|
||||
});
|
||||
}
|
||||
if matrix_point.speed > 100f64 {
|
||||
log::error!(
|
||||
@ -165,8 +163,7 @@ pub fn load_config(config_path: &str) -> crate::Result<Config> {
|
||||
return Err(ConfigError::FanSpeedTooHigh {
|
||||
value: matrix_point.speed,
|
||||
index,
|
||||
}
|
||||
.into());
|
||||
});
|
||||
}
|
||||
if let Some(last_point) = last_point {
|
||||
if matrix_point.speed < last_point.speed {
|
||||
@ -180,8 +177,7 @@ pub fn load_config(config_path: &str) -> crate::Result<Config> {
|
||||
current: matrix_point.speed,
|
||||
last: last_point.speed,
|
||||
index,
|
||||
}
|
||||
.into());
|
||||
});
|
||||
}
|
||||
if matrix_point.temp < last_point.temp {
|
||||
log::error!(
|
||||
@ -194,8 +190,7 @@ pub fn load_config(config_path: &str) -> crate::Result<Config> {
|
||||
current: matrix_point.temp,
|
||||
last: last_point.temp,
|
||||
index,
|
||||
}
|
||||
.into());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,10 +202,10 @@ pub fn load_config(config_path: &str) -> crate::Result<Config> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod parse_config {
|
||||
use crate::config::TempInput;
|
||||
use amdgpu::{AmdGpuError, Card};
|
||||
use serde::Deserialize;
|
||||
|
||||
use amdgpu::{AmdGpuError, Card, TempInput};
|
||||
|
||||
#[derive(Deserialize, PartialEq, Debug)]
|
||||
pub struct Foo {
|
||||
card: Card,
|
9
amdgpu-config/src/lib.rs
Normal file
9
amdgpu-config/src/lib.rs
Normal file
@ -0,0 +1,9 @@
|
||||
#[cfg(feature = "fan")]
|
||||
pub mod fan;
|
||||
#[cfg(feature = "monitor")]
|
||||
pub mod monitor;
|
||||
#[cfg(feature = "voltage")]
|
||||
pub mod voltage;
|
||||
|
||||
/// pulse width modulation fan level (0-255)
|
||||
pub static PULSE_WIDTH_MODULATION: &str = "pwm1";
|
53
amdgpu-config/src/monitor.rs
Normal file
53
amdgpu-config/src/monitor.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use amdgpu::utils::ensure_config;
|
||||
use amdgpu::LogLevel;
|
||||
|
||||
pub static DEFAULT_MONITOR_CONFIG_PATH: &str = "/etc/amdfand/monitor.toml";
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Config {
|
||||
/// Minimal log level
|
||||
log_level: LogLevel,
|
||||
/// Time in milliseconds
|
||||
interval: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ConfigError {
|
||||
#[error("{0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
log_level: LogLevel::Error,
|
||||
interval: 5000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn log_level(&self) -> LogLevel {
|
||||
self.log_level
|
||||
}
|
||||
|
||||
pub fn interval(&self) -> u32 {
|
||||
self.interval
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_config(config_path: &str) -> Result<Config, ConfigError> {
|
||||
let mut config: Config = ensure_config::<Config, ConfigError, _>(config_path)?;
|
||||
|
||||
if config.interval < 100 {
|
||||
log::warn!(
|
||||
"Minimal interval is 100ms, overriding {}ms",
|
||||
config.interval
|
||||
);
|
||||
config.interval = 100;
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
31
amdgpu-config/src/voltage.rs
Normal file
31
amdgpu-config/src/voltage.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use amdgpu::utils::ensure_config;
|
||||
use amdgpu::LogLevel;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ConfigError {
|
||||
#[error("{0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Config {
|
||||
log_level: LogLevel,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
log_level: LogLevel::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn log_level(&self) -> LogLevel {
|
||||
self.log_level
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_config(config_path: &str) -> Result<Config, ConfigError> {
|
||||
ensure_config::<Config, ConfigError, _>(config_path)
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "amdgpu"
|
||||
version = "1.0.7"
|
||||
version = "1.0.8"
|
||||
edition = "2018"
|
||||
description = "Subcomponent of AMDGPU fan control service"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
5
amdgpu/README.md
Normal file
5
amdgpu/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# amdgpu-config
|
||||
|
||||
This is shared data for `amdfand`, `amdvold` and `amdmond`.
|
||||
|
||||
For more information please check those services.
|
@ -1,6 +1,7 @@
|
||||
use crate::AmdGpuError;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::AmdGpuError;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Card(pub u32);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{AmdGpuError, Card, ROOT_DIR};
|
||||
use crate::{utils, AmdGpuError, Card, ROOT_DIR};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HwMonName(pub String);
|
||||
@ -30,7 +30,7 @@ impl HwMon {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> std::io::Result<String> {
|
||||
pub fn name(&self) -> utils::Result<String> {
|
||||
self.hw_mon_read("name")
|
||||
}
|
||||
|
||||
@ -70,21 +70,21 @@ impl HwMon {
|
||||
.unwrap_or(fallback)
|
||||
}
|
||||
|
||||
pub fn hw_mon_read(&self, name: &str) -> std::io::Result<String> {
|
||||
std::fs::read_to_string(self.mon_file_path(name)).map(|s| String::from(s.trim()))
|
||||
pub fn hw_mon_read(&self, name: &str) -> utils::Result<String> {
|
||||
utils::read_to_string(self.mon_file_path(name)).map(|s| String::from(s.trim()))
|
||||
}
|
||||
|
||||
pub fn device_read(&self, name: &str) -> std::io::Result<String> {
|
||||
std::fs::read_to_string(self.device_dir().join(name)).map(|s| String::from(s.trim()))
|
||||
pub fn device_read(&self, name: &str) -> utils::Result<String> {
|
||||
utils::read_to_string(self.device_dir().join(name)).map(|s| String::from(s.trim()))
|
||||
}
|
||||
|
||||
pub fn hw_mon_write(&self, name: &str, value: u64) -> std::io::Result<()> {
|
||||
std::fs::write(self.mon_file_path(name), format!("{}", value))?;
|
||||
pub fn hw_mon_write(&self, name: &str, value: u64) -> utils::Result<()> {
|
||||
utils::write(self.mon_file_path(name), format!("{}", value))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn device_write<C: AsRef<[u8]>>(&self, name: &str, value: C) -> std::io::Result<()> {
|
||||
std::fs::write(self.device_dir().join(name), value)?;
|
||||
pub fn device_write<C: AsRef<[u8]>>(&self, name: &str, value: C) -> utils::Result<()> {
|
||||
utils::write(self.device_dir().join(name), value)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,35 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub use card::*;
|
||||
pub use error::*;
|
||||
pub use temp_input::*;
|
||||
|
||||
mod card;
|
||||
mod error;
|
||||
pub mod hw_mon;
|
||||
mod temp_input;
|
||||
pub mod utils;
|
||||
|
||||
pub use card::*;
|
||||
pub use error::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use temp_input::*;
|
||||
|
||||
pub static CONFIG_DIR: &str = "/etc/amdfand";
|
||||
|
||||
pub static ROOT_DIR: &str = "/sys/class/drm";
|
||||
pub static HW_MON_DIR: &str = "hwmon";
|
||||
|
||||
/// pulse width modulation fan control minimum level (0)
|
||||
pub static PULSE_WIDTH_MODULATION_MIN: &str = "pwm1_min";
|
||||
|
||||
/// pulse width modulation fan control maximum level (255)
|
||||
pub static PULSE_WIDTH_MODULATION_MAX: &str = "pwm1_max";
|
||||
|
||||
/// pulse width modulation fan level (0-255)
|
||||
pub static PULSE_WIDTH_MODULATION: &str = "pwm1";
|
||||
|
||||
/// pulse width modulation fan control method (0: no fan speed control, 1: manual fan speed control using pwm interface, 2: automatic fan speed control)
|
||||
pub static PULSE_WIDTH_MODULATION_MODE: &str = "pwm1_enable";
|
||||
|
||||
// static PULSE_WIDTH_MODULATION_DISABLED: &str = "0";
|
||||
pub static PULSE_WIDTH_MODULATION_AUTO: &str = "2";
|
||||
|
||||
pub type Result<T> = std::result::Result<T, AmdGpuError>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
|
||||
|
@ -1,5 +1,45 @@
|
||||
use std::io::ErrorKind;
|
||||
|
||||
use crate::hw_mon::HwMon;
|
||||
use crate::{hw_mon, Card, ROOT_DIR};
|
||||
use crate::{hw_mon, Card, CONFIG_DIR, ROOT_DIR};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, AmdGpuError>;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AmdGpuError {
|
||||
#[error("Write to {path:?} failed. {io}")]
|
||||
Write { io: std::io::Error, path: String },
|
||||
#[error("Read from {path:?} failed. {io}")]
|
||||
Read { io: std::io::Error, path: String },
|
||||
#[error("File {0:?} does not exists")]
|
||||
FileNotFound(String),
|
||||
}
|
||||
|
||||
pub fn read_to_string<P: AsRef<std::path::Path>>(path: P) -> Result<String> {
|
||||
std::fs::read_to_string(&path).map_err(|io| {
|
||||
if io.kind() == std::io::ErrorKind::NotFound {
|
||||
AmdGpuError::FileNotFound(path.as_ref().to_str().map(String::from).unwrap_or_default())
|
||||
} else {
|
||||
AmdGpuError::Read {
|
||||
io,
|
||||
path: path.as_ref().to_str().map(String::from).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write<P: AsRef<std::path::Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
|
||||
std::fs::write(&path, contents).map_err(|io| {
|
||||
if io.kind() == std::io::ErrorKind::NotFound {
|
||||
AmdGpuError::FileNotFound(path.as_ref().to_str().map(String::from).unwrap_or_default())
|
||||
} else {
|
||||
AmdGpuError::Write {
|
||||
io,
|
||||
path: path.as_ref().to_str().map(String::from).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// linear mapping from the xrange to the yrange
|
||||
pub fn linear_map(x: f64, x1: f64, x2: f64, y1: f64, y2: f64) -> f64 {
|
||||
@ -41,3 +81,45 @@ pub fn hw_mons(filter: bool) -> std::io::Result<Vec<HwMon>> {
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn ensure_config<Config, Error, P>(config_path: P) -> std::result::Result<Config, Error>
|
||||
where
|
||||
Config: serde::Serialize + serde::de::DeserializeOwned + Default + Sized,
|
||||
P: AsRef<std::path::Path>,
|
||||
Error: From<std::io::Error>,
|
||||
{
|
||||
match std::fs::read_to_string(&config_path) {
|
||||
Ok(s) => Ok(toml::from_str::<Config>(s.as_str()).unwrap()),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
let config = Config::default();
|
||||
std::fs::write(config_path, toml::to_string(&config).unwrap())?;
|
||||
Ok(config)
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{:?}", e);
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_temp_inputs(hw_mon: &HwMon) -> Vec<String> {
|
||||
let dir = match std::fs::read_dir(hw_mon.mon_dir()) {
|
||||
Ok(d) => d,
|
||||
_ => return vec![],
|
||||
};
|
||||
dir.filter_map(|f| f.ok())
|
||||
.filter_map(|f| {
|
||||
f.file_name()
|
||||
.to_str()
|
||||
.filter(|s| s.starts_with("temp") && s.ends_with("_input"))
|
||||
.map(String::from)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn ensure_config_dir() -> 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)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
24
amdmond/Cargo.toml
Normal file
24
amdmond/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "amdmond"
|
||||
version = "1.0.8"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
amdgpu = { path = "../amdgpu", version = "1.0.8" }
|
||||
amdgpu-config = { path = "../amdgpu-config", version = "1.0.8", features = ["monitor", "fan"] }
|
||||
|
||||
serde = { version = "1.0.126", features = ["derive"] }
|
||||
toml = { version = "0.5.8" }
|
||||
csv = { version = "1.1.6" }
|
||||
|
||||
thiserror = "1.0.30"
|
||||
gumdrop = { version = "0.8.0" }
|
||||
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
|
||||
log = { version = "0.4.14" }
|
||||
pretty_env_logger = { version = "0.4.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
amdgpu = { path = "../amdgpu", version = "1.0" }
|
||||
amdgpu-config = { path = "../amdgpu-config", version = "1.0", features = ["monitor", "fan"] }
|
66
amdmond/README.md
Normal file
66
amdmond/README.md
Normal file
@ -0,0 +1,66 @@
|
||||
# AMD Monitoring daemon
|
||||
|
||||
## Watch mode
|
||||
|
||||
Tool will check temperature and prints:
|
||||
|
||||
* Minimal modulation
|
||||
* Maximal modulation
|
||||
* Current modulation
|
||||
* Current fan speed in percentage (PWD / PWD MAX * 100)
|
||||
* Current value of each temperature sensor (typically temp1_input is which should be observed)
|
||||
|
||||
> `modulation` is a value between 0-255 which indicate how fast fan should be moving
|
||||
|
||||
```bash
|
||||
/usr/bin/amdmond watch --format short
|
||||
```
|
||||
|
||||
### Formats
|
||||
|
||||
There are 2 possible formats.
|
||||
|
||||
* `short` - very compact info
|
||||
* `long` - more human-readable info
|
||||
|
||||
## Log File mode
|
||||
|
||||
This tool can be used to track GPU temperature and amdfand speed curve management to prevent GPU card from generating
|
||||
unnecessary noise.
|
||||
|
||||
It will create csv log file with:
|
||||
|
||||
* time
|
||||
* temperature
|
||||
* card modulation
|
||||
* matrix point temperature
|
||||
* matrix point speed
|
||||
|
||||
```bash
|
||||
/usr/bin/amdmond log_file -s /var/log/amdmon.csv
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
cargo install amdmond
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### minimal:
|
||||
|
||||
```
|
||||
amdmond log_file -s /var/log/amdmon.csv
|
||||
```
|
||||
|
||||
Required arguments:
|
||||
|
||||
* `-s`, `--stat-file STAT-FILE` Full path to statistics file
|
||||
|
||||
Optional arguments:
|
||||
|
||||
* `-h`, `--help` Help message
|
||||
* `-v`, `--version` Print version
|
||||
* `-c`, `--config CONFIG` Config location
|
||||
* `-i`, `--interval INTERVAL` Time between each check. 1000 is 1s, by default 5s
|
114
amdmond/src/command.rs
Normal file
114
amdmond/src/command.rs
Normal file
@ -0,0 +1,114 @@
|
||||
use crate::{log_file, watch, AmdMonError};
|
||||
use amdgpu::hw_mon::HwMon;
|
||||
use amdgpu::utils::load_temp_inputs;
|
||||
use amdgpu::{TempInput, PULSE_WIDTH_MODULATION_MAX, PULSE_WIDTH_MODULATION_MIN};
|
||||
use amdgpu_config::{fan, PULSE_WIDTH_MODULATION};
|
||||
|
||||
#[derive(gumdrop::Options)]
|
||||
pub enum Command {
|
||||
Watch(watch::Watch),
|
||||
LogFile(log_file::LogFile),
|
||||
}
|
||||
|
||||
impl Default for Command {
|
||||
fn default() -> Self {
|
||||
Self::Watch(watch::Watch::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AmdMon {
|
||||
temp_input: Option<TempInput>,
|
||||
inputs: Vec<String>,
|
||||
hw_mon: HwMon,
|
||||
/// Minimal modulation (between 0-255)
|
||||
pub pwm_min: Option<u32>,
|
||||
/// Maximal modulation (between 0-255)
|
||||
pub pwm_max: Option<u32>,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for AmdMon {
|
||||
type Target = HwMon;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.hw_mon
|
||||
}
|
||||
}
|
||||
|
||||
impl AmdMon {
|
||||
pub(crate) fn wrap_all(mons: Vec<HwMon>, config: &fan::Config) -> Vec<Self> {
|
||||
mons.into_iter()
|
||||
.map(|hw_mon| Self::wrap(hw_mon, config))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn wrap(hw_mon: HwMon, config: &fan::Config) -> Self {
|
||||
Self {
|
||||
temp_input: config.temp_input().cloned(),
|
||||
inputs: load_temp_inputs(&hw_mon),
|
||||
hw_mon,
|
||||
pwm_min: None,
|
||||
pwm_max: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gpu_temp(&self) -> Vec<(String, crate::Result<f64>)> {
|
||||
self.inputs
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
let temp = self
|
||||
.read_gpu_temp(name.as_str())
|
||||
.map(|temp| temp as f64 / 1000f64);
|
||||
(name, temp)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn read_gpu_temp(&self, name: &str) -> crate::Result<u64> {
|
||||
let value = self
|
||||
.hw_mon_read(name)?
|
||||
.parse::<u64>()
|
||||
.map_err(AmdMonError::NonIntTemp)?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn pwm(&self) -> crate::Result<u32> {
|
||||
let value = self
|
||||
.hw_mon_read(PULSE_WIDTH_MODULATION)?
|
||||
.parse()
|
||||
.map_err(AmdMonError::NonIntPwm)?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn pwm_min(&mut self) -> u32 {
|
||||
if self.pwm_min.is_none() {
|
||||
self.pwm_min = Some(self.value_or(PULSE_WIDTH_MODULATION_MIN, 0));
|
||||
};
|
||||
self.pwm_min.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn pwm_max(&mut self) -> u32 {
|
||||
if self.pwm_max.is_none() {
|
||||
self.pwm_max = Some(self.value_or(PULSE_WIDTH_MODULATION_MAX, 255));
|
||||
};
|
||||
self.pwm_max.unwrap_or(255)
|
||||
}
|
||||
|
||||
pub fn max_gpu_temp(&self) -> crate::Result<f64> {
|
||||
if let Some(input) = self.temp_input.as_ref() {
|
||||
let value = self.read_gpu_temp(&input.as_string())?;
|
||||
return Ok(value as f64 / 1000f64);
|
||||
}
|
||||
let mut results = Vec::with_capacity(self.inputs.len());
|
||||
for name in self.inputs.iter() {
|
||||
results.push(self.read_gpu_temp(name).unwrap_or(0));
|
||||
}
|
||||
results.sort_unstable();
|
||||
let value = results
|
||||
.last()
|
||||
.copied()
|
||||
.map(|temp| temp as f64 / 1000f64)
|
||||
.ok_or(AmdMonError::EmptyTempSet)?;
|
||||
Ok(value)
|
||||
}
|
||||
}
|
98
amdmond/src/log_file.rs
Normal file
98
amdmond/src/log_file.rs
Normal file
@ -0,0 +1,98 @@
|
||||
use crate::command::AmdMon;
|
||||
use crate::AmdMonError;
|
||||
use amdgpu::utils::hw_mons;
|
||||
use amdgpu_config::fan;
|
||||
use amdgpu_config::fan::DEFAULT_FAN_CONFIG_PATH;
|
||||
use amdgpu_config::monitor::Config;
|
||||
|
||||
#[derive(gumdrop::Options)]
|
||||
pub struct LogFile {
|
||||
#[options(help = "Help message")]
|
||||
help: bool,
|
||||
#[options(help = "Full path to statistics file")]
|
||||
stat_file: String,
|
||||
#[options(help = "Time between each check. 1000 is 1s, by default 5s")]
|
||||
interval: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct Stat {
|
||||
time: chrono::DateTime<chrono::Local>,
|
||||
temperature: f64,
|
||||
modulation: u32,
|
||||
speed_setting: f64,
|
||||
temperature_setting: f64,
|
||||
}
|
||||
|
||||
pub fn run(command: LogFile, config: Config) -> crate::Result<()> {
|
||||
let fan_config = fan::load_config(DEFAULT_FAN_CONFIG_PATH)?;
|
||||
|
||||
let duration = std::time::Duration::new(
|
||||
0,
|
||||
command.interval.unwrap_or_else(|| config.interval()) * 1_000_000,
|
||||
);
|
||||
log::info!("Updating each: {:?}", duration);
|
||||
|
||||
let _ = std::fs::remove_file(command.stat_file.as_str());
|
||||
let stat_file = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(false)
|
||||
.write(true)
|
||||
.open(command.stat_file.as_str())?;
|
||||
|
||||
let mut writer = csv::WriterBuilder::new()
|
||||
.double_quote(true)
|
||||
.has_headers(true)
|
||||
.buffer_capacity(100)
|
||||
.from_writer(stat_file);
|
||||
|
||||
let mon = {
|
||||
let mons = hw_mons(true)?;
|
||||
if mons.is_empty() {
|
||||
return Err(AmdMonError::NoHwMon);
|
||||
}
|
||||
AmdMon::wrap(hw_mons(true)?.remove(0), &fan_config)
|
||||
};
|
||||
|
||||
loop {
|
||||
let time = chrono::Local::now();
|
||||
let temperature = {
|
||||
let mut temperatures = mon.gpu_temp();
|
||||
|
||||
if let Some(input) = fan_config.temp_input() {
|
||||
let input_name = input.as_string();
|
||||
temperatures
|
||||
.into_iter()
|
||||
.find(|(name, _value)| name == &input_name)
|
||||
.and_then(|(_, v)| v.ok())
|
||||
.unwrap_or_default()
|
||||
} else if temperatures.len() > 1 {
|
||||
temperatures.remove(1).1.unwrap_or_default()
|
||||
} else {
|
||||
temperatures
|
||||
.get(0)
|
||||
.and_then(|(_, v)| v.as_ref().ok().copied())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
};
|
||||
let (speed_setting, temperature_setting) =
|
||||
if let Some(speed_matrix_point) = fan_config.speed_matrix_point(temperature) {
|
||||
(speed_matrix_point.speed, speed_matrix_point.temp)
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let stat = Stat {
|
||||
time,
|
||||
temperature,
|
||||
modulation: mon.pwm()?,
|
||||
speed_setting,
|
||||
temperature_setting,
|
||||
};
|
||||
|
||||
writer.serialize(stat)?;
|
||||
writer.flush()?;
|
||||
|
||||
std::thread::sleep(duration);
|
||||
}
|
||||
}
|
99
amdmond/src/main.rs
Normal file
99
amdmond/src/main.rs
Normal file
@ -0,0 +1,99 @@
|
||||
mod command;
|
||||
mod log_file;
|
||||
mod watch;
|
||||
|
||||
use amdgpu::utils;
|
||||
use gumdrop::Options;
|
||||
|
||||
use crate::command::Command;
|
||||
use amdgpu::utils::ensure_config_dir;
|
||||
use amdgpu_config::monitor::{load_config, Config, DEFAULT_MONITOR_CONFIG_PATH};
|
||||
use amdgpu_config::{fan, monitor};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AmdMonError {
|
||||
#[error("Mon AMD GPU card was found")]
|
||||
NoHwMon,
|
||||
#[error("{0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("{0}")]
|
||||
MonConfigError(#[from] monitor::ConfigError),
|
||||
#[error("{0}")]
|
||||
FanConfigError(#[from] fan::ConfigError),
|
||||
#[error("{0}")]
|
||||
AmdUtils(#[from] utils::AmdGpuError),
|
||||
#[error("{0}")]
|
||||
Csv(#[from] csv::Error),
|
||||
#[error("AMD GPU temperature is malformed. It should be number. {0:?}")]
|
||||
NonIntTemp(std::num::ParseIntError),
|
||||
#[error("AMD GPU fan speed is malformed. It should be number. {0:?}")]
|
||||
NonIntPwm(std::num::ParseIntError),
|
||||
#[error("Monitor format is not valid. Available values are: short, s, long l, verbose and v")]
|
||||
InvalidMonitorFormat,
|
||||
#[error("Failed to read AMD GPU temperatures from tempX_input. No input was found")]
|
||||
EmptyTempSet,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, AmdMonError>;
|
||||
|
||||
#[derive(gumdrop::Options)]
|
||||
pub struct Opts {
|
||||
#[options(help = "Help message")]
|
||||
pub help: bool,
|
||||
#[options(help = "Print version")]
|
||||
pub version: bool,
|
||||
#[options(help = "Config location")]
|
||||
pub config: Option<String>,
|
||||
#[options(command)]
|
||||
pub command: Option<Command>,
|
||||
}
|
||||
|
||||
fn run(config: Config) -> Result<()> {
|
||||
let opts: Opts = Opts::parse_args_default_or_exit();
|
||||
|
||||
if opts.version {
|
||||
println!("amdfand {}", env!("CARGO_PKG_VERSION"));
|
||||
std::process::exit(0);
|
||||
}
|
||||
match opts.command {
|
||||
Some(Command::Watch(w)) => watch::run(w, config),
|
||||
Some(Command::LogFile(l)) => log_file::run(l, config),
|
||||
_ => {
|
||||
println!("{}", <Opts as gumdrop::Options>::usage());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup() -> Result<(String, Config)> {
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
std::env::set_var("RUST_LOG", "DEBUG");
|
||||
}
|
||||
pretty_env_logger::init();
|
||||
ensure_config_dir()?;
|
||||
|
||||
let config_path = Opts::parse_args_default_or_exit()
|
||||
.config
|
||||
.unwrap_or_else(|| DEFAULT_MONITOR_CONFIG_PATH.to_string());
|
||||
let config = load_config(&config_path)?;
|
||||
log::info!("{:?}", config);
|
||||
log::set_max_level(config.log_level().as_str().parse().unwrap());
|
||||
Ok((config_path, config))
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let (_config_path, config) = match setup() {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
match run(config) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
use crate::command::Fan;
|
||||
use crate::AmdFanError;
|
||||
use amdgpu::utils::{hw_mons, linear_map};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::config::Config;
|
||||
use amdgpu::utils::{hw_mons, linear_map};
|
||||
use amdgpu_config::fan::DEFAULT_FAN_CONFIG_PATH;
|
||||
use amdgpu_config::{fan, monitor};
|
||||
|
||||
use crate::command::AmdMon;
|
||||
use crate::AmdMonError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MonitorFormat {
|
||||
@ -18,35 +20,45 @@ impl Default for MonitorFormat {
|
||||
}
|
||||
|
||||
impl FromStr for MonitorFormat {
|
||||
type Err = AmdFanError;
|
||||
type Err = AmdMonError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"short" | "s" => Ok(MonitorFormat::Short),
|
||||
"verbose" | "v" | "long" | "l" => Ok(MonitorFormat::Verbose),
|
||||
_ => Err(AmdFanError::InvalidMonitorFormat),
|
||||
_ => Err(AmdMonError::InvalidMonitorFormat),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, gumdrop::Options)]
|
||||
pub struct Monitor {
|
||||
pub struct Watch {
|
||||
#[options(help = "Help message")]
|
||||
help: bool,
|
||||
#[options(help = "Help message")]
|
||||
#[options(help = "Monitor format")]
|
||||
format: MonitorFormat,
|
||||
}
|
||||
|
||||
/// Start print cards temperature and fan speed
|
||||
pub fn run(monitor: Monitor, config: Config) -> crate::Result<()> {
|
||||
match monitor.format {
|
||||
MonitorFormat::Short => short(config),
|
||||
MonitorFormat::Verbose => verbose(config),
|
||||
impl Default for Watch {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
help: false,
|
||||
format: MonitorFormat::Short,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verbose(config: Config) -> crate::Result<()> {
|
||||
let mut hw_mons = Fan::wrap_all(hw_mons(true)?, &config);
|
||||
/// Start print cards temperature and fan speed
|
||||
pub fn run(monitor: Watch, _config: monitor::Config) -> crate::Result<()> {
|
||||
let fan_config = fan::load_config(DEFAULT_FAN_CONFIG_PATH)?;
|
||||
match monitor.format {
|
||||
MonitorFormat::Short => short(fan_config),
|
||||
MonitorFormat::Verbose => verbose(fan_config),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verbose(config: fan::Config) -> crate::Result<()> {
|
||||
let mut hw_mons = AmdMon::wrap_all(hw_mons(true)?, &config);
|
||||
|
||||
loop {
|
||||
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
|
||||
@ -67,7 +79,7 @@ pub fn verbose(config: Config) -> crate::Result<()> {
|
||||
min as f64,
|
||||
max as f64,
|
||||
0f64,
|
||||
100f64
|
||||
100f64,
|
||||
))
|
||||
.round(),
|
||||
);
|
||||
@ -88,8 +100,8 @@ pub fn verbose(config: Config) -> crate::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn short(config: Config) -> crate::Result<()> {
|
||||
let mut hw_mons = Fan::wrap_all(hw_mons(true)?, &config);
|
||||
pub fn short(config: fan::Config) -> crate::Result<()> {
|
||||
let mut hw_mons = AmdMon::wrap_all(hw_mons(true)?, &config);
|
||||
loop {
|
||||
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
|
||||
for hw_mon in hw_mons.iter_mut() {
|
||||
@ -110,7 +122,7 @@ pub fn short(config: Config) -> crate::Result<()> {
|
||||
min as f64,
|
||||
max as f64,
|
||||
0f64,
|
||||
100f64
|
||||
100f64,
|
||||
)
|
||||
.round(),
|
||||
);
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "amdvold"
|
||||
version = "1.0.7"
|
||||
version = "1.0.8"
|
||||
edition = "2018"
|
||||
description = "AMDGPU fan control service"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@ -9,7 +9,8 @@ categories = ["hardware-support"]
|
||||
repository = "https://github.com/Eraden/amdgpud"
|
||||
|
||||
[dependencies]
|
||||
amdgpu = { path = "../amdgpu", version = "1.0.7" }
|
||||
amdgpu = { path = "../amdgpu", version = "1.0.8" }
|
||||
amdgpu-config = { path = "../amdgpu-config", version = "1.0.8", features = ["voltage"] }
|
||||
|
||||
serde = { version = "1.0.126", features = ["derive"] }
|
||||
toml = { version = "0.5.8" }
|
||||
@ -21,3 +22,4 @@ pretty_env_logger = { version = "0.4.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
amdgpu = { path = "../amdgpu", version = "1.0" }
|
||||
amdgpu-config = { path = "../amdgpu-config", version = "1.0", features = ["voltage"] }
|
||||
|
51
amdvold/README.md
Normal file
51
amdvold/README.md
Normal file
@ -0,0 +1,51 @@
|
||||
# AMD graphic card voltage manager
|
||||
|
||||
This tool can be used to overclock you AMD graphic card on Linux
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
cargo install amdvold
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Available commands:
|
||||
|
||||
* `setup-info` - prints information how to enable voltage management on Linux (see Requirements)
|
||||
* `print-states` - prints current card states
|
||||
* `change-state` - change card voltage states
|
||||
* `apply-changes` - apply changes
|
||||
|
||||
## Changing states
|
||||
|
||||
Positional arguments:
|
||||
* `index` Profile number
|
||||
* `module` Either memory or engine
|
||||
* `frequency` New GPU module frequency
|
||||
* `voltage` New GPU module voltage
|
||||
|
||||
Optional arguments:
|
||||
* `-a`, `--apply-immediately` Apply changes immediately after change
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
amdvold 1 engine 1450MHz 772mV
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
To enable AMD GPU voltage manipulation kernel parameter must be added, please do one of the following:
|
||||
|
||||
* In GRUB add to "GRUB_CMDLINE_LINUX_DEFAULT" following text "amdgpu.ppfeaturemask=0xffffffff", example:
|
||||
|
||||
GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 cryptdevice=/dev/nvme0n1p3:cryptroot amdgpu.ppfeaturemask=0xffffffff psi=1"
|
||||
|
||||
Easiest way is to modify "/etc/default/grub" and generate new grub config.
|
||||
|
||||
* If you have hooks enabled add in "/etc/modprobe.d/amdgpu.conf" to "options" following text "amdgpu.ppfeaturemask=0xffffffff", example:
|
||||
|
||||
options amdgpu si_support=1 cik_support=1 vm_fragment_size=9 audio=0 dc=0 aspm=0 ppfeaturemask=0xffffffff
|
||||
|
||||
(only "ppfeaturemask=0xffffffff" is required and if you don't have "options amdgpu" you can just add "options amdgpu ppfeaturemask=0xffffffff")
|
@ -1,6 +1,7 @@
|
||||
use amdgpu::utils::hw_mons;
|
||||
|
||||
use crate::command::VoltageManipulator;
|
||||
use crate::{Config, VoltageError};
|
||||
use amdgpu::utils::hw_mons;
|
||||
|
||||
#[derive(Debug, gumdrop::Options)]
|
||||
pub struct ApplyChanges {
|
||||
|
@ -1,7 +1,8 @@
|
||||
use amdgpu::utils::hw_mons;
|
||||
|
||||
use crate::clock_state::{Frequency, Voltage};
|
||||
use crate::command::{HardwareModule, VoltageManipulator};
|
||||
use crate::{Config, VoltageError};
|
||||
use amdgpu::utils::hw_mons;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ChangeStateError {
|
||||
@ -17,6 +18,8 @@ pub enum ChangeStateError {
|
||||
|
||||
#[derive(Debug, gumdrop::Options)]
|
||||
pub struct ChangeState {
|
||||
#[options(help = "Help message")]
|
||||
help: bool,
|
||||
#[options(help = "Profile number", free)]
|
||||
index: u16,
|
||||
#[options(help = "Either memory or engine", free)]
|
||||
@ -36,6 +39,7 @@ pub fn run(command: ChangeState, config: &Config) -> crate::Result<()> {
|
||||
}
|
||||
let mon = mons.remove(0);
|
||||
let ChangeState {
|
||||
help: _,
|
||||
index,
|
||||
module,
|
||||
frequency,
|
||||
|
@ -385,47 +385,47 @@ VDDC_CURVE_VOLT[2]: 750mV 1200mV
|
||||
CurvePoint {
|
||||
freq: Frequency {
|
||||
value: 800,
|
||||
unit: "MHz".to_string()
|
||||
unit: "MHz".to_string(),
|
||||
},
|
||||
voltage: Voltage {
|
||||
value: 706,
|
||||
unit: "mV".to_string()
|
||||
}
|
||||
unit: "mV".to_string(),
|
||||
},
|
||||
},
|
||||
CurvePoint {
|
||||
freq: Frequency {
|
||||
value: 1450,
|
||||
unit: "MHz".to_string()
|
||||
unit: "MHz".to_string(),
|
||||
},
|
||||
voltage: Voltage {
|
||||
value: 772,
|
||||
unit: "mV".to_string()
|
||||
}
|
||||
unit: "mV".to_string(),
|
||||
},
|
||||
},
|
||||
CurvePoint {
|
||||
freq: Frequency {
|
||||
value: 2100,
|
||||
unit: "MHz".to_string()
|
||||
unit: "MHz".to_string(),
|
||||
},
|
||||
voltage: Voltage {
|
||||
value: 1143,
|
||||
unit: "mV".to_string()
|
||||
}
|
||||
}
|
||||
unit: "mV".to_string(),
|
||||
},
|
||||
},
|
||||
],
|
||||
engine_label_lowest: Some(Frequency {
|
||||
value: 800,
|
||||
unit: "Mhz".to_string()
|
||||
unit: "Mhz".to_string(),
|
||||
}),
|
||||
engine_label_highest: Some(Frequency {
|
||||
value: 2100,
|
||||
unit: "Mhz".to_string()
|
||||
unit: "Mhz".to_string(),
|
||||
}),
|
||||
memory_label_lowest: Some(Frequency {
|
||||
value: 875,
|
||||
unit: "MHz".to_string()
|
||||
unit: "MHz".to_string(),
|
||||
}),
|
||||
memory_label_highest: None
|
||||
memory_label_highest: None,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
use amdgpu::hw_mon::HwMon;
|
||||
|
||||
use crate::apply_changes::ApplyChanges;
|
||||
use crate::change_state::ChangeState;
|
||||
use crate::clock_state::{ClockState, Frequency, Voltage};
|
||||
use crate::print_states::PrintStates;
|
||||
use crate::setup_info::SetupInfo;
|
||||
use crate::{Config, VoltageError};
|
||||
use amdgpu::hw_mon::HwMon;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum HardwareModule {
|
||||
|
@ -1,40 +0,0 @@
|
||||
use amdgpu::LogLevel;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ConfigError {}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Config {
|
||||
log_level: LogLevel,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
log_level: LogLevel::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn log_level(&self) -> LogLevel {
|
||||
self.log_level
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_config(config_path: &str) -> crate::Result<Config> {
|
||||
let config = match std::fs::read_to_string(config_path) {
|
||||
Ok(s) => toml::from_str(&s).unwrap(),
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => {
|
||||
let config = Config::default();
|
||||
std::fs::write(config_path, toml::to_string(&config).unwrap())?;
|
||||
config
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{:?}", e);
|
||||
panic!();
|
||||
}
|
||||
};
|
||||
Ok(config)
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
use amdgpu::{utils, AmdGpuError};
|
||||
use amdgpu_config::voltage::ConfigError;
|
||||
|
||||
use crate::change_state::ChangeStateError;
|
||||
use crate::clock_state::ClockStateError;
|
||||
use crate::config::ConfigError;
|
||||
use amdgpu::AmdGpuError;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum VoltageError {
|
||||
@ -19,4 +20,6 @@ pub enum VoltageError {
|
||||
ClockState(#[from] ClockStateError),
|
||||
#[error("{0:}")]
|
||||
ChangeStateError(#[from] ChangeStateError),
|
||||
#[error("{0:}")]
|
||||
AmdUtils(#[from] utils::AmdGpuError),
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
use crate::command::VoltageCommand;
|
||||
use crate::config::{load_config, Config};
|
||||
use crate::error::VoltageError;
|
||||
use amdgpu::CONFIG_DIR;
|
||||
use gumdrop::Options;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
use amdgpu::utils::ensure_config_dir;
|
||||
use amdgpu_config::voltage::{load_config, Config};
|
||||
|
||||
use crate::command::VoltageCommand;
|
||||
use crate::error::VoltageError;
|
||||
|
||||
mod apply_changes;
|
||||
mod change_state;
|
||||
mod clock_state;
|
||||
mod command;
|
||||
mod config;
|
||||
mod error;
|
||||
mod print_states;
|
||||
mod setup_info;
|
||||
@ -29,6 +29,7 @@ pub struct Opts {
|
||||
#[options(command)]
|
||||
command: Option<command::VoltageCommand>,
|
||||
}
|
||||
|
||||
fn run(config: Config) -> Result<()> {
|
||||
let opts: Opts = Opts::parse_args_default_or_exit();
|
||||
|
||||
@ -54,9 +55,7 @@ fn setup() -> Result<(String, Config)> {
|
||||
std::env::set_var("RUST_LOG", "DEBUG");
|
||||
}
|
||||
pretty_env_logger::init();
|
||||
if std::fs::read(CONFIG_DIR).map_err(|e| e.kind() == ErrorKind::NotFound) == Err(true) {
|
||||
std::fs::create_dir_all(CONFIG_DIR)?;
|
||||
}
|
||||
ensure_config_dir()?;
|
||||
|
||||
let config_path = Opts::parse_args_default_or_exit()
|
||||
.config
|
||||
|
@ -1,6 +1,7 @@
|
||||
use amdgpu::utils::hw_mons;
|
||||
|
||||
use crate::command::VoltageManipulator;
|
||||
use crate::Config;
|
||||
use amdgpu::utils::hw_mons;
|
||||
|
||||
#[derive(Debug, gumdrop::Options)]
|
||||
pub struct PrintStates {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::config::Config;
|
||||
use amdgpu_config::voltage::Config;
|
||||
|
||||
pub static ENABLE_VOLTAGE_INFO: &str = include_str!("../assets/enable_voltage_info.txt");
|
||||
|
||||
|
9
build.sh
9
build.sh
@ -1,6 +1,11 @@
|
||||
#!/usr/bin/env zsh
|
||||
|
||||
cargo build --release
|
||||
strip target/x86_64-unknown-linux-musl/release/amdfand
|
||||
upx --best --lzma target/x86_64-unknown-linux-musl/release/amdfand
|
||||
|
||||
strip target/x86_64-unknown-linux-musl/release/amdfand
|
||||
strip target/x86_64-unknown-linux-musl/release/amdvold
|
||||
strip target/x86_64-unknown-linux-musl/release/amdmond
|
||||
|
||||
upx --best --lzma target/x86_64-unknown-linux-musl/release/amdfand
|
||||
upx --best --lzma target/x86_64-unknown-linux-musl/release/amdvold
|
||||
upx --best --lzma target/x86_64-unknown-linux-musl/release/amdmond
|
||||
|
18
publish.sh
Executable file
18
publish.sh
Executable file
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env zsh
|
||||
|
||||
git_root() { echo "$(git rev-parse --show-toplevel)" }
|
||||
|
||||
cd $(git_root)/amdgpu
|
||||
cargo publish
|
||||
|
||||
cd $(git_root)/amdgpu-config
|
||||
cargo publish
|
||||
|
||||
cd $(git_root)/amdfand
|
||||
cargo publish
|
||||
|
||||
cd $(git_root)/amdvold
|
||||
cargo publish
|
||||
|
||||
cd $(git_root)/amdmond
|
||||
cargo publish
|
@ -5,6 +5,7 @@ pidfile="/run/${SVCNAME}.pid"
|
||||
command="/usr/bin/amdfand"
|
||||
command_args="service"
|
||||
command_user="root"
|
||||
command_background=true
|
||||
|
||||
depend() {
|
||||
need udev
|
||||
|
12
services/amdmond
Executable file
12
services/amdmond
Executable file
@ -0,0 +1,12 @@
|
||||
#!/sbin/openrc-run
|
||||
|
||||
description="AMD card monitoring tool."
|
||||
pidfile="/run/${SVCNAME}.pid"
|
||||
command="/usr/bin/amdfand log_file -s /var/log/amdmon.csv"
|
||||
command_args="service"
|
||||
command_user="root"
|
||||
command_background=true
|
||||
|
||||
depend() {
|
||||
need udev
|
||||
}
|
9
services/amdmond.service
Normal file
9
services/amdmond.service
Normal file
@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=AMD GPU monitoring tool
|
||||
After=sysinit.target local-fs.target
|
||||
[Service]
|
||||
Restart=on-failure
|
||||
RestartSec=4
|
||||
ExecStart=/usr/bin/amdmond log_file -s /var/log/amdmon.csv
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -5,6 +5,7 @@ pidfile="/run/${SVCNAME}.pid"
|
||||
command="/usr/bin/amdvold"
|
||||
command_args="service"
|
||||
command_user="root"
|
||||
command_background=true
|
||||
|
||||
depend() {
|
||||
need udev
|
||||
|
Loading…
Reference in New Issue
Block a user