# SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB)
# Copyright (c) 2021 Nvidia, Inc. All rights reserved. See COPYING file

#cython: language_level=3

from cpython.mem cimport PyMem_Malloc, PyMem_Free
from libc.string cimport strcpy
import weakref

from pyverbs.pyverbs_error import PyverbsRDMAError
cimport pyverbs.providers.mlx5.libmlx5 as dv
from pyverbs.base import PyverbsRDMAErrno
from pyverbs.base cimport close_weakrefs
from pyverbs.device cimport Context
cimport pyverbs.libibverbs as v


cdef class Mlx5VfioAttr(PyverbsObject):
    """
    Mlx5VfioAttr class, represents mlx5dv_vfio_context_attr C struct.
    """
    def __init__(self, pci_name, flags=0, comp_mask=0):
        self.pci_name = pci_name
        self.attr.flags = flags
        self.attr.comp_mask = comp_mask

    def __dealloc__(self):
        if self.attr.pci_name != NULL:
            PyMem_Free(<void*>self.attr.pci_name)
            self.attr.pci_name = NULL

    @property
    def flags(self):
        return self.attr.flags
    @flags.setter
    def flags(self, val):
        self.attr.flags = val

    @property
    def comp_mask(self):
        return self.attr.comp_mask
    @comp_mask.setter
    def comp_mask(self, val):
        self.attr.comp_mask = val

    @property
    def pci_name(self):
        return self.attr.pci_name[:]
    @pci_name.setter
    def pci_name(self, val):
        if self.attr.pci_name != NULL:
            PyMem_Free(<void*>self.attr.pci_name)
        pci_name_bytes = val.encode()
        self.attr.pci_name = <char*>PyMem_Malloc(len(pci_name_bytes))
        strcpy(<char*>self.attr.pci_name, pci_name_bytes)


cdef class Mlx5VfioContext(Mlx5Context):
    """
    Mlx5VfioContext class is used to easily initialize and open a context over
    a mlx5 vfio device.
    It is initialized based on the passed mlx5 vfio attributes (Mlx5VfioAttr),
    by getting the relevant vfio device and opening it (creating a context).
    """
    def __init__(self, Mlx5VfioAttr attr):
        super(Context, self).__init__()
        cdef v.ibv_device **dev_list

        self.name = attr.pci_name
        self.pds = weakref.WeakSet()
        self.devx_umems = weakref.WeakSet()
        self.devx_objs = weakref.WeakSet()
        self.uars = weakref.WeakSet()
        self.devx_eqs = weakref.WeakSet()

        dev_list = dv.mlx5dv_get_vfio_device_list(&attr.attr)
        if dev_list == NULL:
            raise PyverbsRDMAErrno('Failed to get VFIO device list')
        self.device = dev_list[0]
        if self.device == NULL:
            raise PyverbsRDMAError('Failed to get VFIO device')
        try:
            self.context = v.ibv_open_device(self.device)
            if self.context == NULL:
                raise PyverbsRDMAErrno('Failed to open mlx5 VFIO device '
                                       f'({self.device.name.decode()})')
        finally:
            v.ibv_free_device_list(dev_list)

    def get_events_fd(self):
        """
        Gets the file descriptor to manage driver events.
        :return: The file descriptor to be used for managing driver events.
        """
        fd = dv.mlx5dv_vfio_get_events_fd(self.context)
        if fd < 0:
            raise PyverbsRDMAError('Failed to get VFIO events FD', -fd)
        return fd

    def process_events(self):
        """
        Process events on the vfio device.
        This method should run from application thread to maintain device events.
        :return: None
        """
        rc = dv.mlx5dv_vfio_process_events(self.context)
        if rc:
            raise PyverbsRDMAError('VFIO process events failed', rc)

    cpdef close(self):
        if self.context != NULL:
            if self.logger:
                self.logger.debug('Closing Mlx5VfioContext')
            close_weakrefs([self.pds, self.devx_objs, self.devx_umems, self.uars, self.devx_eqs])
            rc = v.ibv_close_device(self.context)
            if rc != 0:
                raise PyverbsRDMAErrno(f'Failed to close device {self.name}')
            self.context = NULL
