blob: cc0e42023c53fa24e026f9d9c2d890e1d769d522 [file] [edit]
# SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB)
# Copyright (c) 2021 Nvidia Inc. All rights reserved. See COPYING file
"""
Test module for mlx5 DevX.
"""
import resource
import unittest
import errno
import os
from tests.mlx5_base import Mlx5DevxRcResources, Mlx5DevxTrafficBase
from tests.test_buf import alloc_buf, device_has_cc_dma_bounce, \
make_cc_pd, register_buf_mr
from pyverbs.providers.mlx5.mlx5dv import Mlx5Context, Mlx5DVContextAttr, \
Mlx5DevxCmdComp, Mlx5DevxObj, Mlx5UMEM
from pyverbs.providers.mlx5.mlx5_enums import mlx5dv_context_attr_flags
from pyverbs.pyverbs_error import PyverbsRDMAError, PyverbsUserError
import pyverbs.mem_alloc as mem
from pyverbs.mr import MR
from pyverbs.libibverbs_enums import ibv_access_flags, ibv_odp_transport_cap_bits
import tests.utils as u
class BufDevxRcResources(Mlx5DevxRcResources):
"""
DevX RC resources for a Confidential Computing (CoCo) guest: every DevX
UMEM and the data MR live in shared/unprotected memory allocated with
Buf on a CC parent domain. Each UMEM is registered through the
DevX dmabuf path using an FD exported from its Buf, so all NIC-DMA'd memory
is shared as a DMA-bounce device requires.
"""
def __init__(self, dev_name, ib_port, gid_index, msg_size=1024,
activate_port_state=False, send_dbr_mode=0):
self.bufs = []
self.dmabuf_fds = []
self.base_pd = None
super().__init__(dev_name, ib_port, gid_index, msg_size,
activate_port_state, send_dbr_mode)
def create_pd(self):
"""Build a CC parent domain and derive the DevX pdn from it."""
from pyverbs.providers.mlx5.mlx5dv_objects import Mlx5DvObj
from pyverbs.providers.mlx5.mlx5_enums import mlx5dv_obj_type
if not device_has_cc_dma_bounce(self.ctx):
raise unittest.SkipTest('Device is not a CC DMA-bounce device')
self.base_pd, self.pd = make_cc_pd(self.ctx)
self.dv_pd = Mlx5DvObj(mlx5dv_obj_type.MLX5DV_OBJ_PD, pd=self.pd).dvpd
def create_mr(self):
"""Register the data buffer as a shared Buf MR on the CC PD."""
access = ibv_access_flags.IBV_ACCESS_REMOTE_WRITE | \
ibv_access_flags.IBV_ACCESS_LOCAL_WRITE | \
ibv_access_flags.IBV_ACCESS_REMOTE_READ
buf = alloc_buf(self.pd, self.msg_size)
self.mr = register_buf_mr(self.pd, buf, self.msg_size, access)
def create_umem(self, size, access=ibv_access_flags.IBV_ACCESS_LOCAL_WRITE,
alignment=resource.getpagesize()):
"""Return a DevX UMEM backed by a Buf exported as a dmabuf FD."""
from pyverbs.providers.mlx5.mlx5_enums import MLX5DV_UMEM_MASK_DMABUF
page_size = resource.getpagesize()
alloc_size = max(size, page_size)
buf = alloc_buf(self.pd, alloc_size)
mem.write(buf.addr, bytes(alloc_size), alloc_size) # Zero-fill the buffer
fd = self.export_buf_dmabuf_fd(buf)
umem = Mlx5UMEM(self.ctx, alloc_size, addr=0, alignment=alignment, access=access,
pgsz_bitmap=page_size, comp_mask=MLX5DV_UMEM_MASK_DMABUF, dmabuf_fd=fd)
umem.umem_addr = buf.addr
self.bufs.append(buf)
self.dmabuf_fds.append(fd)
return umem
def export_buf_dmabuf_fd(self, buf):
"""Export the Buf's dmabuf FD, skipping when it is not dmabuf-backed."""
try:
return buf.export_dmabuf_fd()
except PyverbsRDMAError as ex:
if ex.error_code == errno.ENODATA:
raise unittest.SkipTest('Buf is not dmabuf-backed')
raise
def close_resources(self):
"""Close the exported dmabuf FDs"""
super().close_resources()
for fd in self.dmabuf_fds:
os.close(fd)
self.dmabuf_fds = []
class Mlx5DevxRcOdpRes(Mlx5DevxRcResources):
@u.requires_odpv2
def create_mr(self):
self.with_odp = True
self.user_addr = mem.mmap(length=self.msg_size,
flags=mem.MAP_ANONYMOUS_ | mem.MAP_PRIVATE_)
access = ibv_access_flags.IBV_ACCESS_LOCAL_WRITE | ibv_access_flags.IBV_ACCESS_REMOTE_READ | \
ibv_access_flags.IBV_ACCESS_ON_DEMAND
self.mr = MR(self.pd, self.msg_size, access, self.user_addr)
class Mlx5DevxRcTrafficTest(Mlx5DevxTrafficBase):
"""
Test various functionality of mlx5 DevX objects
"""
def test_devx_rc_qp_send_imm_traffic(self):
"""
Creates two DevX RC QPs and modifies them to RTS state.
Then does SEND_IMM traffic.
"""
self.create_players(Mlx5DevxRcResources)
# Send traffic
self.send_imm_traffic()
def test_devx_rc_qp_send_imm_buf_umem_traffic(self):
"""
Run DevX RC SEND_IMM traffic where all NIC memory (QP, CQ, doorbell
UMEMs and the data MR) is shared CoCo memory: buffers on a CC parent
domain, registered as UMEMs via their dmabuf FD.
"""
self.create_players(BufDevxRcResources)
self.send_imm_traffic()
def test_devx_rc_qp_send_imm_doorbell_less_traffic(self):
"""
Creates two DevX RC QPs with dbr less ext and modifies them to RTS state.
Then does SEND_IMM traffic.
"""
from tests.mlx5_prm_structs import SendDbrMode
self.create_players(Mlx5DevxRcResources, send_dbr_mode=SendDbrMode.NO_DBR_EXT)
# Send traffic
self.send_imm_traffic()
@u.requires_odp('rc', ibv_odp_transport_cap_bits.IBV_ODP_SUPPORT_SEND | ibv_odp_transport_cap_bits.IBV_ODP_SUPPORT_RECV)
def test_devx_rc_qp_odp_traffic(self):
"""
Creates two DevX RC QPs using ODP enabled MKeys.
Then does SEND_IMM traffic.
"""
self.create_players(Mlx5DevxRcOdpRes)
# Send traffic
self.send_imm_traffic()
class Mlx5DevxApiTest(Mlx5DevxTrafficBase):
def setUp(self):
super().setUp()
self.devx_res = None
def tearDown(self):
super().tearDown()
if self.devx_res:
self.devx_res.close_resources()
def _create_devx_ctx(self):
try:
attr = Mlx5DVContextAttr(mlx5dv_context_attr_flags.MLX5DV_CONTEXT_FLAGS_DEVX)
return Mlx5Context(attr, self.dev_name)
except PyverbsUserError as ex:
raise unittest.SkipTest(f'Could not open mlx5 context ({ex})')
except PyverbsRDMAError:
raise unittest.SkipTest('Opening mlx5 DevX context is not supported')
def test_devx_async_query(self):
"""
Test DevX Async Query API.
Creating a DevX QP and query it using DevX async query.
"""
self.devx_res = Mlx5DevxRcResources(**self.dev_info)
self.cmd_comp = Mlx5DevxCmdComp(self.devx_res.ctx)
from tests.mlx5_prm_structs import QueryQpIn, QueryQpOut
query_qp_in = QueryQpIn(qpn=self.devx_res.qpn)
qp_wr_id = 100
try:
self.devx_res.qp.query_async(query_qp_in, len(QueryQpOut()), wr_id=qp_wr_id,
cmd_comp=self.cmd_comp)
wr_id, out_data = self.cmd_comp.get_async_cmd_comp()
except PyverbsRDMAError as ex:
if ex.error_code == errno.EOPNOTSUPP:
raise unittest.SkipTest('Async command completion is not supported')
raise ex
query_qp_out = QueryQpOut(out_data)
self.assertTrue(query_qp_out.status == 0,
'Query Devx QP by Async Query API failed with non-zero status: '
f'{query_qp_out.status}')
self.assertTrue(wr_id == qp_wr_id,
f'Mismatched work request ID. Expected: {qp_wr_id}, Actual: {wr_id}')
self.assertTrue(query_qp_out.sw_qpc.log_rq_size == self.devx_res.log_rq_size,
f'Mismatched RQ size. Expected: {self.devx_res.log_rq_size}, '
f'Actual: {query_qp_out.sw_qpc.log_rq_size}')
@u.skip_unsupported
def test_umem_export_import(self):
"""
Create UMEM, export, import, then close both.
"""
with self._create_devx_ctx() as ctx:
with Mlx5UMEM(ctx, size=resource.getpagesize()) as umem:
original_id = umem.umem_id
data = umem.export()
with Mlx5UMEM.import_umem(ctx, data) as imported_umem:
self.assertEqual(imported_umem.umem_id, original_id,
f'Imported UMEM ID {imported_umem.umem_id}'
f' does not match original {original_id}')
@u.skip_unsupported
def test_devx_obj_export_import(self):
"""
Create a DevX flow counter object, export to opaque buffer,
import from buffer, then close both.
"""
from tests.mlx5_prm_structs import AllocFlowCounterIn, AllocFlowCounterOut
with self._create_devx_ctx() as ctx:
with Mlx5DevxObj(ctx, AllocFlowCounterIn(),
len(AllocFlowCounterOut())) as counter:
data = counter.export()
with Mlx5DevxObj.import_obj(ctx, data) as imported_counter:
self.assertIsNotNone(imported_counter,
'Failed to import DevX object '
'from exported data')