blob: 706653b7c56aba14615c60b1de76be26f3b09be2 [file] [log] [blame]
# SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB)
# Copyright (c) 2019, Mellanox Technologies. All rights reserved. See COPYING file
# Copyright (c) 2020, Intel Corporation. All rights reserved. See COPYING file
import resource
import logging
import weakref
from posix.mman cimport mmap, munmap, MAP_PRIVATE, PROT_READ, PROT_WRITE, \
MAP_ANONYMOUS, MAP_HUGETLB, MAP_SHARED
from pyverbs.pyverbs_error import PyverbsError, PyverbsRDMAError, \
PyverbsUserError
from libc.stdint cimport uintptr_t, SIZE_MAX
from pyverbs.utils import rereg_error_to_str
from pyverbs.base import PyverbsRDMAErrno
from posix.stdlib cimport posix_memalign
from libc.string cimport memcpy, memset
cimport pyverbs.libibverbs_enums as e
from pyverbs.device cimport DM
from libc.stdlib cimport free, malloc
from pyverbs.device cimport Context
from .cmid cimport CMID
from .pd cimport PD
from .dmabuf cimport DmaBuf
from pyverbs.base cimport close_weakrefs
cdef extern from 'sys/mman.h':
cdef void* MAP_FAILED
HUGE_PAGE_SIZE = 0x200000
cdef class MR(PyverbsCM):
"""
MR class represents ibv_mr. Buffer allocation in done in the c'tor. Freeing
it is done in close().
"""
def __init__(self, creator not None, length=0, access=0, address=None,
implicit=False, **kwargs):
"""
Allocate a user-level buffer of length <length> and register a Memory
Region of the given length and access flags.
:param creator: A PD/CMID object. In case of CMID is passed the MR will
be registered using rdma_reg_msgs/write/read according
to the passed access flag of local_write/remote_write or
remote_read respectively.
:param length: Length (in bytes) of MR's buffer.
:param access: Access flags, see ibv_access_flags enum
:param address: Memory address to register (Optional). If it's not
provided, a memory will be allocated in the class
initialization.
:param implicit: Implicit the MR address.
:param kwargs: Arguments:
* *handle*
A valid kernel handle for a MR object in the given PD (creator).
If passed, the MR will be imported and associated with the
context that is associated with the given PD using ibv_import_mr.
:return: The newly created MR on success
"""
super().__init__()
cdef int mmap_len = 0
if self.mr != NULL:
return
self.is_huge = True if access & e.IBV_ACCESS_HUGETLB else False
if address:
self.is_user_addr = True
# uintptr_t is guaranteed to be large enough to hold any pointer.
# In order to safely cast addr to void*, it is firstly cast to uintptr_t.
self.buf = <void*><uintptr_t>address
mr_handle = kwargs.get('handle')
# If a MR handle is passed import MR and finish
if mr_handle is not None:
pd = <PD>creator
self.mr = v.ibv_import_mr(pd.pd, mr_handle)
if self.mr == NULL:
raise PyverbsRDMAErrno('Failed to import MR')
self._is_imported = True
self.pd = pd
pd.add_ref(self)
return
# Allocate a buffer
if not address and length > 0:
self._allocate_buffer(<size_t>length, self.is_huge, &mmap_len)
if self.buf == NULL:
raise PyverbsError('Failed to allocate MR buffer of size {l}'.
format(l=length))
self.mmap_length = mmap_len
if isinstance(creator, PD):
pd = <PD>creator
if implicit:
self.mr = v.ibv_reg_mr(pd.pd, NULL, SIZE_MAX, access)
else:
self.mr = v.ibv_reg_mr(pd.pd, self.buf, length, access)
self.pd = pd
pd.add_ref(self)
elif isinstance(creator, CMID):
cmid = <CMID>creator
if access == e.IBV_ACCESS_LOCAL_WRITE:
self.mr = cm.rdma_reg_msgs(cmid.id, self.buf, length)
elif access == e.IBV_ACCESS_REMOTE_WRITE:
self.mr = cm.rdma_reg_write(cmid.id, self.buf, length)
elif access == e.IBV_ACCESS_REMOTE_READ:
self.mr = cm.rdma_reg_read(cmid.id, self.buf, length)
self.cmid = cmid
cmid.add_ref(self)
if self.mr == NULL:
raise PyverbsRDMAErrno('Failed to register a MR. length: {l}, access flags: {a}'.
format(l=length, a=access))
self.logger.debug('Registered ibv_mr. Length: {l}, access flags {a}'.
format(l=length, a=access))
def unimport(self):
v.ibv_unimport_mr(self.mr)
self.close()
def __dealloc__(self):
self.close()
cpdef close(self):
"""
Closes the underlying C object of the MR and frees the memory allocated.
MR may be deleted directly or indirectly by closing its context, which
leaves the Python PD object without the underlying C object, so during
destruction, need to check whether or not the C object exists.
In case of an imported MR no deregistration will be done, it's left
for the original MR, in order to prevent double dereg by the GC.
:return: None
"""
if self.mr != NULL:
if self.logger:
self.logger.debug('Closing MR')
if not self._is_imported:
rc = v.ibv_dereg_mr(self.mr)
if rc != 0:
raise PyverbsRDMAError('Failed to dereg MR', rc)
if not self.is_user_addr:
self._free_buffer(self.is_huge, self.mmap_length)
self.mr = NULL
self.pd = None
self.buf = NULL
self.cmid = None
def write(self, data, length, offset=0):
"""
Write user data to the MR's buffer using memcpy
:param data: User data to write
:param length: Length of the data to write
:param offset: Writing offset
:return: None
"""
if not self.buf or length < 0:
raise PyverbsUserError('The MR buffer isn\'t allocated or length'
f' {length} is invalid')
# If data is a string, cast it to bytes as Python3 doesn't
# automatically convert it.
cdef int off = offset
if isinstance(data, str):
data = data.encode()
memcpy(<char*>(self.buf + off), <char *>data, length)
cpdef read(self, length, offset):
"""
Reads data from the MR's buffer
:param length: Length of data to read
:param offset: Reading offset
:return: The data on the buffer in the requested offset
"""
cdef char *data
cdef int off = offset # we can't use offset in the next line, as it is
# a Python object and not C
if offset < 0:
raise PyverbsUserError(f'Invalid offset {offset}')
if not self.buf or length < 0:
raise PyverbsUserError('The MR buffer isn\'t allocated or length'
f' {length} is invalid')
data = <char*>(self.buf + off)
return data[:length]
def rereg(self, flags, PD pd=None, addr=0, length=0, access=0):
"""
Modifies the attributes of an existing memory region.
:param flags: Bit-mask used to indicate which of the properties of the
MR are being modified
:param pd: New PD
:param addr: New addr to reg the MR on
:param length: New length of memory to reg
:param access: New MR access
:return: None
"""
ret = v.ibv_rereg_mr(self.mr, flags, pd.pd, <void*><uintptr_t>addr,
length, access)
if ret != 0:
err_msg = rereg_error_to_str(ret)
raise PyverbsRDMAErrno(f'Failed to rereg MR: {err_msg}')
if flags & e.IBV_REREG_MR_CHANGE_TRANSLATION:
if not self.is_user_addr:
self._free_buffer(self.is_huge, self.mmap_length)
self.buf = <void*><uintptr_t>addr
self.is_user_addr = True
if flags & e.IBV_REREG_MR_CHANGE_PD:
(<PD>self.pd).remove_ref(self)
self.pd = pd
pd.add_ref(self)
cdef void _allocate_buffer(self, size_t length, bint is_huge, int *mmap_length):
cdef void *buf = NULL
cdef size_t rounded
cdef int rc
if length == 0:
mmap_length[0] = 0
return
if is_huge:
rounded = length + (HUGE_PAGE_SIZE - length % HUGE_PAGE_SIZE) if \
length % HUGE_PAGE_SIZE else length
buf = mmap(NULL, rounded, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0)
if buf == MAP_FAILED:
return
mmap_length[0] = <int>rounded
else:
rc = posix_memalign(&buf, resource.getpagesize(), length)
if rc:
return
mmap_length[0] = 0
memset(buf, 0, length)
self.buf = buf
cdef void _free_buffer(self, bint is_huge, int mmap_length):
if self.buf == NULL:
return
if is_huge:
munmap(self.buf, mmap_length)
else:
free(self.buf)
@property
def buf(self):
return <uintptr_t>self.buf
@property
def lkey(self):
return self.mr.lkey
@property
def rkey(self):
return self.mr.rkey
@property
def length(self):
return self.mr.length
@property
def handle(self):
return self.mr.handle
def __str__(self):
print_format = '{:22}: {:<20}\n'
return 'MR:\n' + \
print_format.format('lkey', self.lkey) + \
print_format.format('rkey', self.rkey) + \
print_format.format('length', self.length) + \
print_format.format('buf', <uintptr_t>self.buf) + \
print_format.format('handle', self.handle)
cdef class MWBindInfo(PyverbsCM):
def __init__(self, MR mr not None, addr, length, mw_access_flags):
super().__init__()
self.mr = mr
self.info.mr = mr.mr
self.info.addr = addr
self.info.length = length
self.info.mw_access_flags = mw_access_flags
@property
def mw_access_flags(self):
return self.info.mw_access_flags
@property
def length(self):
return self.info.length
@property
def addr(self):
return self.info.addr
def __str__(self):
print_format = '{:22}: {:<20}\n'
return 'MWBindInfo:\n' +\
print_format.format('Addr', self.info.addr) +\
print_format.format('Length', self.info.length) +\
print_format.format('MW access flags', self.info.mw_access_flags)
cdef class MWBind(PyverbsCM):
def __init__(self, MWBindInfo info not None,send_flags, wr_id=0):
super().__init__()
self.mw_bind.wr_id = wr_id
self.mw_bind.send_flags = send_flags
self.mw_bind.bind_info = info.info
def __str__(self):
print_format = '{:22}: {:<20}\n'
return 'MWBind:\n' +\
print_format.format('WR id', self.mw_bind.wr_id) +\
print_format.format('Send flags', self.mw_bind.send_flags)
cdef class MW(PyverbsCM):
def __init__(self, PD pd not None, v.ibv_mw_type mw_type):
"""
Initializes a memory window object of the given type
:param pd: A PD object
:param mw_type: Type of of the memory window, see ibv_mw_type enum
:return:
"""
super().__init__()
self.mw = NULL
self.mw = v.ibv_alloc_mw(pd.pd, mw_type)
if self.mw == NULL:
raise PyverbsRDMAErrno('Failed to allocate MW')
self.pd = pd
pd.add_ref(self)
self.logger.debug('Allocated memory window of type {t}'.
format(t=mwtype2str(mw_type)))
def __dealloc__(self):
self.close()
cpdef close(self):
"""
Closes the underlying C MW object.
MW may be deleted directly or by deleting its PD, which leaves the
Python object without the underlying MW.
Need to check that the underlying MW wasn't dealloced before.
:return: None
"""
if self.mw is not NULL:
if self.logger:
self.logger.debug('Closing MW')
rc = v.ibv_dealloc_mw(self.mw)
if rc != 0:
raise PyverbsRDMAError('Failed to dealloc MW', rc)
self.mw = NULL
self.pd = None
@property
def handle(self):
return self.mw.handle
@property
def rkey(self):
return self.mw.rkey
@property
def type(self):
return self.mw.type
def __str__(self):
print_format = '{:22}: {:<20}\n'
return 'MW:\n' +\
print_format.format('Rkey', self.mw.rkey) +\
print_format.format('Handle', self.mw.handle) +\
print_format.format('MW Type', mwtype2str(self.mw.type))
cdef class DMMR(MR):
def __init__(self, PD pd not None, length, access, DM dm, offset):
"""
Initializes a DMMR (Device Memory Memory Region) of the given length
and access flags using the given PD and DM objects.
:param pd: A PD object
:param length: Length in bytes
:param access: Access flags, see ibv_access_flags enum
:param dm: A DM (device memory) object to be used for this DMMR
:param offset: Byte offset from the beginning of the allocated device
memory buffer
:return: The newly create DMMR
"""
# Initialize the logger here as the parent's __init__ is called after
# the DMMR is allocated. Allocation can fail, which will lead to
# exceptions thrown during object's teardown.
self.logger = logging.getLogger(self.__class__.__name__)
self.mr = v.ibv_reg_dm_mr(pd.pd, dm.dm, offset, length, access)
if self.mr == NULL:
raise PyverbsRDMAErrno('Failed to register a device MR. length: {len}, access flags: {flags}'.
format(len=length, flags=access,))
super().__init__(pd, length, access)
self.pd = pd
self.dm = dm
pd.add_ref(self)
dm.add_ref(self)
self.logger.debug('Registered device ibv_mr. Length: {len}, access flags {flags}'.
format(len=length, flags=access))
def write(self, data, length, offset=0):
if isinstance(data, str):
data = data.encode()
return self.dm.copy_to_dm(offset, data, length)
cpdef read(self, length, offset):
return self.dm.copy_from_dm(offset, length)
cdef class DmaBufMR(MR):
def __init__(self, PD pd not None, length, access, dmabuf=None,
offset=0, gpu=0, gtt=0):
"""
Initializes a DmaBufMR (DMA-BUF Memory Region) of the given length
and access flags using the given PD and DmaBuf objects.
:param pd: A PD object
:param length: Length in bytes
:param access: Access flags, see ibv_access_flags enum
:param dmabuf: A DmaBuf object or a FD representing a dmabuf.
DmaBuf object will be allocated if None is passed.
:param offset: Byte offset from the beginning of the dma-buf
:param gpu: GPU unit for internal dmabuf allocation
:param gtt: If true allocate internal dmabuf from GTT instead of VRAM
:return: The newly created DMABUFMR
"""
self.logger = logging.getLogger(self.__class__.__name__)
if dmabuf is None:
self.is_dmabuf_internal = True
dmabuf = DmaBuf(length + offset, gpu, gtt)
fd = dmabuf.fd if isinstance(dmabuf, DmaBuf) else dmabuf
self.mr = v.ibv_reg_dmabuf_mr(pd.pd, offset, length, offset, fd, access)
if self.mr == NULL:
raise PyverbsRDMAErrno(f'Failed to register a dma-buf MR. length: {length}, access flags: {access}')
super().__init__(pd, length, access)
self.pd = pd
self.dmabuf = dmabuf
self.offset = offset
pd.add_ref(self)
if isinstance(dmabuf, DmaBuf):
dmabuf.add_ref(self)
self.logger.debug(f'Registered dma-buf ibv_mr. Length: {length}, access flags {access}')
def __dealloc__(self):
self.close()
cpdef close(self):
"""
Closes the underlying C object of the MR and frees the memory allocated.
:return: None
"""
if self.mr != NULL:
if self.logger:
self.logger.debug('Closing dma-buf MR')
rc = v.ibv_dereg_mr(self.mr)
if rc != 0:
raise PyverbsRDMAError('Failed to dereg dma-buf MR', rc)
self.pd = None
self.mr = NULL
# Set self.mr to NULL before closing dmabuf because this method is
# re-entered when close_weakrefs() is called inside dmabuf.close().
if self.is_dmabuf_internal:
self.dmabuf.close()
self.dmabuf = None
@property
def offset(self):
return self.offset
@property
def dmabuf(self):
return self.dmabuf
def write(self, data, length, offset=0):
"""
Write user data to the dma-buf backing the MR
:param data: User data to write
:param length: Length of the data to write
:param offset: Writing offset
:return: None
"""
if isinstance(data, str):
data = data.encode()
cdef int off = offset + self.offset
cdef void *buf = mmap(NULL, length + off, PROT_READ | PROT_WRITE,
MAP_SHARED, self.dmabuf.drm_fd,
self.dmabuf.map_offset)
if buf == MAP_FAILED:
raise PyverbsError(f'Failed to map dma-buf of size {length}')
memcpy(<char*>(buf + off), <char *>data, length)
munmap(buf, length + off)
cpdef read(self, length, offset):
"""
Reads data from the dma-buf backing the MR
:param length: Length of data to read
:param offset: Reading offset
:return: The data on the buffer in the requested offset
"""
cdef int off = offset + self.offset
cdef void *buf = mmap(NULL, length + off, PROT_READ | PROT_WRITE,
MAP_SHARED, self.dmabuf.drm_fd,
self.dmabuf.map_offset)
if buf == MAP_FAILED:
raise PyverbsError(f'Failed to map dma-buf of size {length}')
cdef char *data =<char*>malloc(length)
memset(data, 0, length)
memcpy(data, <char*>(buf + off), length)
munmap(buf, length + off)
res = data[:length]
free(data)
return res
cdef class DmaHandleInitAttr(PyverbsObject):
def __init__(self, comp_mask=0, cpu_id=0, ph=0, tph_mem_type=0):
"""
Initialize a DmaHandleInitAttr object with the specified configuration.
:param comp_mask: Bitmask of initialization attributes
:param cpu_id: CPU identifier for DMA operations
:param ph: Processing hints for DMA operations
:param tph_mem_type: Type of target memory
"""
super().__init__()
self.init_attr.comp_mask = comp_mask
self.init_attr.cpu_id = cpu_id
self.init_attr.ph = ph
self.init_attr.tph_mem_type = tph_mem_type
@property
def comp_mask(self):
return self.init_attr.comp_mask
@comp_mask.setter
def comp_mask(self, value):
self.init_attr.comp_mask = value
@property
def cpu_id(self):
return self.init_attr.cpu_id
@cpu_id.setter
def cpu_id(self, value):
self.init_attr.cpu_id = value
@property
def ph(self):
return self.init_attr.ph
@ph.setter
def ph(self, value):
self.init_attr.ph = value
@property
def tph_mem_type(self):
return self.init_attr.tph_mem_type
@tph_mem_type.setter
def tph_mem_type(self, value):
self.init_attr.tph_mem_type = value
def __str__(self):
print_format = '{:22}: {:<20}\n'
return 'DmaHandleInitAttr:\n' + \
print_format.format('comp_mask', self.comp_mask) + \
print_format.format('cpu_id', self.cpu_id) + \
print_format.format('ph', self.ph) + \
print_format.format('tph_mem_type', self.tph_mem_type)
cdef class DMAHandle(PyverbsCM):
def __init__(self, Context ctx not None, DmaHandleInitAttr init_attr not None):
"""
Initialize a DMAHandle object with the specified configuration.
:param ctx: A Context object representing the RDMA device context
:param init_attr: A DmaHandleInitAttr object containing initialization attributes
:raises PyverbsError: If DMA handle allocation fails
"""
super().__init__()
self.dmah = v.ibv_alloc_dmah(ctx.context, &init_attr.init_attr)
if self.dmah == NULL:
raise PyverbsRDMAErrno('Failed to create DMA Handle')
self.mrs = weakref.WeakSet()
self.ctx = ctx
ctx.add_ref(self)
def __dealloc__(self):
self.close()
cpdef close(self):
"""
Close and deallocate the DMA handle.
:raises PyverbsRDMAError: If deallocation fails
"""
if self.dmah != NULL:
if self.logger:
self.logger.debug('Closing DMA Handle')
close_weakrefs([self.mrs])
rc = v.ibv_dealloc_dmah(self.dmah)
if rc != 0:
raise PyverbsRDMAError('Failed to dealloc DMA Handle', rc)
self.dmah = NULL
self.ctx = None
cdef add_ref(self, obj):
if isinstance(obj, MREx):
self.mrs.add(obj)
else:
raise PyverbsError('Unrecognized object type')
def mwtype2str(mw_type):
mw_types = {1:'IBV_MW_TYPE_1', 2:'IBV_MW_TYPE_2'}
try:
return mw_types[mw_type]
except KeyError:
return 'Unknown MW type ({t})'.format(t=mw_type)
cdef class MREx(MR):
"""
MREx class represents a memory region registered using the extended API ibv_reg_mr_ex.
This class provides more flexibility in memory registration compared to the basic MR class.
"""
def __init__(self, PD pd not None, length=0, access=0, address=None,
iova=None, fd=None, fd_offset=0, dmah=None, implicit=False, **kwargs):
"""
Register a memory region using the extended API ibv_reg_mr_ex.
:param pd: A PD object
:param length: Length (in bytes) of MR's buffer
:param access: Access flags, see ibv_access_flags enum
:param address: Memory address to register (Optional)
:param iova: IOVA address to register (Optional)
:param fd: File descriptor for dma-buf based registration (Optional)
:param fd_offset: Offset in the dma-buf (Optional)
:param dmah: DMA handle for registration (Optional)
:param implicit: If True, register implicit MR
:param kwargs: Additional arguments
:return: The newly created MREx on success
"""
PyverbsCM.__init__(self)
cdef int mmap_len = 0
cdef v.ibv_mr_init_attr in_
self.is_huge = True if access & e.IBV_ACCESS_HUGETLB else False
self.is_user_addr = False
self.mmap_length = 0
# Handle memory allocation if no address is provided
if not address and length > 0 and fd is None:
self._allocate_buffer(<size_t>length, self.is_huge, &mmap_len)
if self.buf == NULL:
raise PyverbsError(f'Failed to allocate MR buffer of size {length}')
self.mmap_length = mmap_len
elif address:
self.is_user_addr = True
self.buf = <void*><uintptr_t>address
memset(&in_, 0, sizeof(in_))
if implicit:
in_.length = SIZE_MAX
in_.comp_mask |= e.IBV_REG_MR_MASK_ADDR
in_.addr = NULL
else:
in_.length = length
if self.buf != NULL:
in_.comp_mask |= e.IBV_REG_MR_MASK_ADDR
in_.addr = self.buf
in_.access = access
if iova is not None:
in_.comp_mask |= e.IBV_REG_MR_MASK_IOVA
in_.iova = iova
if fd is not None:
in_.comp_mask |= e.IBV_REG_MR_MASK_FD | e.IBV_REG_MR_MASK_FD_OFFSET
in_.fd = fd
in_.fd_offset = fd_offset
if dmah is not None:
in_.comp_mask |= e.IBV_REG_MR_MASK_DMAH
in_.dmah = (<DMAHandle>dmah).dmah
self.mr = v.ibv_reg_mr_ex(pd.pd, &in_)
if self.mr == NULL:
# Clean up allocated memory if registration fails
if not self.is_user_addr and self.buf != NULL:
self._free_buffer(self.is_huge, self.mmap_length)
raise PyverbsRDMAErrno('Failed to register MR using extended API')
self.pd = pd
pd.add_ref(self)
if dmah is not None:
(<DMAHandle>dmah).add_ref(self)
self.dmah = dmah
self.logger.debug(f'Registered MR using extended API. Length: {length}, '
f'access flags {access}')
def __str__(self):
print_format = '{:22}: {:<20}\n'
return 'MREx:\n' + \
print_format.format('lkey', self.lkey) + \
print_format.format('rkey', self.rkey) + \
print_format.format('length', self.length) + \
print_format.format('buf', <uintptr_t>self.buf) + \
print_format.format('handle', self.handle)
cpdef close(self):
"""Close MREx and release its association with DMAHandle."""
if self.mr != NULL:
super(MREx, self).close()
self.dmah = None