1use std::collections::HashMap;
24use std::collections::HashSet;
25use std::sync::Arc;
26use std::sync::Mutex;
27use std::sync::OnceLock;
28
29use anyhow::Context;
30
31use super::device_selection::get_cuda_device_to_ibv_device;
32use super::domain::IbvDomain;
33use super::domain::IbvDomainImpl;
34use super::domain::register_dmabuf_range;
35use super::domain::register_host_or_dmabuf_mr;
36use super::memory_region::IbvMemoryRegionKeepalive;
37use super::memory_region::IbvMemoryRegionView;
38use super::mlx_queue_pair::MlxQueuePair;
39use super::primitives::GidScope;
40use super::primitives::GidType;
41use super::primitives::IbvConfig;
42use super::primitives::IbvContext;
43use super::primitives::IbvDeviceInfo;
44use super::primitives::IbvMr;
45use super::primitives::IbvPd;
46use super::primitives::IbvQp;
47use super::queue_pair::connect;
48use super::queue_pair::get_qp_info;
49use crate::backend::ibverbs::mlx_device::MlxDevice;
50use crate::local_memory::KeepaliveLocalMemory;
51use crate::local_memory::is_device_ptr;
52
53const MR_ALIGNMENT: usize = 2 * 1024 * 1024;
56const MAX_MR_SIZE: usize = 4 * 1024 * 1024 * 1024 - MR_ALIGNMENT;
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub struct ScannedSegment {
65 pub address: usize,
67 pub size: usize,
69 pub cuda_ordinal: i32,
71 pub is_expandable: bool,
73}
74
75pub type CudaSegmentScanner = Arc<dyn Fn() -> Vec<ScannedSegment> + Send + Sync>;
79
80static CUDA_SEGMENT_SCANNER: Mutex<Option<CudaSegmentScanner>> = Mutex::new(None);
81
82pub fn register_cuda_segment_scanner(scanner: CudaSegmentScanner) {
85 *CUDA_SEGMENT_SCANNER
86 .lock()
87 .expect("CUDA segment scanner lock poisoned") = Some(scanner);
88}
89
90fn scan_cuda_segments() -> Vec<ScannedSegment> {
93 let scanner = CUDA_SEGMENT_SCANNER
98 .lock()
99 .expect("CUDA segment scanner lock poisoned")
100 .clone();
101 scanner.as_deref().map(|scan| scan()).unwrap_or_default()
102}
103
104pub(super) trait MlxDomainOps: Send + Sync + 'static {
115 fn device_name(&self) -> String;
117
118 fn mlx5dv_enabled(&self) -> bool;
120
121 fn assigned_cuda_devices(&self) -> Vec<i32>;
124
125 fn scan_segments(&self) -> Vec<ScannedSegment>;
127
128 unsafe fn register_dmabuf_range(
135 &self,
136 pd: &Arc<IbvPd>,
137 addr: usize,
138 size: usize,
139 access: i32,
140 ) -> anyhow::Result<IbvMr>;
141
142 unsafe fn create_loopback_qp(
151 &self,
152 domain: &IbvDomain<MlxDomain>,
153 config: &IbvConfig,
154 ) -> anyhow::Result<IbvQp>;
155
156 unsafe fn bind_mr_list(
167 &self,
168 pd: &IbvPd,
169 qp: &IbvQp,
170 access: i32,
171 mrs: Vec<Arc<IbvMr>>,
172 mkey_max_entries: usize,
173 ) -> anyhow::Result<Mlx5dvMkey>;
174}
175
176pub(super) struct ProdMlxDomainOps {
183 device_name: String,
184 mlx5dv_enabled: bool,
185}
186
187impl ProdMlxDomainOps {
188 fn new(context: &IbvContext, device_info: &IbvDeviceInfo) -> Self {
189 let ctx = context.as_ptr();
190 let mlx5dv_enabled = if ctx.is_null() {
193 false
194 } else {
195 unsafe { rdmaxcel_sys::mlx5dv_is_supported((*ctx).device) }
198 };
199 Self {
200 device_name: device_info.name().to_string(),
201 mlx5dv_enabled,
202 }
203 }
204}
205
206impl MlxDomainOps for ProdMlxDomainOps {
207 fn device_name(&self) -> String {
208 self.device_name.clone()
209 }
210
211 fn mlx5dv_enabled(&self) -> bool {
212 self.mlx5dv_enabled
213 }
214
215 fn assigned_cuda_devices(&self) -> Vec<i32> {
216 get_cuda_device_to_ibv_device::<MlxDevice>()
217 .iter()
218 .enumerate()
219 .filter_map(|(ordinal, nic)| {
220 nic.as_ref()
221 .filter(|n| n.name() == &self.device_name)
222 .map(|_| ordinal as i32)
223 })
224 .collect()
225 }
226
227 fn scan_segments(&self) -> Vec<ScannedSegment> {
228 scan_cuda_segments()
229 }
230
231 unsafe fn register_dmabuf_range(
232 &self,
233 pd: &Arc<IbvPd>,
234 addr: usize,
235 size: usize,
236 access: i32,
237 ) -> anyhow::Result<IbvMr> {
238 unsafe { register_dmabuf_range(pd, addr, size, access) }
240 }
241
242 unsafe fn create_loopback_qp(
243 &self,
244 domain: &IbvDomain<MlxDomain>,
245 config: &IbvConfig,
246 ) -> anyhow::Result<IbvQp> {
247 let qp = unsafe { MlxQueuePair::create_ibv_qp(domain, config) }
251 .context("could not create loopback QP for mkey binding")?;
252 let context = qp.context().as_ptr();
253 let access_flags = domain.access_flags();
254 let gid = domain.device_info().select_gid(
255 config.port_num,
256 Some(GidScope::Global),
257 Some(GidType::RoCEv2),
258 )?;
259
260 let info = unsafe { get_qp_info(qp.as_ptr(), context, config, gid) }
265 .context("could not query loopback QP info for mkey binding")?;
266 unsafe { connect(qp.as_ptr(), config, access_flags, &info, gid.index()) }
268 .context("could not connect loopback QP for mkey binding")?;
269
270 Ok(qp)
271 }
272
273 unsafe fn bind_mr_list(
274 &self,
275 pd: &IbvPd,
276 qp: &IbvQp,
277 access: i32,
278 mrs: Vec<Arc<IbvMr>>,
279 mkey_max_entries: usize,
280 ) -> anyhow::Result<Mlx5dvMkey> {
281 if pd.as_ptr().is_null() || qp.as_ptr().is_null() {
282 anyhow::bail!("bind_mr_list called with a null protection domain or queue pair");
283 }
284 let ptrs: Vec<*mut rdmaxcel_sys::ibv_mr> = mrs
285 .iter()
286 .map(|m| {
287 let p = m.as_ptr();
288 if p.is_null() {
289 Err(anyhow::anyhow!(
290 "bind_mr_list called with a null memory region"
291 ))
292 } else {
293 Ok(p)
294 }
295 })
296 .collect::<anyhow::Result<Vec<_>>>()?;
297 let mut mkey: *mut rdmaxcel_sys::mlx5dv_mkey = std::ptr::null_mut();
299 let ret = unsafe {
302 rdmaxcel_sys::rdmaxcel_bind_mr_list(
303 pd.as_ptr(),
304 qp.as_ptr(),
305 access,
306 ptrs.as_ptr() as *mut *mut rdmaxcel_sys::ibv_mr,
307 ptrs.len(),
308 mkey_max_entries,
309 &mut mkey,
310 )
311 };
312 if ret != 0 {
313 anyhow::bail!("rdmaxcel_bind_mr_list failed: error code {}", ret);
314 }
315 Ok(unsafe { Mlx5dvMkey::from_raw(mkey, mrs) })
317 }
318}
319
320#[derive(Debug)]
328pub(super) struct Mlx5dvMkey {
329 mkey: *mut rdmaxcel_sys::mlx5dv_mkey,
330 lkey: u32,
331 rkey: u32,
332 mrs: Vec<Arc<IbvMr>>,
333}
334
335unsafe impl Send for Mlx5dvMkey {}
341unsafe impl Sync for Mlx5dvMkey {}
343
344impl Mlx5dvMkey {
345 pub(super) unsafe fn from_raw(
353 mkey: *mut rdmaxcel_sys::mlx5dv_mkey,
354 mrs: Vec<Arc<IbvMr>>,
355 ) -> Self {
356 let (lkey, rkey) = unsafe { ((*mkey).lkey, (*mkey).rkey) };
359 Self {
360 mkey,
361 lkey,
362 rkey,
363 mrs,
364 }
365 }
366
367 fn keys(&self) -> (u32, u32) {
369 (self.lkey, self.rkey)
370 }
371
372 fn mrs(&self) -> &Vec<Arc<IbvMr>> {
374 &self.mrs
375 }
376
377 #[cfg(test)]
381 pub(super) fn with_test_keys(lkey: u32, rkey: u32, mrs: Vec<Arc<IbvMr>>) -> Self {
382 Self {
383 mkey: std::ptr::null_mut(),
384 lkey,
385 rkey,
386 mrs,
387 }
388 }
389}
390
391impl Drop for Mlx5dvMkey {
392 fn drop(&mut self) {
393 if !self.mkey.is_null() {
394 unsafe { rdmaxcel_sys::rdmaxcel_destroy_mkey(self.mkey) };
397 }
398 }
399}
400
401#[derive(Debug)]
408struct RegisteredSegmentState {
409 stale_mkeys: Vec<Mlx5dvMkey>,
412 mkey: Option<Mlx5dvMkey>,
415 size: usize,
417}
418
419struct RegisteredSegment {
427 ops: Arc<dyn MlxDomainOps>,
428 base_virtual_addr: usize,
429 mkey_max_entries: usize,
431 state: Mutex<RegisteredSegmentState>,
432}
433
434impl std::fmt::Debug for RegisteredSegment {
435 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
436 f.debug_struct("RegisteredSegment")
437 .field(
438 "base_virtual_addr",
439 &format!("0x{:x}", self.base_virtual_addr),
440 )
441 .field("state", &self.state)
442 .finish_non_exhaustive()
443 }
444}
445
446impl RegisteredSegment {
447 fn empty(
451 ops: Arc<dyn MlxDomainOps>,
452 base_virtual_addr: usize,
453 mkey_max_entries: usize,
454 ) -> Self {
455 Self {
456 ops,
457 base_virtual_addr,
458 mkey_max_entries,
459 state: Mutex::new(RegisteredSegmentState {
460 stale_mkeys: Vec::new(),
461 mkey: None,
462 size: 0,
463 }),
464 }
465 }
466
467 fn size(&self) -> usize {
469 self.state.lock().expect("segment state lock poisoned").size
470 }
471
472 fn covers(&self, addr: usize, size: usize) -> bool {
474 let end = self.base_virtual_addr + self.size();
475 addr >= self.base_virtual_addr && addr <= end && size <= end - addr
476 }
477
478 unsafe fn grow(
491 &self,
492 pd: &Arc<IbvPd>,
493 qp: &IbvQp,
494 access: i32,
495 scanned_seg: &ScannedSegment,
496 ) -> anyhow::Result<()> {
497 let mut state = self.state.lock().expect("segment state lock poisoned");
498 assert!(
499 scanned_seg.address == self.base_virtual_addr,
500 "grow called for base 0x{:x} on a segment based at 0x{:x}",
501 scanned_seg.address,
502 self.base_virtual_addr
503 );
504 assert!(
507 scanned_seg.size >= state.size,
508 "segment at 0x{:x} shrank from {} to {} bytes; segments must never shrink",
509 self.base_virtual_addr,
510 state.size,
511 scanned_seg.size
512 );
513 if scanned_seg.size == state.size {
515 return Ok(());
516 }
517 let new_tail = unsafe {
520 register_range(
521 &self.ops,
522 pd,
523 access,
524 self.base_virtual_addr + state.size,
525 scanned_seg.size - state.size,
526 )
527 }?;
528
529 let mut all = state
534 .mkey
535 .as_ref()
536 .map(|k| k.mrs().clone())
537 .unwrap_or_default();
538 all.extend(new_tail);
539 if all.len() > self.mkey_max_entries {
544 anyhow::bail!(
545 "segment at 0x{:x} needs {} MRs, exceeding mkey max entries {}",
546 self.base_virtual_addr,
547 all.len(),
548 self.mkey_max_entries
549 );
550 }
551 let new_mkey = unsafe {
553 self.ops
554 .bind_mr_list(pd, qp, access, all, self.mkey_max_entries)
555 }?;
556
557 if let Some(prior) = state.mkey.replace(new_mkey) {
560 state.stale_mkeys.push(prior);
561 }
562 state.size = scanned_seg.size;
563 Ok(())
564 }
565
566 fn view(seg: &Arc<Self>, addr: usize, size: usize) -> IbvMemoryRegionView {
572 let (lkey, rkey) = {
573 let state = seg.state.lock().expect("segment state lock poisoned");
574 state
577 .mkey
578 .as_ref()
579 .map(Mlx5dvMkey::keys)
580 .expect("view of a segment with no bound key")
581 };
582 let guard: Arc<dyn IbvMemoryRegionKeepalive> = seg.clone();
586 IbvMemoryRegionView::new(
587 addr,
588 addr - seg.base_virtual_addr,
589 size,
590 lkey,
591 rkey,
592 seg.ops.device_name(),
593 guard,
594 )
595 }
596}
597
598impl IbvMemoryRegionKeepalive for RegisteredSegment {}
601
602unsafe fn register_range(
611 ops: &Arc<dyn MlxDomainOps>,
612 pd: &Arc<IbvPd>,
613 access: i32,
614 start: usize,
615 len: usize,
616) -> anyhow::Result<Vec<Arc<IbvMr>>> {
617 let mut mrs: Vec<Arc<IbvMr>> = Vec::new();
618 let mut chunk_start = start;
619 let mut remaining = len;
620 while remaining > 0 {
621 let chunk = remaining.min(MAX_MR_SIZE);
622 let result = if chunk.is_multiple_of(MR_ALIGNMENT) {
623 unsafe { ops.register_dmabuf_range(pd, chunk_start, chunk, access) }
625 } else {
626 Err(anyhow::anyhow!(
627 "CUDA chunk size {} is not a multiple of {}",
628 chunk,
629 MR_ALIGNMENT
630 ))
631 };
632 mrs.push(Arc::new(result?));
635 chunk_start += chunk;
636 remaining -= chunk;
637 }
638 Ok(mrs)
639}
640
641pub struct MlxDomain {
647 ops: Arc<dyn MlxDomainOps>,
648 config: IbvConfig,
650 mlx5dv_enabled: bool,
651 cuda_ordinals: Vec<i32>,
654 mkey_max_entries: usize,
656 loopback: OnceLock<IbvQp>,
660 segments: Mutex<HashMap<(usize, i32), Arc<RegisteredSegment>>>,
665}
666
667impl std::fmt::Debug for MlxDomain {
668 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
669 f.debug_struct("MlxDomain")
670 .field("mlx5dv_enabled", &self.mlx5dv_enabled)
671 .field("cuda_ordinals", &self.cuda_ordinals)
672 .finish_non_exhaustive()
673 }
674}
675
676impl MlxDomain {
677 fn new_with_ops(
681 ops: Arc<dyn MlxDomainOps>,
682 config: IbvConfig,
683 mkey_max_entries: usize,
684 ) -> Self {
685 let mlx5dv_enabled = ops.mlx5dv_enabled();
686 let cuda_ordinals = ops.assigned_cuda_devices();
687 Self {
688 ops,
689 config,
690 mlx5dv_enabled,
691 cuda_ordinals,
692 mkey_max_entries,
693 loopback: OnceLock::new(),
694 segments: Mutex::new(HashMap::new()),
695 }
696 }
697
698 fn loopback_qp_ptr(&self, domain: &IbvDomain<MlxDomain>) -> anyhow::Result<&IbvQp> {
700 if let Some(qp) = self.loopback.get() {
704 return Ok(qp);
705 }
706 let qp = unsafe { self.ops.create_loopback_qp(domain, &self.config) }?;
709 let _ = self.loopback.set(qp);
710 Ok(self.loopback.get().expect("loopback just set"))
711 }
712
713 unsafe fn register_cuda_mlx5dv_mr(
724 &self,
725 domain: &IbvDomain<MlxDomain>,
726 addr: usize,
727 size: usize,
728 ) -> anyhow::Result<IbvMemoryRegionView> {
729 let pd = domain.pd();
730 let access = self.access_flags();
731 let mut segments = self
732 .segments
733 .lock()
734 .expect("mlx domain segments lock poisoned");
735
736 if let Some(seg) = segments.values().find(|s| s.covers(addr, size)) {
738 return Ok(RegisteredSegment::view(seg, addr, size));
739 }
740
741 let scanned: Vec<ScannedSegment> = self
743 .ops
744 .scan_segments()
745 .into_iter()
746 .filter(|s| self.cuda_ordinals.contains(&s.cuda_ordinal))
747 .collect();
748
749 let qp = self.loopback_qp_ptr(domain)?;
750
751 let mut snapshot: HashSet<(usize, i32)> = HashSet::new();
752 for scanned_seg in &scanned {
753 let key = (scanned_seg.address, scanned_seg.cuda_ordinal);
754 snapshot.insert(key);
755 match segments.get(&key) {
756 Some(seg) if seg.size() == scanned_seg.size => {}
758 Some(seg) => unsafe { seg.grow(pd, qp, access, scanned_seg) }?,
763 None => {
765 let fresh = Arc::new(RegisteredSegment::empty(
766 self.ops.clone(),
767 scanned_seg.address,
768 self.mkey_max_entries,
769 ));
770 unsafe { fresh.grow(pd, qp, access, scanned_seg) }?;
772 segments.insert(key, fresh);
773 }
774 }
775 }
776
777 segments.retain(|key, _| snapshot.contains(key));
781
782 segments
784 .values()
785 .find(|s| s.covers(addr, size))
786 .map(|s| RegisteredSegment::view(s, addr, size))
787 .ok_or_else(|| {
788 anyhow::anyhow!(
789 "CUDA address 0x{:x} + size {} is not covered by any scanned segment on the CUDA ordinals {:?} mapped to this NIC",
790 addr,
791 size,
792 self.cuda_ordinals,
793 )
794 })
795 }
796}
797
798impl IbvDomainImpl for MlxDomain {
799 type QueuePair = MlxQueuePair;
800
801 unsafe fn new(context: &IbvContext, device_info: &IbvDeviceInfo, config: &IbvConfig) -> Self {
802 Self::new_with_ops(
803 Arc::new(ProdMlxDomainOps::new(context, device_info)),
804 config.clone(),
805 device_info.max_sge().max(0) as usize,
807 )
808 }
809
810 fn access_flags(&self) -> i32 {
811 (rdmaxcel_sys::ibv_access_flags::IBV_ACCESS_LOCAL_WRITE
812 | rdmaxcel_sys::ibv_access_flags::IBV_ACCESS_REMOTE_WRITE
813 | rdmaxcel_sys::ibv_access_flags::IBV_ACCESS_REMOTE_READ
814 | rdmaxcel_sys::ibv_access_flags::IBV_ACCESS_REMOTE_ATOMIC)
815 .0 as i32
816 }
817
818 unsafe fn register_mr(
819 domain: &IbvDomain<MlxDomain>,
820 mem: &KeepaliveLocalMemory,
821 ) -> anyhow::Result<IbvMemoryRegionView> {
822 let this = domain.domain_impl();
823 if this.mlx5dv_enabled && is_device_ptr(mem.addr()) {
824 match unsafe { this.register_cuda_mlx5dv_mr(domain, mem.addr(), mem.size()) } {
827 Ok(view) => return Ok(view),
828 Err(e) => {
829 tracing::warn!("mlx5dv CUDA registration failed, falling back to dmabuf: {e}")
830 }
831 }
832 }
833 unsafe { register_host_or_dmabuf_mr(domain, mem) }
837 }
838}
839
840#[cfg(test)]
841mod tests {
842 use std::sync::Mutex;
843 use std::sync::MutexGuard;
844
845 use super::super::primitives::IbvDeviceInfo;
846 use super::*;
847
848 const MIB2: usize = 2 * 1024 * 1024;
849 const SERVED_NIC: &str = "mlx5_0";
850
851 fn seg(address: usize, size: usize, cuda_ordinal: i32) -> ScannedSegment {
852 ScannedSegment {
853 address,
854 size,
855 cuda_ordinal,
856 is_expandable: false,
857 }
858 }
859
860 #[derive(Debug, Clone, PartialEq, Eq)]
862 struct DmabufCall {
863 addr: usize,
864 size: usize,
865 access: i32,
866 }
867
868 #[derive(Debug, Clone)]
871 struct BindCall {
872 mrs: Vec<Arc<IbvMr>>,
873 }
874
875 #[derive(Default)]
882 struct MockState {
883 device_name: String,
884 mlx5dv_enabled: bool,
885 served_ordinals: Vec<i32>,
886 scan: Vec<ScannedSegment>,
887 next_handle: usize,
888 loopback_created: usize,
891 fail_dmabuf_after: Option<usize>,
892 fail_bind: bool,
893 scan_calls: usize,
894 dmabuf_calls: Vec<DmabufCall>,
896 bind_calls: Vec<BindCall>,
898 }
899
900 impl MockState {
901 fn mint(&mut self) -> usize {
902 self.next_handle += 0x1000;
903 self.next_handle
904 }
905 }
906
907 struct MockOps {
908 state: Mutex<MockState>,
909 }
910
911 impl MockOps {
912 fn new(device_name: &str, mlx5dv_enabled: bool, served_ordinals: &[i32]) -> Arc<Self> {
913 Arc::new(Self {
914 state: Mutex::new(MockState {
915 device_name: device_name.to_string(),
916 mlx5dv_enabled,
917 served_ordinals: served_ordinals.to_vec(),
918 next_handle: 0x1_0000,
919 ..Default::default()
920 }),
921 })
922 }
923
924 fn lock(&self) -> MutexGuard<'_, MockState> {
925 self.state.lock().expect("mock state poisoned")
926 }
927 }
928
929 impl MlxDomainOps for MockOps {
930 fn device_name(&self) -> String {
931 self.lock().device_name.clone()
932 }
933
934 fn mlx5dv_enabled(&self) -> bool {
935 self.lock().mlx5dv_enabled
936 }
937
938 fn assigned_cuda_devices(&self) -> Vec<i32> {
939 self.lock().served_ordinals.clone()
940 }
941
942 fn scan_segments(&self) -> Vec<ScannedSegment> {
943 let mut s = self.lock();
944 s.scan_calls += 1;
945 s.scan.clone()
946 }
947
948 unsafe fn register_dmabuf_range(
949 &self,
950 _pd: &Arc<IbvPd>,
951 addr: usize,
952 size: usize,
953 access: i32,
954 ) -> anyhow::Result<IbvMr> {
955 let mut s = self.lock();
956 s.dmabuf_calls.push(DmabufCall { addr, size, access });
957 if let Some(n) = s.fail_dmabuf_after
958 && s.dmabuf_calls.len() > n
959 {
960 anyhow::bail!("mock dmabuf registration failure");
961 }
962 Ok(IbvMr::null())
964 }
965
966 unsafe fn create_loopback_qp(
967 &self,
968 _domain: &IbvDomain<MlxDomain>,
969 _config: &IbvConfig,
970 ) -> anyhow::Result<IbvQp> {
971 self.lock().loopback_created += 1;
973 Ok(IbvQp::null())
974 }
975
976 unsafe fn bind_mr_list(
977 &self,
978 _pd: &IbvPd,
979 _qp: &IbvQp,
980 _access: i32,
981 mrs: Vec<Arc<IbvMr>>,
982 _mkey_max_entries: usize,
983 ) -> anyhow::Result<Mlx5dvMkey> {
984 let mut s = self.lock();
985 if s.fail_bind {
986 anyhow::bail!("mock bind failure");
987 }
988 s.bind_calls.push(BindCall { mrs: mrs.clone() });
989 let v = s.mint() as u32;
992 Ok(Mlx5dvMkey::with_test_keys(v, v ^ 0xffff, mrs))
993 }
994 }
995
996 const TEST_MKEY_MAX_ENTRIES: usize = 32;
999
1000 fn domain(mock: Arc<MockOps>) -> Arc<IbvDomain<MlxDomain>> {
1004 let mlx = MlxDomain::new_with_ops(mock, IbvConfig::default(), TEST_MKEY_MAX_ENTRIES);
1005 unsafe {
1008 Arc::new(IbvDomain::for_test(
1009 Arc::new(IbvPd::null()),
1010 IbvDeviceInfo::for_test_named("test"),
1011 mlx,
1012 ))
1013 }
1014 }
1015
1016 fn register_cuda(
1018 domain: &Arc<IbvDomain<MlxDomain>>,
1019 addr: usize,
1020 size: usize,
1021 ) -> anyhow::Result<IbvMemoryRegionView> {
1022 unsafe {
1024 domain
1025 .domain_impl()
1026 .register_cuda_mlx5dv_mr(domain, addr, size)
1027 }
1028 }
1029
1030 fn null_pd() -> Arc<IbvPd> {
1033 Arc::new(IbvPd::null())
1034 }
1035
1036 fn null_qp() -> IbvQp {
1037 IbvQp::null()
1038 }
1039
1040 fn dyn_ops(ops: &Arc<MockOps>) -> Arc<dyn MlxDomainOps> {
1042 ops.clone()
1043 }
1044
1045 fn bind_fresh(ops: &Arc<MockOps>, base: usize, size: usize) -> Arc<RegisteredSegment> {
1047 let segment = Arc::new(RegisteredSegment::empty(
1048 dyn_ops(ops),
1049 base,
1050 TEST_MKEY_MAX_ENTRIES,
1051 ));
1052 unsafe { segment.grow(&null_pd(), &null_qp(), 0, &seg(base, size, 0)) }
1054 .expect("fresh bind should succeed");
1055 segment
1056 }
1057
1058 #[test]
1061 fn test_scanner_registration_round_trips() {
1062 register_cuda_segment_scanner(Arc::new(|| vec![seg(0x1000, MIB2, 3)]));
1063 assert_eq!(
1064 scan_cuda_segments(),
1065 vec![seg(0x1000, MIB2, 3)],
1066 "the registered scanner's output is returned"
1067 );
1068 }
1069
1070 #[test]
1071 fn test_fresh_bind_registers_one_mr_and_key() {
1072 let ops = MockOps::new(SERVED_NIC, true, &[0]);
1073 let base = 0x10_0000_0000;
1074 let rs = bind_fresh(&ops, base, MIB2);
1075
1076 assert_eq!(rs.size(), MIB2);
1077 assert!(
1078 rs.state.lock().unwrap().stale_mkeys.is_empty(),
1079 "a fresh segment has no superseded keys"
1080 );
1081 assert_eq!(
1082 rs.state.lock().unwrap().mkey.as_ref().unwrap().mrs().len(),
1083 1,
1084 "the current key binds the one MR"
1085 );
1086 let s = ops.lock();
1087 assert_eq!(
1088 s.dmabuf_calls,
1089 vec![DmabufCall {
1090 addr: base,
1091 size: MIB2,
1092 access: 0,
1093 }],
1094 "the whole segment is registered as one MR at its base"
1095 );
1096 assert_eq!(s.bind_calls.len(), 1);
1097 assert_eq!(s.bind_calls[0].mrs.len(), 1, "one MR bound");
1098 }
1099
1100 #[test]
1101 fn test_large_segment_splits_into_chunks() {
1102 let ops = MockOps::new(SERVED_NIC, true, &[0]);
1103 let base = 0x10_0000_0000;
1104 let rs = bind_fresh(&ops, base, MAX_MR_SIZE + MIB2);
1107
1108 assert_eq!(
1109 rs.state.lock().unwrap().mkey.as_ref().unwrap().mrs().len(),
1110 2
1111 );
1112 let s = ops.lock();
1113 assert_eq!(
1114 s.dmabuf_calls,
1115 vec![
1116 DmabufCall {
1117 addr: base,
1118 size: MAX_MR_SIZE,
1119 access: 0,
1120 },
1121 DmabufCall {
1122 addr: base + MAX_MR_SIZE,
1123 size: MIB2,
1124 access: 0,
1125 },
1126 ],
1127 "the range splits into a MAX_MR_SIZE chunk and a 2 MiB tail"
1128 );
1129 assert_eq!(s.bind_calls.len(), 1);
1130 assert_eq!(
1131 s.bind_calls[0].mrs.len(),
1132 2,
1133 "both chunk MRs bound to one key"
1134 );
1135 }
1136
1137 #[test]
1138 fn test_grow_reuses_mrs_and_retires_prior_key() {
1139 let ops = MockOps::new(SERVED_NIC, true, &[0]);
1140 let base = 0x10_0000_0000;
1141 let rs = bind_fresh(&ops, base, MIB2);
1142 let mkey_a = rs.state.lock().unwrap().mkey.as_ref().unwrap().keys();
1143 let mr_a = ops.lock().bind_calls[0].mrs[0].clone();
1144
1145 unsafe { rs.grow(&null_pd(), &null_qp(), 0, &seg(base, 2 * MIB2, 0)) }
1147 .expect("growth should succeed");
1148
1149 assert_eq!(rs.size(), 2 * MIB2);
1150 assert_eq!(
1151 rs.state.lock().unwrap().mkey.as_ref().unwrap().mrs().len(),
1152 2,
1153 "the original MR is reused and one tail MR appended"
1154 );
1155 assert_eq!(
1156 rs.state.lock().unwrap().stale_mkeys.len(),
1157 1,
1158 "the prior key is parked as stale"
1159 );
1160 assert_eq!(
1161 rs.state.lock().unwrap().stale_mkeys[0].keys(),
1162 mkey_a,
1163 "the parked key is the original, not a fresh one"
1164 );
1165 let s = ops.lock();
1166 assert_eq!(
1168 s.dmabuf_calls,
1169 vec![
1170 DmabufCall {
1171 addr: base,
1172 size: MIB2,
1173 access: 0,
1174 },
1175 DmabufCall {
1176 addr: base + MIB2,
1177 size: MIB2,
1178 access: 0,
1179 },
1180 ],
1181 "growth registers only the new tail, at base + MIB2"
1182 );
1183 assert_eq!(s.bind_calls.len(), 2);
1184 assert_eq!(s.bind_calls[0].mrs.len(), 1);
1185 assert!(
1186 Arc::ptr_eq(&s.bind_calls[0].mrs[0], &mr_a),
1187 "the first bind covered just the original MR"
1188 );
1189 assert_eq!(s.bind_calls[1].mrs.len(), 2);
1190 assert!(
1191 Arc::ptr_eq(&s.bind_calls[1].mrs[0], &mr_a)
1192 && !Arc::ptr_eq(&s.bind_calls[1].mrs[1], &mr_a),
1193 "the second bind reuses the original MR and adds a new tail"
1194 );
1195 }
1196
1197 #[test]
1198 fn test_grow_failure_leaves_segment_unchanged() {
1199 let ops = MockOps::new(SERVED_NIC, true, &[0]);
1200 let base = 0x10_0000_0000;
1201 let rs = bind_fresh(&ops, base, MIB2);
1202 ops.lock().fail_dmabuf_after = Some(2);
1204
1205 let result = unsafe {
1207 rs.grow(
1208 &null_pd(),
1209 &null_qp(),
1210 0,
1211 &seg(base, MAX_MR_SIZE + 2 * MIB2, 0),
1212 )
1213 };
1214 assert!(
1215 result.is_err(),
1216 "growth fails when a tail MR fails to register"
1217 );
1218
1219 assert_eq!(rs.size(), MIB2, "size unchanged after a failed growth");
1220 assert_eq!(
1221 rs.state.lock().unwrap().mkey.as_ref().unwrap().mrs().len(),
1222 1,
1223 "no tail MRs committed"
1224 );
1225 assert!(
1226 rs.state.lock().unwrap().stale_mkeys.is_empty(),
1227 "no key retired"
1228 );
1229 assert_eq!(
1230 ops.lock().bind_calls.len(),
1231 1,
1232 "only the original bind happened; the growth bind was never reached"
1233 );
1234 }
1235
1236 #[test]
1237 fn test_grow_to_equal_size_is_noop() {
1238 let ops = MockOps::new(SERVED_NIC, true, &[0]);
1239 let base = 0x10_0000_0000;
1240 let rs = bind_fresh(&ops, base, 2 * MIB2);
1241 let binds_before = ops.lock().bind_calls.len();
1242
1243 unsafe { rs.grow(&null_pd(), &null_qp(), 0, &seg(base, 2 * MIB2, 0)) }
1246 .expect("equal-size grow is a no-op");
1247
1248 assert_eq!(rs.size(), 2 * MIB2, "size is unchanged");
1249 let s = ops.lock();
1250 assert_eq!(s.bind_calls.len(), binds_before, "no new bind performed");
1251 assert_eq!(s.dmabuf_calls.len(), 1, "no new MR registered");
1252 }
1253
1254 #[test]
1255 fn test_view_pins_segment_and_anchors_at_base() {
1256 let ops = MockOps::new(SERVED_NIC, true, &[0]);
1257 let base = 0x10_0000_0000;
1258 let rs = bind_fresh(&ops, base, MIB2);
1259
1260 assert!(rs.covers(base + 0x400, 4096));
1261 let view = RegisteredSegment::view(&rs, base + 0x400, 4096);
1262 assert_eq!(
1263 view.rdma_addr, 0x400,
1264 "rdma_addr is the offset from the segment base"
1265 );
1266 assert_eq!(view.size, 4096);
1267 assert_eq!(view.device_name, SERVED_NIC);
1268 assert_eq!(
1269 Arc::strong_count(&rs),
1270 2,
1271 "the view's guard pins the segment alive (our ref + the view's)"
1272 );
1273
1274 drop(view);
1275 assert_eq!(
1276 Arc::strong_count(&rs),
1277 1,
1278 "dropping the view releases its segment ref"
1279 );
1280 }
1281
1282 #[test]
1283 fn test_partial_dmabuf_failure_cleans_up() {
1284 let ops = MockOps::new(SERVED_NIC, true, &[0]);
1285 ops.lock().fail_dmabuf_after = Some(1);
1286 let base = 0x10_0000_0000;
1287 let segment = RegisteredSegment::empty(dyn_ops(&ops), base, TEST_MKEY_MAX_ENTRIES);
1288
1289 let result =
1292 unsafe { segment.grow(&null_pd(), &null_qp(), 0, &seg(base, MAX_MR_SIZE + MIB2, 0)) };
1293 assert!(
1294 result.is_err(),
1295 "the first bind fails when a chunk registration fails"
1296 );
1297 assert_eq!(segment.size(), 0, "the segment is left empty");
1298 assert!(
1299 segment.state.lock().unwrap().mkey.is_none(),
1300 "no key installed"
1301 );
1302 let s = ops.lock();
1303 assert!(s.bind_calls.is_empty(), "no bind performed");
1304 }
1305
1306 #[test]
1307 fn test_bind_failure_cleans_up() {
1308 let ops = MockOps::new(SERVED_NIC, true, &[0]);
1309 ops.lock().fail_bind = true;
1310 let base = 0x10_0000_0000;
1311 let segment = RegisteredSegment::empty(dyn_ops(&ops), base, TEST_MKEY_MAX_ENTRIES);
1312
1313 let result = unsafe { segment.grow(&null_pd(), &null_qp(), 0, &seg(base, MIB2, 0)) };
1315 assert!(result.is_err(), "the bind fails when bind_mr_list fails");
1316 assert_eq!(segment.size(), 0, "the segment is left empty");
1317 assert!(
1318 segment.state.lock().unwrap().mkey.is_none(),
1319 "no key installed on bind failure"
1320 );
1321 }
1322
1323 #[test]
1324 fn test_grow_exceeding_mkey_max_entries_errors() {
1325 let ops = MockOps::new(SERVED_NIC, true, &[0]);
1326 let base = 0x10_0000_0000;
1327 let segment = RegisteredSegment::empty(dyn_ops(&ops), base, 1);
1330 let result =
1332 unsafe { segment.grow(&null_pd(), &null_qp(), 0, &seg(base, MAX_MR_SIZE + MIB2, 0)) };
1333 assert!(
1334 result.is_err(),
1335 "grow must reject a segment needing more MRs than mkey max entries"
1336 );
1337 assert_eq!(segment.size(), 0, "the segment is left unchanged");
1338 assert!(
1339 ops.lock().bind_calls.is_empty(),
1340 "no bind attempted past the cap"
1341 );
1342 }
1343
1344 #[test]
1347 fn test_cuda_ordinals_and_mlx5dv_enabled_derived_from_ops() {
1348 let mock = MockOps::new(SERVED_NIC, true, &[0, 2]);
1349 let domain = domain(mock);
1350 assert_eq!(domain.domain_impl().cuda_ordinals, vec![0, 2]);
1351 assert!(domain.domain_impl().mlx5dv_enabled);
1352 }
1353
1354 #[test]
1355 fn test_fresh_bind() {
1356 let base = 0x10_0000_0000;
1357 let mock = MockOps::new(SERVED_NIC, true, &[0]);
1358 mock.lock().scan = vec![seg(base, MIB2, 0)];
1359 let domain = domain(mock.clone());
1360
1361 let view = register_cuda(&domain, base, 4096).expect("fresh bind should succeed");
1362
1363 assert_eq!(view.rdma_addr, 0, "view is anchored at the segment base");
1366 assert_eq!(view.size, 4096);
1367 assert_eq!(view.device_name, SERVED_NIC);
1368 assert_eq!(mock.lock().bind_calls.len(), 1, "one segment bound");
1369 }
1370
1371 #[test]
1372 fn test_fast_path_skips_scan() {
1373 let mock = MockOps::new(SERVED_NIC, true, &[0]);
1374 mock.lock().scan = vec![seg(0x10_0000_0000, MIB2, 0)];
1375 let domain = domain(mock.clone());
1376
1377 register_cuda(&domain, 0x10_0000_0000, 4096).unwrap();
1378 let scans_after_first = mock.lock().scan_calls;
1379
1380 register_cuda(&domain, 0x10_0000_0400, 4096).unwrap();
1383 let s = mock.lock();
1384 assert_eq!(s.scan_calls, scans_after_first, "fast path skips the scan");
1385 assert_eq!(s.bind_calls.len(), 1, "fast path performs no new bind");
1386 }
1387
1388 #[test]
1389 fn test_segment_growth_via_register() {
1390 let base = 0x10_0000_0000;
1391 let mock = MockOps::new(SERVED_NIC, true, &[0]);
1392 mock.lock().scan = vec![seg(base, MIB2, 0)];
1393 let domain = domain(mock.clone());
1394
1395 register_cuda(&domain, base, 4096).unwrap();
1396
1397 mock.lock().scan = vec![seg(base, 2 * MIB2, 0)];
1401 let view =
1402 register_cuda(&domain, base + MIB2, 4096).expect("growth registration should succeed");
1403
1404 assert_eq!(view.rdma_addr, MIB2, "the view anchors into the grown tail");
1405 assert_eq!(
1406 mock.lock().bind_calls.len(),
1407 2,
1408 "growth created a new key (a second bind), retiring the prior one"
1409 );
1410 }
1411
1412 #[test]
1413 fn test_unserved_ordinal_is_not_covered() {
1414 let mock = MockOps::new(SERVED_NIC, true, &[0]);
1415 mock.lock().scan = vec![seg(0x10_0000_0000, MIB2, 5)];
1417 let domain = domain(mock.clone());
1418
1419 let result = register_cuda(&domain, 0x10_0000_0000, 4096);
1420 assert!(
1421 result.is_err(),
1422 "memory on an unserved ordinal is not bound"
1423 );
1424 assert!(
1425 mock.lock().bind_calls.is_empty(),
1426 "no bind attempted for an unserved ordinal"
1427 );
1428 }
1429
1430 #[test]
1431 fn test_loopback_qp_created_once() {
1432 let base = 0x10_0000_0000;
1433 let other = 0x20_0000_0000;
1434 let mock = MockOps::new(SERVED_NIC, true, &[0]);
1435 mock.lock().scan = vec![seg(base, MIB2, 0)];
1436 let domain = domain(mock.clone());
1437
1438 register_cuda(&domain, base, 4096).unwrap();
1441 assert_eq!(
1442 mock.lock().loopback_created,
1443 1,
1444 "the first binding creates the loopback QP"
1445 );
1446
1447 mock.lock().scan = vec![seg(base, 2 * MIB2, 0)];
1450 register_cuda(&domain, base + MIB2, 4096).unwrap();
1451 assert_eq!(
1452 mock.lock().loopback_created,
1453 1,
1454 "growth reuses the cached loopback QP"
1455 );
1456
1457 mock.lock().scan = vec![seg(base, 2 * MIB2, 0), seg(other, MIB2, 0)];
1458 register_cuda(&domain, other, 4096).unwrap();
1459 assert_eq!(
1460 mock.lock().loopback_created,
1461 1,
1462 "binding a new segment reuses the cached loopback QP"
1463 );
1464 }
1465
1466 #[test]
1467 fn test_multi_device_subset_growth_new_segments_and_boundaries() {
1468 let a = 0x10_0000_0000usize; let a2 = 0x18_0000_0000usize; let b = 0x20_0000_0000usize; let c = 0x30_0000_0000usize; let mock = MockOps::new(SERVED_NIC, true, &[0, 2]);
1475 mock.lock().scan = vec![seg(a, MIB2, 0), seg(b, MIB2, 1), seg(c, 2 * MIB2, 2)];
1476 let domain = domain(mock.clone());
1477 let access = domain.domain_impl().access_flags();
1480
1481 let assert_view =
1487 |view: &IbvMemoryRegionView, virtual_addr: usize, rdma_addr: usize, size: usize| {
1488 assert_eq!(
1489 view.virtual_addr, virtual_addr,
1490 "virtual_addr is the requested address"
1491 );
1492 assert_eq!(
1493 view.rdma_addr, rdma_addr,
1494 "rdma_addr is the offset from the segment base"
1495 );
1496 assert_eq!(view.size, size, "size matches the request");
1497 assert_eq!(view.device_name, SERVED_NIC, "view names the serving NIC");
1498 assert_ne!(view.lkey, 0, "a bound view carries a real lkey");
1499 assert_eq!(
1500 view.rkey,
1501 view.lkey ^ 0xffff,
1502 "rkey and lkey are derived from the same mkey"
1503 );
1504 };
1505
1506 let view_a = register_cuda(&domain, a, 0x2000).expect("served ordinal binds");
1510 assert_view(&view_a, a, 0, 0x2000);
1511 let (mr_a, mr_c) = {
1512 let s = mock.lock();
1513 assert_eq!(s.bind_calls.len(), 2, "one bind per served segment");
1514 assert_eq!(s.bind_calls[0].mrs.len(), 1, "segment a binds a single MR");
1517 assert_eq!(s.bind_calls[1].mrs.len(), 1, "segment c binds a single MR");
1518 assert_eq!(
1522 s.dmabuf_calls,
1523 vec![
1524 DmabufCall {
1525 addr: a,
1526 size: MIB2,
1527 access,
1528 },
1529 DmabufCall {
1530 addr: c,
1531 size: 2 * MIB2,
1532 access,
1533 },
1534 ],
1535 "a and c are each registered once, covering their full extent"
1536 );
1537 assert_eq!(s.scan_calls, 1);
1538 let (mr_a, mr_c) = (
1539 s.bind_calls[0].mrs[0].clone(),
1540 s.bind_calls[1].mrs[0].clone(),
1541 );
1542 assert!(!Arc::ptr_eq(&mr_a, &mr_c), "a and c are distinct MRs");
1543 (mr_a, mr_c)
1544 };
1545
1546 let view_c =
1549 register_cuda(&domain, c + 0x800, 0x4000).expect("segment c covers the request");
1550 assert_view(&view_c, c + 0x800, 0x800, 0x4000);
1551 assert_ne!(
1552 view_c.lkey, view_a.lkey,
1553 "distinct segments are backed by distinct keys"
1554 );
1555 assert_eq!(
1556 mock.lock().scan_calls,
1557 1,
1558 "fast path serves c without a scan"
1559 );
1560
1561 let tail = register_cuda(&domain, c + 2 * MIB2 - 0x6000, 0x6000)
1565 .expect("a request ending exactly at the boundary is covered");
1566 assert_view(&tail, c + 2 * MIB2 - 0x6000, 2 * MIB2 - 0x6000, 0x6000);
1567 assert_eq!(
1568 (tail.lkey, tail.rkey),
1569 (view_c.lkey, view_c.rkey),
1570 "both views of segment c share its current key"
1571 );
1572 assert!(
1573 register_cuda(&domain, c + 2 * MIB2 - 2048, 4096).is_err(),
1574 "a request straddling the segment's end boundary is rejected"
1575 );
1576
1577 mock.lock().scan = vec![
1581 seg(a, 3 * MIB2, 0),
1582 seg(b, MIB2, 1),
1583 seg(c, 2 * MIB2, 2),
1584 seg(a2, MIB2, 0),
1585 ];
1586 let grown =
1587 register_cuda(&domain, a + 2 * MIB2, 0x3000).expect("a's grown tail is now covered");
1588 assert_view(&grown, a + 2 * MIB2, 2 * MIB2, 0x3000);
1589 assert_ne!(
1590 grown.lkey, view_a.lkey,
1591 "growing segment a rotated it onto a new key"
1592 );
1593 {
1594 let s = mock.lock();
1595 assert_eq!(s.bind_calls.len(), 4);
1598 assert_eq!(
1601 s.bind_calls[2].mrs.len(),
1602 2,
1603 "a's growth binds the reused original MR plus the new tail"
1604 );
1605 assert!(
1606 Arc::ptr_eq(&s.bind_calls[2].mrs[0], &mr_a),
1607 "the original MR is reused, not re-registered"
1608 );
1609 let mr_a_tail = s.bind_calls[2].mrs[1].clone();
1610 assert_eq!(
1612 s.bind_calls[3].mrs.len(),
1613 1,
1614 "the new segment a2 binds a single MR"
1615 );
1616 let mr_a2 = s.bind_calls[3].mrs[0].clone();
1617 let all = [&mr_a, &mr_c, &mr_a_tail, &mr_a2];
1619 for (i, x) in all.iter().enumerate() {
1620 for y in &all[i + 1..] {
1621 assert!(
1622 !Arc::ptr_eq(x, y),
1623 "a, a-tail, c, and a2 are four distinct MRs"
1624 );
1625 }
1626 }
1627 assert_eq!(
1632 s.dmabuf_calls,
1633 vec![
1634 DmabufCall {
1635 addr: a,
1636 size: MIB2,
1637 access,
1638 },
1639 DmabufCall {
1640 addr: c,
1641 size: 2 * MIB2,
1642 access,
1643 },
1644 DmabufCall {
1645 addr: a + MIB2,
1646 size: 2 * MIB2,
1647 access,
1648 },
1649 DmabufCall {
1650 addr: a2,
1651 size: MIB2,
1652 access,
1653 },
1654 ],
1655 "growth registers only a's new tail and a2; c is not re-registered"
1656 );
1657 }
1658
1659 let view_a2 = register_cuda(&domain, a2 + 0x400, 0x800)
1662 .expect("the new segment a2 covers the request");
1663 assert_view(&view_a2, a2 + 0x400, 0x400, 0x800);
1664 assert_ne!(
1665 view_a2.lkey, grown.lkey,
1666 "the new segment a2 has a key distinct from a's"
1667 );
1668 assert_ne!(
1669 view_a2.lkey, view_c.lkey,
1670 "the new segment a2 has a key distinct from c's"
1671 );
1672 assert!(
1673 register_cuda(&domain, a2 + MIB2 - 512, 1024).is_err(),
1674 "a request past a2's end boundary is rejected"
1675 );
1676
1677 assert!(
1680 register_cuda(&domain, b, 4096).is_err(),
1681 "the unserved ordinal-1 segment is never served"
1682 );
1683 assert!(
1684 !mock.lock().dmabuf_calls.iter().any(|call| call.addr == b),
1685 "the unserved ordinal-1 segment was never registered"
1686 );
1687 }
1688}