/
githubmirror
/
rdma-core
Обзор
Документация
Войти
/
githubmirror
/
rdma-core
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
tests/test_buf.py
379 строк
16 KB
Jiri Pirko
tests: Cover DMA-buf fd export for ibv_buf
14 июл 2026, 17:41
14 июл 2026, 17:41
c8fe8e7
Код
Авторство
О чём код?
# SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB) # Copyright (c) 2026 NVIDIA Corporation . All rights reserved. See COPYING file """ Tests for the provider-aware buffer API: ibv_alloc_buf(), ibv_free_buf(), ibv_reg_buf_mr() and the ibv_reg_mr_ex() IBV_REG_MR_MASK_BUF path. """ import unittest import errno import os import resource from pyverbs.pyverbs_error import PyverbsRDMAError from pyverbs.pd import PD, ParentDomain, ParentDomainInitAttr from pyverbs.mr import MR, Buf, BufMR, MREx from pyverbs.cq import CqInitAttrEx, CQEX import pyverbs.device as d from pyverbs.libibverbs_enums import ibv_access_flags, ibv_atomic_cap, \ ibv_cq_init_attr_mask, ibv_wr_opcode, ibv_parent_domain_init_attr_mask, \ IBV_WC_STANDARD_FLAGS, _IBV_DEVICE_CC_DMA_BOUNCE from tests.base import PyverbsAPITestCase, RCResources, UDResources, \ RDMATestCase import tests.utils as u # errnos that mean the environment cannot provide CC shared buffers / parent # domains; the affected test is skipped rather than failed. SKIP_ERRNOS = (errno.EOPNOTSUPP, errno.ENOENT, errno.ENODEV) PAGE_SIZE = resource.getpagesize() def device_has_cc_dma_bounce(ctx): """Whether the device reports IBV_DEVICE_CC_DMA_BOUNCE.""" return bool(ctx.query_device_ex().device_cap_flags_ex & _IBV_DEVICE_CC_DMA_BOUNCE) def make_cc_pd(ctx): """ Allocate a base PD and a CC PD opting in to unprotected/shared memory for CoCo guests. Returns a (base_pd, cc_pd) tuple; the base PD must outlive the CC PD. Skips if CC PDs are unsupported. """ base_pd = PD(ctx) attr = ParentDomainInitAttr( pd=base_pd, comp_mask=ibv_parent_domain_init_attr_mask. IBV_PARENT_DOMAIN_INIT_ATTR_ALLOW_CC_UNPROTECTED_ALLOC) try: pd = ParentDomain(ctx, attr=attr) except PyverbsRDMAError as ex: base_pd.close() if ex.error_code in SKIP_ERRNOS: raise unittest.SkipTest('CC PD is not supported') raise return base_pd, pd def alloc_buf(pd, size): """ Allocate a buffer of ibv_buf type, skipping if the provider does not support it """ try: return Buf(pd, size) except PyverbsRDMAError as ex: if ex.error_code in SKIP_ERRNOS: raise unittest.SkipTest('ibv_alloc_buf() is not supported') raise def register_buf_mr(pd, buf, length, access, offset=0, via_reg_mr_ex=False): """ Register (a subrange of) buf, either through ibv_reg_buf_mr() or, when via_reg_mr_ex is set, through the ibv_reg_mr_ex() IBV_REG_MR_MASK_BUF path. """ try: if via_reg_mr_ex: return MREx(pd, length=length, access=access, buf=buf, address=buf.addr + offset) return BufMR(pd, buf, length, access, offset=offset) except PyverbsRDMAError as ex: if ex.error_code in SKIP_ERRNOS: raise unittest.SkipTest('Buffer MR registration is not supported') raise def init_buf_resource(res, cc, via_reg_mr_ex, mr_access, buf_size, mr_offset): """Store the buffer parameters before the base resource init runs.""" res.cc = cc res.via_reg_mr_ex = via_reg_mr_ex res.mr_access = mr_access res.buf_size = buf_size res.mr_offset = mr_offset res.base_pd = None res.data_buf = None def create_pd(res): """Create the resource's PD: a plain PD or a CC PD.""" if res.cc: res.base_pd, res.pd = make_cc_pd(res.ctx) else: # A plain PD registers private memory, which a DMA-bounce device # rejects; that device is exercised by the cc=True resources. if device_has_cc_dma_bounce(res.ctx): raise unittest.SkipTest('Plain-memory registration is rejected on ' 'a DMA-bounce device') res.pd = PD(res.ctx) def create_cq(res): """Create the resource's extended CQ, bound to the CC PD for cc.""" comp_mask = ibv_cq_init_attr_mask.IBV_CQ_INIT_ATTR_MASK_FLAGS if res.cc: comp_mask |= ibv_cq_init_attr_mask.IBV_CQ_INIT_ATTR_MASK_PD cqia = CqInitAttrEx(cqe=res.num_msgs, wc_flags=IBV_WC_STANDARD_FLAGS, parent_domain=res.pd if res.cc else None, comp_mask=comp_mask) try: res.cq = CQEX(res.ctx, cqia) except PyverbsRDMAError as ex: if ex.error_code in SKIP_ERRNOS: raise unittest.SkipTest('Extended CQ is not supported') raise def create_buf_mr(res, mr_len, buf_size, offset): """Allocate an ibv_buf and register (a subrange of) it as the MR.""" if buf_size is None: buf_size = mr_len + offset res.data_buf = alloc_buf(res.pd, buf_size) res.mr = register_buf_mr(res.pd, res.data_buf, mr_len, res.mr_access, offset=offset, via_reg_mr_ex=res.via_reg_mr_ex) class BufRC(RCResources): """RC resources whose data buffer is allocated with ibv_alloc_buf().""" def __init__(self, *args, cc=False, via_reg_mr_ex=False, mr_access=ibv_access_flags.IBV_ACCESS_LOCAL_WRITE, buf_size=None, mr_offset=0, **kwargs): init_buf_resource(self, cc, via_reg_mr_ex, mr_access, buf_size, mr_offset) super().__init__(*args, **kwargs) def create_pd(self): create_pd(self) def create_cq(self): create_cq(self) def create_mr(self): create_buf_mr(self, self.msg_size, self.buf_size, self.mr_offset) def create_qp_attr(self): attr = super().create_qp_attr() attr.qp_access_flags = self.mr_access return attr class BufUD(UDResources): """UD resources whose data buffer is allocated with ibv_alloc_buf().""" def __init__(self, *args, cc=False, via_reg_mr_ex=False, mr_access=ibv_access_flags.IBV_ACCESS_LOCAL_WRITE, buf_size=None, mr_offset=0, **kwargs): init_buf_resource(self, cc, via_reg_mr_ex, mr_access, buf_size, mr_offset) super().__init__(*args, **kwargs) def create_pd(self): create_pd(self) def create_cq(self): create_cq(self) def create_mr(self): # UD prepends a GRH on receive, so the buffer needs room for it. mr_len = self.msg_size + self.GRH_SIZE create_buf_mr(self, mr_len, self.buf_size, self.mr_offset) class BufAPITest(PyverbsAPITestCase): """Single-node API tests for ibv_alloc_buf()/ibv_reg_buf_mr().""" def get_pd(self, cc=False): """Create a PD, or a CC PD when cc is set.""" if cc: _, pd = make_cc_pd(self.ctx) return pd if device_has_cc_dma_bounce(self.ctx): raise unittest.SkipTest('Plain-memory registration is rejected on ' 'a DMA-bounce device') return PD(self.ctx) def get_buf(self, pd, size): return alloc_buf(pd, size) def get_mr(self, pd, buf, length, offset=0, via_reg_mr_ex=False): return register_buf_mr(pd, buf, length, ibv_access_flags.IBV_ACCESS_LOCAL_WRITE, offset=offset, via_reg_mr_ex=via_reg_mr_ex) def test_buf_export_dmabuf_fd_plain_pd(self): """Plain PD: no DMA-buf fd is available for ordinary memory.""" buf = self.get_buf(self.get_pd(), PAGE_SIZE) with self.assertRaises(PyverbsRDMAError) as cm: buf.export_dmabuf_fd() self.assertEqual(cm.exception.error_code, errno.ENODATA, 'Non-DMA-buf backed buffers must report ENODATA') def test_buf_export_dmabuf_fd_on_bounce_device(self): """DMA-bounce device: a buffer's DMA-buf fd is exported.""" if not device_has_cc_dma_bounce(self.ctx): raise unittest.SkipTest('Device does not report CC_DMA_BOUNCE') buf = self.get_buf(self.get_pd(cc=True), PAGE_SIZE) fd = buf.export_dmabuf_fd() os.close(fd) def check_multiple_mrs_one_buf(self, pd, via_reg_mr_ex=False): """Register two disjoint subranges of one buffer and access them.""" buf = self.get_buf(pd, 2 * PAGE_SIZE) mr1 = self.get_mr(pd, buf, PAGE_SIZE, offset=0, via_reg_mr_ex=via_reg_mr_ex) mr2 = self.get_mr(pd, buf, PAGE_SIZE, offset=PAGE_SIZE, via_reg_mr_ex=via_reg_mr_ex) self.assertNotEqual(mr1.lkey, mr2.lkey, 'MR lkeys for disjoint subranges must differ') self.assertEqual(mr1.buf, buf.addr, 'MR1 address does not match buffer start') self.assertEqual(mr2.buf, buf.addr + PAGE_SIZE, 'MR2 address does not match its subrange start') mr1.write('a' * PAGE_SIZE, PAGE_SIZE) mr2.write('b' * PAGE_SIZE, PAGE_SIZE) self.assertEqual(mr1.read(PAGE_SIZE, 0), b'a' * PAGE_SIZE, 'MR1 readback does not match written data') self.assertEqual(mr2.read(PAGE_SIZE, 0), b'b' * PAGE_SIZE, 'MR2 readback does not match written data') def test_multiple_mrs_one_buf_plain_pd_reg_buf_mr(self): """Plain PD: registered with ibv_reg_buf_mr().""" self.check_multiple_mrs_one_buf(self.get_pd()) def test_multiple_mrs_one_buf_plain_pd_reg_mr_ex(self): """Plain PD: registered with the ibv_reg_mr_ex() MASK_BUF path.""" self.check_multiple_mrs_one_buf(self.get_pd(), via_reg_mr_ex=True) def test_multiple_mrs_one_buf_cc_pd_reg_buf_mr(self): """CC PD: registered with ibv_reg_buf_mr().""" self.check_multiple_mrs_one_buf(self.get_pd(cc=True)) def check_reg_wrong_allocating_pd_fails(self, pd, other_pd): """Registering a buffer with a non-allocating PD must fail.""" buf = self.get_buf(pd, PAGE_SIZE) with self.assertRaises(PyverbsRDMAError) as cm: register_buf_mr(other_pd, buf, PAGE_SIZE, ibv_access_flags.IBV_ACCESS_LOCAL_WRITE) self.assertEqual(cm.exception.error_code, errno.EINVAL, 'Registering a buffer with a non-allocating PD ' 'must fail with EINVAL') def test_buf_reg_wrong_allocating_pd_fails_plain_pd(self): """Plain PDs: a non-allocating PD is rejected.""" self.check_reg_wrong_allocating_pd_fails(self.get_pd(), self.get_pd()) def test_buf_reg_wrong_allocating_pd_fails_cc_pd(self): """CC PDs: a non-allocating PD is rejected.""" self.check_reg_wrong_allocating_pd_fails(self.get_pd(cc=True), self.get_pd(cc=True)) def check_reg_length_exceeds_buffer_fails(self, pd): """Registering a length larger than the buffer must fail.""" buf = self.get_buf(pd, PAGE_SIZE) with self.assertRaises(PyverbsRDMAError) as cm: register_buf_mr(pd, buf, 2 * PAGE_SIZE, ibv_access_flags.IBV_ACCESS_LOCAL_WRITE) self.assertEqual(cm.exception.error_code, errno.EINVAL, 'Registering a length larger than the buffer ' 'must fail with EINVAL') def test_buf_reg_length_exceeds_buffer_fails_plain_pd(self): """Plain PD: a too-large length is rejected.""" self.check_reg_length_exceeds_buffer_fails(self.get_pd()) def test_buf_reg_length_exceeds_buffer_fails_cc_pd(self): """CC PD: a too-large length is rejected.""" self.check_reg_length_exceeds_buffer_fails(self.get_pd(cc=True)) def check_reg_offset_length_exceeds_buffer_fails(self, pd): """Registering offset + length past the buffer must fail.""" buf = self.get_buf(pd, PAGE_SIZE) with self.assertRaises(PyverbsRDMAError) as cm: register_buf_mr(pd, buf, PAGE_SIZE, ibv_access_flags.IBV_ACCESS_LOCAL_WRITE, offset=PAGE_SIZE) self.assertEqual(cm.exception.error_code, errno.EINVAL, 'Registering past the buffer end via offset ' 'must fail with EINVAL') def test_buf_reg_offset_length_exceeds_buffer_fails_plain_pd(self): """Plain PD: an out-of-range offset+length is rejected.""" self.check_reg_offset_length_exceeds_buffer_fails(self.get_pd()) def test_buf_reg_offset_length_exceeds_buffer_fails_cc_pd(self): """CC PD: an out-of-range offset+length is rejected.""" self.check_reg_offset_length_exceeds_buffer_fails(self.get_pd(cc=True)) def test_plain_mr_rejected_on_bounce_device(self): """DMA-bounce device: a plain ibv_reg_mr() is rejected.""" if not device_has_cc_dma_bounce(self.ctx): raise unittest.SkipTest('Device does not report CC_DMA_BOUNCE') with PD(self.ctx) as pd: with self.assertRaises( PyverbsRDMAError, msg='Plain ibv_reg_mr() must be rejected on a ' 'DMA-bounce device'): MR(pd, PAGE_SIZE, ibv_access_flags.IBV_ACCESS_LOCAL_WRITE) class BufTrafficTest(RDMATestCase): """RC/UD traffic over ibv_alloc_buf() data buffers.""" def test_buf_rc_send_cc_pd_reg_buf_mr(self): """CC PD: RC send/recv over a buffer MR.""" self.create_players(BufRC, cc=True) u.traffic(**self.traffic_args, is_cq_ex=True) def test_buf_rc_send_large_msg_cc_pd_reg_buf_mr(self): """CC PD: RC send/recv with a multi-page buffer MR.""" self.create_players(BufRC, cc=True, msg_size=16384) u.traffic(**self.traffic_args, is_cq_ex=True) def test_buf_rc_send_mr_subrange_cc_pd_reg_buf_mr(self): """CC PD: RC send/recv over a buffer-subrange MR.""" self.create_players(BufRC, cc=True, mr_offset=PAGE_SIZE, buf_size=PAGE_SIZE + 16384, msg_size=8192) u.traffic(**self.traffic_args, is_cq_ex=True) def test_buf_rc_rdma_write_imm_cc_pd_reg_buf_mr(self): """CC PD: RC RDMA write-with-immediate into a buffer MR.""" access = (ibv_access_flags.IBV_ACCESS_LOCAL_WRITE | ibv_access_flags.IBV_ACCESS_REMOTE_WRITE) self.create_players(BufRC, cc=True, mr_access=access) u.traffic(**self.traffic_args, is_cq_ex=True, send_op=ibv_wr_opcode.IBV_WR_RDMA_WRITE_WITH_IMM) def test_buf_rc_rdma_read_cc_pd_reg_buf_mr(self): """CC PD: RC RDMA read from a buffer MR.""" access = (ibv_access_flags.IBV_ACCESS_LOCAL_WRITE | ibv_access_flags.IBV_ACCESS_REMOTE_READ) self.create_players(BufRC, cc=True, mr_access=access) u.rdma_traffic(**self.traffic_args, is_cq_ex=True, send_op=ibv_wr_opcode.IBV_WR_RDMA_READ) def test_buf_rc_atomic_fetch_add_cc_pd_reg_buf_mr(self): """CC PD: RC atomic fetch&add on a buffer MR.""" with d.Context(name=self.dev_name) as ctx: atomic_caps = ctx.query_device().atomic_caps if atomic_caps == ibv_atomic_cap.IBV_ATOMIC_NONE: raise unittest.SkipTest('Atomic operations are not supported') access = (ibv_access_flags.IBV_ACCESS_LOCAL_WRITE | ibv_access_flags.IBV_ACCESS_REMOTE_WRITE | ibv_access_flags.IBV_ACCESS_REMOTE_ATOMIC) self.create_players(BufRC, cc=True, mr_access=access, msg_size=8) u.atomic_traffic(**self.traffic_args, is_cq_ex=True, send_op=ibv_wr_opcode.IBV_WR_ATOMIC_FETCH_AND_ADD) def test_buf_ud_send_cc_pd_reg_buf_mr(self): """CC PD: UD send/recv over a buffer MR.""" self.create_players(BufUD, cc=True) u.traffic(**self.traffic_args, is_cq_ex=True) def test_buf_rc_send_plain_pd_reg_buf_mr(self): """Plain PD: RC send/recv over a buffer MR.""" self.create_players(BufRC, cc=False) u.traffic(**self.traffic_args, is_cq_ex=True) def test_buf_rc_send_cc_pd_reg_mr_ex(self): """CC PD: RC send/recv via ibv_reg_mr_ex().""" self.create_players(BufRC, cc=True, via_reg_mr_ex=True) u.traffic(**self.traffic_args, is_cq_ex=True)