feat: numerous i915 additions

This commit is contained in:
Ilya Zlobintsev 2025-01-11 11:07:41 +02:00
parent 543265ddb2
commit c5d2996a30
66 changed files with 409 additions and 101 deletions

View File

@ -377,11 +377,12 @@ impl AmdGpuController {
.context("GPU has no hardware monitor") .context("GPU has no hardware monitor")
} }
fn get_current_gfxclk(&self) -> Option<u16> { fn get_current_gfxclk(&self) -> Option<u64> {
self.drm_handle self.drm_handle
.as_ref() .as_ref()
.and_then(|drm_handle| drm_handle.get_gpu_metrics().ok()) .and_then(|drm_handle| drm_handle.get_gpu_metrics().ok())
.and_then(|metrics| metrics.get_current_gfxclk()) .and_then(|metrics| metrics.get_current_gfxclk())
.map(u64::from)
} }
fn get_full_vbios_version(&self) -> Option<String> { fn get_full_vbios_version(&self) -> Option<String> {

View File

@ -1,14 +1,15 @@
use super::{CommonControllerInfo, GpuController}; use super::{CommonControllerInfo, GpuController};
use crate::{bindings::intel::IntelDrm, config, server::vulkan::get_vulkan_info}; use crate::{bindings::intel::IntelDrm, config, server::vulkan::get_vulkan_info};
use amdgpu_sysfs::gpu_handle::power_profile_mode::PowerProfileModesTable; use amdgpu_sysfs::{gpu_handle::power_profile_mode::PowerProfileModesTable, hw_mon::Temperature};
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use futures::future::LocalBoxFuture; use futures::future::LocalBoxFuture;
use lact_schema::{ use lact_schema::{
ClocksInfo, ClocksTable, ClockspeedStats, DeviceInfo, DeviceStats, DrmInfo, IntelClocksTable, ClocksInfo, ClocksTable, ClockspeedStats, DeviceInfo, DeviceStats, DrmInfo, IntelClocksTable,
IntelDrmInfo, LinkInfo, PowerStates, VramStats, IntelDrmInfo, LinkInfo, PowerStates, PowerStats, VoltageStats, VramStats,
}; };
use std::{ use std::{
cell::Cell, cell::Cell,
collections::{BTreeMap, HashMap},
fmt::Display, fmt::Display,
fs, fs,
io::{BufRead, BufReader}, io::{BufRead, BufReader},
@ -20,6 +21,7 @@ use std::{
}; };
use tracing::{debug, error, info, trace, warn}; use tracing::{debug, error, info, trace, warn};
#[derive(Clone, Copy)]
enum DriverType { enum DriverType {
I915, I915,
Xe, Xe,
@ -29,9 +31,12 @@ pub struct IntelGpuController {
driver_type: DriverType, driver_type: DriverType,
common: CommonControllerInfo, common: CommonControllerInfo,
tile_gts: Vec<PathBuf>, tile_gts: Vec<PathBuf>,
hwmon_path: Option<PathBuf>,
drm_file: fs::File, drm_file: fs::File,
drm: Rc<IntelDrm>, drm: Rc<IntelDrm>,
last_gpu_busy: Cell<Option<(Instant, u64)>>, last_gpu_busy: Cell<Option<(Instant, u64)>>,
last_energy_value: Cell<Option<(Instant, u64)>>,
initial_power_cap: Option<f64>,
} }
impl IntelGpuController { impl IntelGpuController {
@ -86,14 +91,29 @@ impl IntelGpuController {
fs::File::open("/dev/null").unwrap() fs::File::open("/dev/null").unwrap()
}; };
Ok(Self { let hwmon_path = fs::read_dir(common.sysfs_path.join("hwmon"))
.ok()
.and_then(|mut read_dir| read_dir.next())
.and_then(Result::ok)
.map(|entry| entry.path());
debug!("Initialized hwmon: {hwmon_path:?}");
let mut controller = Self {
common, common,
driver_type, driver_type,
tile_gts, tile_gts,
hwmon_path,
drm_file, drm_file,
drm, drm,
last_gpu_busy: Cell::new(None), last_gpu_busy: Cell::new(None),
}) last_energy_value: Cell::new(None),
initial_power_cap: None,
};
let stats = controller.get_stats(None);
controller.initial_power_cap = stats.power.cap_current.filter(|cap| *cap != 0.0);
Ok(controller)
} }
} }
@ -130,32 +150,25 @@ impl GpuController for IntelGpuController {
} }
} }
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn apply_config<'a>( fn apply_config<'a>(
&'a self, &'a self,
config: &'a config::Gpu, config: &'a config::Gpu,
) -> LocalBoxFuture<'a, anyhow::Result<()>> { ) -> LocalBoxFuture<'a, anyhow::Result<()>> {
Box::pin(async { Box::pin(async {
match self.driver_type {
DriverType::Xe => {
if let Some(max_clock) = config.clocks_configuration.max_core_clock { if let Some(max_clock) = config.clocks_configuration.max_core_clock {
self.write_gt_file("freq0/max_freq", &max_clock.to_string()) self.write_freq(FrequencyType::Max, max_clock)
.context("Could not set max clock")?; .context("Could not set max clock")?;
} }
if let Some(min_clock) = config.clocks_configuration.min_core_clock { if let Some(min_clock) = config.clocks_configuration.min_core_clock {
self.write_gt_file("freq0/min_freq", &min_clock.to_string()) self.write_freq(FrequencyType::Min, min_clock)
.context("Could not set min clock")?; .context("Could not set min clock")?;
} }
}
DriverType::I915 => { if let Some(cap) = config.power_cap {
if let Some(max_clock) = config.clocks_configuration.max_core_clock { self.write_hwmon_file("power1_max", &((cap * 1_000_000.0) as u64).to_string())
self.write_file("../gt_max_freq_mhz", &max_clock.to_string()) .context("Could not set power cap")?;
.context("Could not set max clock")?;
}
if let Some(min_clock) = config.clocks_configuration.min_core_clock {
self.write_file("../gt_min_freq_mhz", &min_clock.to_string())
.context("Could not set min clock")?;
}
}
} }
Ok(()) Ok(())
@ -163,22 +176,11 @@ impl GpuController for IntelGpuController {
} }
fn get_stats(&self, _gpu_config: Option<&config::Gpu>) -> DeviceStats { fn get_stats(&self, _gpu_config: Option<&config::Gpu>) -> DeviceStats {
let current_gfxclk; let current_gfxclk = self.read_freq(FrequencyType::Cur);
let gpu_clockspeed; let gpu_clockspeed = self
.read_freq(FrequencyType::Act)
match self.driver_type { .filter(|value| *value != 0)
DriverType::Xe => { .or(current_gfxclk);
current_gfxclk = self.read_gt_file("freq0/cur_freq");
gpu_clockspeed = self
.read_gt_file("freq0/act_freq")
.filter(|freq| *freq != 0)
.or_else(|| current_gfxclk.map(u64::from));
}
DriverType::I915 => {
current_gfxclk = self.read_file("../gt_cur_freq_mhz");
gpu_clockspeed = self.read_file("../gt_act_freq_mhz");
}
}
let clockspeed = ClockspeedStats { let clockspeed = ClockspeedStats {
gpu_clockspeed, gpu_clockspeed,
@ -186,37 +188,56 @@ impl GpuController for IntelGpuController {
vram_clockspeed: None, vram_clockspeed: None,
}; };
DeviceStats { let cap_current = self
clockspeed, .read_hwmon_file("power1_max")
vram: VramStats { .map(|value: f64| value / 1_000_000.0)
.map(|cap| if cap == 0.0 { 100.0 } else { cap }); // Placeholder max value
let power = PowerStats {
average: None,
current: self.get_power_usage(),
cap_current,
cap_min: Some(0.0),
cap_max: self
.read_hwmon_file::<f64>("power1_rated_max")
.filter(|max| *max != 0.0)
.map(|cap| cap / 1_000_000.0)
.or_else(|| cap_current.map(|current| current * 2.0)),
cap_default: self.initial_power_cap,
};
let voltage = VoltageStats {
gpu: self.read_hwmon_file("in0_input"),
northbridge: None,
};
let vram = VramStats {
total: self total: self
.drm_try_2(IntelDrm::drm_intel_get_aperture_sizes) .drm_try_2(IntelDrm::drm_intel_get_aperture_sizes)
.map(|(_, total)| total as u64), .map(|(_, total)| total as u64),
used: None, used: None,
}, };
DeviceStats {
clockspeed,
vram,
busy_percent: self.get_busy_percent(), busy_percent: self.get_busy_percent(),
power,
temps: self.get_temperatures(),
voltage,
throttle_info: self.get_throttle_info(),
..Default::default() ..Default::default()
} }
} }
fn get_clocks_info(&self) -> anyhow::Result<ClocksInfo> { fn get_clocks_info(&self) -> anyhow::Result<ClocksInfo> {
let clocks_table = match self.driver_type { let clocks_table = IntelClocksTable {
DriverType::Xe => IntelClocksTable {
gt_freq: self gt_freq: self
.read_gt_file("freq0/min_freq") .read_freq(FrequencyType::Min)
.zip(self.read_gt_file("freq0/max_freq")), .zip(self.read_freq(FrequencyType::Max)),
rp0_freq: self.read_gt_file("freq0/rp0_freq"), rp0_freq: self.read_freq(FrequencyType::Rp0),
rpe_freq: self.read_gt_file("freq0/rpe_freq"), rpe_freq: self.read_freq(FrequencyType::Rpe),
rpn_freq: self.read_gt_file("freq0/rpn_freq"), rpn_freq: self.read_freq(FrequencyType::Rpn),
},
DriverType::I915 => IntelClocksTable {
gt_freq: self
.read_file("../gt_min_freq_mhz")
.zip(self.read_file("../gt_max_freq_mhz")),
rpn_freq: self.read_file("../gt_RPn_freq_mhz"),
rpe_freq: self.read_file("../gt_RP1_freq_mhz"),
rp0_freq: self.read_file("../gt_RP0_freq_mhz"),
},
}; };
let table = if clocks_table == IntelClocksTable::default() { let table = if clocks_table == IntelClocksTable::default() {
@ -237,7 +258,20 @@ impl GpuController for IntelGpuController {
fn reset_pmfw_settings(&self) {} fn reset_pmfw_settings(&self) {}
#[allow(clippy::cast_possible_truncation)]
fn cleanup_clocks(&self) -> anyhow::Result<()> { fn cleanup_clocks(&self) -> anyhow::Result<()> {
if let Some(rp0) = self.read_freq(FrequencyType::Rp0) {
if let Err(err) = self.write_freq(FrequencyType::Max, rp0 as i32) {
warn!("could not reset max clock: {err:#}");
}
}
if let Some(rpn) = self.read_freq(FrequencyType::Rpn) {
if let Err(err) = self.write_freq(FrequencyType::Min, rpn as i32) {
warn!("could not reset min clock: {err:#}");
}
}
Ok(()) Ok(())
} }
@ -264,29 +298,12 @@ impl IntelGpuController {
self.tile_gts.first().map(PathBuf::as_ref) self.tile_gts.first().map(PathBuf::as_ref)
} }
/// Based on the input path, this has the following behaviour:
/// - Basic relative paths are resolved relative to the sysfs device
/// - Parent paths (starting with (../) are resolved on the sysfs card entry, without resolving device symlink
fn sysfs_file_path(&self, path: impl AsRef<Path>) -> PathBuf {
let path = path.as_ref();
match path.strip_prefix("../") {
Ok(path_relative_to_parent) => self
.common
.sysfs_path
.parent()
.expect("Device path has no parent")
.join(path_relative_to_parent),
Err(_) => self.common.sysfs_path.join(path),
}
}
fn read_file<T>(&self, path: impl AsRef<Path>) -> Option<T> fn read_file<T>(&self, path: impl AsRef<Path>) -> Option<T>
where where
T: FromStr, T: FromStr,
T::Err: Display, T::Err: Display,
{ {
let file_path = self.sysfs_file_path(path); let file_path = self.common.sysfs_path.join(path);
trace!("reading file from '{}'", file_path.display()); trace!("reading file from '{}'", file_path.display());
@ -310,7 +327,7 @@ impl IntelGpuController {
} }
fn write_file(&self, path: impl AsRef<Path>, contents: &str) -> anyhow::Result<()> { fn write_file(&self, path: impl AsRef<Path>, contents: &str) -> anyhow::Result<()> {
let file_path = self.sysfs_file_path(path); let file_path = self.common.sysfs_path.join(path);
if file_path.exists() { if file_path.exists() {
fs::write(&file_path, contents) fs::write(&file_path, contents)
@ -321,23 +338,24 @@ impl IntelGpuController {
} }
} }
fn read_gt_file<T>(&self, file_name: &str) -> Option<T> fn read_hwmon_file<T>(&self, file_name: &str) -> Option<T>
where where
T: FromStr, T: FromStr,
T::Err: Display, T::Err: Display,
{ {
self.first_tile_gt().and_then(|gt_path| { self.hwmon_path.as_ref().and_then(|hwmon_path| {
let file_path = gt_path.join(file_name); let file_path = hwmon_path.join(file_name);
self.read_file(file_path) self.read_file(file_path)
}) })
} }
fn write_gt_file(&self, file_name: &str, contents: &str) -> anyhow::Result<()> { fn write_hwmon_file(&self, file_name: &str, contents: &str) -> anyhow::Result<()> {
if let Some(gt_path) = self.first_tile_gt() { debug!("writing value '{contents}' to '{file_name}'");
let file_path = gt_path.join(file_name); if let Some(hwmon_path) = &self.hwmon_path {
let file_path = hwmon_path.join(file_name);
self.write_file(file_path, contents) self.write_file(file_path, contents)
} else { } else {
Err(anyhow!("No GTs available")) Err(anyhow!("No hwmon available"))
} }
} }
@ -426,4 +444,128 @@ impl IntelGpuController {
None None
} }
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
fn get_power_usage(&self) -> Option<f64> {
self.read_hwmon_file::<u64>("power1_input")
.or_else(|| {
let energy = self.read_hwmon_file("energy1_input")?;
let timestamp = Instant::now();
match self.last_energy_value.replace(Some((timestamp, energy))) {
Some((last_timestamp, last_energy)) => {
let time_delta = timestamp - last_timestamp;
let energy_delta = energy - last_energy;
Some(energy_delta / time_delta.as_millis() as u64 * 1000)
}
None => None,
}
})
.map(|value| value as f64 / 1_000_000.0)
}
fn get_temperatures(&self) -> HashMap<String, Temperature> {
self.read_hwmon_file::<f32>("temp1_input")
.into_iter()
.map(|temp| {
let key = "gpu".to_owned();
let temperature = Temperature {
current: Some(temp / 1000.0),
crit: None,
crit_hyst: None,
};
(key, temperature)
})
.collect()
}
fn read_freq(&self, freq: FrequencyType) -> Option<u64> {
self.freq_path(freq).and_then(|path| self.read_file(&path))
}
fn write_freq(&self, freq: FrequencyType, value: i32) -> anyhow::Result<()> {
let path = self.freq_path(freq).context("Frequency info not found")?;
self.write_file(path, &value.to_string())
.context("Could not write frequency")?;
Ok(())
}
fn freq_path(&self, freq: FrequencyType) -> Option<PathBuf> {
let path = &self.common.sysfs_path;
match self.driver_type {
DriverType::I915 => {
let card_path = path.parent().expect("Device has no parent path");
let infix = match freq {
FrequencyType::Cur => "cur",
FrequencyType::Act => "act",
FrequencyType::Min => "min",
FrequencyType::Max => "max",
FrequencyType::Rp0 => "RP0",
FrequencyType::Rpe => "RP1",
FrequencyType::Rpn => "RPn",
};
Some(card_path.join(format!("gt_{infix}_freq_mhz")))
}
DriverType::Xe => self.first_tile_gt().map(|gt_path| {
let prefix = match freq {
FrequencyType::Cur => "cur",
FrequencyType::Act => "act",
FrequencyType::Min => "min",
FrequencyType::Max => "max",
FrequencyType::Rp0 => "rp0",
FrequencyType::Rpe => "rpe",
FrequencyType::Rpn => "rpn",
};
gt_path.join("freq0").join(format!("{prefix}_freq"))
}),
}
}
fn get_throttle_info(&self) -> Option<BTreeMap<String, Vec<String>>> {
match self.driver_type {
DriverType::I915 => {
let mut reasons = BTreeMap::new();
let card_path = self
.common
.sysfs_path
.parent()
.expect("Device has no parent path");
let gt_path = card_path.join("gt").join("gt0");
let gt_files = fs::read_dir(gt_path).ok()?;
for file in gt_files.flatten() {
if let Some(name) = file.file_name().to_str() {
if let Some(reason) = name.strip_prefix("throttle_reason_") {
if reason == "status" {
continue;
}
if let Some(value) = self.read_file::<i32>(file.path()) {
if value != 0 {
reasons.insert(reason.to_owned(), vec![]);
}
}
}
}
}
Some(reasons)
}
DriverType::Xe => None,
}
}
}
#[derive(Clone, Copy)]
enum FrequencyType {
Cur,
Act,
Min,
Max,
Rp0,
Rpe,
Rpn,
} }

View File

@ -0,0 +1 @@
226:1

View File

@ -0,0 +1 @@
2.5 GT/s PCIe

View File

@ -0,0 +1,6 @@
DRIVER=i915
PCI_CLASS=30000
PCI_ID=8086:56A5
PCI_SUBSYS_ID=1849:6004
PCI_SLOT_NAME=0000:0b:00.0
MODALIAS=pci:v00008086d000056A5sv00001849sd00006004bc03sc00i00

View File

@ -0,0 +1 @@
0x8086

View File

@ -0,0 +1 @@
No error state collected

View File

@ -0,0 +1 @@
0

View File

@ -0,0 +1 @@
2450

View File

@ -0,0 +1 @@
600

View File

@ -0,0 +1 @@
300

View File

@ -0,0 +1 @@
600

View File

@ -0,0 +1 @@
2450

View File

@ -0,0 +1 @@
600

View File

@ -0,0 +1 @@
2450

View File

@ -0,0 +1 @@
300

View File

@ -0,0 +1,4 @@
MAJOR=226
MINOR=1
DEVNAME=dri/card1
DEVTYPE=drm_minor

View File

@ -0,0 +1,92 @@
---
source: lact-daemon/src/tests/mod.rs
expression: device_info
---
{
"clocks_info": {
"table": {
"type": "intel",
"value": {
"gt_freq": [
300,
2450
],
"rp0_freq": 2450,
"rpe_freq": 600,
"rpn_freq": 300
}
}
},
"info": {
"driver": "i915",
"drm_info": {
"vram_clock_ratio": 1.0
},
"link_info": {},
"pci_info": {
"device_pci_info": {
"model": "DG2 [Arc A380]",
"model_id": "56A5",
"vendor": "Intel Corporation",
"vendor_id": "8086"
},
"subsystem_pci_info": {
"model_id": "6004",
"vendor": "ASRock Incorporation",
"vendor_id": "1849"
}
},
"vulkan_info": {
"api_version": "",
"device_name": "",
"driver": {
"version": 0
},
"enabled_layers": [],
"extensions": {},
"features": {}
}
},
"pci_info": {
"device_pci_info": {
"model": "DG2 [Arc A380]",
"model_id": "56A5",
"vendor": "Intel Corporation",
"vendor_id": "8086"
},
"subsystem_pci_info": {
"model_id": "6004",
"vendor": "ASRock Incorporation",
"vendor_id": "1849"
}
},
"power_profile_modes": null,
"stats": {
"clockspeed": {
"current_gfxclk": 600,
"gpu_clockspeed": 600
},
"fan": {
"control_enabled": false,
"pmfw_info": {}
},
"power": {
"cap_current": 55.0,
"cap_default": 55.0,
"cap_max": 110.0,
"cap_min": 0.0,
"current": 0.0
},
"temps": {
"gpu": {
"crit": null,
"crit_hyst": null,
"current": 55.0
}
},
"voltage": {
"gpu": 603
},
"vram": {}
}
}

View File

@ -70,7 +70,9 @@ expression: device_info
"control_enabled": false, "control_enabled": false,
"pmfw_info": {} "pmfw_info": {}
}, },
"power": {}, "power": {
"cap_min": 0.0
},
"temps": {}, "temps": {},
"voltage": {}, "voltage": {},
"vram": {} "vram": {}

View File

@ -70,7 +70,9 @@ expression: device_info
"control_enabled": false, "control_enabled": false,
"pmfw_info": {} "pmfw_info": {}
}, },
"power": {}, "power": {
"cap_min": 0.0
},
"temps": {}, "temps": {},
"voltage": {}, "voltage": {},
"vram": {} "vram": {}

View File

@ -169,14 +169,14 @@ fn format_clockspeed(value: Option<u64>, ratio: f64) -> String {
format!("{:.3} GHz", value.unwrap_or(0) as f64 / 1000.0 * ratio) format!("{:.3} GHz", value.unwrap_or(0) as f64 / 1000.0 * ratio)
} }
fn format_current_gfxclk(value: Option<u16>) -> String { fn format_current_gfxclk(value: Option<u64>) -> String {
if let Some(v) = value { if let Some(v) = value {
// if the APU/GPU dose not acually support current_gfxclk, // if the APU/GPU dose not acually support current_gfxclk,
// the value will be `u16::MAX (65535)` // the value will be `u16::MAX (65535)`
if v == u16::MAX { if v >= u16::MAX as u64 || v == 0 {
"N/A".to_string() "N/A".to_string()
} else { } else {
format_clockspeed(Some(v as u64), 1.0) format_clockspeed(Some(v), 1.0)
} }
} else { } else {
"N/A".to_string() "N/A".to_string()

View File

@ -89,6 +89,10 @@ mod imp {
let text = format!("{}/{} W", section.current_value(), section.max_value()); let text = format!("{}/{} W", section.current_value(), section.max_value());
section.set_value_text(text); section.set_value_text(text);
}); });
obj.connect_max_value_notify(move |section| {
let text = format!("{}/{} W", section.current_value(), section.max_value());
section.set_value_text(text);
});
self.reset_button.connect_clicked(clone!( self.reset_button.connect_clicked(clone!(
#[strong] #[strong]

View File

@ -169,13 +169,13 @@ pub struct NvidiaClocksTable {
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
pub struct IntelClocksTable { pub struct IntelClocksTable {
pub gt_freq: Option<(u32, u32)>, pub gt_freq: Option<(u64, u64)>,
/// - rpn_freq: The Render Performance (RP) N level, which is the minimal one. /// - rpn_freq: The Render Performance (RP) N level, which is the minimal one.
pub rpn_freq: Option<u32>, pub rpn_freq: Option<u64>,
/// - rpe_freq: The Render Performance (RP) E level, which is the efficient one. /// - rpe_freq: The Render Performance (RP) E level, which is the efficient one.
pub rpe_freq: Option<u32>, pub rpe_freq: Option<u64>,
/// - rp0_freq: The Render Performance (RP) 0 level, which is the maximum one. /// - rp0_freq: The Render Performance (RP) 0 level, which is the maximum one.
pub rp0_freq: Option<u32>, pub rp0_freq: Option<u64>,
} }
#[derive(Serialize, Deserialize, Default, Debug, Clone)] #[derive(Serialize, Deserialize, Default, Debug, Clone)]
@ -288,7 +288,7 @@ pub struct PmfwInfo {
pub struct ClockspeedStats { pub struct ClockspeedStats {
pub gpu_clockspeed: Option<u64>, pub gpu_clockspeed: Option<u64>,
/// Target clock /// Target clock
pub current_gfxclk: Option<u16>, pub current_gfxclk: Option<u64>,
pub vram_clockspeed: Option<u64>, pub vram_clockspeed: Option<u64>,
} }