libdrm_amdgpu_sys/pci/
bus_info.rs

1use crate::AMDGPU;
2
3/// PCI information (Domain, Bus, Device, Function)
4#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
5pub struct BUS_INFO {
6    pub domain: u16,
7    pub bus: u8,
8    pub dev: u8,
9    pub func: u8,
10}
11
12use super::{LINK, STATUS};
13
14use std::path::PathBuf;
15
16impl BUS_INFO {
17    /// Get device sysfs path
18    pub fn get_sysfs_path(&self) -> PathBuf {
19        PathBuf::from("/sys/bus/pci/devices/").join(self.to_string())
20    }
21
22    /// Get device hwmon path
23    pub fn get_hwmon_path(&self) -> Option<PathBuf> {
24        let base = self.get_sysfs_path().join("hwmon");
25        let entry = std::fs::read_dir(base).ok()?.next()?.ok()?;
26
27        Some(entry.path())
28    }
29
30    fn get_drm_path(&self, type_name: &str) -> std::io::Result<PathBuf> {
31        let base = PathBuf::from("/dev/dri");
32
33        let name = format!("by-path/pci-{}-{type_name}", self);
34        let pci_by_path = std::fs::canonicalize(base.join(name));
35
36        pci_by_path.or_else(|e| {
37            std::fs::read_dir(self.get_sysfs_path().join("drm"))?
38                .find_map(|v| {
39                    let file_name = v.ok()?.file_name().into_string().ok()?;
40                    if file_name.starts_with(type_name) {
41                        Some(base.join(file_name))
42                    } else {
43                        None
44                    }
45                })
46                .ok_or(e)
47        })
48    }
49
50    /// Get DRM render path
51    pub fn get_drm_render_path(&self) -> std::io::Result<PathBuf> {
52        self.get_drm_path("render")
53    }
54
55    /// Get DRM card path
56    pub fn get_drm_card_path(&self) -> std::io::Result<PathBuf> {
57        self.get_drm_path("card")
58    }
59
60    /// Get device debug path
61    pub fn get_debug_dri_path(&self) -> std::io::Result<PathBuf> {
62        let s = format!("amdgpu dev={}", self);
63
64        std::fs::read_dir("/sys/kernel/debug/dri/")?
65            .filter_map(|entry| Some(entry.ok()?.path()))
66            .find(|path| {
67                let Ok(name) = std::fs::read_to_string(path.join("name")) else { return false };
68
69                name.starts_with(&s)
70            })
71            .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))
72    }
73
74    /// Get GPU maximum/minimum link speed/width from DPM
75    pub fn get_min_max_link_info_from_dpm(&self) -> Option<[LINK; 2]> {
76        LINK::get_min_max_link_info_from_dpm(self.get_sysfs_path())
77    }
78
79    /// Get GPU current link speed/width from DPM
80    pub fn get_current_link_info_from_dpm(&self) -> Option<LINK> {
81        LINK::get_current_link_info_from_dpm(self.get_sysfs_path())
82    }
83
84    /// Get GPU maximum link speed/width from sysfs
85    pub fn get_max_gpu_link(&self) -> Option<LINK> {
86        let mut tmp = self.get_system_pcie_port_sysfs_path();
87
88        tmp.pop();
89
90        LINK::get_max_link(&tmp)
91    }
92
93    /// Get system maximum link speed/width from sysfs
94    pub fn get_max_system_link(&self) -> Option<LINK> {
95        LINK::get_max_link(&self.get_system_pcie_port_sysfs_path())
96    }
97
98    fn from_pathbuf(path: PathBuf) -> Option<Self> {
99        path
100            .canonicalize().ok()?
101            .file_name()?
102            .to_str()?
103            .parse().ok()
104    }
105
106    /// Recent AMD GPUs have multiple endpoints, and the PCIe speed/width actually
107    /// runs in that system for the GPU is output to `pp_dpm_pcie`.
108    /// ref: <https://gitlab.freedesktop.org/drm/amd/-/issues/1967>
109    pub fn get_gpu_pcie_port_bus(&self) -> Self {
110        let mut path = self.get_system_pcie_port_sysfs_path();
111
112        path.pop();
113
114        if let Some(pci) = Self::from_pathbuf(path) {
115            pci
116        } else {
117            *self
118        }
119    }
120
121    pub fn get_system_pcie_port_bus(&self) -> Self {
122        let path = self.get_system_pcie_port_sysfs_path();
123
124        if let Some(pci) = Self::from_pathbuf(path) {
125            pci
126        } else {
127            *self
128        }
129    }
130
131    /// Recent AMD GPUs have multiple endpoints, and the PCIe speed/width actually
132    /// runs in that system for the GPU is output to `pp_dpm_pcie`.
133    /// ref: <https://gitlab.freedesktop.org/drm/amd/-/issues/1967>
134    fn get_system_pcie_port_sysfs_path(&self) -> PathBuf {
135        const VENDOR_ATI: &str = "0x1002\n";
136        // 0x6: Bridge, 0x4: PCI-to-PCI Bridge
137        const CLASS: &str = "0x060400\n";
138
139        let mut tmp = self.get_sysfs_path().join("../"); // pcie port
140
141        for _ in 0..2 {
142            let [Ok(vendor), Ok(class)] = [
143                std::fs::read_to_string(tmp.join("vendor")),
144                std::fs::read_to_string(tmp.join("class")),
145            ] else { break };
146
147            if vendor == VENDOR_ATI && class == CLASS {
148                tmp.push("../");
149            } else {
150                break;
151            }
152        }
153
154        tmp
155    }
156
157    /// Get GPU current link speed/width from sysfs
158    pub fn get_current_link_info(&self) -> Option<LINK> {
159        LINK::get_from_sysfs_with_status(self.get_sysfs_path(), STATUS::Current)
160    }
161
162    /// Get GPU maximum link speed/width from sysfs
163    pub fn get_max_link_info(&self) -> Option<LINK> {
164        LINK::get_from_sysfs_with_status(self.get_sysfs_path(), STATUS::Max)
165    }
166
167    fn parse_id(&self, file_name: &str) -> Option<u32> {
168        let sysfs_path = self.get_sysfs_path();
169        let id = std::fs::read_to_string(sysfs_path.join(file_name)).ok()?;
170
171        u32::from_str_radix(id.trim_start_matches("0x").trim_end(), 16).ok()
172    }
173
174    /// Get PCI Device ID from sysfs
175    pub fn get_device_id(&self) -> Option<u32> {
176        self.parse_id("device")
177    }
178
179    /// Get PCI Revision ID from sysfs
180    pub fn get_revision_id(&self) -> Option<u32> {
181        self.parse_id("revision")
182    }
183
184    /// Find device marketing name from `amdgpu.ids`  
185    /// Link: <https://gitlab.freedesktop.org/mesa/drm/-/blob/main/data/amdgpu.ids>
186    pub fn find_device_name(&self) -> Option<String> {
187        let device_id = self.get_device_id()?;
188        let revision_id = self.get_revision_id()?;
189
190        AMDGPU::find_device_name(device_id, revision_id)
191    }
192
193    pub fn find_device_name_or_default_name(&self) -> String {
194        self.find_device_name().unwrap_or(AMDGPU::DEFAULT_DEVICE_NAME.to_string())
195    }
196
197    pub fn check_if_device_is_active(&self) -> bool {
198        let path = self.get_sysfs_path().join("power/runtime_status");
199        let Ok(s) = std::fs::read_to_string(path) else { return false };
200
201        s.starts_with("active")
202    }
203}
204
205#[derive(Debug, PartialEq, Eq)]
206pub struct ParseBusInfoError;
207
208impl std::str::FromStr for BUS_INFO {
209    type Err = ParseBusInfoError;
210
211    fn from_str(s: &str) -> Result<Self, Self::Err> {
212        let mut split = s.split(&[':', '.']).take(4);
213        let [domain, bus, dev, func] = [split.next(), split.next(), split.next(), split.next()]
214            .map(|s| s.ok_or(ParseBusInfoError));
215        let domain = u16::from_str_radix(domain?, 16).map_err(|_| ParseBusInfoError);
216        let [bus, dev, func] = [bus, dev, func].map(|v| {
217            u8::from_str_radix(v?, 16).map_err(|_| ParseBusInfoError)
218        });
219
220        Ok(Self {
221            domain: domain?,
222            bus: bus?,
223            dev: dev?,
224            func: func?,
225        })
226    }
227}
228
229#[test]
230fn test_pci_bus_info_parse() {
231    let s = "0000:0d:00.0".parse();
232    let bus = BUS_INFO { domain: 0x0, bus: 0xd, dev: 0x0, func: 0x0 };
233
234    assert_eq!(s, Ok(bus));
235}
236
237use std::fmt;
238impl fmt::Display for BUS_INFO {
239    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
240        write!(f,
241            "{:04x}:{:02x}:{:02x}.{:01x}",
242            self.domain, self.bus, self.dev, self.func
243        )
244    }
245}