Line | Count | Source |
1 | | // SPDX-License-Identifier: Apache-2.0 |
2 | | //! `ROCm` userspace detection. |
3 | | //! |
4 | | //! Unlike the rest of this crate, `ROCm` has no driver-side version to read: KFD |
5 | | //! sysfs exposes a topology generation counter and nothing else, and `amdgpu` |
6 | | //! declares no `MODULE_VERSION`. The release version lives in the userspace |
7 | | //! install instead, in a plain text file the `rocm-core` package writes: |
8 | | //! |
9 | | //! ```text |
10 | | //! /opt/rocm/.info/version → 6.2.4-123 |
11 | | //! ``` |
12 | | //! |
13 | | //! So this is a filesystem probe, not a library call — nothing links, and a |
14 | | //! host without `ROCm` simply reports `None`. `ROCM_PATH` is honoured first, |
15 | | //! which is how side-by-side installs (`/opt/rocm-6.2.4`) are selected. |
16 | | //! |
17 | | //! Note what this does *not* mean: `ROCm` being absent says nothing about |
18 | | //! whether the GPU works for compute. The kernel side is independent, and is |
19 | | //! what [`crate::GpuInfo::gfx_target`] reports. |
20 | | |
21 | | use crate::{RocmHost, RocmVersion}; |
22 | | use std::path::{Path, PathBuf}; |
23 | | |
24 | | /// Conventional install prefix, used when `ROCM_PATH` is unset. |
25 | | const DEFAULT_ROOT: &str = "/opt/rocm"; |
26 | | |
27 | | /// Path of the version file relative to an install root. |
28 | | const VERSION_FILE: &str = ".info/version"; |
29 | | |
30 | | /// Parse a `.info/version` file: `major.minor.patch`, followed by a build |
31 | | /// suffix the shared parser ignores (`6.2.4-123` → `6.2.4`). |
32 | 11 | fn parse_version(content: &str) -> Option<RocmVersion> { |
33 | 11 | let (major6 , minor6 , patch6 ) = crate::parse_dotted_version(content)?5 ; |
34 | 6 | Some(RocmVersion { |
35 | 6 | major, |
36 | 6 | minor, |
37 | 6 | patch, |
38 | 6 | }) |
39 | 11 | } |
40 | | |
41 | | /// Read the `ROCm` version from one install root, if it holds one. |
42 | 5 | fn version_at(root: &Path) -> Option<RocmVersion> { |
43 | 5 | std::fs::read_to_string(root.join(VERSION_FILE)) |
44 | 5 | .ok() |
45 | 5 | .and_then(|content| parse_version0 (&content0 )) |
46 | 5 | } |
47 | | |
48 | | /// Install roots to try, most specific first. |
49 | 5 | fn roots() -> Vec<PathBuf> { |
50 | 5 | let mut roots = Vec::new(); |
51 | | // An explicit `ROCM_PATH` wins: it is how a side-by-side install is picked, |
52 | | // so honouring `/opt/rocm` over it would report the wrong version. |
53 | 5 | if let Some(path0 ) = std::env::var_os("ROCM_PATH") { |
54 | 0 | roots.push(PathBuf::from(path)); |
55 | 5 | } |
56 | 5 | roots.push(PathBuf::from(DEFAULT_ROOT)); |
57 | 5 | roots |
58 | 5 | } |
59 | | |
60 | 4 | pub(crate) fn host() -> Option<RocmHost> { |
61 | 4 | let version0 = roots().iter().find_map(|root| version_at(root))?; |
62 | 0 | Some(RocmHost { version }) |
63 | 4 | } |
64 | | |
65 | | #[cfg(test)] |
66 | | mod tests { |
67 | | use super::*; |
68 | | |
69 | | #[test] |
70 | 1 | fn parses_release_with_build_suffix() { |
71 | | // The exact form `rocm-core` writes. |
72 | 1 | assert_eq!( |
73 | 1 | parse_version("6.2.4-123\n"), |
74 | 1 | Some(RocmVersion::new(6, 2, 4)) |
75 | | ); |
76 | 1 | assert_eq!(parse_version("5.7.1-63"), Some(RocmVersion::new(5, 7, 1))); |
77 | 1 | assert_eq!( |
78 | 1 | parse_version(" 6.0.0-91 "), |
79 | 1 | Some(RocmVersion::new(6, 0, 0)) |
80 | | ); |
81 | 1 | } |
82 | | |
83 | | #[test] |
84 | 1 | fn parses_release_without_suffix_or_patch() { |
85 | 1 | assert_eq!(parse_version("6.2.4"), Some(RocmVersion::new(6, 2, 4))); |
86 | 1 | assert_eq!(parse_version("6.2"), Some(RocmVersion::new(6, 2, 0))); |
87 | 1 | assert_eq!(parse_version("6.2+build"), Some(RocmVersion::new(6, 2, 0))); |
88 | 1 | } |
89 | | |
90 | | #[test] |
91 | 1 | fn rejects_malformed_versions() { |
92 | 1 | assert_eq!(parse_version(""), None); |
93 | 1 | assert_eq!(parse_version("6"), None, "a bare major is not a version"); |
94 | 1 | assert_eq!(parse_version("six.two.four"), None); |
95 | 1 | assert_eq!(parse_version("6.2.x"), None); |
96 | 1 | assert_eq!(parse_version("-6.2.4"), None); |
97 | 1 | } |
98 | | |
99 | | #[test] |
100 | 1 | fn versions_order_major_first() { |
101 | 1 | assert!(RocmVersion::new(6, 2, 4) > RocmVersion::new(6, 2, 0)); |
102 | 1 | assert!(RocmVersion::new(6, 0, 0) > RocmVersion::new(5, 7, 1)); |
103 | 1 | assert!(RocmVersion::new(6, 10, 0) > RocmVersion::new(6, 9, 9)); |
104 | 1 | } |
105 | | |
106 | | #[test] |
107 | 1 | fn missing_install_root_reports_nothing() { |
108 | 1 | assert_eq!(version_at(Path::new("/nonexistent-rocm-root")), None); |
109 | 1 | } |
110 | | |
111 | | #[test] |
112 | 1 | fn rocm_path_is_searched_before_the_default() { |
113 | 1 | let roots = roots(); |
114 | 1 | assert_eq!( |
115 | 1 | roots.last().map(PathBuf::as_path), |
116 | 1 | Some(Path::new(DEFAULT_ROOT)), |
117 | | "the conventional prefix is always the fallback", |
118 | | ); |
119 | 1 | if std::env::var_os("ROCM_PATH").is_some() { |
120 | 0 | assert_eq!(roots.len(), 2, "an explicit ROCM_PATH is tried first"); |
121 | 1 | } |
122 | 1 | } |
123 | | |
124 | | #[test] |
125 | 1 | fn host_lookup_never_panics() { |
126 | | // Environment-dependent: most hosts have no ROCm at all. |
127 | 1 | if let Some(rocm0 ) = host() { |
128 | 0 | assert!(rocm.version.major > 0); |
129 | 1 | } |
130 | 1 | } |
131 | | } |