Skip to content

Commit

Permalink
graphics: Get NVIDIA GPU features without driver loaded
Browse files Browse the repository at this point in the history
Store the device ID at PCI enumeration so it does not need to be fetched
from sysfs again later.

Search /usr/share/doc for the NVIDIA driver folder instead of getting
the version of the loaded driver.

Allows getting the dGPU features when the driver is unloaded and the PCI
device has been removed from the bus.

Signed-off-by: Tim Crawford <tcrawford@system76.com>
  • Loading branch information
crawfxrd committed Apr 11, 2022
1 parent 77def87 commit 6c20493
Showing 1 changed file with 46 additions and 28 deletions.
74 changes: 46 additions & 28 deletions src/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::{
fs,
io::{self, Write},
iter::FromIterator,
path,
process::{self, ExitStatus},
};
use sysfs_class::{PciDevice, SysClass};
Expand Down Expand Up @@ -96,16 +97,19 @@ pub enum GraphicsDeviceError {

pub struct GraphicsDevice {
id: String,
devid: u16,
functions: Vec<PciDevice>,
}

impl GraphicsDevice {
pub fn new(id: String, functions: Vec<PciDevice>) -> GraphicsDevice {
GraphicsDevice { id, functions }
pub fn new(id: String, devid: u16, functions: Vec<PciDevice>) -> GraphicsDevice {
GraphicsDevice { id, devid, functions }
}

pub fn exists(&self) -> bool { self.functions.iter().any(|func| func.path().exists()) }

pub fn device(&self) -> u16 { self.devid }

pub unsafe fn unbind(&self) -> Result<(), GraphicsDeviceError> {
for func in &self.functions {
if func.path().exists() {
Expand Down Expand Up @@ -236,19 +240,35 @@ impl Graphics {
match dev.vendor()? {
0x1002 => {
log::info!("{}: AMD graphics", dev.id());
amd.push(GraphicsDevice::new(dev.id().to_owned(), functions(dev)));
amd.push(GraphicsDevice::new(
dev.id().to_owned(),
dev.device()?,
functions(dev),
));
}
0x10DE => {
log::info!("{}: NVIDIA graphics", dev.id());
nvidia.push(GraphicsDevice::new(dev.id().to_owned(), functions(dev)));
nvidia.push(GraphicsDevice::new(
dev.id().to_owned(),
dev.device()?,
functions(dev),
));
}
0x8086 => {
log::info!("{}: Intel graphics", dev.id());
intel.push(GraphicsDevice::new(dev.id().to_owned(), functions(dev)));
intel.push(GraphicsDevice::new(
dev.id().to_owned(),
dev.device()?,
functions(dev),
));
}
vendor => {
log::info!("{}: Other({:X}) graphics", dev.id(), vendor);
other.push(GraphicsDevice::new(dev.id().to_owned(), functions(dev)));
other.push(GraphicsDevice::new(
dev.id().to_owned(),
dev.device()?,
functions(dev),
));
}
}
}
Expand All @@ -270,27 +290,25 @@ impl Graphics {
Ok(hotplug::REQUIRES_NVIDIA.contains(&model.trim()))
}

fn nvidia_version(&self) -> Result<String, GraphicsDeviceError> {
fs::read_to_string("/sys/module/nvidia/version")
.map_err(GraphicsDeviceError::SysFs)
.map(|s| s.trim().to_string())
}

fn get_nvidia_device_id(&self) -> Result<u32, GraphicsDeviceError> {
let device = format!("/sys/bus/pci/devices/{}/device", self.nvidia[0].id);
let id = fs::read_to_string(device).map_err(GraphicsDeviceError::SysFs)?;
let id = id.trim_start_matches("0x").trim();
u32::from_str_radix(id, 16).map_err(|e| {
GraphicsDeviceError::SysFs(io::Error::new(io::ErrorKind::InvalidData, e.to_string()))
})
}

fn get_nvidia_device(&self, id: u32) -> Result<NvidiaDevice, GraphicsDeviceError> {
let version = self.nvidia_version()?;
let major =
version.split('.').next().unwrap_or_default().parse::<u32>().unwrap_or_default();
fn get_nvidia_device(&self, id: u16) -> Result<NvidiaDevice, GraphicsDeviceError> {
let docs: Vec<path::PathBuf> = fs::read_dir("/usr/share/doc")
.map_err(|e| {
GraphicsDeviceError::Json(io::Error::new(io::ErrorKind::InvalidData, e.to_string()))
})?
.filter_map(Result::ok)
.map(|f| f.path())
.filter(|f| f.starts_with("nvidia-driver-"))
.collect();

// There should be only 1 driver version installed.
if docs.len() != 1 {
return Err(GraphicsDeviceError::Json(io::Error::new(
io::ErrorKind::InvalidData,
"NVIDIA drivers misconfigured",
)));
}

let supported_gpus = format!("/usr/share/doc/nvidia-driver-{}/supported-gpus.json", major);
let supported_gpus = docs[0].join("supported_gpus.json");
let raw = fs::read_to_string(supported_gpus).map_err(GraphicsDeviceError::Json)?;
let gpus: SupportedGpus = serde_json::from_str(&raw).map_err(|e| {
GraphicsDeviceError::Json(io::Error::new(io::ErrorKind::InvalidData, e.to_string()))
Expand All @@ -299,7 +317,7 @@ impl Graphics {
// There may be multiple entries that share the same device ID.
for dev in gpus.chips {
let did = dev.devid.trim_start_matches("0x").trim();
let did = u32::from_str_radix(did, 16).unwrap_or_default();
let did = u16::from_str_radix(did, 16).unwrap_or_default();
if did == id {
return Ok(dev);
}
Expand All @@ -312,7 +330,7 @@ impl Graphics {
}

fn gpu_supports_runtimepm(&self) -> Result<bool, GraphicsDeviceError> {
let id = self.get_nvidia_device_id()?;
let id = self.nvidia[0].device();
let dev = self.get_nvidia_device(id)?;
log::info!("Device 0x{:04} features: {:?}", id, dev.features);
Ok(dev.features.contains(&"runtimepm".to_string()))
Expand Down

0 comments on commit 6c20493

Please sign in to comment.