diff --git a/Cargo.lock b/Cargo.lock index 00c1993..6e39716 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,9 +307,10 @@ name = "cardwire-cli" version = "0.5.0" dependencies = [ "anyhow", - "cardwire-core", "clap", "clap_complete", + "serde", + "serde_json", "tokio", "zbus", ] @@ -320,7 +321,9 @@ version = "0.5.0" dependencies = [ "cardwire-ebpf", "log", + "serde", "thiserror 2.0.18", + "zbus", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 701b2f0..34142f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ license = "GPL-3.0" [workspace.dependencies] tokio = { version = "1.52.1", features = ["full"] } -zbus = { version = "5.15.0", features = ["tokio"] } +zbus = { version = "5.15.0", features = ["tokio", "option-as-array"] } clap = { version = "4.6.1", features = ["derive"] } config = { version = "0.15.19", features = ["toml"] } serde = { version = "1.0.228", features = ["derive"] } diff --git a/crates/cardwire-cli/Cargo.toml b/crates/cardwire-cli/Cargo.toml index 5937ff8..3b425fb 100644 --- a/crates/cardwire-cli/Cargo.toml +++ b/crates/cardwire-cli/Cargo.toml @@ -9,12 +9,14 @@ license.workspace = true description = "CLI for cardwire GPU management" [dependencies] -cardwire-core = { path = "../cardwire-core" } tokio.workspace = true zbus.workspace = true clap.workspace = true clap_complete.workspace = true anyhow.workspace = true +serde_json.workspace = true +serde.workspace = true + [[bin]] name = "cardwire" path = "src/main.rs" diff --git a/crates/cardwire-cli/src/args.rs b/crates/cardwire-cli/src/args.rs index 8110382..5dc4b00 100644 --- a/crates/cardwire-cli/src/args.rs +++ b/crates/cardwire-cli/src/args.rs @@ -1,12 +1,21 @@ use clap::{ArgAction, Args as ClapArgs, Parser, Subcommand, ValueEnum}; use clap_complete::Shell; +use std::fmt; #[derive(Clone, Debug, ValueEnum)] pub enum CliMode { Integrated, Hybrid, Manual, } - +impl fmt::Display for CliMode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + CliMode::Integrated => write!(f, "Integrated"), + CliMode::Hybrid => write!(f, "Hybrid"), + CliMode::Manual => write!(f, "Manual"), + } + } +} #[derive(Parser)] #[command(version, about)] pub struct Args { diff --git a/crates/cardwire-cli/src/dbus.rs b/crates/cardwire-cli/src/dbus.rs index 92bcee5..45e33d1 100644 --- a/crates/cardwire-cli/src/dbus.rs +++ b/crates/cardwire-cli/src/dbus.rs @@ -1,4 +1,6 @@ -use cardwire_core::gpu::GpuRow; +use crate::display::{GpuDevice, PciDevice}; +use std::collections::BTreeMap; + use zbus::{Proxy, connection::Connection}; pub struct DaemonClient<'a> { proxy: Proxy<'a>, @@ -17,16 +19,19 @@ impl<'a> DaemonClient<'a> { Ok(Self { proxy }) } - pub async fn set_mode(&self, mode: &String) -> zbus::Result<()> { - self.proxy.call("SetMode", &(mode,)).await + pub async fn set_mode(&self, mode: &u32) -> zbus::fdo::Result<()> { + self.proxy.set_property("Mode", mode).await } - pub async fn get_mode(&self) -> zbus::Result { - self.proxy.call("GetMode", &()).await + pub async fn get_mode(&self) -> zbus::Result { + self.proxy.get_property("Mode").await } - pub async fn list_gpus(&self) -> zbus::Result> { - self.proxy.call("ListGpus", &()).await + pub async fn list_devices(&self) -> zbus::Result> { + self.proxy.call("ListDevices", &()).await + } + pub async fn list_devices_pci(&self) -> zbus::Result> { + self.proxy.call("ListDevicesPci", &()).await } pub async fn set_gpu_block(&self, id: u32, blocked: bool) -> zbus::Result<()> { diff --git a/crates/cardwire-cli/src/display.rs b/crates/cardwire-cli/src/display.rs new file mode 100644 index 0000000..b9f3571 --- /dev/null +++ b/crates/cardwire-cli/src/display.rs @@ -0,0 +1,121 @@ +//! The purpose of this file is to format the received String from daemon into a displayable format +//! for the user + +use std::collections::BTreeMap; + +use anyhow::{Ok, Result}; +// Define the struct here instead of importing from cardwire_core, +// I want cardwire-cli to be independent of the rest of cardwire +// This allow other dev to make their own client for cardwire +// Here the struct are used to parse the json +#[derive(serde::Deserialize, serde::Serialize, zbus::zvariant::Type, Debug)] +pub struct GpuDevice { + id: u32, + name: String, + pci: String, + render: u32, + card: u32, + default: bool, + blocked: bool, + nvidia: bool, + nvidia_minor: String, +} +#[derive(serde::Deserialize, serde::Serialize, zbus::zvariant::Type)] +pub struct PciDevice { + pci_address: String, + iommu_group: String, + vendor_id: String, + device_id: String, + vendor_name: String, + device_name: String, + driver: String, + class: String, +} + +/// Take a Map and print it +pub fn print_devices(gpu_list: BTreeMap, is_json: bool) -> Result<()> { + if is_json { + println!("{}", serde_json::to_string_pretty(&gpu_list)?); + } else { + pretty_print_gpu(gpu_list); + }; + + Ok(()) +} +/// Take a Map and print it +pub fn print_devices_pci(pci_list: BTreeMap) -> Result<()> { + println!("{}", serde_json::to_string_pretty(&pci_list)?); + Ok(()) +} +/// Take a Map and print it into a good looking table +fn pretty_print_gpu(gpu_list: BTreeMap) { + let mut id_w = 2usize; + let mut name_w = 4usize; + let mut pci_w = 3usize; + let mut render_w = 6usize; + let mut card_w = 4usize; + let default_w = 7usize; + let blocked_w = 7usize; + + // Calculate widths + for (id, gpu) in &gpu_list { + id_w = id_w.max(*id); + name_w = name_w.max(gpu.name.len()); + pci_w = pci_w.max(gpu.pci.len()); + // Full render string is "renderD" + device number + let render_full = format!("renderD{}", gpu.render); + render_w = render_w.max(render_full.len()); + let card_full = format!("card{}", gpu.card); + card_w = card_w.max(card_full.len()); + } + + // Header + println!( + "{: { - eprintln!("{}", description.unwrap_or_else(|| name.to_string())) - } - zbus::Error::FDO(fdo_err) => match *fdo_err { - zbus::fdo::Error::ServiceUnknown(content) => { - eprint!("error: {} \n is the service up?", content) - } - other => eprintln!("FDO error: {}", other), - }, - e => eprintln!("error: {e:?}"), - } -} +const BIN_NAME: &str = "cardwire"; #[tokio::main] async fn main() -> anyhow::Result<()> { let args = Args::parse(); - /* - Handle completion before connecting to dbus - */ - + // Handle completion before connecting to dbus if let Commands::Completion { shell } = args.command { let mut cmd = Args::command(); clap_complete::generate(shell, &mut cmd, BIN_NAME, &mut std::io::stdout()); return Ok(()); } + // Now connect let connection: zbus::Connection = zbus::connection::Builder::system()?.build().await?; let client: DaemonClient<'_> = DaemonClient::connect(&connection).await?; match args.command { Commands::Set { mode } => { - let mode_string = match mode { - CliMode::Integrated => "integrated".to_string(), - CliMode::Hybrid => "hybrid".to_string(), - CliMode::Manual => "manual".to_string(), + let mode_u32 = match mode { + CliMode::Integrated => 0, + CliMode::Hybrid => 1, + CliMode::Manual => 2, }; - match client.set_mode(&mode_string).await { - Ok(_) => println!("Mode has been set to {}", mode_string), - Err(e) => handle_error(e), + match client.set_mode(&mode_u32).await { + Ok(_) => println!("Mode has been set to {}", mode), + Err(e) => handle_error(e.into()), }; } Commands::Get => { match client.get_mode().await { - Ok(response) => println!("{}", response), + Ok(response) => { + let response: CliMode = match response { + 0 => CliMode::Integrated, + 1 => CliMode::Hybrid, + 2 => CliMode::Manual, + // shouldn't happen + _ => CliMode::Manual, + }; + println!("Current Mode: {}", response) + } Err(e) => handle_error(e), }; } - Commands::List { full: _, json: _ } => match client.list_gpus().await { - Ok(mut response) => { - response.sort_by_key(|row| row.0); - output::print_gpu_table(&response); + Commands::List { full, json } => { + if full { + match client.list_devices_pci().await { + Ok(response) => { + print_devices_pci(response)?; + } + Err(e) => handle_error(e), + } + } else { + match client.list_devices().await { + Ok(response) => { + print_devices(response, json)?; + } + Err(e) => handle_error(e), + } } - Err(e) => handle_error(e), - }, + } Commands::Gpu { id, action } => { match client.set_gpu_block(id, action.block).await { Ok(_) => println!("Mode has been set to {} on GPU {}", action.block, id), @@ -74,3 +78,17 @@ async fn main() -> anyhow::Result<()> { Ok(()) } +fn handle_error(err: zbus::Error) { + match err { + zbus::Error::MethodError(name, description, _) => { + eprintln!("{}", description.unwrap_or_else(|| name.to_string())) + } + zbus::Error::FDO(fdo_err) => match *fdo_err { + zbus::fdo::Error::ServiceUnknown(content) => { + eprint!("error: {} \n is the service up?", content) + } + other => eprintln!("FDO error: {}", other), + }, + e => eprintln!("error: {e:?}"), + } +} diff --git a/crates/cardwire-cli/src/output.rs b/crates/cardwire-cli/src/output.rs deleted file mode 100644 index 2bcab39..0000000 --- a/crates/cardwire-cli/src/output.rs +++ /dev/null @@ -1,65 +0,0 @@ -use cardwire_core::gpu::GpuRow; - -pub fn print_gpu_table(rows: &[GpuRow]) { - let mut id_w = 2usize; - let mut name_w = 4usize; - let mut pci_w = 3usize; - let mut render_w = 6usize; - let default_w = 7usize; - let blocked_w = 7usize; - - // Calculate widths - for (id, name, pci, render, _, _) in rows { - id_w = id_w.max(id.to_string().len()); - name_w = name_w.max(name.len()); - pci_w = pci_w.max(pci.len()); - // Full render string is "renderD" + device number - let render_full = format!("renderD{}", render); - render_w = render_w.max(render_full.len()); - } - - // Header - println!( - "{:) -> io::Result> { +pub fn read_gpu(pci_devices: &BTreeMap) -> io::Result> { let gpus: Vec = pci_devices .values() .filter(|device| { @@ -32,10 +34,10 @@ pub fn read_gpu(pci_devices: &HashMap) -> io::Result io::Result { let nvidia: bool = device.vendor_id.as_deref() == Some("0x10de"); - let nvidia_minor: u32 = if nvidia { - nvidia_get_minor(&device.pci_address).unwrap_or(99) + let nvidia_minor: Option = if nvidia { + nvidia_get_minor(&device.pci_address) } else { - 99 + None }; Ok(Gpu { @@ -146,7 +148,7 @@ fn nvidia_get_minor(pci_address: &str) -> Option { .ok() } /// Method from kwin -pub fn check_default_drm_class(gpu_list: &mut HashMap) -> io::Result<()> { +pub fn check_default_drm_class(gpu_list: &mut BTreeMap) -> io::Result<()> { let class_path = Path::new("/sys/class/drm"); let mut drm_entries = Vec::new(); if class_path.exists() { @@ -238,11 +240,13 @@ pub fn check_default_drm_class(gpu_list: &mut HashMap) -> io::Result for gpu in gpu_list.values_mut() { if gpu.id == *default.0.unwrap() as u32 { gpu.default = Some(true); + } else { + gpu.default = Some(false); } } // Default GPU gets ID 0, rest ordered by PCI address - let mut gpus: Vec = gpu_list.drain().map(|(_, gpu)| gpu).collect(); + let mut gpus: Vec = std::mem::take(gpu_list).into_values().collect(); gpus.sort_by(|a, b| b.default.cmp(&a.default).then(a.pci.cmp(&b.pci))); *gpu_list = gpus .into_iter() diff --git a/crates/cardwire-core/src/gpu/ebpf.rs b/crates/cardwire-core/src/gpu/ebpf.rs index 01b1e0e..6da1a75 100644 --- a/crates/cardwire-core/src/gpu/ebpf.rs +++ b/crates/cardwire-core/src/gpu/ebpf.rs @@ -33,9 +33,11 @@ pub fn is_gpu_blocked(blocker: &GpuBlocker, gpu: &Gpu) -> GpuResult { .is_render_blocked(render_id) .map_err(map_gpu_error)? && if gpu.nvidia { + // unwrap because it should be Some if it's an nvidia gpu, if not it's a bug and should + // be reported blocker .inner - .is_nvidia_blocked(*gpu.nvidia_minor()) + .is_nvidia_blocked(gpu.nvidia_minor().unwrap()) .map_err(map_gpu_error)? } else { true @@ -50,7 +52,7 @@ pub fn block_gpu(blocker: &mut GpuBlocker, gpu: &Gpu, block: bool) -> GpuResult< blocker.inner.block_render(render_id)?; blocker.inner.block_pci(gpu.pci_address())?; if gpu.nvidia { - blocker.inner.block_nvidia(*gpu.nvidia_minor())? + blocker.inner.block_nvidia(gpu.nvidia_minor().unwrap())? } Ok(()) } else { @@ -58,7 +60,7 @@ pub fn block_gpu(blocker: &mut GpuBlocker, gpu: &Gpu, block: bool) -> GpuResult< blocker.inner.unblock_render(render_id)?; blocker.inner.unblock_pci(gpu.pci_address())?; if gpu.nvidia { - blocker.inner.unblock_nvidia(*gpu.nvidia_minor())? + blocker.inner.unblock_nvidia(gpu.nvidia_minor().unwrap())? } Ok(()) } diff --git a/crates/cardwire-core/src/gpu/mod.rs b/crates/cardwire-core/src/gpu/mod.rs index 7e37379..95780a2 100644 --- a/crates/cardwire-core/src/gpu/mod.rs +++ b/crates/cardwire-core/src/gpu/mod.rs @@ -6,4 +6,4 @@ mod models; pub use discover::{check_default_drm_class, read_gpu}; pub use ebpf::{GpuBlocker, block_gpu, is_gpu_blocked}; pub use errors::GpuResult; -pub use models::{Gpu, GpuRow}; +pub use models::{DbusGpuDevice, Gpu}; diff --git a/crates/cardwire-core/src/gpu/models.rs b/crates/cardwire-core/src/gpu/models.rs index af2bda2..1600e54 100644 --- a/crates/cardwire-core/src/gpu/models.rs +++ b/crates/cardwire-core/src/gpu/models.rs @@ -1,4 +1,4 @@ -#[derive(Clone)] +#[derive(Clone, serde::Serialize, serde::Deserialize, zbus::zvariant::Type)] pub struct Gpu { pub id: u32, pub name: String, @@ -7,9 +7,8 @@ pub struct Gpu { pub card: u32, pub default: Option, pub nvidia: bool, - pub nvidia_minor: u32, + pub nvidia_minor: Option, } - impl Gpu { pub fn pci_address(&self) -> &str { &self.pci @@ -34,13 +33,23 @@ impl Gpu { pub fn card_node(&self) -> &u32 { &self.card } - pub fn is_nvidia(&self) -> &bool { - &self.nvidia + pub fn is_nvidia(&self) -> bool { + self.nvidia } - pub fn nvidia_minor(&self) -> &u32 { + pub fn nvidia_minor(&self) -> &Option { &self.nvidia_minor } } -// GpuRow for display -pub type GpuRow = (u32, String, String, String, bool, bool); +#[derive(Clone, serde::Serialize, serde::Deserialize, zbus::zvariant::Type)] +pub struct DbusGpuDevice { + pub id: u32, + pub name: String, + pub pci: String, + pub render: u32, + pub card: u32, + pub default: bool, + pub blocked: bool, + pub nvidia: bool, + pub nvidia_minor: String, +} diff --git a/crates/cardwire-core/src/pci/mod.rs b/crates/cardwire-core/src/pci/mod.rs index b3244a7..85fbea9 100644 --- a/crates/cardwire-core/src/pci/mod.rs +++ b/crates/cardwire-core/src/pci/mod.rs @@ -5,5 +5,5 @@ mod pci_device; pub use errors::IommuError; pub use iommu::{is_iommu_enabled, read_iommu_groups}; -pub use models::{IommuGroup, PciDevice}; +pub use models::{DbusPciDevice, IommuGroup, PciDevice}; pub use pci_device::read_pci_devices; diff --git a/crates/cardwire-core/src/pci/models.rs b/crates/cardwire-core/src/pci/models.rs index db23e54..94e4cf5 100644 --- a/crates/cardwire-core/src/pci/models.rs +++ b/crates/cardwire-core/src/pci/models.rs @@ -1,4 +1,4 @@ -#[derive(Clone)] +#[derive(Clone, serde::Serialize, serde::Deserialize, zbus::zvariant::Type)] pub struct PciDevice { pub pci_address: String, pub iommu_group: Option, @@ -10,6 +10,19 @@ pub struct PciDevice { pub class: Option, } +#[derive(Clone, serde::Serialize, serde::Deserialize, zbus::zvariant::Type)] +pub struct DbusPciDevice { + pub pci_address: String, + // Strings to be able to put nothing + pub iommu_group: String, + pub vendor_id: String, + pub device_id: String, + pub vendor_name: String, + pub device_name: String, + pub driver: String, + pub class: String, +} + pub struct IommuGroup { pub id: usize, pub devices: Vec, diff --git a/crates/cardwire-core/src/pci/pci_device.rs b/crates/cardwire-core/src/pci/pci_device.rs index 9f654a3..af9a283 100644 --- a/crates/cardwire-core/src/pci/pci_device.rs +++ b/crates/cardwire-core/src/pci/pci_device.rs @@ -1,8 +1,10 @@ use crate::pci::{IommuError, PciDevice, is_iommu_enabled, read_iommu_groups}; use log::{error, info, warn}; -use std::{collections::HashMap, fs, fs::File, io, io::BufRead, path::Path}; +use std::{ + collections::{BTreeMap, HashMap}, fs, fs::File, io, io::BufRead, path::Path +}; -pub fn read_pci_devices() -> Result, IommuError> { +pub fn read_pci_devices() -> Result, IommuError> { match is_iommu_enabled() { true => { info!("IOMMU detected, reading pci devices using iommu dir"); @@ -15,13 +17,13 @@ pub fn read_pci_devices() -> Result, IommuError> { } } -fn read_pci_devices_using_iommu() -> Result, IommuError> { +fn read_pci_devices_using_iommu() -> Result, IommuError> { let iommu_groups = read_iommu_groups()?; let pci_names = load_pci_name_db(Path::new("/usr/share/hwdata/pci.ids")).unwrap_or_else(|e| { warn!("Failed to load PCI name DB: {}", e); PciNameDb::default() }); - let mut devices_map = HashMap::new(); + let mut devices_map = BTreeMap::new(); for (group_id, group) in iommu_groups { // read "device" folder, look at each PCI ADDRESS for pci_address in group.devices { @@ -57,13 +59,13 @@ fn read_pci_devices_using_iommu() -> Result, IommuErr } Ok(devices_map) } -fn read_pci_devices_using_sysfs() -> Result, IommuError> { +fn read_pci_devices_using_sysfs() -> Result, IommuError> { let sysfs = Path::new("/sys/bus/pci/devices"); let pci_names = load_pci_name_db(Path::new("/usr/share/hwdata/pci.ids")).unwrap_or_else(|e| { warn!("Failed to load PCI name DB: {}", e); PciNameDb::default() }); - let mut devices_map = HashMap::new(); + let mut devices_map = BTreeMap::new(); let sysfs_dir = fs::read_dir(sysfs).map_err(|e| { error!("Failed to read sysfs PCI devices at {:?}: {}", sysfs, e); IommuError::Io(e) diff --git a/crates/cardwire-daemon/src/config.rs b/crates/cardwire-daemon/src/config.rs index cc534ce..ecbdc54 100644 --- a/crates/cardwire-daemon/src/config.rs +++ b/crates/cardwire-daemon/src/config.rs @@ -3,7 +3,7 @@ use anyhow::{Context, Ok}; use cardwire_core::gpu::{Gpu, GpuBlocker, is_gpu_blocked}; use log::{info, warn}; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fs, io}; +use std::{collections::BTreeMap, fs, io}; const CONFIG_PATH: &str = "/etc/cardwire"; const STATE_PATH: &str = "/var/lib/cardwire"; @@ -54,7 +54,7 @@ impl CardwireConfig { // This is the easiest way i found to have a good looking json, might change later #[derive(Serialize, Deserialize)] pub struct CardwireGpuState { - gpu: HashMap, + gpu: BTreeMap, } // A gpu contain a pci and a blocked state, // TODO: A more precise way to identify a GPU, but not dangerous since cardwire does not block @@ -79,14 +79,14 @@ impl CardwireGpuState { Ok(gpu_state) } // Parse directly into CardwireGpuState - fn parse_gpu_state(state_file: &str) -> anyhow::Result> { + fn parse_gpu_state(state_file: &str) -> anyhow::Result> { if !(fs::exists(state_file)?) { Self::create_default_state().context("Could not create default gpu_state.json")?; } let gpu_state = fs::read_to_string(state_file) .with_context(|| format!("Could not read file {}", state_file))?; - let content: HashMap = + let content: BTreeMap = serde_json::from_str(&gpu_state).context("Could not parse string into json")?; Ok(content) } @@ -98,7 +98,7 @@ impl CardwireGpuState { /// Save the new state into the daemon and to the gpu_state.json file pub async fn save_state( &mut self, - gpu_list: &HashMap, + gpu_list: &BTreeMap, blocker: &GpuBlocker, ) -> anyhow::Result<()> { // Prevent overwriting default config if it's not replaceable @@ -196,7 +196,7 @@ fn create_default_file(kind: FileKind) -> anyhow::Result<()> { .context("could not create default folder for gpu_state.json")?; // Default gpu_state for cardwire // TODO: Move to default trait? - let mut defaut_hash: HashMap = HashMap::new(); + let mut defaut_hash: BTreeMap = BTreeMap::new(); let _ = defaut_hash.insert("Null".to_string(), CardwireGpuUnit { block: false }); let default_gpu_state = serde_json::to_string_pretty(&defaut_hash)?; // write diff --git a/crates/cardwire-daemon/src/dbus.rs b/crates/cardwire-daemon/src/dbus.rs index e8d089e..d069a01 100644 --- a/crates/cardwire-daemon/src/dbus.rs +++ b/crates/cardwire-daemon/src/dbus.rs @@ -1,5 +1,9 @@ +use std::collections::BTreeMap; + use crate::models::{Daemon, Modes}; -use cardwire_core::gpu::{GpuRow, block_gpu, is_gpu_blocked}; +use cardwire_core::{ + gpu::{DbusGpuDevice, block_gpu, is_gpu_blocked}, pci::DbusPciDevice +}; use log::{error, info, warn}; use zbus::{fdo, interface}; @@ -8,7 +12,8 @@ impl Daemon { /* Set the mode */ - pub(crate) async fn set_mode(&self, mode: String) -> fdo::Result<()> { + #[zbus(property)] + pub(crate) async fn set_mode(&self, mode: u32) -> fdo::Result<()> { // Valide inputs and turn into a Modes let mode = Modes::parse(&mode)?; // Get current_config lock @@ -70,10 +75,14 @@ impl Daemon { info!("Switched to {}", mode); Ok(()) } - - pub(crate) async fn get_mode(&self) -> String { + #[zbus(property)] + pub(crate) async fn mode(&self) -> fdo::Result { let current_mode = self.state.mode_state.read().await; - format!("Current Mode: {}", current_mode.mode()) + match current_mode.mode() { + Modes::Integrated => Ok(0), + Modes::Hybrid => Ok(1), + Modes::Manual => Ok(2), + } } pub(crate) async fn set_gpu_block(&self, gpu_id: u32, block: bool) -> fdo::Result<()> { @@ -111,32 +120,52 @@ impl Daemon { Ok(()) } - pub(crate) async fn list_gpus(&self) -> Vec { - //self.list_gpu_rows().await - let mut rows = Vec::with_capacity(self.state.gpu_list.len()); + pub(crate) async fn list_devices(&self) -> fdo::Result> { let blocker = self.state.ebpf_blocker.read().await; - for gpu in self.state.gpu_list.values() { - let blocked: bool = match is_gpu_blocked(&blocker, gpu) { - Ok(b) => b, - Err(e) => { - error!( - "Couldn't check gpu's lock state for {}: {}", - gpu.pci_address(), - e - ); - false - } + let list = self.state.gpu_list.clone(); + let mut dbus_list: BTreeMap = BTreeMap::new(); + for (id, gpu) in list { + let temp_gpu = DbusGpuDevice { + id: gpu.id, + pci: gpu.pci.clone(), + render: gpu.render, + name: gpu.name.clone(), + card: gpu.card, + default: gpu.default.unwrap_or(false), + blocked: is_gpu_blocked(&blocker, &gpu).unwrap_or(false), + nvidia: gpu.is_nvidia(), + nvidia_minor: if gpu.nvidia_minor().is_some() { + gpu.nvidia_minor().unwrap().to_string() + } else { + "".to_string() + }, }; - rows.push(( - gpu.id(), - gpu.name().to_string(), - gpu.pci_address().to_string(), - gpu.render_node().to_string(), - gpu.is_default(), - blocked, - )); + dbus_list.insert(id, temp_gpu); } - rows.sort_by_key(|row| row.0); - rows + Ok(dbus_list) + } + + pub(crate) async fn list_devices_pci(&self) -> fdo::Result> { + let pci_list = &self.state.pci_devices; + let mut dbus_list: BTreeMap = BTreeMap::new(); + for (id, pci) in pci_list { + let temp_pci = DbusPciDevice { + pci_address: pci.pci_address.clone(), + iommu_group: if let Some(iommu) = pci.iommu_group { + iommu.to_string() + } else { + "".to_string() + }, + vendor_id: pci.vendor_id.clone().unwrap_or("".to_string()), + device_id: pci.device_id.clone().unwrap_or("".to_string()), + vendor_name: pci.vendor_name.clone().unwrap_or("".to_string()), + device_name: pci.device_name.clone().unwrap_or("".to_string()), + driver: pci.driver.clone().unwrap_or("".to_string()), + class: pci.class.clone().unwrap_or("".to_string()), + }; + dbus_list.insert(id.clone(), temp_pci); + } + + Ok(dbus_list) } } diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index fc54d16..3c231d4 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -5,7 +5,7 @@ use cardwire_core::{ }; use log::warn; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fmt}; +use std::{collections::BTreeMap, fmt}; use tokio::sync::RwLock; use zbus::fdo::Error; @@ -28,11 +28,11 @@ impl fmt::Display for Modes { } impl Modes { - pub fn parse(input: &str) -> zbus::fdo::Result { - match input.to_ascii_lowercase().as_str() { - "integrated" => Ok(Self::Integrated), - "hybrid" => Ok(Self::Hybrid), - "manual" => Ok(Self::Manual), + pub fn parse(input: &u32) -> zbus::fdo::Result { + match input { + 0 => Ok(Self::Integrated), + 1 => Ok(Self::Hybrid), + 2 => Ok(Self::Manual), unknown => Err(Error::InvalidArgs(format!( "unknown mode: {unknown} \n expected integrated|hybrid|manual" ))), @@ -44,10 +44,10 @@ pub struct DaemonState { pub config: RwLock, pub gpu_state: RwLock, pub mode_state: RwLock, - pub gpu_list: HashMap, + pub gpu_list: BTreeMap, pub ebpf_blocker: RwLock, // for future uses, related to vfio - pub _pci_devices: HashMap, + pub pci_devices: BTreeMap, pub _iommu: bool, } impl DaemonState { @@ -92,7 +92,7 @@ impl Daemon { config: RwLock::new(config), gpu_state: RwLock::new(gpu_state), mode_state: RwLock::new(mode_state), - _pci_devices: pci_devices, + pci_devices, _iommu: iommu, gpu_list, ebpf_blocker: RwLock::new(ebpf_blocker), @@ -109,9 +109,15 @@ impl Daemon { drop(blocker); drop(config); // Apply mode - let mode_to_apply = mode.mode().to_string(); + let mode_to_apply = mode.mode(); drop(mode); - self.set_mode(mode_to_apply).await?; + let mode_to_apply: usize = match mode_to_apply { + Modes::Integrated => 0, + Modes::Hybrid => 1, + Modes::Manual => 2, + }; + + self.set_mode(mode_to_apply as u32).await?; Ok(()) } diff --git a/nix/ci-15gpu.nix b/nix/ci-15gpu.nix index bafa7b5..17560d4 100644 --- a/nix/ci-15gpu.nix +++ b/nix/ci-15gpu.nix @@ -82,7 +82,7 @@ machine.succeed(f'cardwire gpu {x} --block') with subtest("Check cardwire to see if 14 gpus got blocked"): - t.assertIn("14", machine.succeed("cardwire list|grep 'on'|wc -l"), "Only 13 or less got blocked") + t.assertIn("14", machine.succeed("cardwire list|grep 'true'|wc -l"), "Only 13 or less got blocked") for x in range(1, 15): cardid = 0 + x @@ -108,7 +108,7 @@ machine.wait_until_succeeds("systemctl start cardwired.service") - t.assertIn("14", machine.succeed("cardwire list|grep 'on'|wc -l"), "Only 13 or less got blocked") + t.assertIn("14", machine.succeed("cardwire list|grep 'true'|wc -l"), "Only 13 or less got blocked") for x in range(1, 15): cardid = 0 + x diff --git a/nix/ci-2gpu.nix b/nix/ci-2gpu.nix index 58a3875..79c557c 100644 --- a/nix/ci-2gpu.nix +++ b/nix/ci-2gpu.nix @@ -61,12 +61,12 @@ # Check if cardwire detect both video card t.assertIn("renderD128", machine.succeed("cardwire list"), "Missing RenderD128 in cardwire") machine.succeed("test -e /dev/dri/renderD129") - t.assertIn("Mode has been set to integrated", machine.succeed("cardwire set integrated"), "Couldn't set to integrated mode") + t.assertIn("Mode has been set to Integrated", machine.succeed("cardwire set integrated"), "Couldn't set to integrated mode") machine.fail(": < /dev/dri/renderD129") t.assertIn("Integrated", machine.succeed("cat /var/lib/cardwire/mode.json"), "mode.json didnt get saved") with subtest("Switchback to hybrid mode"): - t.assertIn("Mode has been set to hybrid", machine.succeed("cardwire set hybrid"), "Couldn't set to hybrid mode") + t.assertIn("Mode has been set to Hybrid", machine.succeed("cardwire set hybrid"), "Couldn't set to hybrid mode") machine.succeed(": < /dev/dri/renderD129") t.assertIn("Hybrid", machine.succeed("cat /var/lib/cardwire/mode.json"), "mode.json didnt get saved") diff --git a/nix/ci-3gpu.nix b/nix/ci-3gpu.nix index 5c89a41..a44dc93 100644 --- a/nix/ci-3gpu.nix +++ b/nix/ci-3gpu.nix @@ -70,7 +70,7 @@ t.assertIn("2", machine.succeed("cat /var/lib/cardwire/gpu_state.json|grep true|wc -l"), "Only one or less GPU got blocked") with subtest("Check cardwire to see if two gpus got blocked"): - t.assertIn("2", machine.succeed("cardwire list|grep 'on'|wc -l"), "Only one or less GPU got blocked") + t.assertIn("2", machine.succeed("cardwire list|grep 'true'|wc -l"), "Only one or less GPU got blocked") machine.fail(": < /dev/dri/renderD129") machine.fail(": < /dev/dri/renderD130") machine.fail(": < /dev/dri/card1") @@ -86,7 +86,7 @@ machine.wait_until_succeeds("systemctl start cardwired.service") - t.assertIn("2", machine.succeed("cardwire list|grep 'on'|wc -l"), "Only one or less GPU got blocked") + t.assertIn("2", machine.succeed("cardwire list|grep 'true'|wc -l"), "Only one or less GPU got blocked") machine.fail(": < /dev/dri/renderD129") machine.fail(": < /dev/dri/renderD130")