Line | Count | Source |
1 | | // SPDX-License-Identifier: Apache-2.0 |
2 | | //! Intel GPU architecture identification from the PCI device id. |
3 | | //! |
4 | | //! Neither `i915` nor `xe` publishes an architecture anywhere a caller can read |
5 | | //! it — there is no Intel equivalent of AMD's KFD topology, and the canonical |
6 | | //! answer (`ze_device_ip_version_ext_t`) requires linking Level Zero. What is |
7 | | //! readable is the PCI device id in `/sys/class/drm/card*/device/device`, which |
8 | | //! identifies the family. |
9 | | //! |
10 | | //! The mapping is therefore data rather than mechanism, and it is the part that |
11 | | //! can be wrong. It is deliberately coarse and deliberately incomplete: |
12 | | //! |
13 | | //! - Ids are matched by **range**, one per architecture, not per SKU. Getting |
14 | | //! the family right for a whole generation is far more tractable than getting |
15 | | //! every product id right, and the family is what callers act on. |
16 | | //! - Anything unrecognised reports `None`. Pre-Xe integrated graphics (Gen9 |
17 | | //! through Gen11 — Skylake to Ice Lake) are not mapped at all. Reporting |
18 | | //! nothing is the honest answer for an id this table has never seen; a wrong |
19 | | //! architecture would be worse than an absent one, because a caller would act |
20 | | //! on it. |
21 | | //! |
22 | | //! **Unverified against real hardware.** Written without an Intel GPU to test |
23 | | //! against. The ranges below are the assumption to confirm first: read |
24 | | //! `/sys/class/drm/card*/device/device` on a real machine and check it against |
25 | | //! what `ocloc -device` or Level Zero reports for the same part. |
26 | | |
27 | | use crate::IntelArch; |
28 | | |
29 | | /// PCI device id ranges, one per architecture family. Inclusive on both ends. |
30 | | /// |
31 | | /// Ranges rather than exact ids so an unlisted SKU within a known generation |
32 | | /// still resolves, which is the common case as new parts ship. |
33 | | const FAMILIES: &[(u16, u16, IntelArch)] = &[ |
34 | | // DG1 — the first discrete Xe part. |
35 | | (0x4905, 0x4909, IntelArch::XeLp), |
36 | | // Alder Lake and Raptor Lake integrated. |
37 | | (0x4600, 0x46FF, IntelArch::XeLp), |
38 | | // Rocket Lake integrated. |
39 | | (0x4C80, 0x4CFF, IntelArch::XeLp), |
40 | | // Tiger Lake integrated. |
41 | | (0x9A40, 0x9AFF, IntelArch::XeLp), |
42 | | // Raptor Lake refresh integrated. |
43 | | (0xA700, 0xA7FF, IntelArch::XeLp), |
44 | | // DG2 — Arc A-series (Alchemist). |
45 | | (0x5690, 0x56CF, IntelArch::XeHpg), |
46 | | // Ponte Vecchio — Data Center GPU Max. |
47 | | (0x0BD0, 0x0BDF, IntelArch::XeHpc), |
48 | | // Meteor Lake and Arrow Lake integrated. |
49 | | (0x7D00, 0x7DFF, IntelArch::XeLpg), |
50 | | // Lunar Lake integrated. |
51 | | (0x6400, 0x64FF, IntelArch::Xe2), |
52 | | // Battlemage — Arc B-series. |
53 | | (0xE200, 0xE2FF, IntelArch::Xe2), |
54 | | ]; |
55 | | |
56 | | /// Architecture family for a PCI device id, or `None` when the id is outside |
57 | | /// every known range. |
58 | | #[allow(dead_code)] // used on Linux + in tests; unused on other targets |
59 | 12 | pub(crate) fn arch_for_device_id(id: u16) -> Option<IntelArch> { |
60 | 12 | FAMILIES |
61 | 12 | .iter() |
62 | 87 | .find12 (|(first, last, _)| (*first..=*last).contains(&id)) |
63 | 12 | .map(|&(_, _, arch)| arch) |
64 | 12 | } |
65 | | |
66 | | /// Parse a sysfs PCI id file (`0x56a0`) into its numeric value. |
67 | | #[allow(dead_code)] // used on Linux + in tests; unused on other targets |
68 | 7 | pub(crate) fn parse_device_id(content: &str) -> Option<u16> { |
69 | 7 | let text = content.trim(); |
70 | 7 | let digits5 = text |
71 | 7 | .strip_prefix("0x") |
72 | 7 | .or_else(|| text3 .strip_prefix3 ("0X"))?2 ; |
73 | 5 | u16::from_str_radix(digits, 16).ok() |
74 | 7 | } |
75 | | |
76 | | #[cfg(test)] |
77 | | mod tests { |
78 | | use super::*; |
79 | | |
80 | | #[test] |
81 | 1 | fn parses_sysfs_pci_ids() { |
82 | 1 | assert_eq!(parse_device_id("0x56a0\n"), Some(0x56A0)); |
83 | 1 | assert_eq!(parse_device_id("0X9A49"), Some(0x9A49)); |
84 | 1 | assert_eq!(parse_device_id(" 0xe20b "), Some(0xE20B)); |
85 | 1 | } |
86 | | |
87 | | #[test] |
88 | 1 | fn rejects_ids_without_a_hex_prefix() { |
89 | | // The bare form would be ambiguous with decimal, so it is not accepted. |
90 | 1 | assert_eq!(parse_device_id("56a0"), None); |
91 | 1 | assert_eq!(parse_device_id("0xzzzz"), None); |
92 | 1 | assert_eq!(parse_device_id("0x1ffff"), None, "wider than u16"); |
93 | 1 | assert_eq!(parse_device_id(""), None); |
94 | 1 | } |
95 | | |
96 | | #[test] |
97 | 1 | fn maps_discrete_parts_to_their_family() { |
98 | | // A770 / A750, and a Battlemage B580. |
99 | 1 | assert_eq!(arch_for_device_id(0x56A0), Some(IntelArch::XeHpg)); |
100 | 1 | assert_eq!(arch_for_device_id(0xE20B), Some(IntelArch::Xe2)); |
101 | | // DG1 and Ponte Vecchio. |
102 | 1 | assert_eq!(arch_for_device_id(0x4905), Some(IntelArch::XeLp)); |
103 | 1 | assert_eq!(arch_for_device_id(0x0BD5), Some(IntelArch::XeHpc)); |
104 | 1 | } |
105 | | |
106 | | #[test] |
107 | 1 | fn maps_integrated_parts_to_their_family() { |
108 | 1 | assert_eq!( |
109 | 1 | arch_for_device_id(0x9A49), |
110 | | Some(IntelArch::XeLp), |
111 | | "Tiger Lake" |
112 | | ); |
113 | 1 | assert_eq!( |
114 | 1 | arch_for_device_id(0x4680), |
115 | | Some(IntelArch::XeLp), |
116 | | "Alder Lake" |
117 | | ); |
118 | 1 | assert_eq!( |
119 | 1 | arch_for_device_id(0x7D55), |
120 | | Some(IntelArch::XeLpg), |
121 | | "Meteor Lake" |
122 | | ); |
123 | 1 | assert_eq!( |
124 | 1 | arch_for_device_id(0x64A0), |
125 | | Some(IntelArch::Xe2), |
126 | | "Lunar Lake" |
127 | | ); |
128 | 1 | } |
129 | | |
130 | | #[test] |
131 | 1 | fn unknown_ids_report_nothing_rather_than_guessing() { |
132 | | // Pre-Xe integrated graphics are deliberately unmapped: Skylake (Gen9) |
133 | | // and Ice Lake (Gen11). |
134 | 1 | assert_eq!(arch_for_device_id(0x1912), None, "Skylake GT2"); |
135 | 1 | assert_eq!(arch_for_device_id(0x8A52), None, "Ice Lake"); |
136 | | // A device id no range covers. |
137 | 1 | assert_eq!(arch_for_device_id(0x0000), None); |
138 | 1 | assert_eq!(arch_for_device_id(0xFFFF), None); |
139 | 1 | } |
140 | | |
141 | | #[test] |
142 | 1 | fn ranges_do_not_overlap() { |
143 | | // Overlapping ranges would make the result depend on table order. |
144 | 10 | for (i, &(first, last, _)) in FAMILIES1 .iter1 ().enumerate1 () { |
145 | 10 | assert!(first <= last, "range {i} is inverted"); |
146 | 45 | for &(other_first, other_last, _) in &FAMILIES[i + 1..]10 { |
147 | 45 | assert!( |
148 | 45 | last < other_first || other_last < first14 , |
149 | | "ranges {first:#06x}..={last:#06x} and \ |
150 | | {other_first:#06x}..={other_last:#06x} overlap", |
151 | | ); |
152 | | } |
153 | | } |
154 | 1 | } |
155 | | } |