Skip to main content

monarch_rdma/backend/ibverbs/queue_pair/
legacy.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9use super::*;
10use crate::backend::ibverbs::primitives::IbvPd;
11
12/// An RDMA Queue Pair (QP) for communication between two endpoints.
13///
14/// Encapsulates the send/receive queues, completion queues, and mlx5dv
15/// device-specific structures needed for RDMA communication.
16///
17/// # Connection Lifecycle
18///
19/// 1. Create with `new()` from context and protection domain pointers
20/// 2. Get connection info with `get_qp_info()`
21/// 3. Exchange connection info with remote peer
22/// 4. Connect to remote endpoint with `connect()`
23/// 5. Perform RDMA operations with `put()` or `get()`
24/// 6. Poll for completions with `poll_completion()`
25///
26/// # Notes
27/// - The `qp` field stores a pointer to `rdmaxcel_qp_t` (not `ibv_qp`).
28///   It is private; external callers reach it via [`Self::as_ptr`].
29/// - `rdmaxcel_qp_t` contains atomic counters and completion caches internally
30/// - `IbvQueuePair` is single-owner: its `Drop` destroys the FFI QP, so
31///   the type is intentionally `!Clone` (and not sent across the wire).
32#[derive(Debug)]
33pub struct IbvQueuePair {
34    pub send_cq: usize,    // *mut rdmaxcel_sys::ibv_cq,
35    pub recv_cq: usize,    // *mut rdmaxcel_sys::ibv_cq,
36    qp: usize,             // *mut rdmaxcel_sys::rdmaxcel_qp_t
37    pub dv_qp: usize,      // *mut rdmaxcel_sys::mlx5dv_qp,
38    pub dv_send_cq: usize, // *mut rdmaxcel_sys::mlx5dv_cq,
39    pub dv_recv_cq: usize, // *mut rdmaxcel_sys::mlx5dv_cq,
40    context: usize,        // *mut rdmaxcel_sys::ibv_context,
41    config: IbvConfig,
42    // The source GID (carrying its table index), resolved from the owning
43    // device's `IbvDeviceInfo` at construction: index 0 on the EFA path,
44    // otherwise the first global RoCE v2 GID on `config.port_num`.
45    gid: Gid,
46    is_efa: bool,
47    // Keepalive for the protection domain this QP was built against, so the PD
48    // (and, through it, the context) outlives the QP regardless of other owners
49    // (the manager, registered MRs, etc.). Never read directly.
50    _pd: Arc<IbvPd>,
51}
52
53impl Drop for IbvQueuePair {
54    fn drop(&mut self) {
55        if self.qp == 0 {
56            return;
57        }
58        // SAFETY: `IbvQueuePair` is `!Clone`, so `Drop` runs at most
59        // once. Any external use of the `qp` pointer goes through an
60        // `unsafe { qp.as_ptr() }` call on a `&IbvQueuePair` borrow,
61        // which statically prevents `Drop` from running concurrently.
62        // Any caller that uses `as_ptr` promises they will not use the
63        // returned pointer after the borrow goes out of scope.
64        unsafe {
65            rdmaxcel_sys::rdmaxcel_qp_destroy(self.qp as *mut rdmaxcel_sys::rdmaxcel_qp);
66        }
67    }
68}
69
70impl IbvQueuePair {
71    /// Returns the underlying `rdmaxcel_qp_t` pointer for callers
72    /// driving the device doorbell or building FFI parameter blocks
73    /// directly.
74    ///
75    /// # Safety
76    ///
77    /// The returned pointer is only valid for as long as the borrow
78    /// of `self` lives. The caller must ensure all reads/writes
79    /// through the pointer complete before this `IbvQueuePair` is
80    /// dropped, since `Drop` calls `rdmaxcel_qp_destroy` on it.
81    pub unsafe fn as_ptr(&self) -> *mut rdmaxcel_sys::rdmaxcel_qp {
82        self.qp as *mut rdmaxcel_sys::rdmaxcel_qp
83    }
84
85    fn is_efa(&self) -> bool {
86        self.is_efa
87    }
88
89    /// Applies hardware initialization delay if this is the first operation since RTS.
90    fn apply_first_op_delay(&self, wr_id: u64) {
91        unsafe {
92            let qp = self.qp as *mut rdmaxcel_sys::rdmaxcel_qp;
93            if wr_id == 0 {
94                let rts_timestamp = rdmaxcel_sys::rdmaxcel_qp_load_rts_timestamp(qp);
95                assert!(
96                    rts_timestamp != u64::MAX,
97                    "First operation attempted before queue pair reached RTS state! Call connect() first."
98                );
99                let current_nanos = std::time::SystemTime::now()
100                    .duration_since(std::time::UNIX_EPOCH)
101                    .unwrap()
102                    .as_nanos() as u64;
103                let elapsed_nanos = current_nanos - rts_timestamp;
104                let elapsed = Duration::from_nanos(elapsed_nanos);
105                let init_delay = Duration::from_millis(self.config.hw_init_delay_ms);
106                if elapsed < init_delay {
107                    let remaining_delay = init_delay - elapsed;
108                    // Sync context within unsafe block; tokio::time::sleep is async
109                    // and converting would require propagating async through the
110                    // entire post_op / ring_doorbell call chain.
111                    std::thread::sleep(remaining_delay);
112                }
113            }
114        }
115    }
116
117    /// Creates a new IbvQueuePair.
118    ///
119    /// Initializes a new Queue Pair (QP) and associated Completion Queues (CQ)
120    /// using the provided context and protection domain. The QP is created in
121    /// the RESET state and must be transitioned via `connect()` before use.
122    ///
123    /// # Errors
124    ///
125    /// Returns errors if CQ or QP creation fails.
126    pub fn new<I: IbvDomainImpl>(
127        domain: &IbvDomain<I>,
128        config: IbvConfig,
129    ) -> Result<Self, anyhow::Error> {
130        tracing::debug!("creating an IbvQueuePair from config {}", config);
131        let context = domain.context().as_ptr();
132        let pd = domain.as_ptr();
133        // Resolve Auto to a concrete QP type based on device capabilities.
134        let resolved_qp_type = resolve_qp_type(config.qp_type);
135        let is_efa = resolved_qp_type == rdmaxcel_sys::RDMA_QP_TYPE_EFA;
136        // EFA is not RoCE, so its `gid_attrs/types` never reports "RoCE v2"; it
137        // always uses GID index 0. RoCE selects the first global RoCE v2 GID.
138        let gid = if is_efa {
139            domain.device_info().gid_at(config.port_num, 0)?
140        } else {
141            domain.device_info().select_gid(
142                config.port_num,
143                Some(GidScope::Global),
144                Some(GidType::RoCEv2),
145            )?
146        };
147        unsafe {
148            let qp = rdmaxcel_sys::rdmaxcel_qp_create(
149                context,
150                pd,
151                config.cq_entries,
152                config.max_send_wr.try_into().unwrap(),
153                config.max_recv_wr.try_into().unwrap(),
154                config.max_send_sge.try_into().unwrap(),
155                config.max_recv_sge.try_into().unwrap(),
156                resolved_qp_type,
157            );
158
159            if qp.is_null() {
160                let os_error = Error::last_os_error();
161                return Err(anyhow::anyhow!(
162                    "failed to create queue pair (QP): {}",
163                    os_error
164                ));
165            }
166
167            let send_cq = (*(*qp).ibv_qp).send_cq;
168            let recv_cq = (*(*qp).ibv_qp).recv_cq;
169
170            // EFA uses standard ibverbs (not mlx5dv), so skip dv setup
171            if is_efa {
172                return Ok(IbvQueuePair {
173                    send_cq: send_cq as usize,
174                    recv_cq: recv_cq as usize,
175                    qp: qp as usize,
176                    dv_qp: 0,
177                    dv_send_cq: 0,
178                    dv_recv_cq: 0,
179                    context: context as usize,
180                    config,
181                    gid,
182                    is_efa: true,
183                    _pd: domain.pd().clone(),
184                });
185            }
186
187            let dv_qp = rdmaxcel_sys::create_mlx5dv_qp((*qp).ibv_qp);
188            let dv_send_cq = rdmaxcel_sys::create_mlx5dv_send_cq((*qp).ibv_qp);
189            let dv_recv_cq = rdmaxcel_sys::create_mlx5dv_recv_cq((*qp).ibv_qp);
190
191            if dv_qp.is_null() || dv_send_cq.is_null() || dv_recv_cq.is_null() {
192                rdmaxcel_sys::ibv_destroy_cq((*(*qp).ibv_qp).recv_cq);
193                rdmaxcel_sys::ibv_destroy_cq((*(*qp).ibv_qp).send_cq);
194                rdmaxcel_sys::ibv_destroy_qp((*qp).ibv_qp);
195                return Err(anyhow::anyhow!(
196                    "failed to init mlx5dv_qp or completion queues"
197                ));
198            }
199
200            if config.use_gpu_direct {
201                let ret = rdmaxcel_sys::register_cuda_memory(dv_qp, dv_recv_cq, dv_send_cq);
202                if ret != 0 {
203                    rdmaxcel_sys::ibv_destroy_cq((*(*qp).ibv_qp).recv_cq);
204                    rdmaxcel_sys::ibv_destroy_cq((*(*qp).ibv_qp).send_cq);
205                    rdmaxcel_sys::ibv_destroy_qp((*qp).ibv_qp);
206                    return Err(anyhow::anyhow!(
207                        "failed to register GPU Direct RDMA memory: {:?}",
208                        ret
209                    ));
210                }
211            }
212            Ok(IbvQueuePair {
213                send_cq: send_cq as usize,
214                recv_cq: recv_cq as usize,
215                qp: qp as usize,
216                dv_qp: dv_qp as usize,
217                dv_send_cq: dv_send_cq as usize,
218                dv_recv_cq: dv_recv_cq as usize,
219                context: context as usize,
220                config,
221                gid,
222                is_efa: false,
223                _pd: domain.pd().clone(),
224            })
225        }
226    }
227
228    /// Returns the connection info needed by a remote peer to connect to this QP.
229    pub fn get_qp_info(&mut self) -> Result<IbvQpInfo, anyhow::Error> {
230        let context = self.context as *mut rdmaxcel_sys::ibv_context;
231        let qp = self.qp as *mut rdmaxcel_sys::rdmaxcel_qp;
232        // SAFETY: `(*qp).ibv_qp` is the live `ibv_qp` owned by this `rdmaxcel_qp`,
233        // and `context` is its live device context.
234        unsafe { super::get_qp_info((*qp).ibv_qp, context, &self.config, self.gid) }
235    }
236
237    /// Returns the current state of the QP.
238    pub fn state(&mut self) -> Result<u32, anyhow::Error> {
239        let qp = self.qp as *mut rdmaxcel_sys::rdmaxcel_qp;
240        // SAFETY: `(*qp).ibv_qp` is the live `ibv_qp` owned by this `rdmaxcel_qp`.
241        unsafe { super::state((*qp).ibv_qp) }
242    }
243
244    /// Transitions the QP through INIT -> RTR -> RTS to establish a connection.
245    ///
246    /// # Arguments
247    ///
248    /// * `connection_info` - The remote connection info to connect to
249    pub fn connect(&mut self, connection_info: &IbvQpInfo) -> Result<(), anyhow::Error> {
250        // EFA: use unified C function for QP state transitions
251        if self.is_efa() {
252            return self.efa_connect(connection_info);
253        }
254
255        let access_flags = (rdmaxcel_sys::ibv_access_flags::IBV_ACCESS_LOCAL_WRITE
256            | rdmaxcel_sys::ibv_access_flags::IBV_ACCESS_REMOTE_WRITE
257            | rdmaxcel_sys::ibv_access_flags::IBV_ACCESS_REMOTE_READ
258            | rdmaxcel_sys::ibv_access_flags::IBV_ACCESS_REMOTE_ATOMIC)
259            .0 as i32;
260        let qp = self.qp as *mut rdmaxcel_sys::rdmaxcel_qp;
261        // SAFETY: `(*qp).ibv_qp` is the live `ibv_qp` owned by this `rdmaxcel_qp`.
262        unsafe {
263            super::connect(
264                (*qp).ibv_qp,
265                &self.config,
266                access_flags,
267                connection_info,
268                self.gid.index(),
269            )
270        }?;
271
272        tracing::debug!(
273            "connection sequence has successfully completed (qp: {:?})",
274            qp
275        );
276
277        // Record the RTS timestamp so the first posted op can apply the hardware
278        // init delay; specific to the legacy `rdmaxcel_qp` doorbell path.
279        let rts_timestamp_nanos = std::time::SystemTime::now()
280            .duration_since(std::time::UNIX_EPOCH)
281            .unwrap()
282            .as_nanos() as u64;
283        // SAFETY: `qp` is the live `rdmaxcel_qp`.
284        unsafe { rdmaxcel_sys::rdmaxcel_qp_store_rts_timestamp(qp, rts_timestamp_nanos) };
285        Ok(())
286    }
287
288    /// Connects via the EFA-specific C function for QP state transitions.
289    fn efa_connect(&mut self, connection_info: &IbvQpInfo) -> Result<(), anyhow::Error> {
290        let qp = self.qp as *mut rdmaxcel_sys::rdmaxcel_qp;
291
292        let gid_ptr = connection_info.gid.as_ref().map_or(std::ptr::null(), |g| {
293            let ibv_gid: &rdmaxcel_sys::ibv_gid = g.as_ref();
294            unsafe { ibv_gid.raw.as_ptr() }
295        });
296
297        unsafe {
298            let ret = rdmaxcel_sys::rdmaxcel_efa_connect(
299                qp,
300                self.config.port_num,
301                self.config.pkey_index,
302                0x4242, // qkey
303                self.config.psn,
304                self.gid.index(),
305                gid_ptr,
306                connection_info.qp_num,
307            );
308            if ret != 0 {
309                let msg = std::ffi::CStr::from_ptr(rdmaxcel_sys::rdmaxcel_error_string(ret))
310                    .to_str()
311                    .unwrap_or("unknown");
312                return Err(anyhow::anyhow!("EFA connect failed: {}", msg));
313            }
314        }
315
316        // Store RTS timestamp for first-op delay
317        let rts_timestamp_nanos = std::time::SystemTime::now()
318            .duration_since(std::time::UNIX_EPOCH)
319            .unwrap()
320            .as_nanos() as u64;
321        unsafe {
322            rdmaxcel_sys::rdmaxcel_qp_store_rts_timestamp(qp, rts_timestamp_nanos);
323        }
324
325        Ok(())
326    }
327
328    pub fn recv(&mut self, lhandle: IbvBuffer, rhandle: IbvBuffer) -> Result<u64, anyhow::Error> {
329        unsafe {
330            let qp = self.qp as *mut rdmaxcel_sys::rdmaxcel_qp;
331            let idx = rdmaxcel_sys::rdmaxcel_qp_fetch_add_recv_wqe_idx(qp);
332            self.post_op(
333                0,
334                lhandle.lkey,
335                0,
336                idx,
337                true,
338                IbvOperation::Recv,
339                0,
340                rhandle.rkey,
341            )
342            .unwrap();
343            rdmaxcel_sys::rdmaxcel_qp_fetch_add_recv_db_idx(qp);
344            Ok(idx)
345        }
346    }
347
348    pub fn put_with_recv(
349        &mut self,
350        lhandle: IbvBuffer,
351        rhandle: IbvBuffer,
352    ) -> Result<Vec<u64>, anyhow::Error> {
353        unsafe {
354            let qp = self.qp as *mut rdmaxcel_sys::rdmaxcel_qp;
355            let idx = rdmaxcel_sys::rdmaxcel_qp_fetch_add_send_wqe_idx(qp);
356            self.post_op(
357                lhandle.addr,
358                lhandle.lkey,
359                lhandle.size,
360                idx,
361                true,
362                IbvOperation::WriteWithImm,
363                rhandle.addr,
364                rhandle.rkey,
365            )
366            .unwrap();
367            rdmaxcel_sys::rdmaxcel_qp_fetch_add_send_db_idx(qp);
368            Ok(vec![idx])
369        }
370    }
371
372    pub fn put(
373        &mut self,
374        lhandle: IbvBuffer,
375        rhandle: IbvBuffer,
376    ) -> Result<Vec<u64>, anyhow::Error> {
377        let total_size = lhandle.size;
378        if rhandle.size < total_size {
379            return Err(anyhow::anyhow!(
380                "remote buffer size ({}) is smaller than local buffer size ({})",
381                rhandle.size,
382                total_size
383            ));
384        }
385
386        let mut remaining = total_size;
387        let mut offset = 0;
388        let mut wr_ids = Vec::new();
389        while remaining > 0 {
390            let chunk_size = std::cmp::min(remaining, MAX_RDMA_MSG_SIZE);
391            let idx = unsafe {
392                rdmaxcel_sys::rdmaxcel_qp_fetch_add_send_wqe_idx(
393                    self.qp as *mut rdmaxcel_sys::rdmaxcel_qp,
394                )
395            };
396            wr_ids.push(idx);
397            self.post_op(
398                lhandle.addr + offset,
399                lhandle.lkey,
400                chunk_size,
401                idx,
402                true,
403                IbvOperation::Write,
404                rhandle.addr + offset,
405                rhandle.rkey,
406            )?;
407            unsafe {
408                rdmaxcel_sys::rdmaxcel_qp_fetch_add_send_db_idx(
409                    self.qp as *mut rdmaxcel_sys::rdmaxcel_qp,
410                );
411            }
412
413            remaining -= chunk_size;
414            offset += chunk_size;
415        }
416
417        Ok(wr_ids)
418    }
419
420    /// Rings the doorbell to execute all enqueued operations.
421    pub fn ring_doorbell(&mut self) -> Result<(), anyhow::Error> {
422        // EFA uses standard ibverbs (not mlx5dv), so skip doorbell ringing
423        if self.is_efa() {
424            return Ok(());
425        }
426
427        unsafe {
428            let qp = self.qp as *mut rdmaxcel_sys::rdmaxcel_qp;
429            let dv_qp = self.dv_qp as *mut rdmaxcel_sys::mlx5dv_qp;
430            let base_ptr = (*dv_qp).sq.buf as *mut u8;
431            let wqe_cnt = (*dv_qp).sq.wqe_cnt;
432            let stride = (*dv_qp).sq.stride;
433            let send_wqe_idx = rdmaxcel_sys::rdmaxcel_qp_load_send_wqe_idx(qp);
434            let mut send_db_idx = rdmaxcel_sys::rdmaxcel_qp_load_send_db_idx(qp);
435            if (wqe_cnt as u64) < (send_wqe_idx - send_db_idx) {
436                return Err(anyhow::anyhow!("Overflow of WQE, possible data loss"));
437            }
438            self.apply_first_op_delay(send_db_idx);
439            while send_db_idx < send_wqe_idx {
440                let offset = (send_db_idx % wqe_cnt as u64) * stride as u64;
441                let src_ptr = base_ptr.wrapping_add(offset as usize);
442                rdmaxcel_sys::db_ring((*dv_qp).bf.reg, src_ptr as *mut std::ffi::c_void);
443                send_db_idx += 1;
444                rdmaxcel_sys::rdmaxcel_qp_store_send_db_idx(qp, send_db_idx);
445            }
446            Ok(())
447        }
448    }
449
450    /// Enqueues a put operation without ringing the doorbell.
451    pub fn enqueue_put(
452        &mut self,
453        lhandle: IbvBuffer,
454        rhandle: IbvBuffer,
455    ) -> Result<Vec<u64>, anyhow::Error> {
456        let idx = unsafe {
457            rdmaxcel_sys::rdmaxcel_qp_fetch_add_send_wqe_idx(
458                self.qp as *mut rdmaxcel_sys::rdmaxcel_qp,
459            )
460        };
461
462        self.send_wqe(
463            lhandle.addr,
464            lhandle.lkey,
465            lhandle.size,
466            idx,
467            true,
468            IbvOperation::Write,
469            rhandle.addr,
470            rhandle.rkey,
471        )?;
472        Ok(vec![idx])
473    }
474
475    /// Enqueues a put-with-receive operation without ringing the doorbell.
476    pub fn enqueue_put_with_recv(
477        &mut self,
478        lhandle: IbvBuffer,
479        rhandle: IbvBuffer,
480    ) -> Result<Vec<u64>, anyhow::Error> {
481        let idx = unsafe {
482            rdmaxcel_sys::rdmaxcel_qp_fetch_add_send_wqe_idx(
483                self.qp as *mut rdmaxcel_sys::rdmaxcel_qp,
484            )
485        };
486
487        self.send_wqe(
488            lhandle.addr,
489            lhandle.lkey,
490            lhandle.size,
491            idx,
492            true,
493            IbvOperation::WriteWithImm,
494            rhandle.addr,
495            rhandle.rkey,
496        )?;
497        Ok(vec![idx])
498    }
499
500    /// Enqueues a get operation without ringing the doorbell.
501    pub fn enqueue_get(
502        &mut self,
503        lhandle: IbvBuffer,
504        rhandle: IbvBuffer,
505    ) -> Result<Vec<u64>, anyhow::Error> {
506        let idx = unsafe {
507            rdmaxcel_sys::rdmaxcel_qp_fetch_add_send_wqe_idx(
508                self.qp as *mut rdmaxcel_sys::rdmaxcel_qp,
509            )
510        };
511
512        self.send_wqe(
513            lhandle.addr,
514            lhandle.lkey,
515            lhandle.size,
516            idx,
517            true,
518            IbvOperation::Read,
519            rhandle.addr,
520            rhandle.rkey,
521        )?;
522        Ok(vec![idx])
523    }
524
525    pub fn get(
526        &mut self,
527        lhandle: IbvBuffer,
528        rhandle: IbvBuffer,
529    ) -> Result<Vec<u64>, anyhow::Error> {
530        let total_size = rhandle.size;
531        if rhandle.size > lhandle.size {
532            return Err(anyhow::anyhow!(
533                "remote buffer size ({}) is larger than local buffer size ({})",
534                rhandle.size,
535                lhandle.size
536            ));
537        }
538
539        let mut remaining = total_size;
540        let mut offset = 0;
541        let mut wr_ids = Vec::new();
542
543        while remaining > 0 {
544            let chunk_size = std::cmp::min(remaining, MAX_RDMA_MSG_SIZE);
545            let idx = unsafe {
546                rdmaxcel_sys::rdmaxcel_qp_fetch_add_send_wqe_idx(
547                    self.qp as *mut rdmaxcel_sys::rdmaxcel_qp,
548                )
549            };
550            wr_ids.push(idx);
551
552            self.post_op(
553                lhandle.addr + offset,
554                lhandle.lkey,
555                chunk_size,
556                idx,
557                true,
558                IbvOperation::Read,
559                rhandle.addr + offset,
560                rhandle.rkey,
561            )?;
562            unsafe {
563                rdmaxcel_sys::rdmaxcel_qp_fetch_add_send_db_idx(
564                    self.qp as *mut rdmaxcel_sys::rdmaxcel_qp,
565                );
566            }
567
568            remaining -= chunk_size;
569            offset += chunk_size;
570        }
571
572        Ok(wr_ids)
573    }
574
575    /// Posts a request to the queue pair.
576    fn post_op(
577        &mut self,
578        laddr: usize,
579        lkey: u32,
580        length: usize,
581        wr_id: u64,
582        signaled: bool,
583        op_type: IbvOperation,
584        raddr: usize,
585        rkey: u32,
586    ) -> Result<(), anyhow::Error> {
587        // EFA: use unified C function
588        if self.is_efa() {
589            return self.post_op_efa(laddr, lkey, length, wr_id, signaled, op_type, raddr, rkey);
590        }
591
592        unsafe {
593            let qp = self.qp as *mut rdmaxcel_sys::rdmaxcel_qp;
594            let context = self.context as *mut rdmaxcel_sys::ibv_context;
595            let ops = &mut (*context).ops;
596            let errno;
597            if op_type == IbvOperation::Recv {
598                let mut sge = rdmaxcel_sys::ibv_sge {
599                    addr: laddr as u64,
600                    length: length as u32,
601                    lkey,
602                };
603                let mut wr = rdmaxcel_sys::ibv_recv_wr {
604                    wr_id,
605                    sg_list: &mut sge as *mut _,
606                    num_sge: 1,
607                    ..Default::default()
608                };
609                let mut bad_wr: *mut rdmaxcel_sys::ibv_recv_wr = std::ptr::null_mut();
610                errno =
611                    ops.post_recv.as_mut().unwrap()((*qp).ibv_qp, &mut wr as *mut _, &mut bad_wr);
612            } else if op_type == IbvOperation::Write
613                || op_type == IbvOperation::Read
614                || op_type == IbvOperation::WriteWithImm
615            {
616                self.apply_first_op_delay(wr_id);
617                let send_flags = if signaled {
618                    rdmaxcel_sys::ibv_send_flags::IBV_SEND_SIGNALED.0
619                } else {
620                    0
621                };
622                let mut sge = rdmaxcel_sys::ibv_sge {
623                    addr: laddr as u64,
624                    length: length as u32,
625                    lkey,
626                };
627                let mut wr = rdmaxcel_sys::ibv_send_wr {
628                    wr_id,
629                    next: std::ptr::null_mut(),
630                    sg_list: &mut sge as *mut _,
631                    num_sge: 1,
632                    opcode: op_type.into(),
633                    send_flags,
634                    wr: Default::default(),
635                    qp_type: Default::default(),
636                    __bindgen_anon_1: Default::default(),
637                    __bindgen_anon_2: Default::default(),
638                };
639
640                wr.wr.rdma.remote_addr = raddr as u64;
641                wr.wr.rdma.rkey = rkey;
642                let mut bad_wr: *mut rdmaxcel_sys::ibv_send_wr = std::ptr::null_mut();
643
644                errno =
645                    ops.post_send.as_mut().unwrap()((*qp).ibv_qp, &mut wr as *mut _, &mut bad_wr);
646            } else {
647                panic!("Not Implemented");
648            }
649
650            if errno != 0 {
651                let os_error = Error::last_os_error();
652                return Err(anyhow::anyhow!("Failed to post send request: {}", os_error));
653            }
654            tracing::debug!(
655                "completed sending {:?} request (lkey: {}, addr: 0x{:x}, length {}) to (raddr 0x{:x}, rkey {})",
656                op_type,
657                lkey,
658                laddr,
659                length,
660                raddr,
661                rkey,
662            );
663
664            Ok(())
665        }
666    }
667
668    /// Posts an RDMA operation via the EFA-specific C function.
669    fn post_op_efa(
670        &mut self,
671        laddr: usize,
672        lkey: u32,
673        length: usize,
674        wr_id: u64,
675        signaled: bool,
676        op_type: IbvOperation,
677        raddr: usize,
678        rkey: u32,
679    ) -> Result<(), anyhow::Error> {
680        let c_op = match op_type {
681            IbvOperation::Write => 0,
682            IbvOperation::Read => 1,
683            IbvOperation::Recv => 2,
684            IbvOperation::WriteWithImm => 3,
685        };
686
687        unsafe {
688            let qp = self.qp as *mut rdmaxcel_sys::rdmaxcel_qp;
689            let ret = rdmaxcel_sys::rdmaxcel_qp_post_op(
690                qp,
691                laddr as *mut std::ffi::c_void,
692                lkey,
693                length,
694                raddr as *mut std::ffi::c_void,
695                rkey,
696                wr_id,
697                signaled as i32,
698                c_op,
699            );
700            if ret != 0 {
701                let msg = std::ffi::CStr::from_ptr(rdmaxcel_sys::rdmaxcel_error_string(ret))
702                    .to_str()
703                    .unwrap_or("unknown");
704                return Err(anyhow::anyhow!("EFA post_op failed: {}", msg));
705            }
706        }
707        Ok(())
708    }
709
710    fn send_wqe(
711        &mut self,
712        laddr: usize,
713        lkey: u32,
714        length: usize,
715        wr_id: u64,
716        signaled: bool,
717        op_type: IbvOperation,
718        raddr: usize,
719        rkey: u32,
720    ) -> Result<DoorBell, anyhow::Error> {
721        // Non-mlx5 devices use the unified C post_op path
722        if self.is_efa() {
723            self.post_op(laddr, lkey, length, wr_id, signaled, op_type, raddr, rkey)?;
724            return Ok(DoorBell {
725                dst_ptr: 0,
726                src_ptr: 0,
727                size: 0,
728            });
729        }
730
731        unsafe {
732            let op_type_val = match op_type {
733                IbvOperation::Write => rdmaxcel_sys::MLX5_OPCODE_RDMA_WRITE,
734                IbvOperation::WriteWithImm => rdmaxcel_sys::MLX5_OPCODE_RDMA_WRITE_IMM,
735                IbvOperation::Read => rdmaxcel_sys::MLX5_OPCODE_RDMA_READ,
736                IbvOperation::Recv => 0,
737            };
738
739            let qp = self.qp as *mut rdmaxcel_sys::rdmaxcel_qp;
740            let dv_qp = self.dv_qp as *mut rdmaxcel_sys::mlx5dv_qp;
741            let _dv_cq = if op_type == IbvOperation::Recv {
742                self.dv_recv_cq as *mut rdmaxcel_sys::mlx5dv_cq
743            } else {
744                self.dv_send_cq as *mut rdmaxcel_sys::mlx5dv_cq
745            };
746
747            let buf = if op_type == IbvOperation::Recv {
748                (*dv_qp).rq.buf as *mut u8
749            } else {
750                (*dv_qp).sq.buf as *mut u8
751            };
752
753            let params = rdmaxcel_sys::wqe_params_t {
754                laddr,
755                lkey,
756                length,
757                wr_id,
758                signaled,
759                op_type: op_type_val,
760                raddr,
761                rkey,
762                qp_num: (*(*qp).ibv_qp).qp_num,
763                buf,
764                dbrec: (*dv_qp).dbrec,
765                wqe_cnt: (*dv_qp).sq.wqe_cnt,
766            };
767
768            if op_type == IbvOperation::Recv {
769                rdmaxcel_sys::recv_wqe(params);
770                std::ptr::write_volatile((*dv_qp).dbrec, 1_u32.to_be());
771            } else {
772                rdmaxcel_sys::send_wqe(params);
773            };
774
775            Ok(DoorBell {
776                dst_ptr: (*dv_qp).bf.reg as usize,
777                src_ptr: (*dv_qp).sq.buf as usize,
778                size: 8,
779            })
780        }
781    }
782
783    /// Polls `target`'s completion queue for a single work completion.
784    ///
785    /// This is a thin wrapper over one `ibv_poll_cq` call. It does no
786    /// per-WR bookkeeping: the caller owns matching each completion back
787    /// to the work request it posted (by [`IbvWc::wr_id`]) and must keep
788    /// polling until the queue drains so that no completion is lost.
789    ///
790    /// # Returns
791    ///
792    /// * `Ok(None)` — the CQ is currently empty.
793    /// * `Ok(Some(Ok(wc)))` — a completion landed with success status.
794    /// * `Ok(Some(Err(_)))` — a completion landed with a non-success
795    ///   status; the [`WorkRequestError`] names the failed request.
796    /// * `Err(_)` — `ibv_poll_cq` itself failed; the QP should be treated
797    ///   as poisoned.
798    pub fn poll_completion(
799        &mut self,
800        target: PollTarget,
801    ) -> Result<Option<Result<IbvWc, WorkRequestError>>, PollCompletionError> {
802        unsafe {
803            let (cq, cq_type) = match target {
804                PollTarget::Send => (self.send_cq as *mut rdmaxcel_sys::ibv_cq, "send"),
805                PollTarget::Recv => (self.recv_cq as *mut rdmaxcel_sys::ibv_cq, "recv"),
806            };
807            let context = self.context as *mut rdmaxcel_sys::ibv_context;
808            let poll_cq = (*context)
809                .ops
810                .poll_cq
811                .expect("poll_cq verb missing from ibv_context ops");
812
813            let mut wc = std::mem::MaybeUninit::<rdmaxcel_sys::ibv_wc>::zeroed().assume_init();
814            let ret = poll_cq(cq, 1, &mut wc);
815
816            if ret < 0 {
817                return Err(PollCompletionError {
818                    message: format!("{} CQ poll failed (ibv_poll_cq returned {})", cq_type, ret),
819                });
820            }
821            if ret == 0 {
822                return Ok(None);
823            }
824
825            // ret >= 1: a single entry was requested, so `wc` holds one
826            // completion. `error()` is `Some` exactly when the status is
827            // not `IBV_WC_SUCCESS`.
828            if let Some((status, vendor_err)) = wc.error() {
829                return Ok(Some(Err(WorkRequestError {
830                    wr_id: wc.wr_id(),
831                    status,
832                    vendor_err,
833                    message: format!(
834                        "{} completion failed for wr_id={}: status={:?}, vendor_err={}",
835                        cq_type,
836                        wc.wr_id(),
837                        status,
838                        vendor_err,
839                    ),
840                })));
841            }
842            Ok(Some(Ok(IbvWc::from(wc))))
843        }
844    }
845}