Skip to main content

ibverbs_rs/ibverbs/device/
context.rs

1use crate::ibverbs::completion_queue::CompletionQueue;
2use crate::ibverbs::device::{Device, IB_PORT};
3use crate::ibverbs::error::{IbvError, IbvResult};
4use crate::ibverbs::protection_domain::ProtectionDomain;
5use ibverbs_sys::*;
6use std::io;
7use std::sync::Arc;
8
9/// A handle to an open RDMA device context.
10///
11/// The `Context` represents an active user-space session with a specific RDMA device.
12/// It serves as the root factory for creating all other RDMA resources.
13///
14/// # Resource Management & Shared Ownership
15///
16/// The `Context` uses **shared ownership** (via [`Arc`]) to manage the underlying device connection.
17/// This design simplifies resource management by allowing multiple handles to the same hardware context.
18///
19/// All resources created from a `Context` (such as [`ProtectionDomain`], [`CompletionQueue`], etc.)
20/// implicitly hold a clone of this `Arc`. This creates a robust ownership hierarchy:
21///
22/// 1.  **Child Keeps Parent Alive**: Even if you drop your main `Context` handle, the underlying
23///     hardware connection remains open as long as *any* child resource (PD, QP, MR) is still alive.
24/// 2.  **Automatic Cleanup**: The actual `ibv_close_device` call only happens when the *last*
25///     reference to the context is dropped.
26///
27/// # Example: The Resource Lifecycle
28///
29/// ```no_run
30/// use ibverbs_rs::ibverbs;
31///
32/// let context = ibverbs::open_device("mlx5_0")?;
33///
34/// // Create resources — they hold a reference to the context internally.
35/// let pd = context.allocate_pd()?;
36/// let cq = context.create_cq(16)?;
37///
38/// // Drop the context explicitly (optional).
39/// // The device connection remains OPEN because 'pd' and 'cq' are still alive.
40/// drop(context);
41///
42/// // End of scope: 'pd' and 'cq' are dropped, ref count hits zero, context closes.
43/// # Ok::<(), Box<dyn std::error::Error>>(())
44/// ```
45#[doc(alias = "ibv_context")]
46#[doc(alias = "ibv_open_device")]
47#[derive(Debug, Clone)]
48pub struct Context {
49    pub(crate) inner: Arc<ContextInner>,
50}
51
52impl Context {
53    /// Creates a Completion Queue (CQ) on this device.
54    ///
55    /// The CQ is used to receive completion notifications for work requests posted to Queue Pairs.
56    /// The returned [`CompletionQueue`] will hold a clone of this `Context`, keeping the
57    /// device connection alive.
58    ///
59    /// # Arguments
60    ///
61    /// * `min_cq_entries` — The *minimum* number of entries the CQ must support.
62    ///   The hardware may allocate a larger queue.
63    ///
64    /// # Errors
65    ///
66    /// * Returns [`IbvError::InvalidInput`] if `min_cq_entries` exceeds the device's capabilities.
67    /// * Returns [`IbvError::Resource`] if the system cannot allocate the queue resources (e.g., out of memory).
68    pub fn create_cq(&self, min_cq_entries: u32) -> IbvResult<CompletionQueue> {
69        CompletionQueue::create(self, min_cq_entries)
70    }
71
72    /// Allocates a Protection Domain (PD) for this context.
73    ///
74    /// A PD is a container for grouping Queue Pairs and Memory Regions.
75    /// The returned [`ProtectionDomain`] will hold a strong reference to this `Context`,
76    /// ensuring the underlying device connection remains open even if the original
77    /// `Context` handle is dropped.
78    ///
79    /// # Errors
80    ///
81    /// * Returns [`IbvError::Resource`] if the PD limit for the device has been reached or if memory allocation fails.
82    pub fn allocate_pd(&self) -> IbvResult<ProtectionDomain> {
83        ProtectionDomain::allocate(self)
84    }
85}
86
87impl Context {
88    /// Opens a context for the given device and verifies port connectivity.
89    ///
90    /// This function performs the following steps:
91    /// 1.  Calls `ibv_open_device` to establish a context.
92    /// 2.  Verifies the RDMA port is in `ACTIVE` or `ARMED` state.
93    ///
94    /// # Errors
95    ///
96    /// * Returns [`IbvError::Permission`] if the process lacks permission to access RDMA devices.
97    /// * Returns [`IbvError::Driver`] if `libibverbs` fails to open the device for OS-specific reasons.
98    /// * Returns [`IbvError::Resource`] if the RDMA port is in any state other than `ACTIVE` or `ARMED`.
99    pub fn from_device(dev: &Device) -> IbvResult<Self> {
100        // SAFETY: `dev.device_ptr` is guaranteed valid by the `DeviceRef` lifetime/invariants.
101        let ibv_ctx = unsafe { ibv_open_device(dev.device_ptr) };
102        if ibv_ctx.is_null() {
103            return Err(IbvError::from_errno_with_msg(
104                io::Error::last_os_error()
105                    .raw_os_error()
106                    .expect("ibv_open_device should set errno on error"),
107                "Failed to open device context",
108            ));
109        }
110
111        let context = Self {
112            inner: Arc::new(ContextInner { ctx: ibv_ctx }),
113        };
114
115        // Enforce that the port is active/armed before returning a usable context.
116        context.inner.query_port()?;
117
118        log::debug!("Context opened");
119        Ok(context)
120    }
121
122    /// Returns a [`Device`] handle for the hardware backing this context.
123    pub fn device(&self) -> Device<'_> {
124        unsafe { Device::from_ptr((&*self.inner.ctx).device) }
125    }
126}
127
128/// Inner wrapper to manage the lifecycle of the raw `ibv_context` pointer.
129pub(crate) struct ContextInner {
130    pub(crate) ctx: *mut ibv_context,
131}
132
133/// SAFETY: libibverbs components are thread safe.
134unsafe impl Sync for ContextInner {}
135/// SAFETY: libibverbs components are thread safe.
136unsafe impl Send for ContextInner {}
137
138impl Drop for ContextInner {
139    fn drop(&mut self) {
140        log::debug!("Context closed");
141        // SAFETY: `self.ctx` is guaranteed valid and open.
142        if unsafe { ibv_close_device(self.ctx) } != 0 {
143            let error = IbvError::from_errno_with_msg(
144                io::Error::last_os_error()
145                    .raw_os_error()
146                    .expect("ibv_close_device should set errno on error"),
147                "Failed to close context",
148            );
149            log::error!("{error}");
150        }
151    }
152}
153
154impl std::fmt::Debug for ContextInner {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        // SAFETY: The `DeviceRef` produced takes a pointer to a valid `ibv_device`
157        // and is used for a shorter lifetime than self.
158        f.debug_struct("Context")
159            .field("device", &unsafe { Device::from_ptr((&*self.ctx).device) })
160            .finish()
161    }
162}
163
164impl ContextInner {
165    /// Queries the properties of the primary port ([`IB_PORT`]).
166    pub(crate) fn query_port(&self) -> IbvResult<ibv_port_attr> {
167        let mut port_attr = ibv_port_attr::default();
168        // SAFETY: `ibv_query_port` is a safe read operation if the context and pointer are valid.
169        let errno = unsafe {
170            ibv_query_port(
171                self.ctx,
172                IB_PORT,
173                &mut port_attr as *mut ibv_port_attr as *mut _,
174            )
175        };
176        if errno != 0 {
177            return Err(IbvError::from_errno_with_msg(errno, "Failed to query port"));
178        }
179
180        match port_attr.state {
181            ibv_port_state::IBV_PORT_ACTIVE | ibv_port_state::IBV_PORT_ARMED => Ok(port_attr),
182            state => Err(IbvError::Resource(format!(
183                "Port is in state {:?} (expected ACTIVE or ARMED)",
184                state
185            ))),
186        }
187    }
188}