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}