diff --git a/crates/cardwire-daemon/src/core/gpu/enumerator.rs b/crates/cardwire-daemon/src/core/gpu/enumerator.rs index 8259ec4..87d14f2 100644 --- a/crates/cardwire-daemon/src/core/gpu/enumerator.rs +++ b/crates/cardwire-daemon/src/core/gpu/enumerator.rs @@ -34,6 +34,11 @@ impl GpuEnumerator { .as_ref() .is_some_and(|class| class.starts_with("0x03")) }) { + info!( + "GPU {} fingerprint: {}", + pci_device.pci_address(), + pci_device.hardware_fingerprint() + ); match self.build_gpu(pci_device) { Ok(gpu) => { gpu_list.insert(id, gpu); diff --git a/crates/cardwire-daemon/src/core/gpu/models.rs b/crates/cardwire-daemon/src/core/gpu/models.rs index 7b9adf6..9329a01 100644 --- a/crates/cardwire-daemon/src/core/gpu/models.rs +++ b/crates/cardwire-daemon/src/core/gpu/models.rs @@ -231,6 +231,8 @@ mod tests { Some(1), Some("0x1002".to_string()), Some("0x1234".to_string()), + Some("0x1002".to_string()), + Some("0x5678".to_string()), Some("AMD".to_string()), Some("RX 7900".to_string()), Some("amdgpu".to_string()), diff --git a/crates/cardwire-daemon/src/core/pci/models.rs b/crates/cardwire-daemon/src/core/pci/models.rs index 4cceca1..5613429 100644 --- a/crates/cardwire-daemon/src/core/pci/models.rs +++ b/crates/cardwire-daemon/src/core/pci/models.rs @@ -4,6 +4,8 @@ pub struct PciDevice { iommu_group: Option, vendor_id: Option, device_id: Option, + subsystem_vendor_id: Option, + subsystem_device_id: Option, vendor_name: Option, device_name: Option, driver: Option, @@ -25,6 +27,12 @@ impl PciDevice { pub fn device_id(&self) -> &Option { &self.device_id } + pub fn subsystem_vendor_id(&self) -> &Option { + &self.subsystem_vendor_id + } + pub fn subsystem_device_id(&self) -> &Option { + &self.subsystem_device_id + } pub fn vendor_name(&self) -> &Option { &self.vendor_name } @@ -49,6 +57,8 @@ impl PciDevice { iommu_group: Option, vendor_id: Option, device_id: Option, + subsystem_vendor_id: Option, + subsystem_device_id: Option, vendor_name: Option, device_name: Option, driver: Option, @@ -61,6 +71,8 @@ impl PciDevice { iommu_group, vendor_id, device_id, + subsystem_vendor_id, + subsystem_device_id, vendor_name, device_name, driver, @@ -69,6 +81,16 @@ impl PciDevice { child_pci, } } + + pub fn hardware_fingerprint(&self) -> String { + format!( + "{}:{}:{}:{}", + self.vendor_id().as_deref().unwrap_or("unknown"), + self.device_id().as_deref().unwrap_or("unknown"), + self.subsystem_vendor_id().as_deref().unwrap_or("unknown"), + self.subsystem_device_id().as_deref().unwrap_or("unknown"), + ) + } } #[derive(Clone, serde::Serialize, serde::Deserialize, zbus::zvariant::Type)] @@ -122,6 +144,8 @@ mod tests { Some(5), Some("0x1002".to_string()), Some("0x7480".to_string()), + Some("0x1002".to_string()), + Some("0x1234".to_string()), Some("AMD".to_string()), Some("Navi 31".to_string()), Some("amdgpu".to_string()), @@ -133,6 +157,8 @@ mod tests { assert_eq!(*pci.iommu_group(), Some(5)); assert_eq!(pci.vendor_id().as_deref(), Some("0x1002")); assert_eq!(pci.device_id().as_deref(), Some("0x7480")); + assert_eq!(pci.subsystem_vendor_id().as_deref(), Some("0x1002")); + assert_eq!(pci.subsystem_device_id().as_deref(), Some("0x1234")); assert_eq!(pci.vendor_name().as_deref(), Some("AMD")); assert_eq!(pci.device_name().as_deref(), Some("Navi 31")); assert_eq!(pci.driver().as_deref(), Some("amdgpu")); @@ -154,11 +180,15 @@ mod tests { None, None, None, + None, + None, ); assert_eq!(pci.pci_address(), "0000:02:00.0"); assert_eq!(*pci.iommu_group(), None); assert_eq!(pci.vendor_id().as_deref(), None); assert_eq!(pci.device_id().as_deref(), None); + assert_eq!(pci.subsystem_vendor_id().as_deref(), None); + assert_eq!(pci.subsystem_device_id().as_deref(), None); assert_eq!(pci.vendor_name().as_deref(), None); assert_eq!(pci.device_name().as_deref(), None); assert_eq!(pci.driver().as_deref(), None); @@ -166,4 +196,48 @@ mod tests { assert_eq!(pci.parent_pci().as_deref(), None); assert_eq!(pci.child_pci().as_deref(), None); } + + #[test] + fn test_hardware_fingerprint_is_deterministic() { + let pci = PciDevice::new( + "0000:01:00.0".to_string(), + None, + Some("0x10de".to_string()), + Some("0x2786".to_string()), + Some("0x1458".to_string()), + Some("0x40f5".to_string()), + None, + None, + None, + None, + None, + None, + ); + + assert_eq!(pci.hardware_fingerprint(), "0x10de:0x2786:0x1458:0x40f5"); + assert_eq!(pci.hardware_fingerprint(), pci.hardware_fingerprint()); + } + + #[test] + fn test_hardware_fingerprint_handles_missing_ids() { + let pci = PciDevice::new( + "0000:02:00.0".to_string(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ); + + assert_eq!( + pci.hardware_fingerprint(), + "unknown:unknown:unknown:unknown" + ); + } } diff --git a/crates/cardwire-daemon/src/core/pci/pci_device.rs b/crates/cardwire-daemon/src/core/pci/pci_device.rs index 5495ac7..e5e9979 100644 --- a/crates/cardwire-daemon/src/core/pci/pci_device.rs +++ b/crates/cardwire-daemon/src/core/pci/pci_device.rs @@ -35,6 +35,8 @@ fn read_pci_devices_using_iommu() -> Result, Cardwir for pci_address in group.devices { let vendor_id = get_vendor_id(&pci_address); let device_id = get_device_id(&pci_address); + let subsystem_vendor_id = get_subsystem_vendor_id(&pci_address); + let subsystem_device_id = get_subsystem_device_id(&pci_address); let vendor_key = vendor_id.as_deref().map(normalize_device_id); let device_key = device_id.as_deref().map(normalize_device_id); @@ -55,6 +57,8 @@ fn read_pci_devices_using_iommu() -> Result, Cardwir Some(group_id), vendor_id, device_id, + subsystem_vendor_id, + subsystem_device_id, vendor_name, device_name, get_driver(&pci_address), @@ -87,6 +91,8 @@ fn read_pci_devices_using_sysfs() -> Result, Cardwir .ok_or("File name contains invalid UTF-8")?; let vendor_id = get_vendor_id(name); let device_id = get_device_id(name); + let subsystem_vendor_id = get_subsystem_vendor_id(name); + let subsystem_device_id = get_subsystem_device_id(name); let vendor_key = vendor_id.as_deref().map(normalize_device_id); let device_key = device_id.as_deref().map(normalize_device_id); @@ -107,6 +113,8 @@ fn read_pci_devices_using_sysfs() -> Result, Cardwir None, vendor_id, device_id, + subsystem_vendor_id, + subsystem_device_id, vendor_name, device_name, get_driver(name), @@ -136,6 +144,24 @@ fn get_device_id(pci_address: &str) -> Option { .ok() } +fn get_subsystem_vendor_id(pci_address: &str) -> Option { + read_sysfs_trim( + Path::new("/sys/bus/pci/devices") + .join(pci_address) + .join("subsystem_vendor"), + ) + .ok() +} + +fn get_subsystem_device_id(pci_address: &str) -> Option { + read_sysfs_trim( + Path::new("/sys/bus/pci/devices") + .join(pci_address) + .join("subsystem_device"), + ) + .ok() +} + fn get_class(pci_address: &str) -> Option { read_sysfs_trim( Path::new("/sys/bus/pci/devices")