use std::ffi::{CStr, c_char, c_uint};
use libloading::Library;
use serde::{Deserialize, Serialize};
use super::CudaTuneError;
use crate::inference::cuda::CudaError;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(
tag = "source",
content = "version",
rename_all = "snake_case",
deny_unknown_fields
)]
pub(super) enum DriverRelease {
Nvml(String),
Sysfs(String),
Procfs(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum DriverIdentity {
Release(DriverRelease),
ApiOnly(i32),
}
impl DriverIdentity {
pub(super) fn require_release(self) -> Result<DriverRelease, CudaTuneError> {
match self {
Self::Release(release) => Ok(release),
Self::ApiOnly(cuda_api) => Err(CudaTuneError::DriverReleaseUnreadable { cuda_api }),
}
}
pub(super) fn read() -> Result<Self, CudaTuneError> {
Self::select(
nvml_version,
|path| std::fs::read_to_string(path),
cuda_version,
)
}
fn select(
nvml: impl FnOnce() -> Option<String>,
mut file: impl FnMut(&str) -> std::io::Result<String>,
cuda: impl FnOnce() -> Result<i32, CudaTuneError>,
) -> Result<Self, CudaTuneError> {
if let Some(version) = nvml().and_then(|text| first_line(&text)) {
return Ok(Self::Release(DriverRelease::Nvml(version)));
}
if let Some(version) = file("/sys/module/nvidia/version")
.ok()
.and_then(|text| first_line(&text))
{
return Ok(Self::Release(DriverRelease::Sysfs(version)));
}
if let Some(version) = file("/proc/driver/nvidia/version")
.ok()
.and_then(|text| first_line(&text))
{
return Ok(Self::Release(DriverRelease::Procfs(version)));
}
let version = cuda()?;
if version <= 0 {
return Err(CudaTuneError::Invalid(
"CUDA driver API version is not positive".into(),
));
}
Ok(Self::ApiOnly(version))
}
}
fn first_line(text: &str) -> Option<String> {
text.lines()
.next()
.map(str::trim)
.filter(|line| !line.is_empty())
.map(str::to_owned)
}
fn nvml_version() -> Option<String> {
let library = unsafe { Library::new("libnvidia-ml.so.1") }.ok()?;
nvml_version_from_library(&library)
}
fn nvml_version_from_library(library: &Library) -> Option<String> {
type Init = unsafe extern "C" fn() -> c_uint;
type GetVersion = unsafe extern "C" fn(*mut c_char, c_uint) -> c_uint;
type Shutdown = unsafe extern "C" fn() -> c_uint;
let (init, get_version, shutdown) = unsafe {
(
library.get::<Init>(b"nvmlInit_v2\0").ok()?,
library
.get::<GetVersion>(b"nvmlSystemGetDriverVersion\0")
.ok()?,
library.get::<Shutdown>(b"nvmlShutdown\0").ok()?,
)
};
if unsafe { init() } != 0 {
return None;
}
let mut bytes = [0u8; 80];
let status = unsafe { get_version(bytes.as_mut_ptr().cast(), bytes.len() as c_uint) };
let _ = unsafe { shutdown() };
if status != 0 {
return None;
}
CStr::from_bytes_until_nul(&bytes)
.ok()?
.to_str()
.ok()
.map(str::to_owned)
}
fn cuda_version() -> Result<i32, CudaTuneError> {
let mut version = 0;
unsafe { cudarc::driver::sys::cuDriverGetVersion(&mut version) }
.result()
.map_err(CudaError::from)?;
Ok(version)
}
#[cfg(test)]
mod tests;