1pub const INTEGRATED_PCI_DEVICE_IDS: &[&str] = &[
39 "0x164e", "0x15c8", "0x15e0", "0x1681", "0x1506", "0x1638", "0x15d8", "0x1640", ];
48
49pub const DISCRETE_MIN_VRAM_BYTES: u64 = 4 * 1024 * 1024 * 1024;
51
52pub const INTEGRATED_GFX_ARCHS: &[&str] = &[
60 "gfx1036", "gfx1103", ];
63
64pub fn is_integrated_by_gfx_arch(gfx: &str) -> bool {
66 let gfx = gfx.trim().to_ascii_lowercase();
67 INTEGRATED_GFX_ARCHS
68 .iter()
69 .any(|a| a.eq_ignore_ascii_case(&gfx))
70}
71
72pub const INTEGRATED_NAME_PATTERNS: &[&str] = &[
74 "Cezanne",
76 "Rembrandt",
77 "Phoenix",
78 "Raphael",
79 "Barcelo",
80 "Pink Sardine",
81 "Yellow Carp",
82 "Green Sardine",
83 "Hawk Point",
84 "Dragon Range",
85 "Fire Range",
86 "Stella",
87 "Krackan",
88 "Strix Point",
89 "Mendocino",
90 "Raven",
91 "Picasso",
92 "Renoir",
93 "Integrated",
95 "iGPU",
96 "APU",
97 "AMD Radeon Graphics",
98 "Radeon Graphics",
99 " 5600G",
101 " 5700G",
102 " 5600GE",
103 " 5700GE",
104 " 5700G",
105 " 5600GT",
106 " 5700GT",
107 " 8600G",
108 " 8700G",
109 " 8500G",
110 " 8300G",
111 "7800X3D",
113 "7950X3D",
114 "7900X3D",
115 "7700X3D",
116 "7600X3D",
117 "Ryzen 7 7735",
119 "Ryzen 7 7840",
120 "Ryzen 7 8840",
121 "Ryzen 5 7535",
122 "Ryzen 5 7640",
123 "Ryzen 5 8640",
124 "Ryzen 9 7945",
125 "Ryzen 9 8945",
126];
127
128fn integrated_name_patterns_lower() -> &'static [String] {
134 use std::sync::OnceLock;
135 static LOWER: OnceLock<Vec<String>> = OnceLock::new();
136 LOWER.get_or_init(|| {
137 INTEGRATED_NAME_PATTERNS
138 .iter()
139 .map(|p| p.to_lowercase())
140 .collect()
141 })
142}
143
144pub fn is_integrated_gpu_name(marketing_name: &str) -> bool {
150 let name_lower = marketing_name.to_lowercase();
151 let name_upper = marketing_name.to_uppercase();
152
153 for pattern in integrated_name_patterns_lower() {
157 if name_lower.contains(pattern) {
158 return true;
159 }
160 }
161
162 if name_upper.contains("RYZEN") && !name_upper.contains("RX") {
164 return true;
165 }
166
167 if let Some(pos) = name_upper.find("RYZEN") {
172 let after_ryzen = &name_upper[pos + 5..];
173 for word in after_ryzen.split_whitespace() {
174 if (word.ends_with('G') || word.ends_with("GE") || word.ends_with("GT"))
175 && word.len() >= 4
176 && !word.starts_with("RX")
177 && !word.starts_with("RADEON")
178 && !word.contains("XT")
179 && !word.contains("XTX")
180 {
181 return true;
182 }
183 }
184 }
185
186 if name_upper.contains("RADEON GRAPHICS")
188 && !name_upper.contains("RX")
189 && (!marketing_name.chars().any(|c| c.is_ascii_digit())
190 || marketing_name.contains("AMD Radeon Graphics"))
191 {
192 return true;
193 }
194
195 false
196}
197
198pub fn is_integrated_by_pci_id(pci_device_id: &str) -> bool {
203 let normalized = pci_device_id.trim().to_ascii_lowercase();
204 let normalized = normalized.trim_start_matches("0x");
205 INTEGRATED_PCI_DEVICE_IDS
206 .iter()
207 .any(|id| id.trim_start_matches("0x").eq_ignore_ascii_case(normalized))
208}
209
210pub fn device_is_integrated(
226 name: Option<&str>,
227 pci_device_id: Option<&str>,
228 gfx_arch: Option<&str>,
229 vram_bytes: Option<u64>,
230) -> bool {
231 if let Some(id) = pci_device_id {
232 if is_integrated_by_pci_id(id) {
233 return true;
234 }
235 }
236 if let Some(g) = gfx_arch {
237 if !g.trim().is_empty() && is_integrated_by_gfx_arch(g) {
238 return true;
239 }
240 }
241 if let Some(name) = name {
242 if !name.trim().is_empty() && is_integrated_gpu_name(name) {
243 return true;
244 }
245 if let Some(vram) = vram_bytes {
262 let upper = name.to_ascii_uppercase();
263 let has_discrete_marker = upper.contains("RADEON RX")
264 || upper.contains(" RX ")
265 || upper.contains("RADEON PRO")
266 || upper.contains("INSTINCT")
267 || upper.contains("FIREPRO");
268 if !name.trim().is_empty() && !has_discrete_marker && vram < DISCRETE_MIN_VRAM_BYTES {
269 return true;
270 }
271 }
272 }
273 false
274}
275
276pub fn read_vram_bytes(pci_slot: &str) -> Option<u64> {
280 let slot = pci_slot.trim().trim_start_matches("0000:");
281 let path = format!("/sys/bus/pci/devices/0000:{slot}/mem_info_vram_total");
282 let value = std::fs::read_to_string(&path).ok()?;
283 value.trim().parse::<u64>().ok()
284}
285
286#[derive(Debug, Clone)]
295pub struct AmdGpu {
296 pub pci_slot: String,
298 pub pci_device_id: String,
300 pub marketing_name: Option<String>,
302 pub gfx_arch: Option<String>,
304 pub vram_bytes: Option<u64>,
306 pub is_integrated: bool,
308 pub rocm_index: Option<u32>,
320}
321
322pub const DISCRETE_PCI_ID_TO_GFX: &[(&str, &str)] = &[
327 ("0x7550", "gfx1201"),
329 ("0x7551", "gfx1201"),
330 ("0x7590", "gfx1200"),
331 ("0x744c", "gfx1100"),
333 ("0x7448", "gfx1100"),
334 ("0x7449", "gfx1100"),
335 ("0x744a", "gfx1100"),
336 ("0x744b", "gfx1100"),
337 ("0x745e", "gfx1100"),
338 ("0x747e", "gfx1101"),
340 ("0x7470", "gfx1101"),
341 ("0x7460", "gfx1101"),
342 ("0x7461", "gfx1101"),
343 ("0x7480", "gfx1102"),
345 ("0x7483", "gfx1102"),
346 ("0x7489", "gfx1102"),
347 ("0x749f", "gfx1102"),
348 ("0x73f0", "gfx1102"),
349 ("0x73bf", "gfx1030"),
351 ("0x73af", "gfx1030"),
352 ("0x73a5", "gfx1030"),
353 ("0x73a1", "gfx1030"),
354 ("0x73a2", "gfx1030"),
355 ("0x73a3", "gfx1030"),
356 ("0x73df", "gfx1031"),
358 ("0x73c3", "gfx1031"),
359 ("0x73ff", "gfx1032"),
361 ("0x73ef", "gfx1032"),
362 ("0x73e0", "gfx1032"),
363 ("0x73e1", "gfx1032"),
364 ("0x73e3", "gfx1032"),
365 ("0x743f", "gfx1034"),
367 ("0x7424", "gfx1034"),
368 ("0x7421", "gfx1034"),
369 ("0x7422", "gfx1034"),
370 ("0x7423", "gfx1034"),
371];
372
373fn normalize_pci_id(id: &str) -> String {
375 id.trim()
376 .to_ascii_lowercase()
377 .trim_start_matches("0x")
378 .to_string()
379}
380
381pub fn pci_id_to_gfx(pci_device_id: &str) -> Option<&'static str> {
383 let normalized = normalize_pci_id(pci_device_id);
384 DISCRETE_PCI_ID_TO_GFX
385 .iter()
386 .find(|(id, _)| normalize_pci_id(id) == normalized)
387 .map(|(_, gfx)| *gfx)
388}
389
390pub fn gfx_from_marketing_name(name: &str) -> Option<&'static str> {
395 let name_lower = name.to_lowercase();
396
397 if name_lower.contains("navi 48") || name_lower.contains("navi48") {
399 return Some("gfx1201");
400 }
401 if name_lower.contains("navi 44") || name_lower.contains("navi44") {
402 return Some("gfx1200");
403 }
404 if name_lower.contains("navi 31") || name_lower.contains("navi31") {
405 return Some("gfx1100");
406 }
407 if name_lower.contains("navi 32") || name_lower.contains("navi32") {
408 return Some("gfx1101");
409 }
410 if name_lower.contains("navi 33") || name_lower.contains("navi33") {
411 return Some("gfx1102");
412 }
413 if name_lower.contains("navi 21") || name_lower.contains("navi21") {
414 return Some("gfx1030");
415 }
416 if name_lower.contains("navi 22") || name_lower.contains("navi22") {
417 return Some("gfx1031");
418 }
419 if name_lower.contains("navi 23") || name_lower.contains("navi23") {
420 return Some("gfx1032");
421 }
422 if name_lower.contains("navi 24") || name_lower.contains("navi24") {
423 return Some("gfx1034");
424 }
425
426 if name_lower.contains("7900 xtx") || name_lower.contains("7900xtx") {
428 return Some("gfx1100");
429 }
430 if name_lower.contains("7900 gre") || name_lower.contains("7900gre") {
431 return Some("gfx1100");
432 }
433 if name_lower.contains("7900 xt") || name_lower.contains("7900xt") {
434 return Some("gfx1100");
435 }
436 if name_lower.contains("7800 xt") || name_lower.contains("7800xt") {
437 return Some("gfx1101");
438 }
439 if name_lower.contains("7800 gre") || name_lower.contains("7800gre") {
440 return Some("gfx1101");
441 }
442 if name_lower.contains("7700 xt") || name_lower.contains("7700xt") {
443 return Some("gfx1101");
444 }
445 if name_lower.contains("7600 xt")
446 || name_lower.contains("7600xt")
447 || name_lower.contains("7600")
448 {
449 return Some("gfx1102");
450 }
451
452 if name_lower.contains("9070 xt")
454 || name_lower.contains("9070xt")
455 || name_lower.contains("9070 gre")
456 || name_lower.contains("9070gre")
457 {
458 return Some("gfx1201");
459 }
460 if name_lower.contains("9060") {
461 return Some("gfx1200");
462 }
463
464 if name_lower.contains("6950 xt")
466 || name_lower.contains("6950xt")
467 || name_lower.contains("6900 xt")
468 || name_lower.contains("6900xt")
469 {
470 return Some("gfx1030");
471 }
472 if name_lower.contains("6800 xt")
473 || name_lower.contains("6800xt")
474 || name_lower.contains("6800")
475 || name_lower.contains("6900")
476 {
477 return Some("gfx1030");
478 }
479 if name_lower.contains("6700 xt")
480 || name_lower.contains("6700xt")
481 || name_lower.contains("6750 xt")
482 || name_lower.contains("6750xt")
483 {
484 return Some("gfx1031");
485 }
486 if name_lower.contains("6600 xt")
487 || name_lower.contains("6600xt")
488 || name_lower.contains("6600")
489 || name_lower.contains("6650")
490 {
491 return Some("gfx1032");
492 }
493 if name_lower.contains("6500 xt") || name_lower.contains("6500xt") {
494 return Some("gfx1034");
495 }
496
497 None
499}
500
501#[derive(Debug, Clone)]
503struct LspciDevice {
504 pci_slot: String,
505 name: String,
506 pci_id: String,
507}
508
509fn parse_lspci_amd() -> Vec<LspciDevice> {
514 let output = match std::process::Command::new("lspci").arg("-nn").output() {
515 Ok(o) if o.status.success() => o.stdout,
516 _ => return Vec::new(),
517 };
518
519 let mut devices = Vec::new();
520 let text = String::from_utf8_lossy(&output);
521
522 for line in text.lines() {
523 let line = line.trim();
524 if !line.contains("AMD") && !line.contains("Advanced Micro Devices") {
526 continue;
527 }
528 if !line.contains("[0300]") && !line.contains("[0302]") {
529 continue;
530 }
531
532 let pci_slot = line
535 .split_whitespace()
536 .next()
537 .unwrap_or("")
538 .trim_start_matches("0000:")
539 .to_string();
540 if pci_slot.is_empty() {
541 continue;
542 }
543
544 let pci_id = if let Some(bracket_start) = line.rfind('[') {
546 let bracket_content = &line[bracket_start + 1..];
547 let bracket_end = bracket_content.find(']').unwrap_or(bracket_content.len());
548 let id_str = &bracket_content[..bracket_end];
549 id_str
550 .split(':')
551 .nth(1)
552 .map(|id| format!("0x{}", id.to_lowercase()))
553 .unwrap_or_else(|| "unknown".to_string())
554 } else {
555 "unknown".to_string()
556 };
557
558 let name = if let Some(class_end) = line.find("]:") {
561 let after_class = &line[class_end + 2..];
562 if let Some(dev_bracket) = after_class.rfind('[') {
563 after_class[..dev_bracket].trim().to_string()
564 } else {
565 "AMD GPU".to_string()
566 }
567 } else {
568 "AMD GPU".to_string()
569 };
570
571 devices.push(LspciDevice {
572 pci_slot,
573 name,
574 pci_id,
575 });
576 }
577
578 devices
579}
580
581fn rocminfo_available() -> bool {
583 std::process::Command::new("rocminfo")
584 .arg("--version")
585 .output()
586 .map(|o| o.status.success())
587 .unwrap_or(false)
588}
589
590fn parse_rocminfo_agents() -> Vec<(u32, String, Option<String>)> {
598 let output = match std::process::Command::new("rocminfo").output() {
599 Ok(o) if o.status.success() => o.stdout,
600 _ => return Vec::new(),
601 };
602
603 let mut agents = Vec::new();
604 let text = String::from_utf8_lossy(&output);
605
606 for agent_block in text.split("****") {
608 let mut name = None;
609 let mut gfx_arch = None;
610 let mut index: Option<u32> = None;
611
612 for line in agent_block.lines() {
613 let line = line.trim();
614 if line.starts_with("Agent ") && index.is_none() {
617 if let Some(rest) = line.strip_prefix("Agent ") {
618 if let Ok(n) = rest.trim().parse::<u32>() {
619 index = Some(n);
620 }
621 }
622 }
623 if let Some(rest) = line.strip_prefix("Name:") {
624 name = Some(rest.trim().to_string());
625 }
626 if let Some(rest) = line.strip_prefix("gfx version:") {
627 gfx_arch = Some(rest.trim().to_string());
628 }
629 }
630
631 if let Some(n) = name {
632 let idx = index.unwrap_or(agents.len() as u32);
636 agents.push((idx, n, gfx_arch));
637 }
638 }
639
640 agents
641}
642
643pub fn detect_amd_gpus() -> Vec<AmdGpu> {
672 use std::collections::{HashMap, HashSet};
673
674 let mut gpus = Vec::new();
675 let mut seen_slots = HashSet::new();
676
677 let lspci_devices = parse_lspci_amd();
679 let mut lspci_map: HashMap<String, LspciDevice> = HashMap::new();
680 for dev in lspci_devices {
681 lspci_map.insert(dev.pci_slot.clone(), dev);
682 }
683
684 let entries = match std::fs::read_dir("/sys/class/drm") {
686 Ok(e) => e,
687 Err(_) => return gpus,
688 };
689
690 for entry in entries.flatten() {
691 let name = entry.file_name().to_string_lossy().to_string();
692 if !name.starts_with("card") || name.contains('-') {
693 continue;
694 }
695
696 let device_path = entry.path().join("device");
697 let canonical = match device_path.canonicalize() {
698 Ok(p) => p,
699 Err(_) => continue,
700 };
701
702 if !seen_slots.insert(canonical.clone()) {
703 continue;
704 }
705
706 let vendor_path = canonical.join("vendor");
708 let vendor = match std::fs::read_to_string(&vendor_path) {
709 Ok(v) => v.trim().to_lowercase(),
710 Err(_) => continue,
711 };
712 if !vendor.starts_with("0x1002") {
713 continue;
714 }
715
716 let class_path = canonical.join("class");
718 let class = match std::fs::read_to_string(&class_path) {
719 Ok(c) => c.trim().to_lowercase(),
720 Err(_) => continue,
721 };
722 let class_val = match class.strip_prefix("0x") {
723 Some(hex) => match u32::from_str_radix(hex, 16) {
724 Ok(v) => v,
725 Err(_) => continue,
726 },
727 None => continue,
728 };
729 const PCI_CLASS_VGA: u32 = 0x030000;
730 const PCI_CLASS_3D: u32 = 0x030200;
731 const CLASS_MASK: u32 = 0xFFFF00;
732 let masked_class = class_val & CLASS_MASK;
733 if masked_class != PCI_CLASS_VGA && masked_class != PCI_CLASS_3D {
734 continue;
735 }
736
737 let device_path_id = canonical.join("device");
739 let sysfs_pci_id = match std::fs::read_to_string(&device_path_id) {
740 Ok(d) => d.trim().to_string(),
741 Err(_) => continue,
742 };
743
744 let uevent_path = canonical.join("uevent");
746 let pci_slot = match std::fs::read_to_string(&uevent_path) {
747 Ok(content) => content
748 .lines()
749 .find(|line| line.starts_with("PCI_SLOT_NAME="))
750 .and_then(|line| line.split('=').nth(1))
751 .map(|s| s.trim_start_matches("0000:").to_string())
752 .unwrap_or_else(|| "unknown".to_string()),
753 Err(_) => "unknown".to_string(),
754 };
755
756 let vram_bytes = read_vram_bytes(&pci_slot);
758
759 let lspci_dev = lspci_map.get(&pci_slot);
761 let marketing_name = lspci_dev
762 .as_ref()
763 .map(|d| d.name.clone())
764 .filter(|n| !n.is_empty());
765 let lspci_pci_id = lspci_dev
766 .as_ref()
767 .map(|d| d.pci_id.clone())
768 .filter(|i| i != "unknown");
769
770 let pci_device_id = lspci_pci_id.unwrap_or_else(|| sysfs_pci_id.clone());
772
773 let mut gfx_arch = pci_id_to_gfx(&pci_device_id).map(|s| s.to_string());
775 if gfx_arch.is_none() {
776 if let Some(ref name) = marketing_name {
777 gfx_arch = gfx_from_marketing_name(name).map(|s| s.to_string());
778 }
779 }
780
781 let is_integrated = device_is_integrated(
783 marketing_name.as_deref(),
784 Some(&pci_device_id),
785 gfx_arch.as_deref(),
786 vram_bytes,
787 );
788
789 gpus.push(AmdGpu {
790 pci_slot,
791 pci_device_id,
792 marketing_name,
793 gfx_arch,
794 vram_bytes,
795 is_integrated,
796 rocm_index: None,
797 });
798 }
799
800 if rocminfo_available() {
802 let rocminfo_agents = parse_rocminfo_agents();
803 if !rocminfo_agents.is_empty() {
804 for (rocm_idx, rocm_name, rocm_gfx) in rocminfo_agents {
809 for gpu in &mut gpus {
810 if let Some(ref name) = gpu.marketing_name {
811 if name.contains(&rocm_name) || rocm_name.contains(name) {
812 if gpu.rocm_index.is_none() {
813 gpu.rocm_index = Some(rocm_idx);
814 }
815 if let Some(ref rg) = rocm_gfx {
816 gpu.gfx_arch = Some(rg.clone());
817 }
818 }
819 }
820 }
821 }
822 }
823 }
824
825 gpus
826}
827
828pub fn detect_discrete_amd_gpus() -> Vec<AmdGpu> {
833 let mut dgpus = detect_amd_gpus()
834 .into_iter()
835 .filter(|gpu| !gpu.is_integrated)
836 .collect::<Vec<_>>();
837 dgpus.sort_by(|a, b| a.pci_slot.cmp(&b.pci_slot));
838 dgpus
839}
840
841#[cfg(test)]
846mod tests {
847 use super::*;
848
849 #[test]
850 fn discrete_cards_are_not_integrated() {
851 assert!(!is_integrated_gpu_name("AMD Radeon RX 7900 XTX"));
852 assert!(!is_integrated_gpu_name("Radeon RX 7800 XT"));
853 assert!(!is_integrated_gpu_name("AMD Radeon RX 6900 XT"));
854 assert!(!is_integrated_gpu_name("AMD Radeon RX 9070 XT"));
855 }
856
857 #[test]
858 fn apu_codenames_are_integrated() {
859 assert!(is_integrated_gpu_name("Raphael"));
860 assert!(is_integrated_gpu_name("raphael")); assert!(is_integrated_gpu_name("RAPHAEL"));
862 assert!(!is_integrated_gpu_name("gfx1036")); assert!(is_integrated_gpu_name("AMD Cezanne (Radeon Graphics)"));
864 assert!(is_integrated_gpu_name("Phoenix"));
865 assert!(is_integrated_gpu_name("Rembrandt"));
866 assert!(is_integrated_gpu_name("Strix Point")); }
868
869 #[test]
870 fn asus_strix_dgpu_is_not_integrated() {
871 assert!(!is_integrated_gpu_name("ASUS ROG Strix Radeon RX 7900 XTX"));
874 assert!(!is_integrated_gpu_name("ROG Strix LC Radeon RX 7800 XT"));
875 }
876
877 #[test]
878 fn ryzen_cpus_with_igpu_are_integrated() {
879 assert!(is_integrated_gpu_name(
880 "AMD Ryzen 7 7800X3D 8-Core Processor"
881 ));
882 assert!(is_integrated_gpu_name(
883 "AMD Ryzen 9 7950X3D 16-Core Processor"
884 ));
885 assert!(is_integrated_gpu_name("AMD Ryzen 5 8600G 6-Core Processor"));
886 assert!(is_integrated_gpu_name("AMD Ryzen 7 8700G"));
887 }
888
889 #[test]
890 fn ryzen_apu_suffix_without_amd_prefix_is_integrated() {
891 assert!(is_integrated_gpu_name("Ryzen 5 5600G"));
894 assert!(is_integrated_gpu_name("Ryzen 7 5700G"));
895 assert!(is_integrated_gpu_name("Ryzen 5 5600GE"));
896 assert!(is_integrated_gpu_name("Ryzen 5 5600GT"));
897 assert!(is_integrated_gpu_name("Ryzen 7 8700G"));
898 }
899
900 #[test]
901 fn low_vram_discrete_cards_are_not_vram_rejected() {
902 let low = 2 * 1024 * 1024 * 1024u64;
905 assert!(!device_is_integrated(
906 Some("AMD Radeon RX 550"),
907 None,
908 None,
909 Some(low)
910 ));
911 assert!(!device_is_integrated(
912 Some("AMD Radeon Pro WX 2100"),
913 None,
914 None,
915 Some(low)
916 ));
917 assert!(!device_is_integrated(
918 Some("AMD Instinct MI50"),
919 None,
920 None,
921 Some(low)
922 ));
923 assert!(device_is_integrated(
925 Some("AMD Device"),
926 Some("0x9999"),
927 None,
928 Some(512 * 1024 * 1024)
929 ));
930 }
931
932 #[test]
933 fn bare_radeon_marketing_name_is_vram_gated() {
934 let low = 2 * 1024 * 1024 * 1024u64;
939 assert!(device_is_integrated(Some("Radeon"), None, None, Some(low)));
940 assert!(device_is_integrated(
941 Some("AMD Radeon"),
942 None,
943 None,
944 Some(low)
945 ));
946
947 assert!(device_is_integrated(
951 Some("AMD Radeon 780M"),
952 None,
953 None,
954 Some(low)
955 ));
956 assert!(device_is_integrated(
957 Some("AMD Radeon 680M"),
958 None,
959 None,
960 Some(low)
961 ));
962
963 let high = 16 * 1024 * 1024 * 1024u64;
967 assert!(!device_is_integrated(
968 Some("Radeon"),
969 None,
970 None,
971 Some(high)
972 ));
973
974 assert!(!device_is_integrated(
976 Some("AMD Radeon RX 7900 XTX"),
977 None,
978 None,
979 Some(low)
980 ));
981 }
982
983 #[test]
984 fn generic_radeon_graphics_is_integrated() {
985 assert!(is_integrated_gpu_name("AMD Radeon Graphics"));
986 assert!(!is_integrated_gpu_name("AMD Radeon RX Graphics 7900"));
987 }
988
989 #[test]
990 fn pci_id_denylist_catches_raphael() {
991 assert!(is_integrated_by_pci_id("0x164e"));
992 assert!(is_integrated_by_pci_id("164E"));
993 assert!(is_integrated_by_pci_id("0x164E"));
994 assert!(!is_integrated_by_pci_id("0x744c")); assert!(!is_integrated_by_pci_id("0x747e")); }
997
998 #[test]
999 fn device_is_integrated_combines_signals() {
1000 assert!(device_is_integrated(Some("Raphael"), None, None, None));
1002 assert!(device_is_integrated(None, Some("0x164e"), None, None));
1004 assert!(device_is_integrated(None, None, Some("gfx1036"), None));
1006 assert!(device_is_integrated(None, None, Some("gfx1103"), None));
1007 assert!(!device_is_integrated(
1009 Some("AMD Radeon RX 7900 XTX"),
1010 Some("0x744c"),
1011 Some("gfx1100"),
1012 Some(24 * 1024 * 1024 * 1024)
1013 ));
1014 assert!(device_is_integrated(
1016 Some("AMD Device"),
1017 Some("0x9999"),
1018 None,
1019 Some(512 * 1024 * 1024)
1020 ));
1021 assert!(!device_is_integrated(
1023 Some("AMD Device"),
1024 Some("0x9999"),
1025 None,
1026 None
1027 ));
1028 }
1029
1030 #[test]
1031 fn gfx_arch_signal_is_safe_for_dgpus() {
1032 assert!(is_integrated_by_gfx_arch("gfx1036"));
1034 assert!(is_integrated_by_gfx_arch("GFX1103"));
1035 assert!(!is_integrated_by_gfx_arch("gfx1100"));
1037 assert!(!is_integrated_by_gfx_arch("gfx1101"));
1038 assert!(!is_integrated_by_gfx_arch("gfx1030"));
1039 }
1040
1041 #[test]
1046 fn pci_id_to_gfx_maps_all_table_entries() {
1047 assert_eq!(pci_id_to_gfx("0x7550"), Some("gfx1201"));
1049 assert_eq!(pci_id_to_gfx("0x7551"), Some("gfx1201"));
1050 assert_eq!(pci_id_to_gfx("0x7590"), Some("gfx1200"));
1051 assert_eq!(pci_id_to_gfx("0x744c"), Some("gfx1100"));
1053 assert_eq!(pci_id_to_gfx("0x7448"), Some("gfx1100"));
1054 assert_eq!(pci_id_to_gfx("0x745e"), Some("gfx1100"));
1055 assert_eq!(pci_id_to_gfx("0x747e"), Some("gfx1101"));
1057 assert_eq!(pci_id_to_gfx("0x7460"), Some("gfx1101"));
1058 assert_eq!(pci_id_to_gfx("0x7480"), Some("gfx1102"));
1060 assert_eq!(pci_id_to_gfx("0x73f0"), Some("gfx1102"));
1061 assert_eq!(pci_id_to_gfx("0x73bf"), Some("gfx1030"));
1063 assert_eq!(pci_id_to_gfx("0x73a1"), Some("gfx1030"));
1064 assert_eq!(pci_id_to_gfx("0x73df"), Some("gfx1031"));
1066 assert_eq!(pci_id_to_gfx("0x73c3"), Some("gfx1031"));
1067 assert_eq!(pci_id_to_gfx("0x73ff"), Some("gfx1032"));
1069 assert_eq!(pci_id_to_gfx("0x73e3"), Some("gfx1032"));
1070 assert_eq!(pci_id_to_gfx("0x743f"), Some("gfx1034"));
1072 assert_eq!(pci_id_to_gfx("0x7421"), Some("gfx1034"));
1073 assert_eq!(pci_id_to_gfx("0x9999"), None);
1075 assert_eq!(pci_id_to_gfx("0x164e"), None); }
1077
1078 #[test]
1079 fn pci_id_to_gfx_normalizes_case_and_prefix() {
1080 assert_eq!(pci_id_to_gfx("0x744c"), Some("gfx1100"));
1081 assert_eq!(pci_id_to_gfx("744C"), Some("gfx1100"));
1082 assert_eq!(pci_id_to_gfx("744c"), Some("gfx1100"));
1083 assert_eq!(pci_id_to_gfx("0X744C"), Some("gfx1100"));
1084 }
1085
1086 #[test]
1087 fn gfx_from_marketing_name_handles_navi_patterns() {
1088 assert_eq!(gfx_from_marketing_name("Navi 48"), Some("gfx1201"));
1090 assert_eq!(gfx_from_marketing_name("navi44"), Some("gfx1200"));
1091 assert_eq!(gfx_from_marketing_name("Navi 31"), Some("gfx1100"));
1092 assert_eq!(gfx_from_marketing_name("Navi 32"), Some("gfx1101"));
1093 assert_eq!(gfx_from_marketing_name("Navi 33"), Some("gfx1102"));
1094 assert_eq!(gfx_from_marketing_name("Navi 21"), Some("gfx1030"));
1095 assert_eq!(gfx_from_marketing_name("Navi 22"), Some("gfx1031"));
1096 assert_eq!(gfx_from_marketing_name("Navi 23"), Some("gfx1032"));
1097 assert_eq!(gfx_from_marketing_name("Navi 24"), Some("gfx1034"));
1098 assert_eq!(gfx_from_marketing_name("Generic GPU"), None);
1100 }
1101
1102 #[test]
1103 fn gfx_from_marketing_name_handles_rdnax_names() {
1104 assert_eq!(
1106 gfx_from_marketing_name("Radeon RX 7900 XTX"),
1107 Some("gfx1100")
1108 );
1109 assert_eq!(
1110 gfx_from_marketing_name("Radeon RX 7900 GRE"),
1111 Some("gfx1100")
1112 );
1113 assert_eq!(
1114 gfx_from_marketing_name("Radeon RX 7800 XT"),
1115 Some("gfx1101")
1116 );
1117 assert_eq!(
1118 gfx_from_marketing_name("Radeon RX 7700 XT"),
1119 Some("gfx1101")
1120 );
1121 assert_eq!(
1122 gfx_from_marketing_name("Radeon RX 7600 XT"),
1123 Some("gfx1102")
1124 );
1125 assert_eq!(
1127 gfx_from_marketing_name("Radeon RX 9070 XT"),
1128 Some("gfx1201")
1129 );
1130 assert_eq!(gfx_from_marketing_name("Radeon RX 9060"), Some("gfx1200"));
1131 assert_eq!(
1133 gfx_from_marketing_name("Radeon RX 6900 XT"),
1134 Some("gfx1030")
1135 );
1136 assert_eq!(
1137 gfx_from_marketing_name("Radeon RX 6800 XT"),
1138 Some("gfx1030")
1139 );
1140 assert_eq!(
1141 gfx_from_marketing_name("Radeon RX 6700 XT"),
1142 Some("gfx1031")
1143 );
1144 assert_eq!(
1145 gfx_from_marketing_name("Radeon RX 6600 XT"),
1146 Some("gfx1032")
1147 );
1148 assert_eq!(
1149 gfx_from_marketing_name("Radeon RX 6500 XT"),
1150 Some("gfx1034")
1151 );
1152 }
1153
1154 #[test]
1155 fn class_filter_excludes_non_gpu_classes() {
1156 let class_bridge = "0x060400";
1159 assert_eq!(
1160 u32::from_str_radix(class_bridge.strip_prefix("0x").unwrap(), 16).unwrap() & 0xFFFF00,
1161 0x060400
1162 );
1163 let class_audio = "0x040300";
1165 assert_eq!(
1166 u32::from_str_radix(class_audio.strip_prefix("0x").unwrap(), 16).unwrap() & 0xFFFF00,
1167 0x040300
1168 );
1169 let class_vga = "0x030000";
1171 assert_eq!(
1172 u32::from_str_radix(class_vga.strip_prefix("0x").unwrap(), 16).unwrap() & 0xFFFF00,
1173 0x030000
1174 );
1175 let class_3d = "0x030200";
1177 assert_eq!(
1178 u32::from_str_radix(class_3d.strip_prefix("0x").unwrap(), 16).unwrap() & 0xFFFF00,
1179 0x030200
1180 );
1181 }
1183}