1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
use crate::AMDGPU::DEVICE_HANDLE;
use crate::*;

pub use bindings::{
    amdgpu_device_handle,
    // amdgpu_device_initialize,
    amdgpu_gds_resource_info,
    amdgpu_gpu_info,
    drm_amdgpu_heap_info,
    drm_amdgpu_info_device,
    drm_amdgpu_info_gds,
    drm_amdgpu_info_vram_gtt,
    drm_amdgpu_memory_info,
    drm_amdgpu_info_vce_clock_table,
};
use bindings::{
    AMDGPU_INFO_NUM_BYTES_MOVED,
    AMDGPU_INFO_DEV_INFO,
    AMDGPU_INFO_GDS_CONFIG,
    AMDGPU_INFO_VRAM_GTT,
    AMDGPU_INFO_MEMORY,
    AMDGPU_INFO_VRAM_USAGE,
    AMDGPU_INFO_VIS_VRAM_USAGE,
    AMDGPU_INFO_GTT_USAGE,
    AMDGPU_INFO_VCE_CLOCK_TABLE,
    AMDGPU_INFO_NUM_VRAM_CPU_PAGE_FAULTS,
};
use core::mem::{size_of, MaybeUninit};

pub struct DeviceHandle(pub(crate) DEVICE_HANDLE, pub(crate) i32);

unsafe impl Send for DeviceHandle {}
unsafe impl Sync for DeviceHandle {}

#[cfg(feature = "std")]
use std::path::{Path, PathBuf};

impl DeviceHandle {
    /// Initialization.
    /// Example of `fd`: `/dev/dri/renderD128`, `/dev/dri/by-path/pci-{[PCI::BUS]}-render`  
    /// It may require a write option (`std::fs::OpenOptions::new().read(true).write(true)`)
    /// for GUI context.  
    /// ref: <https://gitlab.freedesktop.org/mesa/mesa/-/issues/2424>
    pub fn init(fd: i32) -> Result<(Self, u32, u32), i32> {
        unsafe {
            let mut amdgpu_dev: MaybeUninit<amdgpu_device_handle> = MaybeUninit::uninit();
            let mut major: MaybeUninit<u32> = MaybeUninit::zeroed();
            let mut minor: MaybeUninit<u32> = MaybeUninit::zeroed();

            let r = bindings::amdgpu_device_initialize(
                fd,
                major.as_mut_ptr(),
                minor.as_mut_ptr(),
                amdgpu_dev.as_mut_ptr(),
            );

            let [major, minor] = [major.assume_init(), minor.assume_init()];
            let amdgpu_dev = Self(amdgpu_dev.assume_init(), fd);

            query_error!(r);

            Ok((amdgpu_dev, major, minor))
        }
    }

    fn deinit(&self) -> Result<i32, i32> {
        let r = unsafe { bindings::amdgpu_device_deinitialize(self.0) };

        query_error!(r);

        Ok(r)
    }

    pub fn get_fd(&self) -> i32 {
        self.1
    }

    /// (`major`, `minor`, `patchlevel`)
    #[deprecated(since = "0.1.3", note = "superseded by `get_drm_version_struct`")]
    pub fn get_drm_version(&self) -> Result<(i32, i32, i32), ()> {
        let fd = self.1;
        let drm_ver_ptr = unsafe { bindings::drmGetVersion(fd) };

        if drm_ver_ptr.is_null() {
            return Err(());
        }

        let ver = unsafe { (
            (*drm_ver_ptr).version_major,
            (*drm_ver_ptr).version_minor,
            (*drm_ver_ptr).version_patchlevel,
        ) };

        unsafe { bindings::drmFreeVersion(drm_ver_ptr) }

        Ok(ver)
    }

    #[cfg(feature = "std")]
    pub fn get_drm_version_struct(&self) -> Result<drmVersion, i32> {
        drmVersion::get(self.1)
    }

    /// Returns the result of reading the register at the specified offset.
    /// If the offset is not allowed, returns `Err(i32)`.
    pub fn read_mm_registers(&self, offset: u32) -> Result<u32, i32> {
        unsafe {
            let mut out: MaybeUninit<u32> = MaybeUninit::zeroed();

            let r = bindings::amdgpu_read_mm_registers(
                self.0,
                offset, // DWORD offset
                1, // count
                0xFFFF_FFFF, // instance mask, full mask
                0, // flags
                out.as_mut_ptr(),
            );

            let out = out.assume_init();

            query_error!(r);

            Ok(out)
        }
    }

    /// From libdrm-2.4.114, it returns the default name ("AMD Radeon Graphics")
    ///  if there is no name that matches amdgpu.ids  
    /// <https://gitlab.freedesktop.org/mesa/drm/-/commit/a81b9ab8f3fb6840b36f732c1dd25fe5e0d68d0a>
    #[cfg(feature = "std")]
    #[deprecated(since = "0.1.3",  note = "superseded by `get_marketing_name_or_default`")]
    pub fn get_marketing_name(&self) -> Result<String, std::str::Utf8Error> {
        use core::ffi::CStr;

        let mark_name = unsafe { bindings::amdgpu_get_marketing_name(self.0) };

        if mark_name.is_null() {
            eprintln!("libdrm_amdgpu_sys: ASIC not found in amdgpu.ids");
            return Ok("".to_string());
        }

        let c_str = unsafe { CStr::from_ptr(mark_name) };

        Ok(c_str.to_str()?.to_string())
    }

    /// Returns the default marketing name ("AMD Radeon Graphics") 
    /// when the device name is not available.
    #[cfg(feature = "std")]
    pub fn get_marketing_name_or_default(&self) -> String {
        use core::ffi::CStr;

        let mark_name_ptr = unsafe { bindings::amdgpu_get_marketing_name(self.0) };

        if mark_name_ptr.is_null() {
            return AMDGPU::DEFAULT_DEVICE_NAME.to_string();
        }

        let c_str = unsafe { CStr::from_ptr(mark_name_ptr) };

        match c_str.to_str() {
            Ok(name) => name,
            Err(_) => AMDGPU::DEFAULT_DEVICE_NAME,
        }.to_string()
    }

    pub fn query_gpu_info(&self) -> Result<amdgpu_gpu_info, i32> {
        unsafe {
            let mut gpu_info: MaybeUninit<amdgpu_gpu_info> = MaybeUninit::uninit();

            let r = bindings::amdgpu_query_gpu_info(self.0, gpu_info.as_mut_ptr());

            let gpu_info = gpu_info.assume_init();

            query_error!(r);

            Ok(gpu_info)
        }
    }

    pub fn query_gds_info(&self) -> Result<amdgpu_gds_resource_info, i32> {
        unsafe {
            let mut gds_info: MaybeUninit<amdgpu_gds_resource_info> = MaybeUninit::uninit();

            let r = bindings::amdgpu_query_gds_info(self.0, gds_info.as_mut_ptr());

            let gds_info = gds_info.assume_init();

            query_error!(r);

            Ok(gds_info)
        }
    }

    pub fn query_sw_info(&self, info: amdgpu_sw_info) -> Result<u32, i32> {
        unsafe {
            let mut val: MaybeUninit<u32> = MaybeUninit::zeroed();

            let r = bindings::amdgpu_query_sw_info(
                self.0,
                info as u32,
                val.as_mut_ptr() as *mut ::core::ffi::c_void,
            );

            let val = val.assume_init();

            query_error!(r);

            Ok(val)
        }
    }

    fn query<T>(&self, info_id: ::core::ffi::c_uint) -> Result<T, i32> {
        unsafe {
            let mut dev: MaybeUninit<T> = MaybeUninit::uninit();

            let r = bindings::amdgpu_query_info(
                self.0,
                info_id,
                size_of::<T>() as u32,
                dev.as_mut_ptr() as *mut ::core::ffi::c_void,
            );

            let dev = dev.assume_init();

            query_error!(r);

            Ok(dev)
        }
    }

    pub fn device_info(&self) -> Result<drm_amdgpu_info_device, i32> {
        Self::query(self, AMDGPU_INFO_DEV_INFO)
    }

    /// Note: `usable_heap_size` equal `real_size - pin_size - reserved_size`, is not fixed.
    pub fn vram_gtt_info(&self) -> Result<drm_amdgpu_info_vram_gtt, i32> {
        // return 
        Self::query(self, AMDGPU_INFO_VRAM_GTT)
    }

    pub fn memory_info(&self) -> Result<drm_amdgpu_memory_info, i32> {
        Self::query(self, AMDGPU_INFO_MEMORY)
    }

    pub fn vram_usage_info(&self) -> Result<u64, i32> {
        Self::query(self, AMDGPU_INFO_VRAM_USAGE)
    }

    pub fn vis_vram_usage_info(&self) -> Result<u64, i32> {
        Self::query(self, AMDGPU_INFO_VIS_VRAM_USAGE)
    }

    pub fn gtt_usage_info(&self) -> Result<u64, i32> {
        Self::query(self, AMDGPU_INFO_GTT_USAGE)
    }

    pub fn gds_info(&self) -> Result<drm_amdgpu_info_gds, i32> {
        Self::query(self, AMDGPU_INFO_GDS_CONFIG)
    }

    /// AMDGPU driver returns invalid [drm_amdgpu_info_vce_clock_table].
    /// ref: <https://gitlab.freedesktop.org/drm/amd/-/issues/2391>
    pub fn vce_clock_info(&self) -> Result<drm_amdgpu_info_vce_clock_table, i32> {
        Self::query(self, AMDGPU_INFO_VCE_CLOCK_TABLE)
    }

    pub fn num_vram_cpu_page_failts(&self) -> Result<u64, i32> {
        Self::query(self, AMDGPU_INFO_NUM_VRAM_CPU_PAGE_FAULTS)
    }

    pub fn num_bytes_moved(&self) -> Result<u64, i32> {
        Self::query(self, AMDGPU_INFO_NUM_BYTES_MOVED)
    }

    /// Get [PCI::BUS_INFO]
    pub fn get_pci_bus_info(&self) -> Result<PCI::BUS_INFO, i32> {
        PCI::BUS_INFO::drm_get_device2(self.1)
    }

    #[cfg(feature = "std")]
    fn get_first_line<P: AsRef<Path>>(path: P) -> Result<String, std::io::Error> {
        use std::fs::File;
        use std::io::{BufRead, BufReader};

        let f = File::open(path)?;
        let mut first_line = String::new();
        let mut buf = BufReader::new(f);
        buf.read_line(&mut first_line)?;

        Ok(first_line)
    }

    #[cfg(feature = "std")]
    fn trim_dpm_clk(line: &str) -> Result<u64, std::num::ParseIntError> {
        const MHZ: &str = "Mhz";
        let mut tmp = String::new();

        /* 0: 214Mhz */
        for s in line.split(' ') {
            if s.ends_with(MHZ) {
                tmp = s.trim_end_matches(MHZ).to_string();
            }
        }

        tmp.parse::<u64>()
    }

    #[cfg(feature = "std")]
    fn get_min_clock(&self, pci: &PCI::BUS_INFO, file_name: &str) -> Option<u64> {
        let path = pci.get_sysfs_path().join(file_name);

        if let Ok(line) = Self::get_first_line(path) {
            if let Ok(clk) = Self::trim_dpm_clk(&line) {
                return Some(clk);
            }
        }

        None
    }

    /// Get the minimum gpu core clock (MHz) from sysfs (`pp_dpm_sclk`).  
    /// Recommend [DeviceHandle::get_min_max_gpu_clock]
    #[cfg(feature = "std")]
    #[deprecated(since = "0.1.2", note = "superseded by `get_min_max_gpu_clock_from_sysfs`")]
    pub fn get_min_gpu_clock_from_sysfs(&self, pci: &PCI::BUS_INFO) -> Option<u64> {
        Self::get_min_clock(self, pci, "pp_dpm_sclk")
    }

    /// Get the minimum memory clock (MHz) from sysfs (`pp_dpm_mclk`).  
    /// Recommend [DeviceHandle::get_min_max_memory_clock]
    #[cfg(feature = "std")]
    #[deprecated(since = "0.1.2", note = "superseded by `get_min_max_memory_clock_from_sysfs`")]
    pub fn get_min_memory_clock_from_sysfs(&self, pci: &PCI::BUS_INFO) -> Option<u64> {
        Self::get_min_clock(self, pci, "pp_dpm_mclk")
    }

    #[cfg(feature = "std")]
    fn get_min_max_clock_from_sysfs<P: Into<PathBuf>>(
        &self,
        path: P,
    ) -> Option<(u32, u32)> {
        const MHZ: usize = "Mhz".len();
        let parse_line = |s: &str| -> Option<u32> {
            let mut split = s.split(' ').skip(1);

            split.next().and_then(|mhz| mhz[..mhz.len()-MHZ].parse::<u32>().ok())
        };

        let s = std::fs::read_to_string(path.into()).ok()?;
        let mut lines = s.lines();

        let first = parse_line(lines.next()?)?;
        let last = match lines.last() {
            Some(last) => parse_line(last)?,
            None => first,
        };

        let min_clk = std::cmp::min(first, last);
        let max_clk = std::cmp::max(first, last);

        Some((min_clk, max_clk))
    }

    /// Get the min/max gpu core clock (MHz) from sysfs (`pp_dpm_mclk`)
    #[cfg(feature = "std")]
    pub fn get_min_max_memory_clock_from_sysfs<P: Into<PathBuf>>(
        &self,
        path: P
    ) -> Option<(u32, u32)> {
        self.get_min_max_clock_from_sysfs(path.into().join("pp_dpm_mclk"))
    }

    /// Get the min/max gpu core clock (MHz) from sysfs (`pp_dpm_mclk`)
    #[cfg(feature = "std")]
    pub fn get_min_max_memory_clock(&self) -> Option<(u32, u32)> {
        let sysfs_path = self.get_sysfs_path().ok()?;
        self.get_min_max_memory_clock_from_sysfs(sysfs_path)
    }

    /// Get the min/max gpu core clock (MHz) from sysfs (`pp_dpm_sclk`)
    #[cfg(feature = "std")]
    pub fn get_min_max_gpu_clock_from_sysfs<P: Into<PathBuf>>(
        &self,
        path: P
    ) -> Option<(u32, u32)> {
        self.get_min_max_clock_from_sysfs(path.into().join("pp_dpm_sclk"))
    }

    /// Get the min/max gpu core clock (MHz) from sysfs (`pp_dpm_sclk`)
    #[cfg(feature = "std")]
    pub fn get_min_max_gpu_clock(&self) -> Option<(u32, u32)> {
        let sysfs_path = self.get_sysfs_path().ok()?;
        self.get_min_max_gpu_clock_from_sysfs(sysfs_path)
    }

    /// 
    #[cfg(feature = "std")]
    pub fn get_sysfs_path(&self) -> Result<PathBuf, i32> {
        let path = self.get_pci_bus_info()?.get_sysfs_path();

        Ok(path)
    }

    /// 
    #[cfg(feature = "std")]
    pub fn get_hwmon_path(&self) -> Option<PathBuf> {
        self.get_pci_bus_info().ok().and_then(|pci| pci.get_hwmon_path())
    }

    /// ref: drivers/gpu/drm/amd/pm/swsmu/smu13/aldebaran_ppt.c
    /// ref: <https://github.com/RadeonOpenCompute/rocm_smi_lib/blob/master/python_smi_tools/rocm_smi.py>
    #[cfg(feature = "std")]
    pub fn check_if_secondary_die(&self) -> bool {
        let Some(power_cap) = self.get_power_cap() else { return false };

        power_cap.check_if_secondary_die()
    }
}

impl Drop for DeviceHandle {
    fn drop(&mut self) {
        self.deinit().unwrap();
    }
}

impl drm_amdgpu_memory_info {
    /// The AMDGPU driver allocates part of VRAM to pre-OS buffer (vbios, frame buffer)
    /// if VRAM is larger than 8GiB
    /// ref: drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c  
    /// ref: <https://gitlab.freedesktop.org/mesa/mesa/blob/main/src/amd/common/ac_gpu_info.c>  
    pub fn check_resizable_bar(&self) -> bool {
        (self.vram.total_heap_size * 9 / 10) <= self.cpu_accessible_vram.total_heap_size
    }
}

#[repr(u32)]
pub enum amdgpu_sw_info {
    address32_hi = 0,
}