blob: b75b5929c358e67bd8a736e74cece1436956eed1 [file] [edit]
# SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB)
# Copyright 2026 Advanced Micro Devices, Inc. All rights reserved.
import unittest
import datetime
import time
import os
import errno
from pyverbs.libibverbs_enums import ibv_qp_type, ibv_wc_status, ibv_wr_opcode, ibv_create_cq_wc_flags, \
IBV_WC_STANDARD_FLAGS
from tests.base import RCResources, RDMATestCase
from pyverbs.pyverbs_error import PyverbsRDMAError
from pyverbs.cq import CqInitAttrEx, CQEX
from pyverbs.qp import QPInitAttr
from pyverbs.cq import PollCqAttr
import tests.utils as u
GIGA = 1000000000
CLOCKFD = 3
CLOCKID_TYPE_BITS = 3
def timestamp_res_cls(base_class):
"""
This is a factory function which creates a class that inherits base_class of
any BaseResources type.
:param base_class: The base resources class to inherit from.
:return: TimeStampRes class.
"""
class TimeStampRes(base_class):
def __init__(self, dev_name, ib_port, gid_index, qp_type, send_ts=None,
recv_ts=None):
self.qp_type = qp_type
self.send_ts = send_ts
self.recv_ts = recv_ts
self.timestamp = None
self.scq = None
self.rcq = None
self.phc_file = None
self.open_phc_dev(dev_name)
super().__init__(dev_name=dev_name, ib_port=ib_port, gid_index=gid_index)
def close_files(self):
if self.phc_file is not None:
self.phc_file.close()
self.phc_file = None
def open_phc_dev(self, dev_name):
try:
phc_name = os.listdir(f'/sys/class/infiniband/{dev_name}/device/ptp')[0]
self.phc_file = open(f'/dev/{phc_name}', 'rb')
self.phc_clkid = (~self.phc_file.fileno() << CLOCKID_TYPE_BITS) | CLOCKFD
except (FileNotFoundError, OSError, IndexError):
raise unittest.SkipTest('No PHC or failed to open')
def read_phc(self):
return time.clock_gettime(self.phc_clkid)
def create_cq(self):
self.scq = self._create_ex_cq(self.send_ts)
self.rcq = self._create_ex_cq(self.recv_ts)
def _create_ex_cq(self, timestamp=None):
"""
Create an Extended CQ.
:param timestamp: If set, the timestamp type to use.
"""
wc_flags = IBV_WC_STANDARD_FLAGS
if timestamp:
wc_flags |= timestamp
cia = CqInitAttrEx(cqe=self.num_msgs, wc_flags=wc_flags)
try:
cq = CQEX(self.ctx, cia)
except PyverbsRDMAError as ex:
if ex.error_code == errno.EOPNOTSUPP:
raise unittest.SkipTest('Create Extended CQ is not supported')
raise ex
return cq
def create_qp_init_attr(self):
return QPInitAttr(qp_type=self.qp_type, scq=self.scq,
rcq=self.rcq, srq=self.srq, cap=self.create_qp_cap())
return TimeStampRes
class TimeStampTest(RDMATestCase):
"""
Test various types of timestamping formats.
"""
def setUp(self):
super().setUp()
self.send_ts = None
self.recv_ts = None
self.qp_type = None
def tearDown(self):
for player in [getattr(self, 'client', None), getattr(self, 'server', None)]:
if player is not None and hasattr(player, 'close_files'):
player.close_files()
super().tearDown()
@property
def resource_arg(self):
return {'send_ts': self.send_ts, 'recv_ts': self.recv_ts,
'qp_type': self.qp_type}
def test_timestamp_free_running_rc_traffic(self):
"""
Test free-running timestamp on RC traffic.
The timestamp is returned as a raw HW PHC tick value, so this test
only verifies that polling succeeds; value validation is done by the
real-time variant.
"""
self.qp_type = ibv_qp_type.IBV_QPT_RC
self.send_ts = self.recv_ts = ibv_create_cq_wc_flags.IBV_WC_EX_WITH_COMPLETION_TIMESTAMP
self.create_players(timestamp_res_cls(RCResources), **self.resource_arg)
self.ts_traffic()
def test_timestamp_real_time_rc_traffic(self):
"""
Test real-time timestamp on RC traffic.
The timestamp is returned in nanoseconds and validated against the
live PHC read from /dev/ptpN.
"""
self.qp_type = ibv_qp_type.IBV_QPT_RC
self.send_ts = self.recv_ts = ibv_create_cq_wc_flags.IBV_WC_EX_WITH_COMPLETION_TIMESTAMP_WALLCLOCK
self.create_players(timestamp_res_cls(RCResources), **self.resource_arg)
self.ts_traffic()
self.verify_ts(self.client.timestamp / GIGA, self.client.read_phc())
self.verify_ts(self.server.timestamp / GIGA, self.server.read_phc())
def verify_ts(self, timestamp, phc_now):
"""
Verify that the timestamp is in the past one second
"""
if self.config['verbosity']:
print(f'timestamp {timestamp} current {phc_now} difference {phc_now - timestamp}')
if timestamp > phc_now:
raise PyverbsRDMAError(f'Completion timestamp is in the future: {timestamp} > {phc_now}')
if timestamp < phc_now - 1:
raise PyverbsRDMAError(f'Completion timestamp is too far in the past: {timestamp} < {phc_now - 1}')
@staticmethod
def poll_cq_ex_ts(cqex, ts_type=None):
"""
Poll completion from the extended CQ.
:param cqex: CQEX to poll from
:param ts_type: If set, read the CQE timestamp in this format
:return: The CQE timestamp if it requested.
"""
polling_timeout = 10
start = datetime.datetime.now()
ts = 0
poll_attr = PollCqAttr()
ret = cqex.start_poll(poll_attr)
while ret == 2 and (datetime.datetime.now() - start).seconds < polling_timeout:
ret = cqex.start_poll(poll_attr)
if ret == 2:
raise PyverbsRDMAError('Failed to poll CQEX - Got timeout')
if ret != 0:
raise PyverbsRDMAError('Failed to poll CQEX')
if cqex.status != ibv_wc_status.IBV_WC_SUCCESS:
raise PyverbsRDMAError(f'Completion status is {cqex.status}')
if ts_type == ibv_create_cq_wc_flags.IBV_WC_EX_WITH_COMPLETION_TIMESTAMP:
ts = cqex.read_timestamp()
if ts_type == ibv_create_cq_wc_flags.IBV_WC_EX_WITH_COMPLETION_TIMESTAMP_WALLCLOCK:
ts = cqex.read_completion_wallclock_ns()
cqex.end_poll()
return ts
def ts_traffic(self):
"""
Run RDMA traffic and read the completions timestamps.
"""
s_recv_wr = u.get_recv_wr(self.server)
u.post_recv(self.server, s_recv_wr)
c_send_wr, _ = u.get_send_elements(self.client, False)
u.send(self.client, c_send_wr, ibv_wr_opcode.IBV_WR_SEND, False, 0)
self.client.timestamp = self.poll_cq_ex_ts(self.client.scq, ts_type=self.send_ts)
self.server.timestamp = self.poll_cq_ex_ts(self.server.rcq, ts_type=self.recv_ts)