switchtec_user_sys/
lib.rs

1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4#![allow(improper_ctypes)]
5#![allow(clippy::missing_safety_doc)]
6#![doc = include_str!("../README.md")]
7
8use std::ffi::{CStr, CString};
9use std::fmt;
10use std::io;
11use std::mem::MaybeUninit;
12use std::os::unix::ffi::OsStrExt;
13use std::path::Path;
14
15/// The raw FFI bindings to `libswitchtec`
16pub mod ffi {
17    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
18}
19
20// re-exported items from `libswitchtec` to make available at the crate level
21mod prelude;
22pub use prelude::*;
23
24/// `SwitchtecDevice` offers an safer way to work with the underlying [`switchtec_dev`] and
25/// represents an open Switchtec PCI Switch device that can be passed into `switchtec-user` C library functions
26///
27/// - [`SwitchtecDevice`] closes the Switchtec character device when it goes out of scope
28pub struct SwitchtecDevice {
29    inner: *mut switchtec_dev,
30}
31
32impl SwitchtecDevice {
33    /// Open the Switchtec PCIe Switch character device at the given `path`,
34    /// returning a `SwitchtecDevice` that can be used to pass into
35    /// `switchtec-user` C library functions
36    ///
37    /// ```no_run
38    /// use switchtec_user_sys::{switchtec_die_temp, SwitchtecDevice};
39    ///
40    /// # fn main() -> anyhow::Result<()> {
41    /// let device = SwitchtecDevice::open("/dev/pciswitch0")?;
42    ///
43    /// // SAFETY: We know that device holds a valid/open switchtec device
44    /// let temperature = unsafe { switchtec_die_temp(*device) };
45    /// println!("Temperature: {temperature}");
46    /// // Switchtec device is closed with `device` goes out of scope
47    /// # Ok(())
48    /// }
49    /// ```
50    pub fn open<T: AsRef<Path>>(path: T) -> io::Result<Self> {
51        let path_c = CString::new(path.as_ref().as_os_str().as_bytes()).map_err(|e| {
52            // TODO: change to io::ErrorKind::InvalidFilename when it stabalizes
53            //       https://github.com/rust-lang/rust/issues/86442
54            io::Error::new(io::ErrorKind::Other, e.to_string())
55        })?;
56        // SAFETY: Checking that the returned `dev` is not null prior to successfully returning
57        // a valid `Self` struct
58        unsafe {
59            let dev = switchtec_open(path_c.as_ptr());
60            if dev.is_null() {
61                Err(get_switchtec_error())
62            } else {
63                Ok(Self { inner: dev })
64            }
65        }
66    }
67
68    /// Get the device name (E.g. "pciswitch0" in "/dev/pciswitch0")
69    ///
70    /// This can fail if the device name is not valid UTF-8
71    ///
72    /// <https://microsemi.github.io/switchtec-user/group__Device.html#ga8d416a587f5e37e818ee937bd0c0dab1>
73    pub fn name(&self) -> io::Result<String> {
74        // SAFETY: We know that device holds a valid/open switchtec device
75        let device_name = unsafe { switchtec_name(self.inner) };
76        if device_name.is_null() {
77            Err(io::Error::new(
78                io::ErrorKind::InvalidData,
79                "no device name returned",
80            ))
81        } else {
82            device_name.as_string()
83        }
84    }
85
86    /// Get the PCIe generation of the device
87    ///
88    /// <https://microsemi.github.io/switchtec-user/group__Device.html#ga9eab19beb39d2104b5defd28787177ae>
89    pub fn boot_phase(&self) -> switchtec_boot_phase {
90        // SAFETY: We know that device holds a valid/open switchtec device
91        unsafe { switchtec_boot_phase(self.inner) }
92    }
93
94    /// Get the firmware version as a user readable string
95    ///
96    /// This can fail if the firmware version is not valid UTF-8
97    ///
98    /// <https://microsemi.github.io/switchtec-user/group__Device.html#gad16f110712bd23170ad69450c361122e>
99    pub fn firmware_version(&self) -> io::Result<String> {
100        const buf_size: usize = 64;
101        let mut buf = MaybeUninit::<[u8; buf_size]>::uninit();
102        // SAFETY: We know that device holds a valid/open switchtec device
103        unsafe {
104            let len = switchtec_get_fw_version(self.inner, buf.as_mut_ptr() as *mut _, buf_size);
105            if len.is_negative() {
106                Err(get_switchtec_error())
107            } else {
108                buf_to_string(&buf.assume_init())
109            }
110        }
111    }
112
113    /// Get the PCIe generation of the device
114    ///
115    /// <https://microsemi.github.io/switchtec-user/group__Device.html#gab9f59d48c410e8dde13acdc519943a26>
116    pub fn generation(&self) -> switchtec_gen {
117        // SAFETY: We know that device holds a valid/open switchtec device
118        unsafe { switchtec_gen(self.inner) }
119    }
120
121    /// Get the partition of the device
122    ///
123    /// <https://microsemi.github.io/switchtec-user/group__Device.html#gac70f47bb86ac6ba1666446f27673cdcf>
124    pub fn partition(&self) -> i32 {
125        // SAFETY: We know that device holds a valid/open switchtec device
126        unsafe { switchtec_partition(self.inner) }
127    }
128
129    /// Get the die temperature of the switchtec device (in Celcius)
130    ///
131    /// <https://microsemi.github.io/switchtec-user/group__Misc.html#ga56317f0a31a83eb896e4a987dbd645df>
132    pub fn die_temp(&self) -> io::Result<f32> {
133        // SAFETY: We know that device holds a valid/open switchtec device
134        let temp = unsafe { switchtec_die_temp(self.inner) };
135        if temp.is_sign_negative() {
136            // Negative value represents an error
137            // https://microsemi.github.io/switchtec-user/group__Misc.html#ga56317f0a31a83eb896e4a987dbd645df
138            return Err(get_switchtec_error());
139        }
140        Ok(temp)
141    }
142}
143
144impl fmt::Debug for SwitchtecDevice {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        f.debug_struct("SwitchtecDevice")
147            .field("name", &self.name().as_deref().unwrap_or("unknown"))
148            .finish()
149    }
150}
151
152impl std::ops::Deref for SwitchtecDevice {
153    type Target = *mut switchtec_dev;
154
155    fn deref(&self) -> &Self::Target {
156        &self.inner
157    }
158}
159
160impl std::ops::Drop for SwitchtecDevice {
161    fn drop(&mut self) {
162        // SAFETY: SwitchtecDevice is only successfully constructed if the `inner` `switchtec_dev`
163        // is not null;
164        unsafe {
165            switchtec_close(self.inner);
166        }
167    }
168}
169
170pub trait CStrExt {
171    /// Convert a C-style string (E.g. `char*`) to a Rust [`String`]
172    ///
173    /// Returns an [`io::Error`] if the string pointer is null or cannot be
174    fn as_string(&self) -> io::Result<String>;
175}
176
177impl CStrExt for *const i8 {
178    /// Copy a C-style `*const i8` string to a [`String`]
179    ///
180    /// ```
181    /// use switchtec_user_sys::CStrExt;
182    /// # use std::ffi::CString;
183    ///
184    /// # fn main() -> anyhow::Result<()> {
185    /// let cstr = CString::new(*b"hello")?;
186    /// // This is a type you might receive from an extern "C" function:
187    /// let str_value: *const i8 = cstr.as_ptr() as *const i8;
188    ///
189    /// let rust_string: String = str_value.as_string()?;
190    /// assert_eq!(&rust_string, "hello");
191    ///
192    /// # Ok(())
193    /// # }
194    /// ```
195    fn as_string(&self) -> io::Result<String> {
196        cstr_to_string(*self)
197    }
198}
199
200impl CStrExt for *mut i8 {
201    /// Copy a C-style `*mut i8` string to a [`String`]
202    ///
203    /// ```
204    /// use switchtec_user_sys::CStrExt;
205    /// # use std::ffi::CString;
206    ///
207    /// # fn main() -> anyhow::Result<()> {
208    /// let cstr = CString::new(*b"hello")?;
209    /// // This is a type you might receive from an extern "C" function:
210    /// let str_value: *mut i8 = cstr.as_ptr() as *mut i8;
211    ///
212    /// let rust_string: String = str_value.as_string()?;
213    /// assert_eq!(&rust_string, "hello");
214    ///
215    /// # Ok(())
216    /// # }
217    /// ```
218    fn as_string(&self) -> io::Result<String> {
219        cstr_to_string(*self)
220    }
221}
222
223fn cstr_to_string(cstr: *const i8) -> io::Result<String> {
224    if cstr.is_null() {
225        Ok("".to_owned())
226    } else {
227        // SAFETY: cstr has been checked for null, we can safely dereference
228        unsafe {
229            let s = CStr::from_ptr(cstr).to_owned();
230            s.into_string().map_err(|e| {
231                io::Error::new(
232                    io::ErrorKind::InvalidData,
233                    format!("error decoding String from {cstr:?}: {e}"),
234                )
235            })
236        }
237    }
238}
239
240/// Parse a String from a buffer that may have tail-padding
241fn buf_to_string(buf: &[u8]) -> io::Result<String> {
242    let valid_bytes: Vec<u8> = buf
243        .iter()
244        // Filter out null bytes
245        .take_while(|b| b != &&0)
246        .copied()
247        .collect();
248    let cstring = CString::new(valid_bytes)?;
249    cstring.into_raw().as_string()
250}
251
252fn get_switchtec_error() -> io::Error {
253    // SAFETY: We're checking that the returned char* is not null
254    let err_message = unsafe {
255        // https://microsemi.github.io/switchtec-user/group__Device.html#ga595e1d62336ba76c59344352c334fa18
256        let err_str = switchtec_strerror();
257        if err_str.is_null() {
258            return io::Error::new(io::ErrorKind::Other, "Unknown error".to_owned());
259        }
260        err_str
261            .as_string()
262            .unwrap_or_else(|_| "Unknown error".to_owned())
263    };
264    io::Error::new(io::ErrorKind::Other, err_message)
265}
266
267#[test]
268fn test_buf_to_string() {
269    let buf = [51, 46, 55, 48, 32, 66, 48, 52, 70, 0, 0, 0, 0, 0, 0, 0];
270    assert_eq!(&buf_to_string(&buf).unwrap(), "3.70 B04F");
271}