Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions crates/cardwire-daemon/src/core/inode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
//! Used to get inodes for specific files
use std::{
collections::BTreeMap, fs::{self}, os::unix::fs::MetadataExt
};

use anyhow::Result;
use log::warn;

use crate::core::pci::PciDevice;

// shouldn't be necessary anymore
const _BLOCKED_PCI_FILES: &[&str] = &[
"config",
"current_link_speed",
"max_link_speed",
"max_link_width",
"current_link_width",
];

pub fn render_to_inode(render: u32) -> Result<u64> {
let render_path = format!("/dev/dri/renderD{}", render);
let metadata = fs::metadata(&render_path).map_err(|e| {
warn!("failed to get inode for {}: {}", render_path, e);
e
})?;
let inode = metadata.ino();

Ok(inode)
}

pub fn card_to_inode(card: u32) -> Result<u64> {
let card_path = format!("/dev/dri/card{}", card);
let metadata = fs::metadata(&card_path).map_err(|e| {
warn!("failed to get inode for {}: {}", card_path, e);
e
})?;
let inode = metadata.ino();

Ok(inode)
}

// Here return a list of inode that contain the pci card, the audio card and their parents
pub fn pci_to_inode(
pci: String,
parent_pci: &Option<String>,
pci_list: &BTreeMap<String, PciDevice>,
) -> Result<Vec<u64>> {
let mut inodes: Vec<u64> = Vec::new();

// quick function that push the inodes into the vec
let push_pci_inode = |pci: &str, inodes: &mut Vec<u64>| {
// First get the link ino
let pci_path = format!("/sys/bus/pci/devices/{}", pci);
if let Ok(metadata) = fs::metadata(&pci_path) {
inodes.push(metadata.ino());
}

// Now without following link
let pci_path = format!("/sys/bus/pci/devices/{}", pci);
if let Ok(metadata) = fs::symlink_metadata(&pci_path) {
inodes.push(metadata.ino());
}
};

// first we push the pci inode
push_pci_inode(&pci, &mut inodes);
// Also push the audio card inode
push_pci_inode(&pci.replace(".0", ".1"), &mut inodes);

let mut current_parent: Option<String> = parent_pci.clone();
while let Some(parent_pci) = current_parent {
if let Some(pci_device) = pci_list.get(&parent_pci) {
current_parent = pci_device.parent_pci().clone();
push_pci_inode(pci_device.pci_address(), &mut inodes);
} else {
warn!("expected parent pci {} not found in pci_list", parent_pci);
break;
}
}

Ok(inodes)
}

/// Used to verify the block status of a single pci
pub fn single_pci_to_inode(pci: &str) -> Result<u64> {
let pci_path = format!("/sys/bus/pci/devices/{}", pci);
let metadata = fs::metadata(&pci_path).map_err(|e| {
warn!("failed to get inode for {}: {}", pci_path, e);
e
})?;
let inode = metadata.ino();

Ok(inode)
}

pub fn nvidia_to_inode(nvidia_minor: u32) -> Result<u64> {
let nvidia_path = format!("/dev/nvidia{}", nvidia_minor);
let metadata = fs::metadata(&nvidia_path).map_err(|e| {
warn!("failed to get inode for {}: {}", nvidia_path, e);
e
})?;
let inode = metadata.ino();

Ok(inode)
}

/// The only gpu vendor that need it's backlight to be blocked is nvidia
pub fn backlight_to_inode(nvidia_minor: u32) -> Result<u64> {
let nvidia_path = format!("/sys/class/backlight/nvidia_{}", nvidia_minor);
let metadata = fs::metadata(&nvidia_path).map_err(|e| {
warn!("failed to get inode for {}: {}", nvidia_path, e);
e
})?;
let inode = metadata.ino();

Ok(inode)
}

pub fn exp_nvidia_inodes() -> Result<Vec<u64>> {
let mut inodes: Vec<u64> = Vec::new();

// Get nvidiactl inode
let nvidiactl = "/dev/nvidiactl";
if let Ok(metadata) = fs::metadata(nvidiactl) {
inodes.push(metadata.ino());
}

// Now try to find the vulkan file
// This is for normal distros
/// Files that get blocked when the NVIDIA block is on
const VULKAN_PATHS: &[&str] = &[
// NixOS
"/run/opengl-driver/share/vulkan/icd.d/",
"/run/opengl-driver-32/share/vulkan/icd.d/",
// Standard Linux
"/etc/vulkan/icd.d/",
"/usr/share/vulkan/icd.d/",
];

for path in VULKAN_PATHS {
let has_nvidia = fs::read_dir(path)
.map(|entries| {
entries.filter_map(|e| e.ok()).any(|entry| {
let name = entry.file_name();
name == "nvidia_icd.json" || name == "nvidia_icd.x86_64.json"
})
})
.unwrap_or(false);

if has_nvidia && let Ok(metadata) = fs::metadata(path) {
inodes.push(metadata.ino());
}
}

Ok(inodes)
}
2 changes: 2 additions & 0 deletions crates/cardwire-daemon/src/core/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub mod errors;
pub mod gpu;
pub mod pci;

pub mod inode;
14 changes: 4 additions & 10 deletions crates/cardwire-daemon/src/interface/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::sync::{
};

use crate::{file::CardwireConfig, interface::Modes};
use cardwire_ebpf::EbpfBlocker;
use cardwire_ebpf::{EbpfBlocker, EbpfSettings};
use tokio::sync::RwLock;
use zbus::{fdo, interface};

Expand Down Expand Up @@ -83,15 +83,9 @@ impl ConfigInterface {
.store(state, Ordering::Relaxed);
let mut blocker = self.blocker.write().await;
// change the value in the ebpf map
if state {
blocker
.block_kind(&state.to_string(), cardwire_ebpf::BlockKind::NvidiaSetting)
.map_err(|e| fdo::Error::Failed(format!("failed to set nvidia block: {}", e)))
} else {
blocker
.unblock_kind(&state.to_string(), cardwire_ebpf::BlockKind::NvidiaSetting)
.map_err(|e| fdo::Error::Failed(format!("failed to set nvidia block: {}", e)))
}
blocker
.set_ebpf_setting(EbpfSettings::ExperimentalNvidia, state.into())
.map_err(|e| fdo::Error::Failed(format!("failed to set nvidia block: {}", e)))
}
#[zbus(property)]
pub async fn battery_auto_switch(&self) -> fdo::Result<bool> {
Expand Down
38 changes: 35 additions & 3 deletions crates/cardwire-daemon/src/interface/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use tokio::{sync::RwLock, task};
use zbus::{fdo, interface};

use crate::{
file::{CardwireGpuState, CardwireModeState}, interface::{ConfigMemory, GpuInterface}
file::{CardwireGpuState, CardwireModeState}, interface::{ConfigMemory, GpuInterface, Modes}
};

#[derive(Clone)]
Expand Down Expand Up @@ -90,6 +90,8 @@ impl DebugInterface {
info!("pci list changed, refreshing the internal gpu list");
// Overwrite old list
*pci_list = new_pci_list.clone();
drop(pci_list); // drop lock to prevent deadlocks when blocking

let mut power_tasks = self.power_tasks.write().await;

// get rid of the old gpu api and the old tasks
Expand All @@ -106,19 +108,49 @@ impl DebugInterface {
gpu_interfaces.clear();
// Read the new list
let mut new_gpu_list =
gpu::read_gpu(&pci_list).map_err(|err| fdo::Error::Failed(err.to_string()))?;
gpu::read_gpu(&new_pci_list).map_err(|err| fdo::Error::Failed(err.to_string()))?;
if let Err(err) = check_default_drm_class(&mut new_gpu_list) {
warn!("Failed to determine default GPU: {}", err);
}
for (id, device) in new_gpu_list {
let gpu = GpuInterface::build(
let mut gpu = GpuInterface::build(
device,
Arc::clone(&self.blocker),
Arc::clone(&self.pci_list),
Arc::clone(&self.gpu_state),
Arc::clone(&self.mode_state),
)
.map_err(|err| fdo::Error::Failed(err.to_string()))?;

let mode = self.mode_state.read().await.mode();
let config = self
.config
.auto_apply_gpu_state
.load(std::sync::atomic::Ordering::Relaxed);

let should_block = match mode {
Modes::Integrated | Modes::Smart => !gpu.device.is_default(),
Modes::Hybrid => false,
Modes::Manual => {
let state = self.gpu_state.read().await;
state.gpu_block_state(gpu.device.pci.pci_address()) && config
}
};

if should_block {
info!(
"GPU {} should be blocked, re-applying block on hotplug",
gpu.device.name()
);
if let Err(e) = gpu.block_gpu().await {
warn!(
"failed to automatically re-block {}: {}",
gpu.device.name(),
e
);
}
}

gpu_interfaces.insert(id, gpu);
}

Expand Down
Loading
Loading