diff --git a/pyverbs/providers/mlx5/libmlx5.pxd b/pyverbs/providers/mlx5/libmlx5.pxd
index aaf75620a9bb..2c5be241a3ec 100644
--- a/pyverbs/providers/mlx5/libmlx5.pxd
+++ b/pyverbs/providers/mlx5/libmlx5.pxd
@@ -1,6 +1,8 @@
 # SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB)
 # Copyright (c) 2019 Mellanox Technologies, Inc. All rights reserved. See COPYING file
 
+include 'mlx5dv_enums.pxd'
+
 from libcpp cimport bool
 
 cimport pyverbs.libibverbs as v
@@ -40,7 +42,20 @@ cdef extern from 'infiniband/mlx5dv.h':
         unsigned int            flow_action_flags
         unsigned int            dc_odp_caps
 
+    cdef struct mlx5dv_dc_init_attr:
+        mlx5dv_dc_type      dc_type
+        unsigned long       dct_access_key
+
+    cdef struct mlx5dv_qp_init_attr:
+        unsigned long       comp_mask
+        unsigned int        create_flags
+        mlx5dv_dc_init_attr dc_init_attr
+        unsigned long       send_ops_flags
+
     bool mlx5dv_is_supported(v.ibv_device *device)
     v.ibv_context* mlx5dv_open_device(v.ibv_device *device,
                                       mlx5dv_context_attr *attr)
     int mlx5dv_query_device(v.ibv_context *ctx, mlx5dv_context *attrs_out)
+    v.ibv_qp *mlx5dv_create_qp(v.ibv_context *context,
+                               v.ibv_qp_init_attr_ex *qp_attr,
+                               mlx5dv_qp_init_attr *mlx5_qp_attr)
diff --git a/pyverbs/providers/mlx5/mlx5dv.pxd b/pyverbs/providers/mlx5/mlx5dv.pxd
index d9fea82af894..918065012d30 100644
--- a/pyverbs/providers/mlx5/mlx5dv.pxd
+++ b/pyverbs/providers/mlx5/mlx5dv.pxd
@@ -6,6 +6,8 @@
 cimport pyverbs.providers.mlx5.libmlx5 as dv
 from pyverbs.base cimport PyverbsObject
 from pyverbs.device cimport Context
+from pyverbs.qp cimport QP
+
 
 cdef class Mlx5Context(Context):
     pass
@@ -15,3 +17,12 @@ cdef class Mlx5DVContextAttr(PyverbsObject):
 
 cdef class Mlx5DVContext(PyverbsObject):
     cdef dv.mlx5dv_context dv
+
+cdef class Mlx5DVDCInitAttr(PyverbsObject):
+    cdef dv.mlx5dv_dc_init_attr attr
+
+cdef class Mlx5DVQPInitAttr(PyverbsObject):
+    cdef dv.mlx5dv_qp_init_attr attr
+
+cdef class Mlx5QP(QP):
+    cdef object dc_type
diff --git a/pyverbs/providers/mlx5/mlx5dv.pyx b/pyverbs/providers/mlx5/mlx5dv.pyx
index dadc9cdcceee..b4a971a5b935 100644
--- a/pyverbs/providers/mlx5/mlx5dv.pyx
+++ b/pyverbs/providers/mlx5/mlx5dv.pyx
@@ -6,7 +6,8 @@ cimport pyverbs.providers.mlx5.mlx5dv_enums as dve
 cimport pyverbs.providers.mlx5.libmlx5 as dv
 from pyverbs.base import PyverbsRDMAErrno
 cimport pyverbs.libibverbs_enums as e
-
+from pyverbs.qp cimport QPInitAttrEx
+from pyverbs.pd cimport PD
 
 cdef class Mlx5DVContextAttr(PyverbsObject):
     """
@@ -179,8 +180,159 @@ cdef class Mlx5DVContext(PyverbsObject):
                print_format.format('DC ODP caps', self.dv.dc_odp_caps)
 
 
+cdef class Mlx5DVDCInitAttr(PyverbsObject):
+    """
+    Represents mlx5dv_dc_init_attr struct, which defines initial attributes
+    for DC QP creation.
+    """
+    def __cinit__(self, dc_type=dve.MLX5DV_DCTYPE_DCI, dct_access_key=0):
+        """
+        Initializes an Mlx5DVDCInitAttr object with the given DC type and DCT
+        access key.
+        :param dc_type: Which DC QP to create (DCI/DCT).
+        :param dct_access_key: Access key to be used by the DCT
+        :return: An initializes object
+        """
+        self.attr.dc_type = dc_type
+        self.attr.dct_access_key = dct_access_key
+
+    def __str__(self):
+        print_format = '{:20}: {:<20}\n'
+        return print_format.format('DC type', dc_type_to_str(self.attr.dc_type)) +\
+               print_format.format('DCT access key', self.attr.dct_access_key)
+
+    @property
+    def dc_type(self):
+        return self.attr.dc_type
+    @dc_type.setter
+    def dc_type(self, val):
+        self.attr.dc_type = val
+
+    @property
+    def dct_access_key(self):
+        return self.attr.dct_access_key
+    @dct_access_key.setter
+    def dct_access_key(self, val):
+        self.attr.dct_access_key = val
+
+
+cdef class Mlx5DVQPInitAttr(PyverbsObject):
+    """
+    Represents mlx5dv_qp_init_attr struct, initial attributes used for mlx5 QP
+    creation.
+    """
+    def __cinit__(self, comp_mask=0, create_flags=0,
+                  Mlx5DVDCInitAttr dc_init_attr=None, send_ops_flags=0):
+        """
+        Initializes an Mlx5DVQPInitAttr object with the given user data.
+        :param comp_mask: A bitmask specifying which fields are valid
+        :param create_flags: A bitwise OR of mlx5dv_qp_create_flags
+        :param dc_init_attr: Mlx5DVDCInitAttr object
+        :param send_ops_flags: A bitwise OR of mlx5dv_qp_create_send_ops_flags
+        :return: An initialized Mlx5DVQPInitAttr object
+        """
+        self.attr.comp_mask = comp_mask
+        self.attr.create_flags = create_flags
+        self.attr.send_ops_flags = send_ops_flags
+        if dc_init_attr is not None:
+            self.attr.dc_init_attr.dc_type = dc_init_attr.dc_type
+            self.attr.dc_init_attr.dct_access_key = dc_init_attr.dct_access_key
+
+    def __str__(self):
+        print_format = '{:20}: {:<20}\n'
+        return print_format.format('Comp mask',
+                                   qp_comp_mask_to_str(self.attr.comp_mask)) +\
+               print_format.format('Create flags',
+                                   qp_create_flags_to_str(self.attr.create_flags)) +\
+               'DC init attr:\n' +\
+               print_format.format('  DC type',
+                                   dc_type_to_str(self.attr.dc_init_attr.dc_type)) +\
+               print_format.format('  DCT access key',
+                                   self.attr.dc_init_attr.dct_access_key) +\
+               print_format.format('Send ops flags',
+                                   send_ops_flags_to_str(self.attr.send_ops_flags))
+
+    @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 create_flags(self):
+        return self.attr.create_flags
+    @create_flags.setter
+    def create_flags(self, val):
+        self.attr.create_flags = val
+
+    @property
+    def send_ops_flags(self):
+        return self.attr.send_ops_flags
+    @send_ops_flags.setter
+    def send_ops_flags(self, val):
+        self.attr.send_ops_flags = val
+
+    @property
+    def dc_type(self):
+        return self.attr.dc_init_attr.dc_type
+    @dc_type.setter
+    def dc_type(self, val):
+        self.attr.dc_init_attr.dc_type = val
+
+    @property
+    def dct_access_key(self):
+        return self.attr.dc_init_attr.dct_access_key
+    @dct_access_key.setter
+    def dct_access_key(self, val):
+        self.attr.dc_init_attr.dct_access_key = val
+
+
+cdef class Mlx5QP(QP):
+    def __cinit__(self, Mlx5Context context, QPInitAttrEx init_attr,
+                  Mlx5DVQPInitAttr dv_init_attr):
+        """
+        Initializes an mlx5 QP according to the user-provided data.
+        :param context: mlx5 Context object
+        :param init_attr: QPInitAttrEx object
+        :param dv_init_attr: Mlx5DVQPInitAttr object
+        :return: An initialized Mlx5QP
+        """
+        cdef PD pd
+        self.dc_type = dv_init_attr.dc_type if dv_init_attr else 0
+        if init_attr.pd is not None:
+            pd = <PD>init_attr.pd
+            pd.add_ref(self)
+        self.qp = \
+            dv.mlx5dv_create_qp(context.context,
+                                &init_attr.attr,
+                                &dv_init_attr.attr if dv_init_attr is not None
+                                else NULL)
+        if self.qp == NULL:
+            raise PyverbsRDMAErrno('Failed to create MLX5 QP.\nQPInitAttrEx '
+                                   'attributes:\n{}\nMLX5DVQPInitAttr:\n{}'.
+                                   format(init_attr, dv_init_attr))
+
+    def _get_comp_mask(self, dst):
+        masks = {dve.MLX5DV_DCTYPE_DCT: {'INIT': e.IBV_QP_PKEY_INDEX |
+                                         e.IBV_QP_PORT | e.IBV_QP_ACCESS_FLAGS,
+                                         'RTR': e.IBV_QP_AV |\
+                                         e.IBV_QP_PATH_MTU |\
+                                         e.IBV_QP_MIN_RNR_TIMER},
+                 dve.MLX5DV_DCTYPE_DCI: {'INIT': e.IBV_QP_PKEY_INDEX |\
+                                         e.IBV_QP_PORT,
+                                         'RTR': e.IBV_QP_PATH_MTU,
+                                         'RTS': e.IBV_QP_TIMEOUT |\
+                                         e.IBV_QP_RETRY_CNT |\
+                                         e.IBV_QP_RNR_RETRY | e.IBV_QP_SQ_PSN |\
+                                         e.IBV_QP_MAX_QP_RD_ATOMIC}}
+        if self.dc_type == 0:
+            return super()._get_comp_mask(dst)
+        return masks[self.dc_type][dst] | e.IBV_QP_STATE
+
+
 def qpts_to_str(qp_types):
-    numberic_types = qp_types
+    numeric_types = qp_types
     qpts_str = ''
     qpts = {e.IBV_QPT_RC: 'RC', e.IBV_QPT_UC: 'UC', e.IBV_QPT_UD: 'UD',
             e.IBV_QPT_RAW_PACKET: 'Raw Packet', e.IBV_QPT_XRC_SEND: 'XRC Send',
@@ -191,7 +343,7 @@ def qpts_to_str(qp_types):
             qp_types -= t
         if qp_types == 0:
             break
-    return qpts_str[:-2] + ' ({})'.format(numberic_types)
+    return qpts_str[:-2] + ' ({})'.format(numeric_types)
 
 
 def bitmask_to_str(bits, values):
@@ -251,3 +403,37 @@ def tunnel_offloads_to_str(tun):
          dve.MLX5DV_RAW_PACKET_CAP_TUNNELED_OFFLOAD_CW_MPLS_OVER_UDP:\
          'Ctrl word + MPLS over UDP'}
     return bitmask_to_str(tun, l)
+
+
+def dc_type_to_str(dctype):
+    l = {dve.MLX5DV_DCTYPE_DCT: 'DCT', dve.MLX5DV_DCTYPE_DCI: 'DCI'}
+    try:
+        return l[dctype]
+    except KeyError:
+        return 'Unknown DC type ({dc})'.format(dc=dctype)
+
+
+def qp_comp_mask_to_str(flags):
+    l = {dve.MLX5DV_QP_INIT_ATTR_MASK_QP_CREATE_FLAGS: 'Create flags',
+         dve.MLX5DV_QP_INIT_ATTR_MASK_DC: 'DC',
+         dve.MLX5DV_QP_INIT_ATTR_MASK_SEND_OPS_FLAGS: 'Send ops flags'}
+    return bitmask_to_str(flags, l)
+
+
+def qp_create_flags_to_str(flags):
+    l = {dve.MLX5DV_QP_CREATE_TUNNEL_OFFLOADS: 'Tunnel offloads',
+         dve.MLX5DV_QP_CREATE_TIR_ALLOW_SELF_LOOPBACK_UC:
+             'Allow UC self loopback',
+         dve.MLX5DV_QP_CREATE_TIR_ALLOW_SELF_LOOPBACK_MC:
+             'Allow MC self loopback',
+         dve.MLX5DV_QP_CREATE_DISABLE_SCATTER_TO_CQE: 'Disable scatter to CQE',
+         dve.MLX5DV_QP_CREATE_ALLOW_SCATTER_TO_CQE: 'Allow scatter to CQE',
+         dve.MLX5DV_QP_CREATE_PACKET_BASED_CREDIT_MODE:
+             'Packet based credit mode'}
+    return bitmask_to_str(flags, l)
+
+
+def send_ops_flags_to_str(flags):
+    l = {dve.MLX5DV_QP_EX_WITH_MR_INTERLEAVED: 'With MR interleaved',
+         dve.MLX5DV_QP_EX_WITH_MR_LIST: 'With MR list'}
+    return bitmask_to_str(flags, l)
diff --git a/pyverbs/providers/mlx5/mlx5dv_enums.pxd b/pyverbs/providers/mlx5/mlx5dv_enums.pxd
index 038a49111a3b..3f7f591e23a5 100644
--- a/pyverbs/providers/mlx5/mlx5dv_enums.pxd
+++ b/pyverbs/providers/mlx5/mlx5dv_enums.pxd
@@ -45,3 +45,24 @@ cdef extern from 'infiniband/mlx5dv.h':
         MLX5DV_FLOW_ACTION_FLAGS_ESP_AES_GCM_SPI_STEERING   = 1 << 2
         MLX5DV_FLOW_ACTION_FLAGS_ESP_AES_GCM_FULL_OFFLOAD   = 1 << 3
         MLX5DV_FLOW_ACTION_FLAGS_ESP_AES_GCM_TX_IV_IS_ESN   = 1 << 4
+
+    cpdef enum mlx5dv_qp_init_attr_mask:
+        MLX5DV_QP_INIT_ATTR_MASK_QP_CREATE_FLAGS    = 1 << 0
+        MLX5DV_QP_INIT_ATTR_MASK_DC                 = 1 << 1
+        MLX5DV_QP_INIT_ATTR_MASK_SEND_OPS_FLAGS     = 1 << 2
+
+    cpdef enum mlx5dv_qp_create_flags:
+        MLX5DV_QP_CREATE_TUNNEL_OFFLOADS            = 1 << 0
+        MLX5DV_QP_CREATE_TIR_ALLOW_SELF_LOOPBACK_UC = 1 << 1
+        MLX5DV_QP_CREATE_TIR_ALLOW_SELF_LOOPBACK_MC = 1 << 2
+        MLX5DV_QP_CREATE_DISABLE_SCATTER_TO_CQE     = 1 << 3
+        MLX5DV_QP_CREATE_ALLOW_SCATTER_TO_CQE       = 1 << 4
+        MLX5DV_QP_CREATE_PACKET_BASED_CREDIT_MODE   = 1 << 5
+
+    cpdef enum mlx5dv_dc_type:
+        MLX5DV_DCTYPE_DCT   = 1
+        MLX5DV_DCTYPE_DCI   = 2
+
+    cpdef enum mlx5dv_qp_create_send_ops_flags:
+        MLX5DV_QP_EX_WITH_MR_INTERLEAVED    = 1 << 0
+        MLX5DV_QP_EX_WITH_MR_LIST           = 1 << 1
