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}