| # SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB) |
| # Copyright (c) 2020 NVIDIA Corporation . All rights reserved. See COPYING file |
| |
| from threading import Thread |
| from queue import Queue |
| import unittest |
| import struct |
| import errno |
| |
| from pyverbs.providers.mlx5.mlx5dv import Mlx5Context, Mlx5DVContextAttr, \ |
| Mlx5DmOpAddr |
| from pyverbs.pyverbs_error import PyverbsRDMAError, PyverbsUserError |
| from tests.mlx5_base import Mlx5PyverbsAPITestCase |
| from pyverbs.providers.mlx5.mlx5_enums import mlx5dv_context_attr_flags |
| import pyverbs.device as d |
| |
| |
| MEMIC_ATOMIC_INCREMENT = 0x0 |
| MEMIC_ATOMIC_TEST_AND_SET = 0x1 |
| |
| MLX5_CMD_OP_QUERY_HCA_CAP = 0x100 |
| MLX5_CMD_MOD_DEVICE_MEMORY_CAP = 0xF |
| MLX5_CMD_OP_QUERY_HCA_CAP_OUT_LEN = 0x1010 |
| |
| |
| def requires_memic_atomic_support(func): |
| def wrapper(instance): |
| cmd_in = struct.pack('!HIH8s', MLX5_CMD_OP_QUERY_HCA_CAP, 0, |
| MLX5_CMD_MOD_DEVICE_MEMORY_CAP << 1 | 0x1, |
| bytes(8)) |
| cmd_out = Mlx5Context.devx_general_cmd(instance.ctx, cmd_in, |
| MLX5_CMD_OP_QUERY_HCA_CAP_OUT_LEN) |
| cmd_view = memoryview(cmd_out) |
| status = cmd_view[0] |
| if status: |
| raise PyverbsRDMAError('Query Device Memory CAPs failed with status' |
| f' ({status})') |
| |
| memic_op_support = int.from_bytes(cmd_view[80:84], 'big') |
| increment_size_sup = cmd_view[20] |
| test_and_set_size_sup = cmd_view[22] |
| # Verify that MEMIC atomic operations (both increment and test_and_set) |
| # are supported with write/read size of 1 Byte. |
| if memic_op_support & 0x3 != 0x3: |
| raise unittest.SkipTest('MEMIC atomic operations are not supported') |
| if not increment_size_sup & test_and_set_size_sup & 0x1: |
| raise unittest.SkipTest( |
| 'MEMIC atomic operations are not supported with 1 Bytes read/write sizes') |
| return func(instance) |
| return wrapper |
| |
| |
| class Mlx5DmOpAddresses(Mlx5PyverbsAPITestCase): |
| def setUp(self): |
| super().setUp() |
| self.dm_size = int(self.attr_ex.max_dm_size / 2) |
| |
| def create_context(self): |
| try: |
| attr = Mlx5DVContextAttr(mlx5dv_context_attr_flags.MLX5DV_CONTEXT_FLAGS_DEVX) |
| self.ctx = 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 _write_to_op_addr(self): |
| try: |
| inc_addr = Mlx5DmOpAddr(self.dm, MEMIC_ATOMIC_INCREMENT) |
| except PyverbsRDMAError as ex: |
| if ex.error_code in [errno.EOPNOTSUPP, errno.EPROTONOSUPPORT]: |
| self.skip_queue.put(unittest.SkipTest( |
| 'MEMIC_ATOMIC_INCREMENT op is not supported')) |
| return |
| raise ex |
| inc_addr.write(b'\x01') |
| inc_addr.unmap(self.dm_size) |
| |
| def _read_from_op_addr(self): |
| try: |
| test_and_set_addr = Mlx5DmOpAddr(self.dm, MEMIC_ATOMIC_TEST_AND_SET) |
| except PyverbsRDMAError as ex: |
| if ex.error_code in [errno.EOPNOTSUPP, errno.EPROTONOSUPPORT]: |
| self.skip_queue.put(unittest.SkipTest( |
| 'MEMIC_ATOMIC_TEST_AND_SET op is not supported')) |
| return |
| raise ex |
| val = test_and_set_addr.read(1) |
| test_and_set_addr.unmap(self.dm_size) |
| return val |
| |
| @requires_memic_atomic_support |
| def test_dm_atomic_ops(self): |
| """ |
| Tests "increment" and "test_and_set" MEMIC atomic operations. |
| The test does two increments to the same buffer data and verifies the |
| values using test_and_set. |
| Then verifies that the latter op sets the buffer as expected. |
| """ |
| with d.DM(self.ctx, d.AllocDmAttr(length=self.dm_size)) as dm: |
| # Set DM buffer to 0 |
| dm.copy_to_dm(0, bytes(self.dm_size), self.dm_size) |
| try: |
| inc_addr = Mlx5DmOpAddr(dm, MEMIC_ATOMIC_INCREMENT) |
| test_and_set_addr = Mlx5DmOpAddr(dm, MEMIC_ATOMIC_TEST_AND_SET) |
| except PyverbsRDMAError as ex: |
| if ex.error_code in [errno.EOPNOTSUPP, errno.EPROTONOSUPPORT]: |
| raise unittest.SkipTest('MEMIC atomic operations are not supported') |
| raise ex |
| inc_addr.write(b'\x01') |
| inc_addr.write(b'\x01') |
| # Now we should read 0x02 and the memory set to 0x1 |
| val = int.from_bytes(test_and_set_addr.read(1), 'big') |
| self.assertEqual(val, 2) |
| # Verify that TEST_AND_SET set the memory to 0x1 |
| val = int.from_bytes(test_and_set_addr.read(1), 'big') |
| self.assertEqual(val, 1) |
| inc_addr.unmap(self.dm_size) |
| test_and_set_addr.unmap(self.dm_size) |
| |
| @requires_memic_atomic_support |
| def test_parallel_dm_atomic_ops(self): |
| """ |
| Runs multiple threads that do test_and_set operation, followed by |
| multiple threads that do increments of +1, to the same DM buffer. |
| Then verifies that the buffer data was incremented as expected. |
| """ |
| threads = [] |
| num_threads = 10 |
| self.skip_queue = Queue() |
| with d.DM(self.ctx, d.AllocDmAttr(length=self.dm_size)) as self.dm: |
| for _ in range(num_threads): |
| threads.append(Thread(target=self._read_from_op_addr)) |
| threads[-1].start() |
| for thread in threads: |
| thread.join() |
| |
| threads = [] |
| for _ in range(num_threads): |
| threads.append(Thread(target=self._write_to_op_addr)) |
| threads[-1].start() |
| for thread in threads: |
| thread.join() |
| |
| if not self.skip_queue.empty(): |
| raise self.skip_queue.get() |
| |
| val = int.from_bytes(self._read_from_op_addr(), 'big') |
| self.assertEqual(val, num_threads + 1, |
| f'Read value is ({val}) is different than expected ({num_threads+1})' ) |