blob: e697c65e0ebbedc3837fb67b65210b20acca1f9c [file]
# SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB)
# Copyright (c) 2025 Nvidia Inc. All rights reserved. See COPYING file
#cython: language_level=3
from libc.stdint cimport uint8_t, uint16_t, uint64_t
from libc.stdlib cimport free, malloc
from cpython.bytes cimport PyBytes_FromStringAndSize
from pyverbs.providers.mlx5.mlx5dv cimport Mlx5DevxObj
cimport pyverbs.providers.mlx5.libmlx5 as dv
from pyverbs.pyverbs_error import PyverbsError
from pyverbs.base import PyverbsRDMAErrno
from pyverbs.device cimport Context
cdef extern from 'unistd.h':
ssize_t read(int fd, void *buf, size_t count)
cdef extern from 'sys/eventfd.h':
int eventfd(unsigned int initval, int flags)
DEFAULT_EQE_SIZE = 1024
cdef class EventChannel(PyverbsCM):
"""
EventChannel class represents Devx Event Channel
(mlx5dv_devx_event_channel rdma-core struct).
"""
def __init__(self, Context context not None, flags=0):
"""
EventChannel class represents Devx Event Channel
(mlx5dv_devx_event_channel rdma-core struct).
:param context: Context to create the schedule resources on.
:param flags: Create flags of the event channel.
"""
super().__init__()
self.ec = dv.mlx5dv_devx_create_event_channel(context.context, flags)
if self.ec == NULL:
raise PyverbsRDMAErrno('Failed to create a devx event channel')
context.event_channels.add(self)
def subscribe(self, events_num, cookie=None, Mlx5DevxObj obj=None,
EventFD fd=None):
"""
Subscribe to the event channel.
:param self: The event channel instance to subscribe to
:param events_num: An array that contains event type numbers to
subscribe. If fd is given, it can be either a number or an array with
one number (since subscription with fd supports only one event number)
:param cookie: A 64b number that can be used as an ID for the
subscription
:param obj: The object that's related to subscribed event (None in case
of an unaffiliated event)
:param fd: EventFD. If given, the subscription will be done on the
eventfd using the event_fd subscription API
"""
if fd:
if cookie:
self.logger.warning('Cookie is ignored in fd subscription')
if hasattr(events_num, '__len__') and len(events_num) > 1:
raise PyverbsError('Only one event must be provided in '
'events_num in fd subscription')
event_num = events_num[0] if hasattr(events_num, '__len__') else \
events_num
self._subscribe_fd(event_num, fd, obj)
else:
if cookie is None:
raise PyverbsError('Cookie must be provided with subscription')
self._subscribe(events_num, cookie, obj)
def _subscribe(self, events_num, cookie, Mlx5DevxObj obj=None):
cdef dv.mlx5dv_devx_obj *devx_obj = obj.obj if obj else NULL
# Size of each event number is 16b which is 2 Bytes
size = len(events_num) * 2
cdef uint16_t *events_n = <uint16_t*>malloc(size)
if not events_n:
raise PyverbsRDMAErrno('Couldn\'t allocate array for events_num')
for i in range(len(events_num)):
events_n[i] = events_num[i]
if dv.mlx5dv_devx_subscribe_devx_event(self.ec, devx_obj, size,
events_n, cookie):
raise PyverbsRDMAErrno('Failed to subscribe to devx event channel')
free(events_n)
def _subscribe_fd(self, event_num, EventFD fd, Mlx5DevxObj obj=None):
cdef dv.mlx5dv_devx_obj *devx_obj = obj.obj if obj else NULL
if dv.mlx5dv_devx_subscribe_devx_event_fd(self.ec, (<EventFD>fd).fd,
devx_obj, event_num):
raise PyverbsRDMAErrno('Failed to subscribe to devx event channel fd')
def get_event(self, event_resp_len=DEFAULT_EQE_SIZE, EventFD fd=None):
"""
Gets an event (if any) that the user subscribed to this channel.
If no events were generated this function will block until the arrival
of an event.
:param event_resp_len: The size in bytes of the allocated event
response. (1024B by default, which satisfies all current event types)
:param fd: EventFD. If given, will use read function directly on the
eventfd to read any gotten events
:return: EventHeader or the read value from the eventfd in case of fd
"""
if fd:
return self._get_event_fd(fd)
return self._get_event(event_resp_len)
def _get_event(self, event_resp_len=DEFAULT_EQE_SIZE):
cdef dv.mlx5dv_devx_async_event_hdr *event_data = \
<dv.mlx5dv_devx_async_event_hdr*>malloc(event_resp_len)
if not event_data:
raise PyverbsRDMAErrno('Couldn\'t allocate array for events_data')
bytes_read = dv.mlx5dv_devx_get_event(self.ec, event_data,
event_resp_len)
if bytes_read < 0:
free(event_data)
raise PyverbsRDMAErrno('Failed to get devx event')
data_bytes = PyBytes_FromStringAndSize(<char*>event_data.out_data,
bytes_read)
event_header = EventHeader(event_data.cookie, data_bytes)
free(event_data)
return event_header
def _get_event_fd(self, fd):
cdef uint64_t buff
rc = read(fd.fd, &buff, sizeof(buff))
if rc < 0:
raise PyverbsRDMAErrno('Failed to get event from FD')
return buff
def __dealloc__(self):
self.close()
cpdef close(self):
if self.logger:
self.logger.debug('Closing EventChannel')
if self.ec != NULL:
dv.mlx5dv_devx_destroy_event_channel(self.ec)
self.ec = NULL
@property
def fd(self):
return self.ec.fd
cdef class EventHeader(PyverbsObject):
"""
Event header that contains the event cookie and the event data.
An instance of this class is returned to the Python user by
EventChannel.get_event() function to represent the returned event in a
friendly manner.
"""
def __init__(self, cookie, data):
"""
Create EventHeader object
:param cookie: Cookie retrieved from the event (used upon subscription)
:param data: Event data
"""
super().__init__()
self.cookie = cookie
self.data = data
@property
def cookie(self):
return self.cookie
@property
def data(self):
return self.data
cdef class EventFD(PyverbsObject):
"""
Represent eventfd, a file descriptor for event notification.
"""
def __init__(self, initval=0, flags=0):
"""
Create an EventFD.
:param initval: The eventfd counter initial value
:param flags: The eventfd flags
"""
super().__init__()
event_fd = eventfd(initval, flags)
if event_fd < 0:
raise PyverbsRDMAErrno('Failed to create a event fd')
self.fd = event_fd
@property
def fd(self):
return self.fd