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