feat: proper intel vram reporting

This commit is contained in:
Ilya Zlobintsev 2025-01-13 17:53:25 +02:00
parent d2debac2ff
commit 8d00a246b5
5 changed files with 384 additions and 214 deletions

View File

@ -1,2 +1,3 @@
#include "drm/xe_drm.h"
#include <libdrm/i915_drm.h>
#include <libdrm/intel_bufmgr.h>

View File

@ -1,5 +1,14 @@
mod drm;
use super::{CommonControllerInfo, GpuController};
use crate::{bindings::intel::IntelDrm, config, server::vulkan::get_vulkan_info};
use crate::{
bindings::intel::{
drm_i915_gem_memory_class_I915_MEMORY_CLASS_DEVICE,
drm_xe_memory_class_DRM_XE_MEM_REGION_CLASS_VRAM, IntelDrm,
},
config,
server::vulkan::get_vulkan_info,
};
use amdgpu_sysfs::{gpu_handle::power_profile_mode::PowerProfileModesTable, hw_mon::Temperature};
use anyhow::{anyhow, Context};
use futures::future::LocalBoxFuture;
@ -116,200 +125,7 @@ impl IntelGpuController {
Ok(controller)
}
}
impl GpuController for IntelGpuController {
fn controller_info(&self) -> &CommonControllerInfo {
&self.common
}
fn get_info(&self) -> DeviceInfo {
let vulkan_info = match get_vulkan_info(&self.common.pci_info) {
Ok(info) => Some(info),
Err(err) => {
warn!("could not load vulkan info: {err}");
None
}
};
let drm_info = DrmInfo {
intel: match self.driver_type {
DriverType::I915 => self.get_drm_info_i915(),
DriverType::Xe => self.get_drm_info_xe(),
},
vram_clock_ratio: 1.0,
..Default::default()
};
DeviceInfo {
pci_info: Some(self.common.pci_info.clone()),
vulkan_info,
driver: self.common.driver.clone(),
vbios_version: None,
link_info: LinkInfo::default(),
drm_info: Some(drm_info),
}
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn apply_config<'a>(
&'a self,
config: &'a config::Gpu,
) -> LocalBoxFuture<'a, anyhow::Result<()>> {
Box::pin(async {
if let Some(max_clock) = config.clocks_configuration.max_core_clock {
self.write_freq(FrequencyType::Max, max_clock)
.context("Could not set max clock")?;
}
if let Some(min_clock) = config.clocks_configuration.min_core_clock {
self.write_freq(FrequencyType::Min, min_clock)
.context("Could not set min clock")?;
}
if let Some(cap) = config.power_cap {
self.write_hwmon_file("power", "_max", &((cap * 1_000_000.0) as u64).to_string())
.context("Could not set power cap")?;
}
Ok(())
})
}
fn get_stats(&self, _gpu_config: Option<&config::Gpu>) -> DeviceStats {
let current_gfxclk = self.read_freq(FrequencyType::Cur);
let gpu_clockspeed = self
.read_freq(FrequencyType::Act)
.filter(|value| *value != 0)
.or(current_gfxclk);
let clockspeed = ClockspeedStats {
gpu_clockspeed,
current_gfxclk,
vram_clockspeed: None,
};
let cap_current = self
.read_hwmon_file("power", "_max")
.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>("power", "_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("in", "_input"),
northbridge: None,
};
let vram = VramStats {
total: self
.drm_try_2(IntelDrm::drm_intel_get_aperture_sizes)
.map(|(_, total)| total as u64),
used: None,
};
let fan = FanStats {
speed_current: self.read_hwmon_file("fan", "_input"),
..Default::default()
};
DeviceStats {
clockspeed,
vram,
busy_percent: self.get_busy_percent(),
power,
temps: self.get_temperatures(),
voltage,
throttle_info: self.get_throttle_info(),
fan,
..Default::default()
}
}
fn get_clocks_info(&self) -> anyhow::Result<ClocksInfo> {
let clocks_table = IntelClocksTable {
gt_freq: self
.read_freq(FrequencyType::Min)
.zip(self.read_freq(FrequencyType::Max)),
rp0_freq: self.read_freq(FrequencyType::Rp0),
rpe_freq: self.read_freq(FrequencyType::Rpe),
rpn_freq: self.read_freq(FrequencyType::Rpn),
};
let table = if clocks_table == IntelClocksTable::default() {
None
} else {
Some(ClocksTable::Intel(clocks_table))
};
Ok(ClocksInfo {
table,
..Default::default()
})
}
fn get_power_states(&self, _gpu_config: Option<&config::Gpu>) -> PowerStates {
let core = [
FrequencyType::Rpn,
FrequencyType::Rpe,
FrequencyType::Rp0,
FrequencyType::Boost,
]
.into_iter()
.filter_map(|freq_type| {
let value = self.read_freq(freq_type)?;
Some(PowerState {
enabled: true,
min_value: None,
value,
index: None,
})
})
.collect();
PowerStates { core, vram: vec![] }
}
fn reset_pmfw_settings(&self) {}
#[allow(clippy::cast_possible_truncation)]
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(())
}
fn get_power_profile_modes(&self) -> anyhow::Result<PowerProfileModesTable> {
Err(anyhow!("Not supported"))
}
fn vbios_dump(&self) -> anyhow::Result<Vec<u8>> {
Err(anyhow!("Not supported"))
}
}
impl IntelGpuController {
#[allow(clippy::unused_self)]
fn debugfs_path(&self) -> PathBuf {
#[cfg(test)]
@ -436,26 +252,6 @@ impl IntelGpuController {
}
}
#[cfg_attr(test, allow(unreachable_code, unused_variables))]
fn drm_try_2<T: Default, O: Default>(
&self,
f: unsafe fn(&IntelDrm, c_int, *mut T, *mut O) -> c_int,
) -> Option<(T, O)> {
#[cfg(test)]
return None;
unsafe {
let mut a = T::default();
let mut b = O::default();
let result = f(&self.drm, self.drm_file.as_raw_fd(), &mut a, &mut b);
if result == 0 {
Some((a, b))
} else {
None
}
}
}
#[allow(
clippy::cast_precision_loss,
clippy::cast_sign_loss,
@ -624,6 +420,250 @@ impl IntelGpuController {
}
Some(reasons)
}
fn get_vram_stats(&self) -> VramStats {
let mut total = None;
let mut used = None;
match self.driver_type {
DriverType::I915 => {
if let Ok(Some(query)) = drm::i915::query_memory_regions(&self.drm_file) {
let mut i915_total = 0;
let mut i915_unallocated = 0;
unsafe {
let regions = query.regions.as_slice(query.num_regions as usize);
for region_info in regions {
if u32::from(region_info.region.memory_class)
== drm_i915_gem_memory_class_I915_MEMORY_CLASS_DEVICE
{
i915_total += region_info.probed_size;
i915_unallocated += region_info.unallocated_size;
}
}
}
if i915_total > 0 {
total = Some(i915_total);
}
if i915_total > 0 {
used = Some(i915_total - i915_unallocated);
}
}
}
DriverType::Xe => {
if let Ok(Some(query)) = drm::xe::query_mem_regions(&self.drm_file) {
let mut xe_total = 0;
let mut xe_used = 0;
unsafe {
let regions = query.mem_regions.as_slice(query.num_mem_regions as usize);
for region_info in regions {
if u32::from(region_info.mem_class)
== drm_xe_memory_class_DRM_XE_MEM_REGION_CLASS_VRAM
{
xe_total += region_info.total_size;
xe_used += region_info.used;
}
}
}
if xe_used > 0 {
total = Some(xe_total);
}
if xe_used > 0 {
used = Some(xe_used);
}
}
}
}
VramStats { total, used }
}
}
impl GpuController for IntelGpuController {
fn controller_info(&self) -> &CommonControllerInfo {
&self.common
}
fn get_info(&self) -> DeviceInfo {
let vulkan_info = match get_vulkan_info(&self.common.pci_info) {
Ok(info) => Some(info),
Err(err) => {
warn!("could not load vulkan info: {err}");
None
}
};
let drm_info = DrmInfo {
intel: match self.driver_type {
DriverType::I915 => self.get_drm_info_i915(),
DriverType::Xe => self.get_drm_info_xe(),
},
vram_clock_ratio: 1.0,
..Default::default()
};
DeviceInfo {
pci_info: Some(self.common.pci_info.clone()),
vulkan_info,
driver: self.common.driver.clone(),
vbios_version: None,
link_info: LinkInfo::default(),
drm_info: Some(drm_info),
}
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn apply_config<'a>(
&'a self,
config: &'a config::Gpu,
) -> LocalBoxFuture<'a, anyhow::Result<()>> {
Box::pin(async {
if let Some(max_clock) = config.clocks_configuration.max_core_clock {
self.write_freq(FrequencyType::Max, max_clock)
.context("Could not set max clock")?;
}
if let Some(min_clock) = config.clocks_configuration.min_core_clock {
self.write_freq(FrequencyType::Min, min_clock)
.context("Could not set min clock")?;
}
if let Some(cap) = config.power_cap {
self.write_hwmon_file("power", "_max", &((cap * 1_000_000.0) as u64).to_string())
.context("Could not set power cap")?;
}
Ok(())
})
}
fn get_stats(&self, _gpu_config: Option<&config::Gpu>) -> DeviceStats {
let current_gfxclk = self.read_freq(FrequencyType::Cur);
let gpu_clockspeed = self
.read_freq(FrequencyType::Act)
.filter(|value| *value != 0)
.or(current_gfxclk);
let clockspeed = ClockspeedStats {
gpu_clockspeed,
current_gfxclk,
vram_clockspeed: None,
};
let cap_current = self
.read_hwmon_file("power", "_max")
.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>("power", "_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("in", "_input"),
northbridge: None,
};
let fan = FanStats {
speed_current: self.read_hwmon_file("fan", "_input"),
..Default::default()
};
DeviceStats {
clockspeed,
vram: self.get_vram_stats(),
busy_percent: self.get_busy_percent(),
power,
temps: self.get_temperatures(),
voltage,
throttle_info: self.get_throttle_info(),
fan,
..Default::default()
}
}
fn get_clocks_info(&self) -> anyhow::Result<ClocksInfo> {
let clocks_table = IntelClocksTable {
gt_freq: self
.read_freq(FrequencyType::Min)
.zip(self.read_freq(FrequencyType::Max)),
rp0_freq: self.read_freq(FrequencyType::Rp0),
rpe_freq: self.read_freq(FrequencyType::Rpe),
rpn_freq: self.read_freq(FrequencyType::Rpn),
};
let table = if clocks_table == IntelClocksTable::default() {
None
} else {
Some(ClocksTable::Intel(clocks_table))
};
Ok(ClocksInfo {
table,
..Default::default()
})
}
fn get_power_states(&self, _gpu_config: Option<&config::Gpu>) -> PowerStates {
let core = [
FrequencyType::Rpn,
FrequencyType::Rpe,
FrequencyType::Rp0,
FrequencyType::Boost,
]
.into_iter()
.filter_map(|freq_type| {
let value = self.read_freq(freq_type)?;
Some(PowerState {
enabled: true,
min_value: None,
value,
index: None,
})
})
.collect();
PowerStates { core, vram: vec![] }
}
fn reset_pmfw_settings(&self) {}
#[allow(clippy::cast_possible_truncation)]
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(())
}
fn get_power_profile_modes(&self) -> anyhow::Result<PowerProfileModesTable> {
Err(anyhow!("Not supported"))
}
fn vbios_dump(&self) -> anyhow::Result<Vec<u8>> {
Err(anyhow!("Not supported"))
}
}
#[derive(Clone, Copy)]

View File

@ -0,0 +1,25 @@
pub mod i915;
pub mod xe;
use std::{alloc, ops::Deref};
pub struct DrmBox<T> {
data: *const T,
layout: alloc::Layout,
}
impl<T> Deref for DrmBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &(*self.data) }
}
}
impl<T> Drop for DrmBox<T> {
fn drop(&mut self) {
unsafe {
alloc::dealloc(self.data as *mut u8, self.layout);
}
}
}

View File

@ -0,0 +1,60 @@
#![allow(
clippy::cast_lossless,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
use super::DrmBox;
use crate::bindings::intel::drm_i915_query_item;
use crate::bindings::intel::{
drm_i915_query, drm_i915_query_memory_regions, DRM_COMMAND_BASE, DRM_I915_QUERY_MEMORY_REGIONS,
DRM_IOCTL_BASE,
};
use nix::{errno::Errno, ioctl_readwrite};
use std::{alloc, fs::File, mem, os::fd::AsRawFd, ptr};
const DRM_I915_QUERY: u32 = 0x39;
ioctl_readwrite!(
i915_query,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_I915_QUERY,
drm_i915_query
);
unsafe fn query_item<T>(fd: i32, query_id: u32) -> Result<Option<DrmBox<T>>, Errno> {
let mut query_item = drm_i915_query_item {
query_id: query_id as u64,
length: 0,
flags: 0,
data_ptr: 0,
};
let mut query = drm_i915_query {
num_items: 1,
flags: 0,
items_ptr: ptr::from_ref(&query_item) as u64,
};
i915_query(fd, &mut query)?;
if query_item.length <= 0 {
return Ok(None);
}
let layout =
alloc::Layout::from_size_align(query_item.length as usize, mem::align_of::<T>()).unwrap();
#[allow(clippy::cast_ptr_alignment)]
let data = alloc::alloc_zeroed(layout) as *const T;
query_item.data_ptr = data as u64;
i915_query(fd, &mut query)?;
Ok(Some(DrmBox { data, layout }))
}
pub fn query_memory_regions(
fd: &File,
) -> Result<Option<DrmBox<drm_i915_query_memory_regions>>, Errno> {
unsafe { query_item(fd.as_raw_fd(), DRM_I915_QUERY_MEMORY_REGIONS) }
}

View File

@ -0,0 +1,44 @@
use crate::bindings::intel::{
drm_xe_device_query, drm_xe_query_mem_regions, DRM_COMMAND_BASE, DRM_IOCTL_BASE,
DRM_XE_DEVICE_QUERY, DRM_XE_DEVICE_QUERY_MEM_REGIONS,
};
use nix::{errno::Errno, ioctl_readwrite};
use std::{alloc, fs::File, mem, os::fd::AsRawFd};
use super::DrmBox;
ioctl_readwrite!(
xe_device_query,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_XE_DEVICE_QUERY,
drm_xe_device_query
);
unsafe fn query_item<T>(fd: i32, query_id: u32) -> Result<Option<DrmBox<T>>, Errno> {
let mut query = drm_xe_device_query {
query: query_id,
size: 0,
data: 0,
extensions: 0,
reserved: [0, 0],
};
xe_device_query(fd, &mut query)?;
if query.size == 0 {
return Ok(None);
}
let layout = alloc::Layout::from_size_align(query.size as usize, mem::align_of::<T>()).unwrap();
#[allow(clippy::cast_ptr_alignment)]
let data = alloc::alloc_zeroed(layout) as *const T;
query.data = data as u64;
xe_device_query(fd, &mut query)?;
Ok(Some(DrmBox { data, layout }))
}
pub fn query_mem_regions(fd: &File) -> Result<Option<DrmBox<drm_xe_query_mem_regions>>, Errno> {
unsafe { query_item(fd.as_raw_fd(), DRM_XE_DEVICE_QUERY_MEM_REGIONS) }
}