From f9aaa8a57b68e9fa8b4bb1d2e2b1b3b3a1258d98 Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 29 Apr 2026 15:33:33 +0200 Subject: [PATCH 01/15] refactor: changed nvidia_minor to be an Option, with None if the gpu isnt nvidia --- crates/cardwire-core/src/gpu/discover.rs | 6 +++--- crates/cardwire-core/src/gpu/ebpf.rs | 8 +++++--- crates/cardwire-core/src/gpu/models.rs | 5 ++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/cardwire-core/src/gpu/discover.rs b/crates/cardwire-core/src/gpu/discover.rs index 3082e19..4f7bb3b 100644 --- a/crates/cardwire-core/src/gpu/discover.rs +++ b/crates/cardwire-core/src/gpu/discover.rs @@ -32,10 +32,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 { 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/models.rs b/crates/cardwire-core/src/gpu/models.rs index af2bda2..6c1fa9e 100644 --- a/crates/cardwire-core/src/gpu/models.rs +++ b/crates/cardwire-core/src/gpu/models.rs @@ -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 @@ -37,7 +36,7 @@ impl Gpu { pub fn is_nvidia(&self) -> &bool { &self.nvidia } - pub fn nvidia_minor(&self) -> &u32 { + pub fn nvidia_minor(&self) -> &Option { &self.nvidia_minor } } From 9e171fdf636e6220174d7628b307b86e80224de4 Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 29 Apr 2026 20:10:47 +0200 Subject: [PATCH 02/15] feat: new cardwire list + remade daemon dbus * daemon dbus now return json instead of preset message * cardwire cli now has --full and --json * cardwire_core gpu model has an blocked option now * nvidia_minor default was changed to None instead of 99 --- Cargo.lock | 4 +- crates/cardwire-cli/Cargo.toml | 4 +- crates/cardwire-cli/src/dbus.rs | 5 +- crates/cardwire-cli/src/display.rs | 132 +++++++++++++++++++++++ crates/cardwire-cli/src/main.rs | 48 ++++----- crates/cardwire-cli/src/output.rs | 65 ----------- crates/cardwire-core/Cargo.toml | 3 +- crates/cardwire-core/src/gpu/discover.rs | 3 + crates/cardwire-core/src/gpu/models.rs | 6 +- crates/cardwire-core/src/pci/models.rs | 2 +- crates/cardwire-daemon/src/dbus.rs | 49 ++++----- crates/cardwire-daemon/src/models.rs | 4 +- 12 files changed, 197 insertions(+), 128 deletions(-) create mode 100644 crates/cardwire-cli/src/display.rs delete mode 100644 crates/cardwire-cli/src/output.rs diff --git a/Cargo.lock b/Cargo.lock index 00c1993..b4ddc25 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,6 +321,7 @@ version = "0.5.0" dependencies = [ "cardwire-ebpf", "log", + "serde", "thiserror 2.0.18", ] 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/dbus.rs b/crates/cardwire-cli/src/dbus.rs index 92bcee5..4736e85 100644 --- a/crates/cardwire-cli/src/dbus.rs +++ b/crates/cardwire-cli/src/dbus.rs @@ -1,4 +1,3 @@ -use cardwire_core::gpu::GpuRow; use zbus::{Proxy, connection::Connection}; pub struct DaemonClient<'a> { proxy: Proxy<'a>, @@ -25,8 +24,8 @@ impl<'a> DaemonClient<'a> { self.proxy.call("GetMode", &()).await } - pub async fn list_gpus(&self) -> zbus::Result> { - self.proxy.call("ListGpus", &()).await + pub async fn list_devices(&self, full: bool) -> zbus::Result { + self.proxy.call("ListDevices", &(full)).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..d8ad09e --- /dev/null +++ b/crates/cardwire-cli/src/display.rs @@ -0,0 +1,132 @@ +//! 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}; +use serde; +use serde_json; +// 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)] +struct GpuDevice { + id: u32, + name: String, + pci: String, + render: u32, + card: u32, + default: Option, + blocked: Option, + nvidia: bool, + nvidia_minor: Option, +} +#[derive(serde::Deserialize, serde::Serialize)] +struct PciDevice { + pci_address: String, + iommu_group: Option, + vendor_id: Option, + device_id: Option, + vendor_name: Option, + device_name: Option, + driver: Option, + class: Option, +} +/// turn a json into a string +pub fn parse_json(json: &String) -> String { + serde_json::from_str(json).unwrap_or("Error parsing json".to_string()) +} + +/// Take a jsonified String and print it +pub fn print_devices(json: &String, is_json: bool, is_pci: bool) -> Result<()> { + match is_pci { + true => { + let output: BTreeMap = serde_json::from_str(json)?; + // prettify + println!("{}", serde_json::to_string_pretty(&output)?); + } + false => { + let output: BTreeMap = serde_json::from_str(json)?; + if is_json { + println!("{}", serde_json::to_string_pretty(&output)?); + } else { + pretty_print_gpu(output); + }; + } + }; + + Ok(()) +} + +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 as usize); + 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?; @@ -52,14 +37,13 @@ async fn main() -> anyhow::Result<()> { } Commands::Get => { match client.get_mode().await { - Ok(response) => println!("{}", response), + Ok(response) => println!("Current Mode: {}", parse_json(&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 } => match client.list_devices(full).await { + Ok(response) => { + print_devices(&response, json, full)?; } Err(e) => handle_error(e), }, @@ -74,3 +58,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 { render: drm_node_path(&device.pci_address, "render")?, card: drm_node_path(&device.pci_address, "card")?, default: None, + blocked: None, nvidia, nvidia_minor, }) @@ -238,6 +239,8 @@ 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); } } diff --git a/crates/cardwire-core/src/gpu/models.rs b/crates/cardwire-core/src/gpu/models.rs index 6c1fa9e..c8e936c 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)] pub struct Gpu { pub id: u32, pub name: String, @@ -6,6 +6,10 @@ pub struct Gpu { pub render: u32, pub card: u32, pub default: Option, + // blocked is purely for display + // the daemon not check itself for this, it will ask the + // ebpf_blocker + pub blocked: Option, pub nvidia: bool, pub nvidia_minor: Option, } diff --git a/crates/cardwire-core/src/pci/models.rs b/crates/cardwire-core/src/pci/models.rs index db23e54..b814e73 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)] pub struct PciDevice { pub pci_address: String, pub iommu_group: Option, diff --git a/crates/cardwire-daemon/src/dbus.rs b/crates/cardwire-daemon/src/dbus.rs index e8d089e..b84e7b6 100644 --- a/crates/cardwire-daemon/src/dbus.rs +++ b/crates/cardwire-daemon/src/dbus.rs @@ -71,9 +71,13 @@ impl Daemon { Ok(()) } - pub(crate) async fn get_mode(&self) -> String { + pub(crate) async fn get_mode(&self) -> fdo::Result { let current_mode = self.state.mode_state.read().await; - format!("Current Mode: {}", current_mode.mode()) + let mut response = current_mode.mode().to_string(); + response = + serde_json::to_string(&response).map_err(|e| fdo::Error::Failed(e.to_string()))?; + + Ok(response) } pub(crate) async fn set_gpu_block(&self, gpu_id: u32, block: bool) -> fdo::Result<()> { @@ -111,32 +115,21 @@ 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()); - 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 - } - }; - rows.push(( - gpu.id(), - gpu.name().to_string(), - gpu.pci_address().to_string(), - gpu.render_node().to_string(), - gpu.is_default(), - blocked, - )); + pub(crate) async fn list_devices(&self, pci: bool) -> fdo::Result { + if pci { + let list = &self.state.pci_devices; + let reponse = + serde_json::to_string(&list).map_err(|e| fdo::Error::Failed(e.to_string()))?; + Ok(reponse) + } else { + let blocker = self.state.ebpf_blocker.read().await; + let mut list = self.state.gpu_list.clone(); + for (_, gpu) in &mut list { + gpu.blocked = Some(is_gpu_blocked(&blocker, gpu).unwrap_or(false)) + } + let reponse = + serde_json::to_string(&list).map_err(|e| fdo::Error::Failed(e.to_string()))?; + Ok(reponse) } - rows.sort_by_key(|row| row.0); - rows } } diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index fc54d16..a80b8f5 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -47,7 +47,7 @@ pub struct DaemonState { pub gpu_list: HashMap, pub ebpf_blocker: RwLock, // for future uses, related to vfio - pub _pci_devices: HashMap, + pub pci_devices: HashMap, 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: pci_devices, _iommu: iommu, gpu_list, ebpf_blocker: RwLock::new(ebpf_blocker), From ebb5f4510130ca3c6f3dc20bdbcd91fb3351b633 Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 29 Apr 2026 21:04:43 +0200 Subject: [PATCH 03/15] fix: split list device into a gpu version and a pci one --- crates/cardwire-cli/src/dbus.rs | 6 +++++- crates/cardwire-daemon/src/dbus.rs | 30 +++++++++++++++--------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/crates/cardwire-cli/src/dbus.rs b/crates/cardwire-cli/src/dbus.rs index 4736e85..c3c9949 100644 --- a/crates/cardwire-cli/src/dbus.rs +++ b/crates/cardwire-cli/src/dbus.rs @@ -25,7 +25,11 @@ impl<'a> DaemonClient<'a> { } pub async fn list_devices(&self, full: bool) -> zbus::Result { - self.proxy.call("ListDevices", &(full)).await + if full { + self.proxy.call("ListDevicesPci", &()).await + } else { + self.proxy.call("ListDevices", &()).await + } } pub async fn set_gpu_block(&self, id: u32, blocked: bool) -> zbus::Result<()> { diff --git a/crates/cardwire-daemon/src/dbus.rs b/crates/cardwire-daemon/src/dbus.rs index b84e7b6..f88821b 100644 --- a/crates/cardwire-daemon/src/dbus.rs +++ b/crates/cardwire-daemon/src/dbus.rs @@ -115,21 +115,21 @@ impl Daemon { Ok(()) } - pub(crate) async fn list_devices(&self, pci: bool) -> fdo::Result { - if pci { - let list = &self.state.pci_devices; - let reponse = - serde_json::to_string(&list).map_err(|e| fdo::Error::Failed(e.to_string()))?; - Ok(reponse) - } else { - let blocker = self.state.ebpf_blocker.read().await; - let mut list = self.state.gpu_list.clone(); - for (_, gpu) in &mut list { - gpu.blocked = Some(is_gpu_blocked(&blocker, gpu).unwrap_or(false)) - } - let reponse = - serde_json::to_string(&list).map_err(|e| fdo::Error::Failed(e.to_string()))?; - Ok(reponse) + pub(crate) async fn list_devices(&self) -> fdo::Result { + let blocker = self.state.ebpf_blocker.read().await; + let mut list = self.state.gpu_list.clone(); + for (_, gpu) in &mut list { + gpu.blocked = Some(is_gpu_blocked(&blocker, gpu).unwrap_or(false)) } + let reponse = + serde_json::to_string(&list).map_err(|e| fdo::Error::Failed(e.to_string()))?; + Ok(reponse) + } + + pub(crate) async fn list_devices_pci(&self) -> fdo::Result { + let list = &self.state.pci_devices; + let reponse = + serde_json::to_string(&list).map_err(|e| fdo::Error::Failed(e.to_string()))?; + Ok(reponse) } } From 616bf12d422b84ed707394c6f020e921a8d3745c Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 29 Apr 2026 21:44:18 +0200 Subject: [PATCH 04/15] fix: removed json strin from dbus result, added a new list func for pci, changed some HashMap to BTreeMap --- Cargo.lock | 1 + Cargo.toml | 2 +- crates/cardwire-cli/src/dbus.rs | 14 ++++---- crates/cardwire-cli/src/display.rs | 41 +++++++++------------- crates/cardwire-cli/src/main.rs | 23 ++++++++---- crates/cardwire-core/Cargo.toml | 3 +- crates/cardwire-core/src/gpu/discover.rs | 10 +++--- crates/cardwire-core/src/gpu/models.rs | 2 +- crates/cardwire-core/src/pci/models.rs | 2 +- crates/cardwire-core/src/pci/pci_device.rs | 14 ++++---- crates/cardwire-daemon/src/config.rs | 12 +++---- crates/cardwire-daemon/src/dbus.rs | 27 ++++++-------- crates/cardwire-daemon/src/models.rs | 8 ++--- 13 files changed, 83 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4ddc25..6e39716 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -323,6 +323,7 @@ dependencies = [ "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/src/dbus.rs b/crates/cardwire-cli/src/dbus.rs index c3c9949..373208a 100644 --- a/crates/cardwire-cli/src/dbus.rs +++ b/crates/cardwire-cli/src/dbus.rs @@ -1,3 +1,6 @@ +use crate::display::{GpuDevice, PciDevice}; +use std::collections::BTreeMap; + use zbus::{Proxy, connection::Connection}; pub struct DaemonClient<'a> { proxy: Proxy<'a>, @@ -24,12 +27,11 @@ impl<'a> DaemonClient<'a> { self.proxy.call("GetMode", &()).await } - pub async fn list_devices(&self, full: bool) -> zbus::Result { - if full { - self.proxy.call("ListDevicesPci", &()).await - } else { - self.proxy.call("ListDevices", &()).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 index d8ad09e..c6f8923 100644 --- a/crates/cardwire-cli/src/display.rs +++ b/crates/cardwire-cli/src/display.rs @@ -4,14 +4,12 @@ use std::collections::BTreeMap; use anyhow::{Ok, Result}; -use serde; -use serde_json; // 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)] -struct GpuDevice { +#[derive(serde::Deserialize, serde::Serialize, zbus::zvariant::Type)] +pub struct GpuDevice { id: u32, name: String, pci: String, @@ -22,8 +20,8 @@ struct GpuDevice { nvidia: bool, nvidia_minor: Option, } -#[derive(serde::Deserialize, serde::Serialize)] -struct PciDevice { +#[derive(serde::Deserialize, serde::Serialize, zbus::zvariant::Type)] +pub struct PciDevice { pci_address: String, iommu_group: Option, vendor_id: Option, @@ -34,32 +32,27 @@ struct PciDevice { class: Option, } /// turn a json into a string -pub fn parse_json(json: &String) -> String { +pub fn parse_json(json: &str) -> String { serde_json::from_str(json).unwrap_or("Error parsing json".to_string()) } /// Take a jsonified String and print it -pub fn print_devices(json: &String, is_json: bool, is_pci: bool) -> Result<()> { - match is_pci { - true => { - let output: BTreeMap = serde_json::from_str(json)?; - // prettify - println!("{}", serde_json::to_string_pretty(&output)?); - } - false => { - let output: BTreeMap = serde_json::from_str(json)?; - if is_json { - println!("{}", serde_json::to_string_pretty(&output)?); - } else { - pretty_print_gpu(output); - }; - } +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(()) } -fn pretty_print_gpu(gpu_list: BTreeMap) { +pub fn print_devices_pci(pci_list: BTreeMap) -> Result<()> { + println!("{}", serde_json::to_string_pretty(&pci_list)?); + Ok(()) +} + +fn pretty_print_gpu(gpu_list: BTreeMap) { let mut id_w = 2usize; let mut name_w = 4usize; let mut pci_w = 3usize; @@ -70,7 +63,7 @@ fn pretty_print_gpu(gpu_list: BTreeMap) { // Calculate widths for (id, gpu) in &gpu_list { - id_w = id_w.max(*id as usize); + 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 diff --git a/crates/cardwire-cli/src/main.rs b/crates/cardwire-cli/src/main.rs index d4413f8..065785a 100644 --- a/crates/cardwire-cli/src/main.rs +++ b/crates/cardwire-cli/src/main.rs @@ -5,7 +5,7 @@ use args::{Args, CliMode, Commands}; use clap::{CommandFactory, Parser}; use dbus::DaemonClient; -use crate::display::{parse_json, print_devices}; +use crate::display::{parse_json, print_devices, print_devices_pci}; const BIN_NAME: &str = "cardwire"; @@ -41,12 +41,23 @@ async fn main() -> anyhow::Result<()> { Err(e) => handle_error(e), }; } - Commands::List { full, json } => match client.list_devices(full).await { - Ok(response) => { - print_devices(&response, json, full)?; + 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), diff --git a/crates/cardwire-core/Cargo.toml b/crates/cardwire-core/Cargo.toml index 58c17b4..089b714 100644 --- a/crates/cardwire-core/Cargo.toml +++ b/crates/cardwire-core/Cargo.toml @@ -12,4 +12,5 @@ description = "Core library for cardwire GPU management" cardwire-ebpf = { path = "../cardwire-ebpf" } thiserror.workspace= true log.workspace = true -serde.workspace = true \ No newline at end of file +serde.workspace = true +zbus.workspace = true \ No newline at end of file diff --git a/crates/cardwire-core/src/gpu/discover.rs b/crates/cardwire-core/src/gpu/discover.rs index c2a3d1c..f314061 100644 --- a/crates/cardwire-core/src/gpu/discover.rs +++ b/crates/cardwire-core/src/gpu/discover.rs @@ -1,8 +1,10 @@ use crate::{gpu::models::Gpu, pci::PciDevice}; use log::{info, warn}; -use std::{collections::HashMap, fs, io, path::Path}; +use std::{ + collections::{BTreeMap, HashMap}, fs, io, path::Path +}; -pub fn read_gpu(pci_devices: &HashMap) -> io::Result> { +pub fn read_gpu(pci_devices: &BTreeMap) -> io::Result> { let gpus: Vec = pci_devices .values() .filter(|device| { @@ -147,7 +149,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() { @@ -245,7 +247,7 @@ pub fn check_default_drm_class(gpu_list: &mut HashMap) -> io::Result } // 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/models.rs b/crates/cardwire-core/src/gpu/models.rs index c8e936c..5f486a9 100644 --- a/crates/cardwire-core/src/gpu/models.rs +++ b/crates/cardwire-core/src/gpu/models.rs @@ -1,4 +1,4 @@ -#[derive(Clone, serde::Serialize, serde::Deserialize)] +#[derive(Clone, serde::Serialize, serde::Deserialize, zbus::zvariant::Type)] pub struct Gpu { pub id: u32, pub name: String, diff --git a/crates/cardwire-core/src/pci/models.rs b/crates/cardwire-core/src/pci/models.rs index b814e73..51647fd 100644 --- a/crates/cardwire-core/src/pci/models.rs +++ b/crates/cardwire-core/src/pci/models.rs @@ -1,4 +1,4 @@ -#[derive(Clone, serde::Serialize, serde::Deserialize)] +#[derive(Clone, serde::Serialize, serde::Deserialize, zbus::zvariant::Type)] pub struct PciDevice { pub pci_address: String, pub iommu_group: Option, 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 f88821b..fdd2eb3 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::{Gpu, block_gpu, is_gpu_blocked}, pci::PciDevice +}; use log::{error, info, warn}; use zbus::{fdo, interface}; @@ -73,11 +77,7 @@ impl Daemon { pub(crate) async fn get_mode(&self) -> fdo::Result { let current_mode = self.state.mode_state.read().await; - let mut response = current_mode.mode().to_string(); - response = - serde_json::to_string(&response).map_err(|e| fdo::Error::Failed(e.to_string()))?; - - Ok(response) + Ok(current_mode.mode().to_string()) } pub(crate) async fn set_gpu_block(&self, gpu_id: u32, block: bool) -> fdo::Result<()> { @@ -115,21 +115,16 @@ impl Daemon { Ok(()) } - pub(crate) async fn list_devices(&self) -> fdo::Result { + pub(crate) async fn list_devices(&self) -> fdo::Result> { let blocker = self.state.ebpf_blocker.read().await; let mut list = self.state.gpu_list.clone(); - for (_, gpu) in &mut list { + for gpu in list.values_mut() { gpu.blocked = Some(is_gpu_blocked(&blocker, gpu).unwrap_or(false)) } - let reponse = - serde_json::to_string(&list).map_err(|e| fdo::Error::Failed(e.to_string()))?; - Ok(reponse) + Ok(list.clone()) } - pub(crate) async fn list_devices_pci(&self) -> fdo::Result { - let list = &self.state.pci_devices; - let reponse = - serde_json::to_string(&list).map_err(|e| fdo::Error::Failed(e.to_string()))?; - Ok(reponse) + pub(crate) async fn list_devices_pci(&self) -> fdo::Result> { + Ok(self.state.pci_devices.clone()) } } diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index a80b8f5..367bc55 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; @@ -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), From eb2839095264652de50aa25b40d51cf379782d6e Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 29 Apr 2026 21:48:01 +0200 Subject: [PATCH 05/15] fix: removed json parsing from cardwire cli --- crates/cardwire-cli/src/display.rs | 10 +++------- crates/cardwire-cli/src/main.rs | 4 ++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/crates/cardwire-cli/src/display.rs b/crates/cardwire-cli/src/display.rs index c6f8923..6939be2 100644 --- a/crates/cardwire-cli/src/display.rs +++ b/crates/cardwire-cli/src/display.rs @@ -31,12 +31,8 @@ pub struct PciDevice { driver: Option, class: Option, } -/// turn a json into a string -pub fn parse_json(json: &str) -> String { - serde_json::from_str(json).unwrap_or("Error parsing json".to_string()) -} -/// Take a jsonified String and print it +/// 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)?); @@ -46,12 +42,12 @@ pub fn print_devices(gpu_list: BTreeMap, is_json: bool) -> Res 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; diff --git a/crates/cardwire-cli/src/main.rs b/crates/cardwire-cli/src/main.rs index 065785a..b847a49 100644 --- a/crates/cardwire-cli/src/main.rs +++ b/crates/cardwire-cli/src/main.rs @@ -5,7 +5,7 @@ use args::{Args, CliMode, Commands}; use clap::{CommandFactory, Parser}; use dbus::DaemonClient; -use crate::display::{parse_json, print_devices, print_devices_pci}; +use crate::display::{print_devices, print_devices_pci}; const BIN_NAME: &str = "cardwire"; @@ -37,7 +37,7 @@ async fn main() -> anyhow::Result<()> { } Commands::Get => { match client.get_mode().await { - Ok(response) => println!("Current Mode: {}", parse_json(&response)), + Ok(response) => println!("Current Mode: {}", response), Err(e) => handle_error(e), }; } From 413c96e5e449e88fcfecbd589958c3ff59ea41aa Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 29 Apr 2026 22:25:38 +0200 Subject: [PATCH 06/15] fix: added a specialized dbus struct for pci/gpu, that allows to remove the Option<> from the dbus message, --- crates/cardwire-cli/src/display.rs | 26 ++++++------ crates/cardwire-cli/src/main.rs | 1 + crates/cardwire-core/src/gpu/discover.rs | 1 - crates/cardwire-core/src/gpu/mod.rs | 2 +- crates/cardwire-core/src/gpu/models.rs | 22 ++++++---- crates/cardwire-core/src/pci/mod.rs | 2 +- crates/cardwire-core/src/pci/models.rs | 13 ++++++ crates/cardwire-daemon/src/dbus.rs | 52 ++++++++++++++++++++---- 8 files changed, 87 insertions(+), 32 deletions(-) diff --git a/crates/cardwire-cli/src/display.rs b/crates/cardwire-cli/src/display.rs index 6939be2..00f49c3 100644 --- a/crates/cardwire-cli/src/display.rs +++ b/crates/cardwire-cli/src/display.rs @@ -8,28 +8,28 @@ use anyhow::{Ok, Result}; // 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)] +#[derive(serde::Deserialize, serde::Serialize, zbus::zvariant::Type, Debug)] pub struct GpuDevice { id: u32, name: String, pci: String, render: u32, card: u32, - default: Option, - blocked: Option, + default: bool, + blocked: bool, nvidia: bool, - nvidia_minor: Option, + nvidia_minor: String, } #[derive(serde::Deserialize, serde::Serialize, zbus::zvariant::Type)] pub struct PciDevice { pci_address: String, - iommu_group: Option, - vendor_id: Option, - device_id: Option, - vendor_name: Option, - device_name: Option, - driver: Option, - class: Option, + 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 @@ -107,8 +107,8 @@ fn pretty_print_gpu(gpu_list: BTreeMap) { gpu.pci, render_full, card_full, - gpu.default.unwrap(), - if gpu.blocked.unwrap() { "on" } else { "off" }, + gpu.default, + gpu.blocked, id_w = id_w, name_w = name_w, pci_w = pci_w, diff --git a/crates/cardwire-cli/src/main.rs b/crates/cardwire-cli/src/main.rs index b847a49..94fcbcc 100644 --- a/crates/cardwire-cli/src/main.rs +++ b/crates/cardwire-cli/src/main.rs @@ -52,6 +52,7 @@ async fn main() -> anyhow::Result<()> { } else { match client.list_devices().await { Ok(response) => { + println!("{:?}", response); print_devices(response, json)?; } Err(e) => handle_error(e), diff --git a/crates/cardwire-core/src/gpu/discover.rs b/crates/cardwire-core/src/gpu/discover.rs index f314061..32b51cb 100644 --- a/crates/cardwire-core/src/gpu/discover.rs +++ b/crates/cardwire-core/src/gpu/discover.rs @@ -50,7 +50,6 @@ fn build_gpu(device: &PciDevice) -> io::Result { render: drm_node_path(&device.pci_address, "render")?, card: drm_node_path(&device.pci_address, "card")?, default: None, - blocked: None, nvidia, nvidia_minor, }) 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 5f486a9..1600e54 100644 --- a/crates/cardwire-core/src/gpu/models.rs +++ b/crates/cardwire-core/src/gpu/models.rs @@ -6,10 +6,6 @@ pub struct Gpu { pub render: u32, pub card: u32, pub default: Option, - // blocked is purely for display - // the daemon not check itself for this, it will ask the - // ebpf_blocker - pub blocked: Option, pub nvidia: bool, pub nvidia_minor: Option, } @@ -37,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) -> &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 51647fd..c34be30 100644 --- a/crates/cardwire-core/src/pci/models.rs +++ b/crates/cardwire-core/src/pci/models.rs @@ -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 Null + 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-daemon/src/dbus.rs b/crates/cardwire-daemon/src/dbus.rs index fdd2eb3..f10e51d 100644 --- a/crates/cardwire-daemon/src/dbus.rs +++ b/crates/cardwire-daemon/src/dbus.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use crate::models::{Daemon, Modes}; use cardwire_core::{ - gpu::{Gpu, block_gpu, is_gpu_blocked}, pci::PciDevice + gpu::{DbusGpuDevice, block_gpu, is_gpu_blocked}, pci::DbusPciDevice }; use log::{error, info, warn}; use zbus::{fdo, interface}; @@ -115,16 +115,52 @@ impl Daemon { Ok(()) } - pub(crate) async fn list_devices(&self) -> fdo::Result> { + pub(crate) async fn list_devices(&self) -> fdo::Result> { let blocker = self.state.ebpf_blocker.read().await; - let mut list = self.state.gpu_list.clone(); - for gpu in list.values_mut() { - gpu.blocked = Some(is_gpu_blocked(&blocker, gpu).unwrap_or(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 { + "Null".to_string() + }, + }; + dbus_list.insert(id, temp_gpu); } - Ok(list.clone()) + Ok(dbus_list) } - pub(crate) async fn list_devices_pci(&self) -> fdo::Result> { - Ok(self.state.pci_devices.clone()) + 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 { + "Null".to_string() + }, + vendor_id: pci.vendor_id.clone().unwrap_or("Null".to_string()), + device_id: pci.device_id.clone().unwrap_or("Null".to_string()), + vendor_name: pci.vendor_name.clone().unwrap_or("Null".to_string()), + device_name: pci.device_name.clone().unwrap_or("Null".to_string()), + driver: pci.driver.clone().unwrap_or("Null".to_string()), + class: pci.class.clone().unwrap_or("Null".to_string()), + }; + dbus_list.insert(id.clone(), temp_pci); + } + + Ok(dbus_list) } } From 9131bef891f148458fc217bbcc3757ab2eeb4f79 Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 29 Apr 2026 22:26:05 +0200 Subject: [PATCH 07/15] fix: forgot to remove println! debug from cardwire cli --- crates/cardwire-cli/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/cardwire-cli/src/main.rs b/crates/cardwire-cli/src/main.rs index 94fcbcc..b847a49 100644 --- a/crates/cardwire-cli/src/main.rs +++ b/crates/cardwire-cli/src/main.rs @@ -52,7 +52,6 @@ async fn main() -> anyhow::Result<()> { } else { match client.list_devices().await { Ok(response) => { - println!("{:?}", response); print_devices(response, json)?; } Err(e) => handle_error(e), From 8c64b426f5d94b9fb079e8c0d9018aedccfc2a3c Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 30 Apr 2026 17:59:52 +0200 Subject: [PATCH 08/15] feat: Mode is now a property --- crates/cardwire-daemon/src/dbus.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/cardwire-daemon/src/dbus.rs b/crates/cardwire-daemon/src/dbus.rs index f10e51d..4232aa5 100644 --- a/crates/cardwire-daemon/src/dbus.rs +++ b/crates/cardwire-daemon/src/dbus.rs @@ -12,6 +12,7 @@ impl Daemon { /* Set the mode */ + #[zbus(property)] pub(crate) async fn set_mode(&self, mode: String) -> fdo::Result<()> { // Valide inputs and turn into a Modes let mode = Modes::parse(&mode)?; @@ -74,8 +75,8 @@ impl Daemon { info!("Switched to {}", mode); Ok(()) } - - pub(crate) async fn get_mode(&self) -> fdo::Result { + #[zbus(property)] + pub(crate) async fn mode(&self) -> fdo::Result { let current_mode = self.state.mode_state.read().await; Ok(current_mode.mode().to_string()) } From e03e47a2912c03b29d1fc1f92e60367db1cdcec4 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 30 Apr 2026 18:50:36 +0200 Subject: [PATCH 09/15] fix: set cli mode to use property --- crates/cardwire-cli/src/dbus.rs | 6 +++--- crates/cardwire-cli/src/main.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/cardwire-cli/src/dbus.rs b/crates/cardwire-cli/src/dbus.rs index 373208a..86e302c 100644 --- a/crates/cardwire-cli/src/dbus.rs +++ b/crates/cardwire-cli/src/dbus.rs @@ -19,12 +19,12 @@ 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: &String) -> zbus::fdo::Result<()> { + self.proxy.set_property("Mode", mode).await } pub async fn get_mode(&self) -> zbus::Result { - self.proxy.call("GetMode", &()).await + self.proxy.get_property("Mode").await } pub async fn list_devices(&self) -> zbus::Result> { diff --git a/crates/cardwire-cli/src/main.rs b/crates/cardwire-cli/src/main.rs index b847a49..6586f61 100644 --- a/crates/cardwire-cli/src/main.rs +++ b/crates/cardwire-cli/src/main.rs @@ -32,7 +32,7 @@ async fn main() -> anyhow::Result<()> { match client.set_mode(&mode_string).await { Ok(_) => println!("Mode has been set to {}", mode_string), - Err(e) => handle_error(e), + Err(e) => handle_error(e.into()), }; } Commands::Get => { From bb3ebd11cb12e8af591a9836aec32cb5bd943fc0 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 30 Apr 2026 18:51:07 +0200 Subject: [PATCH 10/15] fix: None option should be empty instead of Null --- crates/cardwire-core/src/pci/models.rs | 2 +- crates/cardwire-daemon/src/dbus.rs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/cardwire-core/src/pci/models.rs b/crates/cardwire-core/src/pci/models.rs index c34be30..94e4cf5 100644 --- a/crates/cardwire-core/src/pci/models.rs +++ b/crates/cardwire-core/src/pci/models.rs @@ -13,7 +13,7 @@ pub struct PciDevice { #[derive(Clone, serde::Serialize, serde::Deserialize, zbus::zvariant::Type)] pub struct DbusPciDevice { pub pci_address: String, - // Strings to be able to put Null + // Strings to be able to put nothing pub iommu_group: String, pub vendor_id: String, pub device_id: String, diff --git a/crates/cardwire-daemon/src/dbus.rs b/crates/cardwire-daemon/src/dbus.rs index 4232aa5..1331df9 100644 --- a/crates/cardwire-daemon/src/dbus.rs +++ b/crates/cardwire-daemon/src/dbus.rs @@ -133,7 +133,7 @@ impl Daemon { nvidia_minor: if gpu.nvidia_minor().is_some() { gpu.nvidia_minor().unwrap().to_string() } else { - "Null".to_string() + "".to_string() }, }; dbus_list.insert(id, temp_gpu); @@ -150,14 +150,14 @@ impl Daemon { iommu_group: if let Some(iommu) = pci.iommu_group { iommu.to_string() } else { - "Null".to_string() + "".to_string() }, - vendor_id: pci.vendor_id.clone().unwrap_or("Null".to_string()), - device_id: pci.device_id.clone().unwrap_or("Null".to_string()), - vendor_name: pci.vendor_name.clone().unwrap_or("Null".to_string()), - device_name: pci.device_name.clone().unwrap_or("Null".to_string()), - driver: pci.driver.clone().unwrap_or("Null".to_string()), - class: pci.class.clone().unwrap_or("Null".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); } From 1c1d2012be115b16c4809b6cfcafc8dafa1afe70 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 30 Apr 2026 20:16:19 +0200 Subject: [PATCH 11/15] feat: changed Mode property to be a u32 --- crates/cardwire-daemon/src/dbus.rs | 10 +++++++--- crates/cardwire-daemon/src/models.rs | 20 +++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/crates/cardwire-daemon/src/dbus.rs b/crates/cardwire-daemon/src/dbus.rs index 1331df9..d069a01 100644 --- a/crates/cardwire-daemon/src/dbus.rs +++ b/crates/cardwire-daemon/src/dbus.rs @@ -13,7 +13,7 @@ impl Daemon { Set the mode */ #[zbus(property)] - pub(crate) async fn set_mode(&self, mode: String) -> fdo::Result<()> { + 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 @@ -76,9 +76,13 @@ impl Daemon { Ok(()) } #[zbus(property)] - pub(crate) async fn mode(&self) -> fdo::Result { + pub(crate) async fn mode(&self) -> fdo::Result { let current_mode = self.state.mode_state.read().await; - Ok(current_mode.mode().to_string()) + 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<()> { diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index 367bc55..3c231d4 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -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" ))), @@ -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(()) } From b318619df74478810d0f33483047af70dfd9f16e Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 30 Apr 2026 20:16:36 +0200 Subject: [PATCH 12/15] fix: cardwire-cli now use u32 for Mode property --- crates/cardwire-cli/src/args.rs | 11 ++++++++++- crates/cardwire-cli/src/dbus.rs | 4 ++-- crates/cardwire-cli/src/main.rs | 23 ++++++++++++++++------- 3 files changed, 28 insertions(+), 10 deletions(-) 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 86e302c..45e33d1 100644 --- a/crates/cardwire-cli/src/dbus.rs +++ b/crates/cardwire-cli/src/dbus.rs @@ -19,11 +19,11 @@ impl<'a> DaemonClient<'a> { Ok(Self { proxy }) } - pub async fn set_mode(&self, mode: &String) -> zbus::fdo::Result<()> { + 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 { + pub async fn get_mode(&self) -> zbus::Result { self.proxy.get_property("Mode").await } diff --git a/crates/cardwire-cli/src/main.rs b/crates/cardwire-cli/src/main.rs index 6586f61..3ad0701 100644 --- a/crates/cardwire-cli/src/main.rs +++ b/crates/cardwire-cli/src/main.rs @@ -24,20 +24,29 @@ async fn main() -> anyhow::Result<()> { 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), + 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!("Current Mode: {}", 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), }; } From 86bd7b9c6ae52819a253060c52bfb8624eeeabd8 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 30 Apr 2026 20:30:56 +0200 Subject: [PATCH 13/15] ci: fix cardwire set assert --- nix/ci-2gpu.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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") From 5147ca76ced28c67d621ee510d4b896caff7fcfc Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 30 Apr 2026 20:42:10 +0200 Subject: [PATCH 14/15] feat: changed cardwire cli default display --- crates/cardwire-cli/src/display.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cardwire-cli/src/display.rs b/crates/cardwire-cli/src/display.rs index 00f49c3..b9f3571 100644 --- a/crates/cardwire-cli/src/display.rs +++ b/crates/cardwire-cli/src/display.rs @@ -107,7 +107,7 @@ fn pretty_print_gpu(gpu_list: BTreeMap) { gpu.pci, render_full, card_full, - gpu.default, + if gpu.default { "(*)" } else { "( )" }, gpu.blocked, id_w = id_w, name_w = name_w, From 98fba572e1a1bb2087bd5f246e3cd85f16b81f05 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 30 Apr 2026 20:48:20 +0200 Subject: [PATCH 15/15] ci: changed "on" to "true" --- nix/ci-15gpu.nix | 4 ++-- nix/ci-3gpu.nix | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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-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")