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}