Coverage Report

Created: 2026-08-20 05:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
src/intel.rs
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
}