Skip to main content

ibverbs_rs/ibverbs/device/
manager.rs

1use crate::ibverbs::device::{Context, Guid};
2use crate::ibverbs::error::{IbvError, IbvResult};
3use ibverbs_sys::*;
4use std::ffi::CStr;
5use std::io;
6use std::marker::PhantomData;
7
8/// Convenience function to open an ibverbs RDMA device by name.
9///
10/// This function searches the list of available ibverbs devices for one whose
11/// name matches `name` and opens a [`Context`] for it.
12///
13/// # Errors
14///
15/// * Returns [`IbvError::NotFound`] if no device with the given name exists.
16/// * Propagates system errors if the device list cannot be retrieved.
17pub fn open_device(name: impl AsRef<str>) -> IbvResult<Context> {
18    let name = name.as_ref();
19    let devices = list_devices()?;
20    let device = devices
21        .iter()
22        .find(|d| d.name() == Some(name))
23        .ok_or_else(|| IbvError::NotFound(format!("Device '{name}' not found")))?;
24    device.open()
25}
26
27/// Returns a list of all available ibverbs RDMA devices.
28///
29/// The returned [`DeviceList`] owns the underlying device list allocated by
30/// `libibverbs` and will free it automatically when dropped.
31///
32/// # Errors
33///
34/// Returns an [`IbvError`] if the underlying `ibv_get_device_list` call fails or returns `NULL`
35/// with a non-zero `errno`.
36pub fn list_devices() -> IbvResult<DeviceList> {
37    let mut num_devices = 0i32;
38    let devices_ptr = unsafe { ibv_get_device_list(&mut num_devices as *mut _) };
39
40    if devices_ptr.is_null() {
41        let errno = io::Error::last_os_error()
42            .raw_os_error()
43            .expect("ibv_get_device_list should set errno on error");
44        // If errno is not zero, error fetching
45        if errno != 0 {
46            return Err(IbvError::from_errno_with_msg(
47                errno,
48                "Failed to list devices",
49            ));
50        }
51    }
52
53    log::debug!("DeviceList created");
54    // ibv_get_device_list guarantees a non-negative count when the pointer is non-null
55    #[allow(clippy::cast_sign_loss)]
56    Ok(DeviceList {
57        devices_ptr,
58        num_devices: num_devices as usize,
59    })
60}
61
62/// Owned list of available ibverbs RDMA devices.
63///
64/// This type wraps the device list returned by `ibv_get_device_list`.
65/// The underlying resources are released automatically when the value
66/// is dropped.
67///
68/// Individual devices can be accessed via iteration or indexing using
69/// [`DeviceList::iter`] or [`DeviceList::get`].
70#[doc(alias = "ibv_device")]
71#[doc(alias = "ibv_get_device_list")]
72pub struct DeviceList {
73    devices_ptr: *mut *mut ibv_device,
74    num_devices: usize,
75}
76
77/// SAFETY: libibverbs components are thread safe.
78unsafe impl Sync for DeviceList {}
79/// SAFETY: libibverbs components are thread safe.
80unsafe impl Send for DeviceList {}
81
82impl Drop for DeviceList {
83    fn drop(&mut self) {
84        if !self.devices_ptr.is_null() {
85            log::debug!("DeviceList dropped");
86            // SAFETY: self.devices_ptr is guaranteed to be a valid pointer returned
87            // by ibv_get_device_list or null (checked above).
88            unsafe { ibv_free_device_list(self.devices_ptr) };
89        }
90    }
91}
92
93impl DeviceList {
94    /// Returns an iterator over all available devices.
95    pub fn iter(&self) -> DeviceListIter<'_> {
96        DeviceListIter { list: self, i: 0 }
97    }
98
99    /// Returns the number of available devices.
100    pub fn len(&self) -> usize {
101        self.num_devices
102    }
103
104    /// Returns `true` if no devices are available.
105    pub fn is_empty(&self) -> bool {
106        self.num_devices == 0
107    }
108
109    /// Returns a reference to the device at the given index.
110    ///
111    /// Returns `None` if the index is out of bounds. The returned [`Device`]
112    /// is bound to the lifetime of this list.
113    pub fn get(&self, index: usize) -> Option<Device<'_>> {
114        if index >= self.num_devices {
115            return None;
116        }
117
118        // SAFETY: Verified `index` is within `num_devices` and `devices_ptr`
119        // is an array of pointers to `ibv_device` structs.
120        Some(unsafe { Device::from_ptr(*self.devices_ptr.add(index)) })
121    }
122}
123
124impl<'a> IntoIterator for &'a DeviceList {
125    type Item = <DeviceListIter<'a> as Iterator>::Item;
126    type IntoIter = DeviceListIter<'a>;
127    fn into_iter(self) -> Self::IntoIter {
128        DeviceListIter { list: self, i: 0 }
129    }
130}
131
132/// An iterator over the devices in a [`DeviceList`].
133///
134/// This struct is created by the [`iter`](DeviceList::iter) method on [`DeviceList`].
135/// Each item yielded is a [`Device`] that borrows from the parent list.
136pub struct DeviceListIter<'iter> {
137    list: &'iter DeviceList,
138    i: usize,
139}
140
141impl<'iter> Iterator for DeviceListIter<'iter> {
142    type Item = Device<'iter>;
143    fn next(&mut self) -> Option<Self::Item> {
144        let opt_device = self.list.get(self.i);
145        if opt_device.is_some() {
146            self.i += 1;
147        }
148        opt_device
149    }
150}
151
152impl std::fmt::Debug for DeviceList {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        f.debug_list().entries(self.iter()).finish()
155    }
156}
157
158/// A reference to an RDMA device.
159///
160/// This type represents a borrowed handle to an RDMA device.
161/// It can be obtained from a [`DeviceList`] or a [`Context`].
162///
163/// The reference is valid only as long as the source object ([`DeviceList`] or [`Context`])
164/// remains alive.
165///
166/// To perform operations on the device, you must first [`open`](Device::open) it to obtain a [`Context`].
167pub struct Device<'a> {
168    pub(super) device_ptr: *mut ibv_device,
169    _dev_list: PhantomData<&'a DeviceList>,
170}
171
172/// SAFETY: libibverbs components are thread safe.
173unsafe impl Sync for Device<'_> {}
174/// SAFETY: libibverbs components are thread safe.
175unsafe impl Send for Device<'_> {}
176
177impl Device<'_> {
178    /// Opens a context for this RDMA device.
179    ///
180    /// The resulting [`Context`] is the primary object used for allocating resources
181    /// (PDs, QPs, CQs) and managing the device.
182    ///
183    /// # Errors
184    ///
185    /// Returns an error if the device cannot be opened (e.g., due to permission issues
186    /// or resource exhaustion).
187    pub fn open(&self) -> IbvResult<Context> {
188        Context::from_device(self)
189    }
190
191    /// Returns the system name of the device (e.g., "mlx5_0").
192    ///
193    /// Returns `None` if the name cannot be retrieved or is not valid UTF-8.
194    pub fn name(&self) -> Option<&str> {
195        // SAFETY: ibv_get_device_name returns a pointer to a static string managed
196        // by libibverbs. It is valid as long as the device ref is valid.
197        let name_ptr = unsafe { ibv_get_device_name(self.device_ptr) };
198        if name_ptr.is_null() {
199            None
200        } else {
201            unsafe { CStr::from_ptr(name_ptr).to_str().ok() }
202        }
203    }
204
205    /// Returns the Global Unique Identifier (GUID) of this RDMA device.
206    ///
207    /// # Errors
208    ///
209    /// Returns an error if the GUID is reserved (invalid) or cannot be read.
210    pub fn guid(&self) -> IbvResult<Guid> {
211        let guid_int = unsafe { ibv_get_device_guid(self.device_ptr) };
212        let guid: Guid = guid_int.into();
213        if guid.is_reserved() {
214            Err(IbvError::from_errno_with_msg(
215                io::Error::last_os_error()
216                    .raw_os_error()
217                    .expect("ibv_get_device_guid should set errno on error"),
218                "GUID is reserved or invalid",
219            ))
220        } else {
221            Ok(guid)
222        }
223    }
224}
225
226impl std::fmt::Debug for Device<'_> {
227    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228        // Format name
229        let name_str = self.name().unwrap_or("<unknown>");
230
231        // Format GUID
232        let guid_str = match self.guid() {
233            Ok(g) => format!("{:?}", g),
234            Err(_) => "<error>".to_string(),
235        };
236
237        f.debug_struct("Device")
238            .field("name", &name_str)
239            .field("guid", &guid_str)
240            .finish()
241    }
242}
243
244impl Device<'_> {
245    /// Wraps a raw `ibv_device` pointer into a `DeviceRef`.
246    ///
247    /// # Safety
248    ///
249    /// * `device_ptr` must be a valid pointer obtained from `ibv_get_device_list` or an active `ibv_context`.
250    /// * The lifetime of the returned `DeviceRef` must not outlive the object that owns the pointer.
251    pub(super) unsafe fn from_ptr(device_ptr: *mut ibv_device) -> Self {
252        Self {
253            device_ptr,
254            _dev_list: PhantomData,
255        }
256    }
257}