Merge pull request #1781 from yishaih/mlx5_misc

mlx5: Misc. items
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 06862ae..e1f9ce0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -88,7 +88,7 @@
 # When this is changed the values in these files need changing too:
 #   debian/control
 #   debian/libibverbs1.symbols
-set(IBVERBS_PABI_VERSION "64")
+set(IBVERBS_PABI_VERSION "65")
 set(IBVERBS_PROVIDER_SUFFIX "-rdmav${IBVERBS_PABI_VERSION}.so")
 
 #-------------------------
diff --git a/debian/control b/debian/control
index b7a731a..736fd1c 100644
--- a/debian/control
+++ b/debian/control
@@ -151,7 +151,7 @@
 Pre-Depends: ${misc:Pre-Depends}
 Depends: adduser, ${misc:Depends}, ${shlibs:Depends}
 Recommends: ibverbs-providers
-Breaks: ibverbs-providers (<< 64~)
+Breaks: ibverbs-providers (<< 65~)
 Description: Library for direct userspace use of RDMA (InfiniBand/iWARP)
  libibverbs is a library that allows userspace processes to use RDMA
  "verbs" as described in the InfiniBand Architecture Specification and
diff --git a/debian/ibverbs-providers.symbols b/debian/ibverbs-providers.symbols
index 3697696..40e627d 100644
--- a/debian/ibverbs-providers.symbols
+++ b/debian/ibverbs-providers.symbols
@@ -188,6 +188,7 @@
  EFA_1.4@EFA_1.4 59
  EFA_1.5@EFA_1.5 63
  EFA_1.6@EFA_1.6 64
+ EFA_1.7@EFA_1.7 65
  efadv_create_driver_qp@EFA_1.0 24
  efadv_create_qp_ex@EFA_1.1 26
  efadv_query_device@EFA_1.1 26
@@ -200,6 +201,7 @@
  efadv_get_max_sq_depth@EFA_1.5 63
  efadv_get_max_rq_depth@EFA_1.5 63
  efadv_qp_from_ibv_qp_ex@EFA_1.6 64
+ efadv_create_comp_cntr@EFA_1.7 65
 libhns.so.1 ibverbs-providers #MINVER#
 * Build-Depends-Package: libibverbs-dev
  HNS_1.0@HNS_1.0 51
diff --git a/debian/libibverbs1.symbols b/debian/libibverbs1.symbols
index 6f90cff..3b74941 100644
--- a/debian/libibverbs1.symbols
+++ b/debian/libibverbs1.symbols
@@ -16,7 +16,8 @@
  IBVERBS_1.16@IBVERBS_1.16 62
  IBVERBS_1.17@IBVERBS_1.17 64
  IBVERBS_1.18@IBVERBS_1.18 65
- (symver)IBVERBS_PRIVATE_64 64
+ (symver)IBVERBS_PRIVATE_65 65
+ _ibv_query_comp_cntr_caps@IBVERBS_1.18 65
  _ibv_query_gid_ex@IBVERBS_1.11 32
  _ibv_query_gid_table@IBVERBS_1.11 32
  ibv_ack_async_event@IBVERBS_1.0 1.1.6
@@ -39,6 +40,7 @@
  ibv_create_ah@IBVERBS_1.1 1.1.6
  ibv_create_ah_from_wc@IBVERBS_1.1 1.1.6
  ibv_create_comp_channel@IBVERBS_1.0 1.1.6
+ ibv_create_comp_cntr@IBVERBS_1.18 65
  ibv_create_cq@IBVERBS_1.0 1.1.6
  ibv_create_cq@IBVERBS_1.1 1.1.6
  ibv_create_qp@IBVERBS_1.0 1.1.6
@@ -53,6 +55,7 @@
  ibv_destroy_ah@IBVERBS_1.0 1.1.6
  ibv_destroy_ah@IBVERBS_1.1 1.1.6
  ibv_destroy_comp_channel@IBVERBS_1.0 1.1.6
+ ibv_destroy_comp_cntr@IBVERBS_1.18 65
  ibv_destroy_cq@IBVERBS_1.0 1.1.6
  ibv_destroy_cq@IBVERBS_1.1 1.1.6
  ibv_destroy_qp@IBVERBS_1.0 1.1.6
@@ -87,6 +90,8 @@
  ibv_import_dm@IBVERBS_1.13 35
  ibv_import_mr@IBVERBS_1.10 31
  ibv_import_pd@IBVERBS_1.10 31
+ ibv_inc_comp_cntr@IBVERBS_1.18 65
+ ibv_inc_err_comp_cntr@IBVERBS_1.18 65
  ibv_init_ah_from_wc@IBVERBS_1.1 1.1.6
  ibv_is_fork_initialized@IBVERBS_1.13 35
  ibv_modify_qp@IBVERBS_1.0 1.1.6
@@ -97,6 +102,7 @@
  ibv_open_device@IBVERBS_1.0 1.1.6
  ibv_open_device@IBVERBS_1.1 1.1.6
  ibv_port_state_str@IBVERBS_1.1 1.1.6
+ ibv_qp_attach_comp_cntr@IBVERBS_1.18 65
  ibv_qp_to_qp_ex@IBVERBS_1.6 24
  ibv_query_device@IBVERBS_1.0 1.1.6
  ibv_query_device@IBVERBS_1.1 1.1.6
@@ -115,6 +121,8 @@
  ibv_query_srq@IBVERBS_1.1 1.1.6
  ibv_rate_to_mbps@IBVERBS_1.1 1.1.8
  ibv_rate_to_mult@IBVERBS_1.0 1.1.6
+ ibv_read_comp_cntr@IBVERBS_1.18 65
+ ibv_read_err_comp_cntr@IBVERBS_1.18 65
  ibv_read_sysfs_file@IBVERBS_1.0 1.1.6
  ibv_reg_buf_mr@IBVERBS_1.17 64
  ibv_reg_dmabuf_mr@IBVERBS_1.12 34
@@ -128,7 +136,9 @@
  ibv_resize_cq@IBVERBS_1.0 1.1.6
  ibv_resize_cq@IBVERBS_1.1 1.1.6
  ibv_resolve_eth_l2_from_gid@IBVERBS_1.1 1.2.0
+ ibv_set_comp_cntr@IBVERBS_1.18 65
  ibv_set_ece@IBVERBS_1.10 31
+ ibv_set_err_comp_cntr@IBVERBS_1.18 65
  ibv_unimport_dm@IBVERBS_1.13 35
  ibv_unimport_mr@IBVERBS_1.10 31
  ibv_unimport_pd@IBVERBS_1.10 31
diff --git a/kernel-headers/rdma/efa-abi.h b/kernel-headers/rdma/efa-abi.h
index d5c18f8..c79b54a 100644
--- a/kernel-headers/rdma/efa-abi.h
+++ b/kernel-headers/rdma/efa-abi.h
@@ -133,6 +133,7 @@
 	EFA_QUERY_DEVICE_CAPS_RDMA_WRITE = 1 << 5,
 	EFA_QUERY_DEVICE_CAPS_UNSOLICITED_WRITE_RECV = 1 << 6,
 	EFA_QUERY_DEVICE_CAPS_CQ_WITH_EXT_MEM = 1 << 7,
+	EFA_QUERY_DEVICE_CAPS_COMP_CNTR = 1 << 8,
 };
 
 struct efa_ibv_ex_query_device_resp {
@@ -163,4 +164,9 @@
 	EFA_IB_METHOD_MR_QUERY = (1U << UVERBS_ID_NS_SHIFT),
 };
 
+enum efa_comp_cntr_create_attrs {
+	EFA_IB_ATTR_CREATE_COMP_CNTR_COMP_BUFFER = (1U << UVERBS_ID_NS_SHIFT),
+	EFA_IB_ATTR_CREATE_COMP_CNTR_ERR_BUFFER,
+};
+
 #endif /* EFA_ABI_USER_H */
diff --git a/kernel-headers/rdma/ib_user_ioctl_cmds.h b/kernel-headers/rdma/ib_user_ioctl_cmds.h
index 839835b..6a3d59d 100644
--- a/kernel-headers/rdma/ib_user_ioctl_cmds.h
+++ b/kernel-headers/rdma/ib_user_ioctl_cmds.h
@@ -57,6 +57,7 @@
 	UVERBS_OBJECT_ASYNC_EVENT,
 	UVERBS_OBJECT_DMAH,
 	UVERBS_OBJECT_DMABUF,
+	UVERBS_OBJECT_COMP_CNTR,
 };
 
 enum {
@@ -75,6 +76,7 @@
 	UVERBS_METHOD_QUERY_GID_TABLE,
 	UVERBS_METHOD_QUERY_GID_ENTRY,
 	UVERBS_METHOD_QUERY_PORT_SPEED,
+	UVERBS_METHOD_QUERY_COMP_CNTR_CAPS,
 };
 
 enum uverbs_attrs_invoke_write_cmd_attr_ids {
@@ -93,6 +95,12 @@
 	UVERBS_ATTR_QUERY_PORT_SPEED_RESP,
 };
 
+enum uverbs_attrs_query_comp_cntr_caps_attr_ids {
+	UVERBS_ATTR_QUERY_COMP_CNTR_CAPS_MAX_COUNTERS,
+	UVERBS_ATTR_QUERY_COMP_CNTR_CAPS_MAX_VALUE,
+	UVERBS_ATTR_QUERY_COMP_CNTR_CAPS_SUPPORTED_QP_ATTACH_OPS,
+};
+
 enum uverbs_attrs_get_context_attr_ids {
 	UVERBS_ATTR_GET_CONTEXT_NUM_COMP_VECTORS,
 	UVERBS_ATTR_GET_CONTEXT_CORE_SUPPORT,
@@ -169,9 +177,16 @@
 	UVERBS_ATTR_DESTROY_QP_RESP,
 };
 
+enum uverbs_attrs_qp_attach_comp_cntr_cmd_attr_ids {
+	UVERBS_ATTR_QP_ATTACH_COMP_CNTR_HANDLE,
+	UVERBS_ATTR_QP_ATTACH_COMP_CNTR_CNTR_HANDLE,
+	UVERBS_ATTR_QP_ATTACH_COMP_CNTR_OP_MASK,
+};
+
 enum uverbs_methods_qp {
 	UVERBS_METHOD_QP_CREATE,
 	UVERBS_METHOD_QP_DESTROY,
+	UVERBS_METHOD_QP_ATTACH_COMP_CNTR,
 };
 
 enum uverbs_attrs_create_srq_cmd_attr_ids {
@@ -438,4 +453,32 @@
 	UVERBS_ATTR_QUERY_GID_ENTRY_RESP_ENTRY,
 };
 
+enum uverbs_methods_comp_cntr {
+	UVERBS_METHOD_COMP_CNTR_CREATE,
+	UVERBS_METHOD_COMP_CNTR_DESTROY,
+	UVERBS_METHOD_COMP_CNTR_MODIFY,
+	UVERBS_METHOD_COMP_CNTR_READ,
+};
+
+enum uverbs_attrs_create_comp_cntr_cmd_attr_ids {
+	UVERBS_ATTR_CREATE_COMP_CNTR_HANDLE,
+};
+
+enum uverbs_attrs_destroy_comp_cntr_cmd_attr_ids {
+	UVERBS_ATTR_DESTROY_COMP_CNTR_HANDLE,
+};
+
+enum uverbs_attrs_modify_comp_cntr_cmd_attr_ids {
+	UVERBS_ATTR_MODIFY_COMP_CNTR_HANDLE,
+	UVERBS_ATTR_MODIFY_COMP_CNTR_ENTRY,
+	UVERBS_ATTR_MODIFY_COMP_CNTR_OP,
+	UVERBS_ATTR_MODIFY_COMP_CNTR_VALUE,
+};
+
+enum uverbs_attrs_read_comp_cntr_cmd_attr_ids {
+	UVERBS_ATTR_READ_COMP_CNTR_HANDLE,
+	UVERBS_ATTR_READ_COMP_CNTR_ENTRY,
+	UVERBS_ATTR_READ_COMP_CNTR_RESP_VALUE,
+};
+
 #endif
diff --git a/kernel-headers/rdma/ib_user_ioctl_verbs.h b/kernel-headers/rdma/ib_user_ioctl_verbs.h
index 51030c2..21f86cc 100644
--- a/kernel-headers/rdma/ib_user_ioctl_verbs.h
+++ b/kernel-headers/rdma/ib_user_ioctl_verbs.h
@@ -300,4 +300,23 @@
 	__aligned_u64 length;
 };
 
+enum ib_uverbs_comp_cntr_entry {
+	IB_UVERBS_COMP_CNTR_ENTRY_COMP,
+	IB_UVERBS_COMP_CNTR_ENTRY_ERR,
+};
+
+enum ib_uverbs_comp_cntr_modify_op {
+	IB_UVERBS_COMP_CNTR_MODIFY_OP_SET,
+	IB_UVERBS_COMP_CNTR_MODIFY_OP_INC,
+};
+
+enum ib_uverbs_qp_attach_comp_cntr_op {
+	IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_SEND = 1 << 0,
+	IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_RECV = 1 << 1,
+	IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_RDMA_READ = 1 << 2,
+	IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_READ = 1 << 3,
+	IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_RDMA_WRITE = 1 << 4,
+	IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_WRITE = 1 << 5,
+};
+
 #endif
diff --git a/kernel-headers/rdma/ib_user_verbs.h b/kernel-headers/rdma/ib_user_verbs.h
index d2aeadb..5653015 100644
--- a/kernel-headers/rdma/ib_user_verbs.h
+++ b/kernel-headers/rdma/ib_user_verbs.h
@@ -1379,4 +1379,37 @@
 	IB_UVERBS_RAW_PACKET_CAP_DELAY_DROP = 1 << 3,
 };
 
+/*
+ * struct ib_uverbs_clock_info - timecounter state shared with userspace
+ *
+ * Drivers that use a software timecounter over a free-running hardware
+ * cycle counter can map this page read-only into userspace, allowing
+ * conversion of hardware timestamps to system time without a syscall.
+ *
+ * Synchronization uses a sequence counter (@sign): the kernel sets bit 0
+ * before updating, then advances by 2 after. Userspace must retry the read
+ * if @sign is odd or changed during the read.
+ *
+ * @sign:            Sequence counter (bit 0 = update in progress)
+ * @resv:            Reserved
+ * @nsec:            Nanoseconds at last update
+ * @cycles:          Cycle counter value at last update
+ * @frac:            Fractional nanoseconds at last update
+ * @mult:            Cycle-to-nanosecond multiplier
+ * @shift:           Cycle-to-nanosecond shift
+ * @mask:            Cycle counter bitmask
+ * @overflow_period: Max interval (nsec) between reads before counter wraps
+ */
+struct ib_uverbs_clock_info {
+	__u32 sign;
+	__u32 resv;
+	__aligned_u64 nsec;
+	__aligned_u64 cycles;
+	__aligned_u64 frac;
+	__u32 mult;
+	__u32 shift;
+	__aligned_u64 mask;
+	__aligned_u64 overflow_period;
+};
+
 #endif /* IB_USER_VERBS_H */
diff --git a/kernel-headers/rdma/ionic-abi.h b/kernel-headers/rdma/ionic-abi.h
index 7b589d3..2c70ac1 100644
--- a/kernel-headers/rdma/ionic-abi.h
+++ b/kernel-headers/rdma/ionic-abi.h
@@ -48,6 +48,7 @@
 	__u8 expdb_qtypes;
 
 	__u8 rsvd2[3];
+	__aligned_u64 phc_offset;
 };
 
 struct ionic_qdesc {
diff --git a/kernel-headers/rdma/irdma-abi.h b/kernel-headers/rdma/irdma-abi.h
index 36f2080..38155af 100644
--- a/kernel-headers/rdma/irdma-abi.h
+++ b/kernel-headers/rdma/irdma-abi.h
@@ -88,6 +88,7 @@
 struct irdma_create_qp_req {
 	__aligned_u64 user_wqe_bufs;
 	__aligned_u64 user_compl_ctx;
+	__aligned_u64 legacy_dontuse[2];
 };
 
 struct irdma_mem_reg_req {
diff --git a/kernel-headers/rdma/mlx5-abi.h b/kernel-headers/rdma/mlx5-abi.h
index 8a6ad6c..a39226c 100644
--- a/kernel-headers/rdma/mlx5-abi.h
+++ b/kernel-headers/rdma/mlx5-abi.h
@@ -472,17 +472,10 @@
 	__u32	reserved;
 };
 
-struct mlx5_ib_clock_info {
-	__u32 sign;
-	__u32 resv;
-	__aligned_u64 nsec;
-	__aligned_u64 cycles;
-	__aligned_u64 frac;
-	__u32 mult;
-	__u32 shift;
-	__aligned_u64 mask;
-	__aligned_u64 overflow_period;
-};
+/*
+ * deprecated, see struct ib_uverbs_clock_info from ib_user_verbs.h
+ */
+#define mlx5_ib_clock_info ib_uverbs_clock_info
 
 enum mlx5_ib_mmap_cmd {
 	MLX5_IB_MMAP_REGULAR_PAGE               = 0,
diff --git a/kernel-headers/rdma/rdma_netlink.h b/kernel-headers/rdma/rdma_netlink.h
index aac9782..3af946e 100644
--- a/kernel-headers/rdma/rdma_netlink.h
+++ b/kernel-headers/rdma/rdma_netlink.h
@@ -605,6 +605,11 @@
 	RDMA_NLDEV_ATTR_FRMR_POOL_KEY_KERNEL_VENDOR_KEY,	/* u64 */
 
 	/*
+	 * Resource summary entry maximum value.
+	 */
+	RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_MAX,		/* u64 */
+
+	/*
 	 * Always the end
 	 */
 	RDMA_NLDEV_ATTR_MAX
diff --git a/libibverbs/CMakeLists.txt b/libibverbs/CMakeLists.txt
index eaf25a1..6fd823e 100644
--- a/libibverbs/CMakeLists.txt
+++ b/libibverbs/CMakeLists.txt
@@ -27,6 +27,7 @@
   cmd.c
   cmd_ah.c
   cmd_counters.c
+  cmd_comp_cntr.c
   cmd_cq.c
   cmd_device.c
   cmd_dm.c
diff --git a/libibverbs/cmd_comp_cntr.c b/libibverbs/cmd_comp_cntr.c
new file mode 100644
index 0000000..cc989f5
--- /dev/null
+++ b/libibverbs/cmd_comp_cntr.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0 or Linux-OpenIB
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All rights reserved.
+ */
+
+#include <infiniband/cmd_write.h>
+
+int ibv_cmd_create_comp_cntr(struct ibv_context *context,
+			     struct ibv_comp_cntr *comp_cntr,
+			     struct ibv_command_buffer *link)
+{
+	DECLARE_COMMAND_BUFFER_LINK(cmdb, UVERBS_OBJECT_COMP_CNTR,
+				    UVERBS_METHOD_COMP_CNTR_CREATE, 1, link);
+	struct ib_uverbs_attr *handle;
+	int ret;
+
+	comp_cntr->context = context;
+
+	handle = fill_attr_out_obj(cmdb, UVERBS_ATTR_CREATE_COMP_CNTR_HANDLE);
+
+	ret = execute_ioctl(context, cmdb);
+	if (ret)
+		return errno;
+
+	comp_cntr->handle = read_attr_obj(UVERBS_ATTR_CREATE_COMP_CNTR_HANDLE, handle);
+	return 0;
+}
+
+int ibv_cmd_destroy_comp_cntr(struct ibv_comp_cntr *comp_cntr)
+{
+	DECLARE_COMMAND_BUFFER(cmdb, UVERBS_OBJECT_COMP_CNTR, UVERBS_METHOD_COMP_CNTR_DESTROY, 1);
+	int ret;
+
+	fill_attr_in_obj(cmdb, UVERBS_ATTR_DESTROY_COMP_CNTR_HANDLE, comp_cntr->handle);
+
+	ret = execute_ioctl(comp_cntr->context, cmdb);
+	if (verbs_is_destroy_err(&ret))
+		return ret;
+
+	return 0;
+}
+
+static int ibv_icmd_modify_comp_cntr(struct ibv_comp_cntr *comp_cntr,
+				     uint8_t entry, uint8_t op, uint64_t value)
+{
+	DECLARE_COMMAND_BUFFER(cmdb, UVERBS_OBJECT_COMP_CNTR, UVERBS_METHOD_COMP_CNTR_MODIFY, 4);
+
+	fill_attr_in_obj(cmdb, UVERBS_ATTR_MODIFY_COMP_CNTR_HANDLE, comp_cntr->handle);
+	fill_attr_const_in(cmdb, UVERBS_ATTR_MODIFY_COMP_CNTR_ENTRY, entry);
+	fill_attr_const_in(cmdb, UVERBS_ATTR_MODIFY_COMP_CNTR_OP, op);
+	fill_attr_in_uint64(cmdb, UVERBS_ATTR_MODIFY_COMP_CNTR_VALUE, value);
+
+	return execute_ioctl(comp_cntr->context, cmdb);
+}
+
+int ibv_cmd_set_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t value)
+{
+	return ibv_icmd_modify_comp_cntr(comp_cntr, IB_UVERBS_COMP_CNTR_ENTRY_COMP,
+					 IB_UVERBS_COMP_CNTR_MODIFY_OP_SET, value);
+}
+
+int ibv_cmd_set_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t value)
+{
+	return ibv_icmd_modify_comp_cntr(comp_cntr, IB_UVERBS_COMP_CNTR_ENTRY_ERR,
+					 IB_UVERBS_COMP_CNTR_MODIFY_OP_SET, value);
+}
+
+int ibv_cmd_inc_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t amount)
+{
+	return ibv_icmd_modify_comp_cntr(comp_cntr, IB_UVERBS_COMP_CNTR_ENTRY_COMP,
+					 IB_UVERBS_COMP_CNTR_MODIFY_OP_INC, amount);
+}
+
+int ibv_cmd_inc_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t amount)
+{
+	return ibv_icmd_modify_comp_cntr(comp_cntr, IB_UVERBS_COMP_CNTR_ENTRY_ERR,
+					 IB_UVERBS_COMP_CNTR_MODIFY_OP_INC, amount);
+}
+
+static int ibv_icmd_read_comp_cntr(struct ibv_comp_cntr *comp_cntr,
+				   uint8_t entry, uint64_t *value)
+{
+	DECLARE_COMMAND_BUFFER(cmdb, UVERBS_OBJECT_COMP_CNTR, UVERBS_METHOD_COMP_CNTR_READ, 3);
+
+	fill_attr_in_obj(cmdb, UVERBS_ATTR_READ_COMP_CNTR_HANDLE, comp_cntr->handle);
+	fill_attr_const_in(cmdb, UVERBS_ATTR_READ_COMP_CNTR_ENTRY, entry);
+	fill_attr_out_ptr(cmdb, UVERBS_ATTR_READ_COMP_CNTR_RESP_VALUE, value);
+
+	return execute_ioctl(comp_cntr->context, cmdb);
+}
+
+int ibv_cmd_read_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t *value)
+{
+	return ibv_icmd_read_comp_cntr(comp_cntr, IB_UVERBS_COMP_CNTR_ENTRY_COMP, value);
+}
+
+int ibv_cmd_read_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t *value)
+{
+	return ibv_icmd_read_comp_cntr(comp_cntr, IB_UVERBS_COMP_CNTR_ENTRY_ERR, value);
+}
+
+int ibv_cmd_qp_attach_comp_cntr(struct ibv_qp *qp, struct ibv_comp_cntr *comp_cntr,
+				struct ibv_qp_attach_comp_cntr_attr *attr)
+{
+	DECLARE_COMMAND_BUFFER(cmdb, UVERBS_OBJECT_QP, UVERBS_METHOD_QP_ATTACH_COMP_CNTR, 3);
+	uint32_t op_mask = 0;
+
+	fill_attr_in_obj(cmdb, UVERBS_ATTR_QP_ATTACH_COMP_CNTR_HANDLE, qp->handle);
+	fill_attr_in_obj(cmdb, UVERBS_ATTR_QP_ATTACH_COMP_CNTR_CNTR_HANDLE, comp_cntr->handle);
+
+	if (attr->op_mask & IBV_QP_ATTACH_COMP_CNTR_OP_SEND)
+		op_mask |= IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_SEND;
+	if (attr->op_mask & IBV_QP_ATTACH_COMP_CNTR_OP_RECV)
+		op_mask |= IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_RECV;
+	if (attr->op_mask & IBV_QP_ATTACH_COMP_CNTR_OP_RDMA_READ)
+		op_mask |= IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_RDMA_READ;
+	if (attr->op_mask & IBV_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_READ)
+		op_mask |= IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_READ;
+	if (attr->op_mask & IBV_QP_ATTACH_COMP_CNTR_OP_RDMA_WRITE)
+		op_mask |= IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_RDMA_WRITE;
+	if (attr->op_mask & IBV_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_WRITE)
+		op_mask |= IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_WRITE;
+
+	fill_attr_in_uint32(cmdb, UVERBS_ATTR_QP_ATTACH_COMP_CNTR_OP_MASK, op_mask);
+
+	return execute_ioctl(qp->context, cmdb);
+}
diff --git a/libibverbs/cmd_device.c b/libibverbs/cmd_device.c
index ba22372..a7b4072 100644
--- a/libibverbs/cmd_device.c
+++ b/libibverbs/cmd_device.c
@@ -698,3 +698,43 @@
 
 	return 0;
 }
+
+int ibv_cmd_query_comp_cntr_caps(struct ibv_context *context,
+				 struct ibv_comp_cntr_caps *caps,
+				 size_t caps_size)
+{
+	DECLARE_COMMAND_BUFFER(cmdb, UVERBS_OBJECT_DEVICE,
+			       UVERBS_METHOD_QUERY_COMP_CNTR_CAPS, 3);
+	uint32_t supported_qp_attach_ops;
+	int ret;
+
+	if (caps_size < sizeof(struct ibv_comp_cntr_caps))
+		return EINVAL;
+
+	fill_attr_out_ptr(cmdb, UVERBS_ATTR_QUERY_COMP_CNTR_CAPS_MAX_COUNTERS,
+			  &caps->max_counters);
+	fill_attr_out_ptr(cmdb, UVERBS_ATTR_QUERY_COMP_CNTR_CAPS_MAX_VALUE,
+			  &caps->max_value);
+	fill_attr_out_ptr(cmdb, UVERBS_ATTR_QUERY_COMP_CNTR_CAPS_SUPPORTED_QP_ATTACH_OPS,
+			  &supported_qp_attach_ops);
+
+	ret = execute_ioctl(context, cmdb);
+	if (ret)
+		return ret;
+
+	caps->supported_qp_attach_ops = 0;
+	if (supported_qp_attach_ops & IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_SEND)
+		caps->supported_qp_attach_ops |= IBV_QP_ATTACH_COMP_CNTR_OP_SEND;
+	if (supported_qp_attach_ops & IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_RECV)
+		caps->supported_qp_attach_ops |= IBV_QP_ATTACH_COMP_CNTR_OP_RECV;
+	if (supported_qp_attach_ops & IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_RDMA_READ)
+		caps->supported_qp_attach_ops |= IBV_QP_ATTACH_COMP_CNTR_OP_RDMA_READ;
+	if (supported_qp_attach_ops & IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_READ)
+		caps->supported_qp_attach_ops |= IBV_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_READ;
+	if (supported_qp_attach_ops & IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_RDMA_WRITE)
+		caps->supported_qp_attach_ops |= IBV_QP_ATTACH_COMP_CNTR_OP_RDMA_WRITE;
+	if (supported_qp_attach_ops & IB_UVERBS_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_WRITE)
+		caps->supported_qp_attach_ops |= IBV_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_WRITE;
+
+	return 0;
+}
diff --git a/libibverbs/driver.h b/libibverbs/driver.h
index ec72c77..1f4ca37 100644
--- a/libibverbs/driver.h
+++ b/libibverbs/driver.h
@@ -391,6 +391,8 @@
 	void (*cq_event)(struct ibv_cq *cq);
 	struct ibv_ah *(*create_ah)(struct ibv_pd *pd,
 				    struct ibv_ah_attr *attr);
+	struct ibv_comp_cntr *(*create_comp_cntr)(struct ibv_context *context,
+						  struct ibv_comp_cntr_init_attr *attr);
 	struct ibv_counters *(*create_counters)(struct ibv_context *context,
 						struct ibv_counters_init_attr *init_attr);
 	struct ibv_cq *(*create_cq)(struct ibv_context *context, int cqe,
@@ -424,6 +426,7 @@
 	int (*dealloc_td)(struct ibv_td *td);
 	int (*dereg_mr)(struct verbs_mr *vmr);
 	int (*destroy_ah)(struct ibv_ah *ah);
+	int (*destroy_comp_cntr)(struct ibv_comp_cntr *comp_cntr);
 	int (*destroy_counters)(struct ibv_counters *counters);
 	int (*destroy_cq)(struct ibv_cq *cq);
 	int (*destroy_flow)(struct ibv_flow *flow);
@@ -445,6 +448,9 @@
 				    uint32_t mr_handle);
 	struct ibv_pd *(*import_pd)(struct ibv_context *context,
 				    uint32_t pd_handle);
+	int (*inc_comp_cntr)(struct ibv_comp_cntr *comp_cntr, uint64_t amount);
+	int (*inc_err_comp_cntr)(struct ibv_comp_cntr *comp_cntr,
+				 uint64_t amount);
 	int (*modify_cq)(struct ibv_cq *cq, struct ibv_modify_cq_attr *attr);
 	int (*modify_flow_action_esp)(struct ibv_flow_action *action,
 				      struct ibv_flow_action_esp_attr *attr);
@@ -469,6 +475,12 @@
 			    struct ibv_ops_wr **bad_op);
 	int (*post_srq_recv)(struct ibv_srq *srq, struct ibv_recv_wr *recv_wr,
 			     struct ibv_recv_wr **bad_recv_wr);
+	int (*qp_attach_comp_cntr)(struct ibv_qp *qp,
+				   struct ibv_comp_cntr *comp_cntr,
+				   struct ibv_qp_attach_comp_cntr_attr *attr);
+	int (*query_comp_cntr_caps)(struct ibv_context *context,
+				    struct ibv_comp_cntr_caps *caps,
+				    size_t caps_size);
 	int (*query_device_ex)(struct ibv_context *context,
 			       const struct ibv_query_device_ex_input *input,
 			       struct ibv_device_attr_ex *attr,
@@ -485,10 +497,13 @@
 	int (*query_rt_values)(struct ibv_context *context,
 			       struct ibv_values_ex *values);
 	int (*query_srq)(struct ibv_srq *srq, struct ibv_srq_attr *srq_attr);
+	int (*read_comp_cntr)(struct ibv_comp_cntr *comp_cntr, uint64_t *value);
 	int (*read_counters)(struct ibv_counters *counters,
 			     uint64_t *counters_value,
 			     uint32_t ncounters,
 			     uint32_t flags);
+	int (*read_err_comp_cntr)(struct ibv_comp_cntr *comp_cntr,
+				  uint64_t *value);
 	struct ibv_mr *(*reg_dm_mr)(struct ibv_pd *pd, struct ibv_dm *dm,
 				    uint64_t dm_offset, size_t length,
 				    unsigned int access);
@@ -503,7 +518,10 @@
 	int (*rereg_mr)(struct verbs_mr *vmr, int flags, struct ibv_pd *pd,
 			void *addr, size_t length, int access);
 	int (*resize_cq)(struct ibv_cq *cq, int cqe);
+	int (*set_comp_cntr)(struct ibv_comp_cntr *comp_cntr, uint64_t value);
 	int (*set_ece)(struct ibv_qp *qp, struct ibv_ece *ece);
+	int (*set_err_comp_cntr)(struct ibv_comp_cntr *comp_cntr,
+				 uint64_t value);
 	void (*unimport_dm)(struct ibv_dm *dm);
 	void (*unimport_mr)(struct ibv_mr *mr);
 	void (*unimport_pd)(struct ibv_pd *pd);
@@ -777,6 +795,24 @@
 int ibv_cmd_alloc_dmah(struct ibv_context *ctx, struct verbs_dmah *st,
 		       struct ibv_dmah_init_attr *attr);
 int ibv_cmd_free_dmah(struct verbs_dmah *dmah);
+
+int ibv_cmd_query_comp_cntr_caps(struct ibv_context *context,
+				 struct ibv_comp_cntr_caps *caps,
+				 size_t caps_size);
+int ibv_cmd_create_comp_cntr(struct ibv_context *context,
+			     struct ibv_comp_cntr *comp_cntr,
+			     struct ibv_command_buffer *link);
+int ibv_cmd_destroy_comp_cntr(struct ibv_comp_cntr *comp_cntr);
+int ibv_cmd_inc_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t amount);
+int ibv_cmd_inc_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t amount);
+int ibv_cmd_read_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t *value);
+int ibv_cmd_read_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t *value);
+int ibv_cmd_set_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t value);
+int ibv_cmd_set_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t value);
+int ibv_cmd_qp_attach_comp_cntr(struct ibv_qp *qp,
+				struct ibv_comp_cntr *comp_cntr,
+				struct ibv_qp_attach_comp_cntr_attr *attr);
+
 int ibv_cmd_reg_dm_mr(struct ibv_pd *pd, struct verbs_dm *dm,
 		      uint64_t offset, size_t length,
 		      unsigned int access, struct verbs_mr *vmr,
diff --git a/libibverbs/dummy_ops.c b/libibverbs/dummy_ops.c
index a27e229..3eacff3 100644
--- a/libibverbs/dummy_ops.c
+++ b/libibverbs/dummy_ops.c
@@ -161,6 +161,13 @@
 	return NULL;
 }
 
+static struct ibv_comp_cntr *create_comp_cntr(struct ibv_context *context,
+					      struct ibv_comp_cntr_init_attr *attr)
+{
+	errno = EOPNOTSUPP;
+	return NULL;
+}
+
 static struct ibv_counters *create_counters(struct ibv_context *context,
 					    struct ibv_counters_init_attr *init_attr)
 {
@@ -271,6 +278,11 @@
 	return EOPNOTSUPP;
 }
 
+static int destroy_comp_cntr(struct ibv_comp_cntr *comp_cntr)
+{
+	return EOPNOTSUPP;
+}
+
 static int destroy_counters(struct ibv_counters *counters)
 {
 	return EOPNOTSUPP;
@@ -359,6 +371,16 @@
 	return NULL;
 }
 
+static int inc_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t amount)
+{
+	return EOPNOTSUPP;
+}
+
+static int inc_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t amount)
+{
+	return EOPNOTSUPP;
+}
+
 static int modify_cq(struct ibv_cq *cq, struct ibv_modify_cq_attr *attr)
 {
 	return EOPNOTSUPP;
@@ -435,6 +457,20 @@
 	return EOPNOTSUPP;
 }
 
+static int qp_attach_comp_cntr(struct ibv_qp *qp,
+			       struct ibv_comp_cntr *comp_cntr,
+			       struct ibv_qp_attach_comp_cntr_attr *attr)
+{
+	return EOPNOTSUPP;
+}
+
+static int query_comp_cntr_caps(struct ibv_context *context,
+				struct ibv_comp_cntr_caps *caps,
+				size_t caps_size)
+{
+	return EOPNOTSUPP;
+}
+
 static int query_device_ex(struct ibv_context *context,
 			   const struct ibv_query_device_ex_input *input,
 			   struct ibv_device_attr_ex *attr, size_t attr_size)
@@ -482,6 +518,11 @@
 	return EOPNOTSUPP;
 }
 
+static int read_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t *value)
+{
+	return EOPNOTSUPP;
+}
+
 static int read_counters(struct ibv_counters *counters,
 			 uint64_t *counters_value,
 			 uint32_t ncounters,
@@ -490,6 +531,11 @@
 	return EOPNOTSUPP;
 }
 
+static int read_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t *value)
+{
+	return EOPNOTSUPP;
+}
+
 static struct ibv_mr *reg_dm_mr(struct ibv_pd *pd, struct ibv_dm *dm,
 				uint64_t dm_offset, size_t length,
 				unsigned int access)
@@ -578,11 +624,21 @@
 	return EOPNOTSUPP;
 }
 
+static int set_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t value)
+{
+	return EOPNOTSUPP;
+}
+
 static int set_ece(struct ibv_qp *qp, struct ibv_ece *ece)
 {
 	return EOPNOTSUPP;
 }
 
+static int set_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t value)
+{
+	return EOPNOTSUPP;
+}
+
 static void unimport_dm(struct ibv_dm *dm)
 {
 }
@@ -623,6 +679,7 @@
 	close_xrcd,
 	cq_event,
 	create_ah,
+	create_comp_cntr,
 	create_counters,
 	create_cq,
 	create_cq_ex,
@@ -640,6 +697,7 @@
 	dealloc_td,
 	dereg_mr,
 	destroy_ah,
+	destroy_comp_cntr,
 	destroy_counters,
 	destroy_cq,
 	destroy_flow,
@@ -657,6 +715,8 @@
 	import_dm,
 	import_mr,
 	import_pd,
+	inc_comp_cntr,
+	inc_err_comp_cntr,
 	modify_cq,
 	modify_flow_action_esp,
 	modify_qp,
@@ -670,6 +730,8 @@
 	post_send,
 	post_srq_ops,
 	post_srq_recv,
+	qp_attach_comp_cntr,
+	query_comp_cntr_caps,
 	query_device_ex,
 	query_ece,
 	query_port,
@@ -678,7 +740,9 @@
 	query_qp_data_in_order,
 	query_rt_values,
 	query_srq,
+	read_comp_cntr,
 	read_counters,
+	read_err_comp_cntr,
 	reg_dm_mr,
 	reg_dmabuf_mr,
 	reg_mr,
@@ -686,7 +750,9 @@
 	req_notify_cq,
 	rereg_mr,
 	resize_cq,
+	set_comp_cntr,
 	set_ece,
+	set_err_comp_cntr,
 	unimport_dm,
 	unimport_mr,
 	unimport_pd,
@@ -758,6 +824,7 @@
 	SET_PRIV_OP(ctx, create_ah);
 	SET_PRIV_OP(ctx, create_cq);
 	SET_PRIV_OP_IC(vctx, create_cq_ex);
+	SET_PRIV_OP_IC(vctx, create_comp_cntr);
 	SET_OP2(vctx, ibv_create_flow, create_flow);
 	SET_OP(vctx, create_flow_action_esp);
 	SET_PRIV_OP(ctx, create_qp);
@@ -773,6 +840,7 @@
 	SET_OP(vctx, destroy_counters);
 	SET_PRIV_OP(ctx, dereg_mr);
 	SET_PRIV_OP(ctx, destroy_ah);
+	SET_PRIV_OP_IC(vctx, destroy_comp_cntr);
 	SET_PRIV_OP(ctx, destroy_cq);
 	SET_OP2(vctx, ibv_destroy_flow, destroy_flow);
 	SET_OP(vctx, destroy_flow_action);
@@ -789,6 +857,8 @@
 	SET_PRIV_OP_IC(vctx, import_dm);
 	SET_PRIV_OP_IC(vctx, import_mr);
 	SET_PRIV_OP_IC(vctx, import_pd);
+	SET_PRIV_OP_IC(vctx, inc_comp_cntr);
+	SET_PRIV_OP_IC(vctx, inc_err_comp_cntr);
 	SET_OP(vctx, modify_cq);
 	SET_OP(vctx, modify_flow_action_esp);
 	SET_PRIV_OP(ctx, modify_qp);
@@ -802,6 +872,8 @@
 	SET_OP(ctx, post_send);
 	SET_OP(vctx, post_srq_ops);
 	SET_OP(ctx, post_srq_recv);
+	SET_PRIV_OP_IC(vctx, qp_attach_comp_cntr);
+	SET_PRIV_OP_IC(ctx, query_comp_cntr_caps);
 	SET_OP(vctx, query_device_ex);
 	SET_PRIV_OP_IC(vctx, query_ece);
 	SET_PRIV_OP_IC(ctx, query_port);
@@ -809,7 +881,9 @@
 	SET_PRIV_OP(ctx, query_qp);
 	SET_PRIV_OP_IC(ctx, query_qp_data_in_order);
 	SET_OP(vctx, query_rt_values);
+	SET_PRIV_OP_IC(vctx, read_comp_cntr);
 	SET_OP(vctx, read_counters);
+	SET_PRIV_OP_IC(vctx, read_err_comp_cntr);
 	SET_PRIV_OP(ctx, query_srq);
 	SET_OP(vctx, reg_dm_mr);
 	SET_PRIV_OP_IC(vctx, reg_dmabuf_mr);
@@ -818,7 +892,9 @@
 	SET_OP(ctx, req_notify_cq);
 	SET_PRIV_OP(ctx, rereg_mr);
 	SET_PRIV_OP(ctx, resize_cq);
+	SET_PRIV_OP_IC(vctx, set_comp_cntr);
 	SET_PRIV_OP_IC(vctx, set_ece);
+	SET_PRIV_OP_IC(vctx, set_err_comp_cntr);
 	SET_PRIV_OP_IC(vctx, unimport_dm);
 	SET_PRIV_OP_IC(vctx, unimport_mr);
 	SET_PRIV_OP_IC(vctx, unimport_pd);
diff --git a/libibverbs/examples/devinfo.c b/libibverbs/examples/devinfo.c
index 1892ccd..7202284 100644
--- a/libibverbs/examples/devinfo.c
+++ b/libibverbs/examples/devinfo.c
@@ -507,10 +507,28 @@
 		printf("\t\t\t\t\tDelay drop\n");
 }
 
+static void print_comp_cntr_attach_ops(uint32_t ops)
+{
+	printf("\tcomp_cntr_caps:\n");
+	if (ops & IBV_QP_ATTACH_COMP_CNTR_OP_SEND)
+		printf("\t\t\t\t\tQP_ATTACH_SEND\n");
+	if (ops & IBV_QP_ATTACH_COMP_CNTR_OP_RECV)
+		printf("\t\t\t\t\tQP_ATTACH_RECV\n");
+	if (ops & IBV_QP_ATTACH_COMP_CNTR_OP_RDMA_READ)
+		printf("\t\t\t\t\tQP_ATTACH_RDMA_READ\n");
+	if (ops & IBV_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_READ)
+		printf("\t\t\t\t\tQP_ATTACH_REMOTE_RDMA_READ\n");
+	if (ops & IBV_QP_ATTACH_COMP_CNTR_OP_RDMA_WRITE)
+		printf("\t\t\t\t\tQP_ATTACH_RDMA_WRITE\n");
+	if (ops & IBV_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_WRITE)
+		printf("\t\t\t\t\tQP_ATTACH_REMOTE_RDMA_WRITE\n");
+}
+
 static int print_hca_cap(struct ibv_device *ib_dev, uint8_t ib_port)
 {
 	struct ibv_context *ctx;
 	struct ibv_device_attr_ex device_attr = {};
+	struct ibv_comp_cntr_caps cc_caps = {};
 	struct ibv_port_attr port_attr;
 	uint64_t port_speed;
 	int rc = 0;
@@ -592,6 +610,10 @@
 			printf("\tmax_srq_sge:\t\t\t%d\n", device_attr.orig_attr.max_srq_sge);
 		}
 		printf("\tmax_pkeys:\t\t\t%d\n", device_attr.orig_attr.max_pkeys);
+		if (!ibv_query_comp_cntr_caps(ctx, &cc_caps)) {
+			printf("\tmax_comp_cntr:\t\t\t%d\n", cc_caps.max_counters);
+			print_comp_cntr_attach_ops(cc_caps.supported_qp_attach_ops);
+		}
 		printf("\tlocal_ca_ack_delay:\t\t%d\n", device_attr.orig_attr.local_ca_ack_delay);
 
 		print_odp_caps(&device_attr);
diff --git a/libibverbs/libibverbs.map.in b/libibverbs/libibverbs.map.in
index 7329bce..e7a1598 100644
--- a/libibverbs/libibverbs.map.in
+++ b/libibverbs/libibverbs.map.in
@@ -186,7 +186,17 @@
 
 IBVERBS_1.18 {
 	global:
+		_ibv_query_comp_cntr_caps;
+		ibv_create_comp_cntr;
+		ibv_destroy_comp_cntr;
 		ibv_export_buf_dmabuf_fd;
+		ibv_inc_comp_cntr;
+		ibv_inc_err_comp_cntr;
+		ibv_qp_attach_comp_cntr;
+		ibv_read_comp_cntr;
+		ibv_read_err_comp_cntr;
+		ibv_set_comp_cntr;
+		ibv_set_err_comp_cntr;
 } IBVERBS_1.17;
 
 /* If any symbols in this stanza change ABI then the entire staza gets a new symbol
@@ -207,6 +217,7 @@
 		ibv_cmd_attach_mcast;
 		ibv_cmd_close_xrcd;
 		ibv_cmd_create_ah;
+		ibv_cmd_create_comp_cntr;
 		ibv_cmd_create_counters;
 		ibv_cmd_create_cq;
 		ibv_cmd_create_cq_ex;
@@ -224,6 +235,7 @@
 		ibv_cmd_dealloc_pd;
 		ibv_cmd_dereg_mr;
 		ibv_cmd_destroy_ah;
+		ibv_cmd_destroy_comp_cntr;
 		ibv_cmd_destroy_counters;
 		ibv_cmd_destroy_cq;
 		ibv_cmd_destroy_flow;
@@ -237,6 +249,8 @@
 		ibv_cmd_free_dm;
 		ibv_cmd_free_dmah;
 		ibv_cmd_get_context;
+		ibv_cmd_inc_comp_cntr;
+		ibv_cmd_inc_err_comp_cntr;
 		ibv_cmd_modify_cq;
 		ibv_cmd_modify_flow_action_esp;
 		ibv_cmd_modify_qp;
@@ -249,6 +263,8 @@
 		ibv_cmd_post_recv;
 		ibv_cmd_post_send;
 		ibv_cmd_post_srq_recv;
+		ibv_cmd_qp_attach_comp_cntr;
+		ibv_cmd_query_comp_cntr_caps;
 		ibv_cmd_query_context;
 		ibv_cmd_query_device_any;
 		ibv_cmd_query_mr;
@@ -256,7 +272,9 @@
 		ibv_cmd_query_port_speed;
 		ibv_cmd_query_qp;
 		ibv_cmd_query_srq;
+		ibv_cmd_read_comp_cntr;
 		ibv_cmd_read_counters;
+		ibv_cmd_read_err_comp_cntr;
 		ibv_cmd_reg_dm_mr;
 		ibv_cmd_reg_dmabuf_mr;
 		ibv_cmd_reg_mr;
@@ -264,6 +282,8 @@
 		ibv_cmd_req_notify_cq;
 		ibv_cmd_rereg_mr;
 		ibv_cmd_resize_cq;
+		ibv_cmd_set_comp_cntr;
+		ibv_cmd_set_err_comp_cntr;
 		ibv_dmabuf_heap_alloc;
 		ibv_dmabuf_heap_cc_shared_init;
 		ibv_dmabuf_heap_destroy;
diff --git a/libibverbs/man/CMakeLists.txt b/libibverbs/man/CMakeLists.txt
index f8eca86..0efe0b0 100644
--- a/libibverbs/man/CMakeLists.txt
+++ b/libibverbs/man/CMakeLists.txt
@@ -15,6 +15,7 @@
   ibv_create_ah.3
   ibv_create_ah_from_wc.3
   ibv_create_comp_channel.3
+  ibv_create_comp_cntr.3.md
   ibv_create_counters.3.md
   ibv_create_cq.3
   ibv_create_cq_ex.3
@@ -58,6 +59,8 @@
   ibv_post_send.3
   ibv_post_srq_ops.3
   ibv_post_srq_recv.3
+  ibv_qp_attach_comp_cntr.3.md
+  ibv_query_comp_cntr_caps.3.md
   ibv_query_device.3
   ibv_query_device_ex.3
   ibv_query_ece.3.md
@@ -102,6 +105,13 @@
   ibv_create_ah.3 ibv_destroy_ah.3
   ibv_create_ah_from_wc.3 ibv_init_ah_from_wc.3
   ibv_create_comp_channel.3 ibv_destroy_comp_channel.3
+  ibv_create_comp_cntr.3 ibv_destroy_comp_cntr.3
+  ibv_create_comp_cntr.3 ibv_set_comp_cntr.3
+  ibv_create_comp_cntr.3 ibv_set_err_comp_cntr.3
+  ibv_create_comp_cntr.3 ibv_inc_comp_cntr.3
+  ibv_create_comp_cntr.3 ibv_inc_err_comp_cntr.3
+  ibv_create_comp_cntr.3 ibv_read_comp_cntr.3
+  ibv_create_comp_cntr.3 ibv_read_err_comp_cntr.3
   ibv_create_counters.3 ibv_destroy_counters.3
   ibv_create_cq.3 ibv_destroy_cq.3
   ibv_create_flow.3 ibv_destroy_flow.3
diff --git a/libibverbs/man/ibv_create_comp_cntr.3.md b/libibverbs/man/ibv_create_comp_cntr.3.md
new file mode 100644
index 0000000..aa5ce43
--- /dev/null
+++ b/libibverbs/man/ibv_create_comp_cntr.3.md
@@ -0,0 +1,180 @@
+---
+date: 2026-02-09
+footer: libibverbs
+header: "Libibverbs Programmer's Manual"
+layout: page
+license: 'Licensed under the OpenIB.org BSD license (FreeBSD Variant) - See COPYING.md'
+section: 3
+title: ibv_create_comp_cntr
+tagline: Verbs
+---
+
+# NAME
+
+**ibv_create_comp_cntr**, **ibv_destroy_comp_cntr** - Create or destroy a
+completion counter
+
+**ibv_set_comp_cntr**, **ibv_set_err_comp_cntr** - Set the value of a
+completion or error counter
+
+**ibv_inc_comp_cntr**, **ibv_inc_err_comp_cntr** - Increment a completion or
+error counter
+
+**ibv_read_comp_cntr**, **ibv_read_err_comp_cntr** - Read the value of a
+completion or error counter
+
+# SYNOPSIS
+
+```c
+#include <infiniband/verbs.h>
+
+struct ibv_comp_cntr *ibv_create_comp_cntr(struct ibv_context *context,
+                                           struct ibv_comp_cntr_init_attr *cc_attr);
+
+int ibv_destroy_comp_cntr(struct ibv_comp_cntr *comp_cntr);
+
+int ibv_set_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t value);
+int ibv_set_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t value);
+int ibv_inc_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t amount);
+int ibv_inc_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t amount);
+int ibv_read_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t *value);
+int ibv_read_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t *value);
+```
+
+# DESCRIPTION
+
+Completion counters provide a lightweight completion mechanism as an
+alternative or extension to completion queues (CQs). Rather than generating
+individual completion queue entries, a completion counter tracks the aggregate
+number of completed operations. This makes them well suited for applications
+that need to know how many requests have completed without requiring
+per-request details, such as credit based flow control or tracking responses
+from remote peers.
+
+Each completion counter maintains two distinct 64-bit values: a completion
+count that is incremented on successful completions, and an error count that
+is incremented when operations complete in error.
+
+**ibv_create_comp_cntr**() allocates a new completion counter for the RDMA
+device context *context*. The properties of the counter are defined by
+*cc_attr*. The maximum number of completion counters a device supports is
+reported by **ibv_query_comp_cntr_caps**(3).
+
+**ibv_destroy_comp_cntr**() releases all resources associated with the
+completion counter *comp_cntr*. The counter must not be attached to any QP
+when destroyed.
+
+**ibv_set_comp_cntr**() sets the completion count of *comp_cntr* to *value*.
+
+**ibv_set_err_comp_cntr**() sets the error count of *comp_cntr* to *value*.
+
+**ibv_inc_comp_cntr**() increments the completion count of *comp_cntr* by
+*amount*.
+
+**ibv_inc_err_comp_cntr**() increments the error count of *comp_cntr* by
+*amount*.
+
+**ibv_read_comp_cntr**() reads the current completion count of *comp_cntr*
+into *value*.
+
+**ibv_read_err_comp_cntr**() reads the current error count of *comp_cntr*
+into *value*.
+
+# ARGUMENTS
+
+## ibv_comp_cntr
+
+```c
+struct ibv_comp_cntr {
+	struct ibv_context *context;
+	uint32_t handle;
+};
+```
+
+*context*
+:	Device context associated with the completion counter.
+
+*handle*
+:	Kernel object handle for the completion counter.
+
+## ibv_comp_cntr_init_attr
+
+```c
+enum ibv_comp_cntr_type {
+	IBV_COMP_CNTR_TYPE_WRS,
+	IBV_COMP_CNTR_TYPE_BYTES,
+};
+
+struct ibv_comp_cntr_init_attr {
+	uint32_t comp_mask;
+	enum ibv_comp_cntr_type type;
+	uint32_t flags;
+};
+```
+
+*comp_mask*
+:	Bitmask specifying what fields in the structure are valid.
+
+*type*
+:	The counting mode for the completion counter. Not all devices support
+	all modes.
+	**IBV_COMP_CNTR_TYPE_WRS** counts completed work requests (default).
+	**IBV_COMP_CNTR_TYPE_BYTES** counts completed bytes.
+
+*flags*
+:	Reserved for future use, for now must be 0.
+
+# RETURN VALUE
+
+**ibv_create_comp_cntr**() returns a pointer to the allocated ibv_comp_cntr
+object, or NULL if the request fails (and sets errno to indicate the failure
+reason).
+
+**ibv_destroy_comp_cntr**(), **ibv_set_comp_cntr**(),
+**ibv_set_err_comp_cntr**(), **ibv_inc_comp_cntr**(),
+**ibv_inc_err_comp_cntr**(), **ibv_read_comp_cntr**(), and
+**ibv_read_err_comp_cntr**() return 0 on success, or the value of errno on
+failure (which indicates the failure reason).
+
+# ERRORS
+
+ENOTSUP
+:	Completion counters are not supported on this device, or the
+	requested operation is not supported for the given counter
+	configuration.
+
+ENOMEM
+:	Not enough resources to create the completion counter.
+
+EINVAL
+:	Invalid argument(s) passed.
+
+EBUSY
+:	The completion counter is still attached to a QP
+	(**ibv_destroy_comp_cntr**() only).
+
+# NOTES
+
+Counter values must only be updated using **ibv_set_comp_cntr**(),
+**ibv_set_err_comp_cntr**(), **ibv_inc_comp_cntr**(), or
+**ibv_inc_err_comp_cntr**().
+
+Updates made to counter values (e.g. via **ibv_set_comp_cntr**() or
+**ibv_inc_comp_cntr**()) may not be immediately visible when reading the
+counter via **ibv_read_comp_cntr**() or **ibv_read_err_comp_cntr**(). A small
+delay may occur between the update and the observed value. However, the final
+updated value will eventually be reflected.
+
+Applications should ensure that the counter value is stable before calling
+**ibv_set_comp_cntr**() or **ibv_set_err_comp_cntr**(). Otherwise, concurrent
+updates may be lost.
+
+# SEE ALSO
+
+**ibv_query_comp_cntr_caps**(3), **ibv_qp_attach_comp_cntr**(3),
+**efadv_create_comp_cntr**(3), **ibv_create_cq**(3), **ibv_create_cq_ex**(3),
+**ibv_create_qp**(3)
+
+# AUTHORS
+
+Michael Margolin <mrgolin@amazon.com>
diff --git a/libibverbs/man/ibv_qp_attach_comp_cntr.3.md b/libibverbs/man/ibv_qp_attach_comp_cntr.3.md
new file mode 100644
index 0000000..e7f2eef
--- /dev/null
+++ b/libibverbs/man/ibv_qp_attach_comp_cntr.3.md
@@ -0,0 +1,123 @@
+---
+date: 2026-02-09
+footer: libibverbs
+header: "Libibverbs Programmer's Manual"
+layout: page
+license: 'Licensed under the OpenIB.org BSD license (FreeBSD Variant) - See COPYING.md'
+section: 3
+title: ibv_qp_attach_comp_cntr
+tagline: Verbs
+---
+
+# NAME
+
+**ibv_qp_attach_comp_cntr** - Attach a completion counter to a QP
+
+# SYNOPSIS
+
+```c
+#include <infiniband/verbs.h>
+
+int ibv_qp_attach_comp_cntr(struct ibv_qp *qp,
+                            struct ibv_comp_cntr *comp_cntr,
+                            struct ibv_qp_attach_comp_cntr_attr *attr);
+```
+
+# DESCRIPTION
+
+**ibv_qp_attach_comp_cntr**() attaches the completion counter *comp_cntr* to
+the queue pair *qp*. The *attr* argument specifies which operation types
+should update the counter.
+
+The QP must be in **IBV_QPS_RESET** or **IBV_QPS_INIT** state when attaching
+a completion counter. Attempting to attach a counter to a QP in any other
+state will fail with EINVAL.
+
+The completion counter starts collecting values for the specified QP once
+attached. Attaching the same completion counter to multiple QPs will
+accumulate values from all attached QPs into the same counter.
+
+The *op_mask* field controls which operation completions are counted. Local
+operations (**IBV_QP_ATTACH_COMP_CNTR_OP_SEND**, **IBV_QP_ATTACH_COMP_CNTR_OP_RECV**,
+**IBV_QP_ATTACH_COMP_CNTR_OP_RDMA_READ**, **IBV_QP_ATTACH_COMP_CNTR_OP_RDMA_WRITE**)
+count completions initiated by the local QP. Remote operations
+(**IBV_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_READ**,
+**IBV_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_WRITE**) count completions of incoming
+RDMA operations initiated by the remote side. Supported *op_mask* values may
+vary by device and can be queried using **ibv_query_comp_cntr_caps**(3);
+unsupported values will result in an ENOTSUP error.
+
+Multiple completion counters can be attached to the same QP, provided their
+*op_mask* values do not overlap. Each QP and operation type pair can be
+associated with at most one completion counter. Attempting to attach a
+counter with an *op_mask* that conflicts with an already attached counter
+will fail.
+
+There is no explicit detach operation. A completion counter is implicitly
+detached when the QP it is attached to is destroyed. A completion counter
+cannot be destroyed while it is still attached to any QP; the QP must be
+destroyed first.
+
+# ARGUMENTS
+
+*qp*
+:	The queue pair to attach the completion counter to.
+
+*comp_cntr*
+:	The completion counter to attach, previously created with
+	**ibv_create_comp_cntr**().
+
+*attr*
+:	Attach attributes specifying which operation types update the counter.
+
+## ibv_qp_attach_comp_cntr_attr
+
+```c
+enum ibv_qp_attach_comp_cntr_op {
+	IBV_QP_ATTACH_COMP_CNTR_OP_SEND                    = 1 << 0,
+	IBV_QP_ATTACH_COMP_CNTR_OP_RECV                    = 1 << 1,
+	IBV_QP_ATTACH_COMP_CNTR_OP_RDMA_READ               = 1 << 2,
+	IBV_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_READ        = 1 << 3,
+	IBV_QP_ATTACH_COMP_CNTR_OP_RDMA_WRITE              = 1 << 4,
+	IBV_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_WRITE       = 1 << 5,
+};
+
+struct ibv_qp_attach_comp_cntr_attr {
+	uint32_t comp_mask;
+	uint32_t op_mask;
+};
+```
+
+*comp_mask*
+:	Bitmask specifying what fields in the structure are valid. Used
+	for extensibility, should be set to 0.
+
+*op_mask*
+:	Bitmask of **ibv_qp_attach_comp_cntr_op** values specifying which
+	operation types should update the counter.
+
+# RETURN VALUE
+
+**ibv_qp_attach_comp_cntr**() returns 0 on success, or the value of errno on
+failure (which indicates the failure reason).
+
+# ERRORS
+
+EINVAL
+:	Invalid argument(s) passed.
+
+ENOTSUP
+:	Requested operation is not supported on this device.
+
+EBUSY
+:	The *op_mask* overlaps with a completion counter already attached
+	to this QP.
+
+# SEE ALSO
+
+**ibv_create_comp_cntr**(3), **ibv_query_comp_cntr_caps**(3),
+**ibv_create_qp**(3)
+
+# AUTHORS
+
+Michael Margolin <mrgolin@amazon.com>
diff --git a/libibverbs/man/ibv_query_comp_cntr_caps.3.md b/libibverbs/man/ibv_query_comp_cntr_caps.3.md
new file mode 100644
index 0000000..c7a367d
--- /dev/null
+++ b/libibverbs/man/ibv_query_comp_cntr_caps.3.md
@@ -0,0 +1,71 @@
+---
+date: 2026-07-17
+footer: libibverbs
+header: "Libibverbs Programmer's Manual"
+layout: page
+license: 'Licensed under the OpenIB.org BSD license (FreeBSD Variant) - See COPYING.md'
+section: 3
+title: ibv_query_comp_cntr_caps
+tagline: Verbs
+---
+
+# NAME
+
+**ibv_query_comp_cntr_caps** - Query completion counter capabilities
+
+# SYNOPSIS
+
+```c
+#include <infiniband/verbs.h>
+
+int ibv_query_comp_cntr_caps(struct ibv_context *context,
+                             struct ibv_comp_cntr_caps *caps);
+```
+
+# DESCRIPTION
+
+**ibv_query_comp_cntr_caps**() queries the completion counter capabilities
+of the RDMA device associated with *context*.
+
+# ARGUMENTS
+
+## ibv_comp_cntr_caps
+
+```c
+struct ibv_comp_cntr_caps {
+	uint64_t max_value;
+	uint32_t max_counters;
+	uint32_t supported_qp_attach_ops;
+};
+```
+
+*max_value*
+:	The maximum value a completion counter can hold. A subsequent
+	increment that would exceed this value wraps the counter to zero.
+
+*max_counters*
+:	The maximum number of completion counters that can be created on
+	this device.
+
+*supported_qp_attach_ops*
+:	A bitmask of **ibv_qp_attach_comp_cntr_op** values indicating which
+	attach operations are supported by the device. See
+	**ibv_qp_attach_comp_cntr**(3) for the list of operations.
+
+# RETURN VALUE
+
+**ibv_query_comp_cntr_caps**() returns 0 on success, or the value of errno
+on failure (which indicates the failure reason).
+
+# ERRORS
+
+ENOTSUP
+:	Completion counters are not supported on this device.
+
+# SEE ALSO
+
+**ibv_create_comp_cntr**(3), **ibv_qp_attach_comp_cntr**(3)
+
+# AUTHORS
+
+Michael Margolin <mrgolin@amazon.com>
diff --git a/libibverbs/verbs.c b/libibverbs/verbs.c
index bb5233b..d91eb42 100644
--- a/libibverbs/verbs.c
+++ b/libibverbs/verbs.c
@@ -792,6 +792,54 @@
 	pthread_mutex_unlock(&cq->mutex);
 }
 
+int _ibv_query_comp_cntr_caps(struct ibv_context *context,
+			      struct ibv_comp_cntr_caps *caps, size_t caps_size)
+{
+	memset(caps, 0, caps_size);
+	return get_ops(context)->query_comp_cntr_caps(context, caps, caps_size);
+}
+
+struct ibv_comp_cntr *ibv_create_comp_cntr(struct ibv_context *context,
+					   struct ibv_comp_cntr_init_attr *cc_attr)
+{
+	return get_ops(context)->create_comp_cntr(context, cc_attr);
+}
+
+int ibv_destroy_comp_cntr(struct ibv_comp_cntr *comp_cntr)
+{
+	return get_ops(comp_cntr->context)->destroy_comp_cntr(comp_cntr);
+}
+
+int ibv_set_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t value)
+{
+	return get_ops(comp_cntr->context)->set_comp_cntr(comp_cntr, value);
+}
+
+int ibv_set_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t value)
+{
+	return get_ops(comp_cntr->context)->set_err_comp_cntr(comp_cntr, value);
+}
+
+int ibv_inc_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t amount)
+{
+	return get_ops(comp_cntr->context)->inc_comp_cntr(comp_cntr, amount);
+}
+
+int ibv_inc_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t amount)
+{
+	return get_ops(comp_cntr->context)->inc_err_comp_cntr(comp_cntr, amount);
+}
+
+int ibv_read_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t *value)
+{
+	return get_ops(comp_cntr->context)->read_comp_cntr(comp_cntr, value);
+}
+
+int ibv_read_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t *value)
+{
+	return get_ops(comp_cntr->context)->read_err_comp_cntr(comp_cntr, value);
+}
+
 LATEST_SYMVER_FUNC(ibv_create_srq, 1_1, "IBVERBS_1.1",
 		   struct ibv_srq *,
 		   struct ibv_pd *pd,
@@ -919,6 +967,12 @@
 	return 0;
 }
 
+int ibv_qp_attach_comp_cntr(struct ibv_qp *qp, struct ibv_comp_cntr *comp_cntr,
+			    struct ibv_qp_attach_comp_cntr_attr *attr)
+{
+	return get_ops(qp->context)->qp_attach_comp_cntr(qp, comp_cntr, attr);
+}
+
 LATEST_SYMVER_FUNC(ibv_destroy_qp, 1_1, "IBVERBS_1.1",
 		   int,
 		   struct ibv_qp *qp)
diff --git a/libibverbs/verbs.h b/libibverbs/verbs.h
index 9ed55d4..cad28af 100644
--- a/libibverbs/verbs.h
+++ b/libibverbs/verbs.h
@@ -2117,6 +2117,42 @@
 	struct ibv_pd		*parent_domain;
 };
 
+enum ibv_qp_attach_comp_cntr_op {
+	IBV_QP_ATTACH_COMP_CNTR_OP_SEND			= 1 << 0,
+	IBV_QP_ATTACH_COMP_CNTR_OP_RECV			= 1 << 1,
+	IBV_QP_ATTACH_COMP_CNTR_OP_RDMA_READ		= 1 << 2,
+	IBV_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_READ	= 1 << 3,
+	IBV_QP_ATTACH_COMP_CNTR_OP_RDMA_WRITE		= 1 << 4,
+	IBV_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_WRITE	= 1 << 5,
+};
+
+struct ibv_comp_cntr_caps {
+	uint64_t max_value;
+	uint32_t max_counters;
+	uint32_t supported_qp_attach_ops; /* Bitmask of ibv_qp_attach_comp_cntr_op */
+};
+
+struct ibv_comp_cntr {
+	struct ibv_context *context;
+	uint32_t handle;
+};
+
+enum ibv_comp_cntr_type {
+	IBV_COMP_CNTR_TYPE_WRS,
+	IBV_COMP_CNTR_TYPE_BYTES,
+};
+
+struct ibv_comp_cntr_init_attr {
+	uint32_t comp_mask; /* Compatibility mask */
+	enum ibv_comp_cntr_type type;
+	uint32_t flags;
+};
+
+struct ibv_qp_attach_comp_cntr_attr {
+	uint32_t comp_mask; /* Compatibility mask */
+	uint32_t op_mask; /* Bitmask of ibv_qp_attach_comp_cntr_op */
+};
+
 enum ibv_parent_domain_init_attr_mask {
 	IBV_PARENT_DOMAIN_INIT_ATTR_ALLOCATORS = 1 << 0,
 	IBV_PARENT_DOMAIN_INIT_ATTR_PD_CONTEXT = 1 << 1,
@@ -3027,6 +3063,77 @@
 
 	return vctx->modify_cq(cq, attr);
 }
+
+int _ibv_query_comp_cntr_caps(struct ibv_context *context,
+			      struct ibv_comp_cntr_caps *caps, size_t caps_size);
+
+/**
+ * ibv_query_comp_cntr_caps - Query completion counter capabilities
+ * @context: Device context.
+ * @caps: Output capabilities struct.
+ */
+static inline int ibv_query_comp_cntr_caps(struct ibv_context *context,
+					   struct ibv_comp_cntr_caps *caps)
+{
+	return _ibv_query_comp_cntr_caps(context, caps, sizeof(*caps));
+}
+
+/**
+ * ibv_create_comp_cntr - Create a completion counter
+ * @context: Device context to create the counter on.
+ * @cc_attr: Attributes for the completion counter.
+ */
+struct ibv_comp_cntr *ibv_create_comp_cntr(struct ibv_context *context,
+					   struct ibv_comp_cntr_init_attr *cc_attr);
+
+/**
+ * ibv_destroy_comp_cntr - Destroy a completion counter
+ * @comp_cntr: The completion counter to destroy.
+ */
+int ibv_destroy_comp_cntr(struct ibv_comp_cntr *comp_cntr);
+
+/**
+ * ibv_set_comp_cntr - Set the completion count value
+ * @comp_cntr: The completion counter to update.
+ * @value: The value to set.
+ */
+int ibv_set_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t value);
+
+/**
+ * ibv_set_err_comp_cntr - Set the error count value
+ * @comp_cntr: The completion counter to update.
+ * @value: The value to set.
+ */
+int ibv_set_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t value);
+
+/**
+ * ibv_inc_comp_cntr - Increment the completion count
+ * @comp_cntr: The completion counter to increment.
+ * @amount: The amount to increment by.
+ */
+int ibv_inc_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t amount);
+
+/**
+ * ibv_inc_err_comp_cntr - Increment the error count
+ * @comp_cntr: The completion counter to increment.
+ * @amount: The amount to increment by.
+ */
+int ibv_inc_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t amount);
+
+/**
+ * ibv_read_comp_cntr - Read the completion count value
+ * @comp_cntr: The completion counter to read.
+ * @value: Output pointer to store the current completion count.
+ */
+int ibv_read_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t *value);
+
+/**
+ * ibv_read_err_comp_cntr - Read the error count value
+ * @comp_cntr: The completion counter to read.
+ * @value: Output pointer to store the current error count.
+ */
+int ibv_read_err_comp_cntr(struct ibv_comp_cntr *comp_cntr, uint64_t *value);
+
 /**
  * ibv_create_srq - Creates a SRQ associated with the specified protection
  *   domain.
@@ -3347,6 +3454,15 @@
 }
 
 /**
+ * ibv_qp_attach_comp_cntr - Attach a completion counter to a QP
+ * @qp: The queue pair to attach the counter to.
+ * @comp_cntr: The completion counter to attach.
+ * @attr: Attach attributes.
+ */
+int ibv_qp_attach_comp_cntr(struct ibv_qp *qp, struct ibv_comp_cntr *comp_cntr,
+			    struct ibv_qp_attach_comp_cntr_attr *attr);
+
+/**
  * ibv_query_qp_data_in_order - Checks whether the data is guaranteed to be
  *   written in-order.
  * @qp: The QP to query.
diff --git a/providers/efa/CMakeLists.txt b/providers/efa/CMakeLists.txt
index 1f3c32c..4054109 100644
--- a/providers/efa/CMakeLists.txt
+++ b/providers/efa/CMakeLists.txt
@@ -3,7 +3,7 @@
 endif()
 
 rdma_shared_provider(efa libefa.map
-	1 1.6.${PACKAGE_VERSION}
+	1 1.7.${PACKAGE_VERSION}
 	${TRACE_FILE}
 	efa.c
 	verbs.c
diff --git a/providers/efa/efa.c b/providers/efa/efa.c
index 5e6d873..7ef6ff3 100644
--- a/providers/efa/efa.c
+++ b/providers/efa/efa.c
@@ -31,30 +31,40 @@
 	.alloc_pd = efa_alloc_pd,
 	.alloc_parent_domain = efa_alloc_parent_domain,
 	.alloc_td = efa_alloc_td,
+	.cq_event = efa_cq_event,
 	.create_ah = efa_create_ah,
+	.create_comp_cntr = efa_create_comp_cntr,
 	.create_cq = efa_create_cq,
 	.create_cq_ex = efa_create_cq_ex,
 	.create_qp = efa_create_qp,
 	.create_qp_ex = efa_create_qp_ex,
-	.cq_event = efa_cq_event,
 	.dealloc_pd = efa_dealloc_pd,
 	.dealloc_td = efa_dealloc_td,
 	.dereg_mr = efa_dereg_mr,
 	.destroy_ah = efa_destroy_ah,
+	.destroy_comp_cntr = efa_destroy_comp_cntr,
 	.destroy_cq = efa_destroy_cq,
 	.destroy_qp = efa_destroy_qp,
+	.inc_comp_cntr = efa_inc_comp_cntr,
+	.inc_err_comp_cntr = efa_inc_err_comp_cntr,
 	.modify_qp = efa_modify_qp,
 	.poll_cq = efa_poll_cq,
 	.post_recv = efa_post_recv,
 	.post_send = efa_post_send,
+	.qp_attach_comp_cntr = efa_qp_attach_comp_cntr,
+	.query_comp_cntr_caps = ibv_cmd_query_comp_cntr_caps,
 	.query_device_ex = efa_query_device_ex,
 	.query_port = efa_query_port,
 	.query_port_speed = efa_query_port_speed,
 	.query_qp = efa_query_qp,
 	.query_qp_data_in_order = efa_query_qp_data_in_order,
+	.read_comp_cntr = efa_read_comp_cntr,
+	.read_err_comp_cntr = efa_read_err_comp_cntr,
 	.reg_dmabuf_mr = efa_reg_dmabuf_mr,
 	.reg_mr = efa_reg_mr,
 	.req_notify_cq = efa_arm_cq,
+	.set_comp_cntr = efa_set_comp_cntr,
+	.set_err_comp_cntr = efa_set_err_comp_cntr,
 	.free_context = efa_free_context,
 };
 
diff --git a/providers/efa/efa.h b/providers/efa/efa.h
index 7a596c9..20567cc 100644
--- a/providers/efa/efa.h
+++ b/providers/efa/efa.h
@@ -105,6 +105,14 @@
 	struct efa_sub_cq sub_cq_arr[];
 };
 
+struct efa_comp_cntr {
+	struct ibv_comp_cntr ibv_comp_cntr;
+	uint64_t comp_val __attribute__((aligned(8)));
+	uint64_t err_val __attribute__((aligned(8)));
+	uint64_t *comp_ptr;
+	uint64_t *err_ptr;
+};
+
 struct efa_wq {
 	uint64_t *wrid;
 	/* wrid_idx_pool: Pool of free indexes in the wrid array, used to select the
@@ -230,6 +238,11 @@
 	return container_of(efadv_cq, struct efa_cq, dv_cq);
 }
 
+static inline struct efa_comp_cntr *to_efa_comp_cntr(struct ibv_comp_cntr *ibvcc)
+{
+	return container_of(ibvcc, struct efa_comp_cntr, ibv_comp_cntr);
+}
+
 static inline struct efa_qp *to_efa_qp(struct ibv_qp *ibvqp)
 {
 	return container_of(ibvqp, struct efa_qp, verbs_qp.qp);
diff --git a/providers/efa/efadv.h b/providers/efa/efadv.h
index 8745988..d863de4 100644
--- a/providers/efa/efadv.h
+++ b/providers/efa/efadv.h
@@ -23,6 +23,7 @@
 	EFADV_DEVICE_ATTR_CAPS_RDMA_WRITE = 1 << 3,
 	EFADV_DEVICE_ATTR_CAPS_UNSOLICITED_WRITE_RECV = 1 << 4,
 	EFADV_DEVICE_ATTR_CAPS_CQ_WITH_EXT_MEM_DMABUF = 1 << 5,
+	EFADV_DEVICE_ATTR_CAPS_COMP_CNTR = 1 << 6,
 };
 
 struct efadv_device_attr {
@@ -191,6 +192,40 @@
 }
 
 enum {
+	EFADV_MEMORY_LOCATION_VA,
+	EFADV_MEMORY_LOCATION_DMABUF,
+};
+
+struct efadv_memory_location {
+	uint8_t *ptr;
+	struct {
+		uint64_t offset;
+		int32_t fd;
+		uint32_t reserved;
+	} dmabuf;
+	uint8_t type;
+	uint8_t reserved[7];
+};
+
+enum {
+	EFADV_COMP_CNTR_INIT_WITH_COMP_EXTERNAL_MEM = 1 << 0,
+	EFADV_COMP_CNTR_INIT_WITH_ERR_EXTERNAL_MEM = 1 << 1,
+};
+
+struct efadv_comp_cntr_init_attr {
+	uint64_t comp_mask;
+	uint32_t flags;
+	uint32_t reserved;
+	struct efadv_memory_location comp_cntr_ext_mem;
+	struct efadv_memory_location err_cntr_ext_mem;
+};
+
+struct ibv_comp_cntr *efadv_create_comp_cntr(struct ibv_context *ibvctx,
+					     struct ibv_comp_cntr_init_attr *attr,
+					     struct efadv_comp_cntr_init_attr *efa_attr,
+					     uint32_t inlen);
+
+enum {
 	EFADV_MR_ATTR_VALIDITY_RECV_IC_ID = 1 << 0,
 	EFADV_MR_ATTR_VALIDITY_RDMA_READ_IC_ID = 1 << 1,
 	EFADV_MR_ATTR_VALIDITY_RDMA_RECV_IC_ID = 1 << 2,
diff --git a/providers/efa/libefa.map b/providers/efa/libefa.map
index 3f51e5e..dee028a 100644
--- a/providers/efa/libefa.map
+++ b/providers/efa/libefa.map
@@ -40,3 +40,8 @@
 	global:
 		efadv_qp_from_ibv_qp_ex;
 } EFA_1.5;
+
+EFA_1.7 {
+	global:
+		efadv_create_comp_cntr;
+} EFA_1.6;
diff --git a/providers/efa/man/efadv_create_comp_cntr.3.md b/providers/efa/man/efadv_create_comp_cntr.3.md
new file mode 100644
index 0000000..92d92dc
--- /dev/null
+++ b/providers/efa/man/efadv_create_comp_cntr.3.md
@@ -0,0 +1,118 @@
+---
+layout: page
+title: EFADV_CREATE_COMP_CNTR
+section: 3
+tagline: Verbs
+date: 2026-04-27
+header: "EFA Direct Verbs Manual"
+footer: efa
+---
+
+# NAME
+
+efadv_create_comp_cntr - Create EFA specific Completion Counter
+
+# SYNOPSIS
+
+```c
+#include <infiniband/efadv.h>
+
+struct ibv_comp_cntr *efadv_create_comp_cntr(struct ibv_context *context,
+					     struct ibv_comp_cntr_init_attr *attr,
+					     struct efadv_comp_cntr_init_attr *efa_attr,
+					     uint32_t inlen);
+```
+
+# DESCRIPTION
+
+**efadv_create_comp_cntr()** creates a Completion Counter with EFA specific
+properties, such as external memory for the counter values.
+
+The argument *attr* is an ibv_comp_cntr_init_attr struct, as defined in
+<infiniband/verbs.h>.
+
+Compatibility is handled using the comp_mask and inlen fields.
+
+```c
+enum {
+	EFADV_MEMORY_LOCATION_VA,
+	EFADV_MEMORY_LOCATION_DMABUF,
+};
+
+struct efadv_memory_location {
+	uint8_t *ptr;
+	struct {
+		uint64_t offset;
+		int32_t fd;
+		uint32_t reserved;
+	} dmabuf;
+	uint8_t type;
+	uint8_t reserved[7];
+};
+
+struct efadv_comp_cntr_init_attr {
+	uint64_t comp_mask;
+	uint32_t flags;
+	uint32_t reserved;
+	struct efadv_memory_location comp_cntr_ext_mem;
+	struct efadv_memory_location err_cntr_ext_mem;
+};
+```
+
+*inlen*
+:	In: Size of struct efadv_comp_cntr_init_attr.
+
+*comp_mask*
+:	Compatibility mask.
+
+*flags*
+:	A bitwise OR of the various values described below.
+
+	**EFADV_COMP_CNTR_INIT_WITH_COMP_EXTERNAL_MEM**:
+		Use application-provided memory for the completion count, as
+		described by *comp_cntr_ext_mem*.
+
+	**EFADV_COMP_CNTR_INIT_WITH_ERR_EXTERNAL_MEM**:
+		Use application-provided memory for the error count, as
+		described by *err_cntr_ext_mem*.
+
+*comp_cntr_ext_mem*
+:	Memory location for the completion count when using external memory.
+
+*err_cntr_ext_mem*
+:	Memory location for the error count when using external memory.
+
+## efadv_memory_location
+
+The external memory is described by an **efadv_memory_location** structure
+which supports two modes:
+
+*type*
+:	**EFADV_MEMORY_LOCATION_VA** for a virtual address, or
+	**EFADV_MEMORY_LOCATION_DMABUF** for a DMA-BUF reference.
+
+*ptr*
+:	Virtual address pointer. Required when type is
+	**EFADV_MEMORY_LOCATION_VA**. When type is
+	**EFADV_MEMORY_LOCATION_DMABUF**, may optionally be set to provide a
+	process-accessible mapping of the DMA-BUF memory.
+
+*dmabuf.fd*
+:	DMA-BUF file descriptor (used when type is
+	**EFADV_MEMORY_LOCATION_DMABUF**).
+
+*dmabuf.offset*
+:	Offset within the DMA-BUF.
+
+# RETURN VALUE
+
+efadv_create_comp_cntr() returns a pointer to the created ibv_comp_cntr, or
+NULL if the request fails.
+
+# SEE ALSO
+
+**efadv**(7), **ibv_create_comp_cntr**(3), **ibv_qp_attach_comp_cntr**(3)
+
+# AUTHORS
+
+Michael Margolin <mrgolin@amazon.com>
diff --git a/providers/efa/man/efadv_query_device.3.md b/providers/efa/man/efadv_query_device.3.md
index f46d362..cff7068 100644
--- a/providers/efa/man/efadv_query_device.3.md
+++ b/providers/efa/man/efadv_query_device.3.md
@@ -92,6 +92,9 @@
 		Indicates that creating CQs with external memory buffers by passing dmabuf is
 		supported.
 
+	EFADV_DEVICE_ATTR_CAPS_COMP_CNTR:
+		Completion counters are supported.
+
 *max_rdma_size*
 :	Maximum RDMA transfer size in bytes.
 
diff --git a/providers/efa/verbs.c b/providers/efa/verbs.c
index 8327cb3..aa76976 100644
--- a/providers/efa/verbs.c
+++ b/providers/efa/verbs.c
@@ -198,6 +198,9 @@
 
 		if (EFA_DEV_CAP(ctx, CQ_WITH_EXT_MEM))
 			attr->device_caps |= EFADV_DEVICE_ATTR_CAPS_CQ_WITH_EXT_MEM_DMABUF;
+
+		if (EFA_DEV_CAP(ctx, COMP_CNTR))
+			attr->device_caps |= EFADV_DEVICE_ATTR_CAPS_COMP_CNTR;
 	}
 
 	if (vext_field_avail(typeof(*attr), max_rdma_size, inlen)) {
@@ -1404,6 +1407,183 @@
 	return 0;
 }
 
+static void efa_fill_buffer_desc_va(struct ib_uverbs_buffer_desc *desc, uint64_t addr,
+				    uint64_t length)
+{
+	desc->type = IB_UVERBS_BUFFER_TYPE_VA;
+	desc->addr = addr;
+	desc->length = length;
+}
+
+static void efa_fill_buffer_desc_dmabuf(struct ib_uverbs_buffer_desc *desc, int32_t fd,
+					uint64_t offset, uint64_t length)
+{
+	desc->type = IB_UVERBS_BUFFER_TYPE_DMABUF;
+	desc->fd = fd;
+	desc->addr = offset;
+	desc->length = length;
+}
+
+static void efa_fill_buffer_desc_from_mem_loc(struct ib_uverbs_buffer_desc *desc,
+					      struct efadv_memory_location *mem, uint64_t length)
+{
+	if (mem->type == EFADV_MEMORY_LOCATION_DMABUF)
+		efa_fill_buffer_desc_dmabuf(desc, mem->dmabuf.fd, mem->dmabuf.offset, length);
+	else
+		efa_fill_buffer_desc_va(desc, (uintptr_t)mem->ptr, length);
+}
+
+static inline bool efa_comp_cntr_mem_type_supported(uint32_t mem_type)
+{
+	return mem_type == EFADV_MEMORY_LOCATION_VA || mem_type == EFADV_MEMORY_LOCATION_DMABUF;
+}
+
+static struct ibv_comp_cntr *efa_create_comp_cntr_impl(struct ibv_context *ibvctx,
+						       struct ibv_comp_cntr_init_attr *attr,
+						       struct efadv_comp_cntr_init_attr *efa_attr)
+{
+	uint32_t supported_efa_flags = EFADV_COMP_CNTR_INIT_WITH_COMP_EXTERNAL_MEM |
+				       EFADV_COMP_CNTR_INIT_WITH_ERR_EXTERNAL_MEM;
+	DECLARE_COMMAND_BUFFER_LINK(cmdb, UVERBS_OBJECT_COMP_CNTR,
+				    UVERBS_METHOD_COMP_CNTR_CREATE, 2, NULL);
+	struct ib_uverbs_buffer_desc comp_desc = {};
+	struct ib_uverbs_buffer_desc err_desc = {};
+	struct efa_comp_cntr *cc;
+	int err;
+
+	if (attr->comp_mask || attr->flags ||
+	    attr->type != IBV_COMP_CNTR_TYPE_WRS ||
+	    efa_attr->comp_mask ||
+	    !check_comp_mask(efa_attr->flags, supported_efa_flags) ||
+	    !efa_comp_cntr_mem_type_supported(efa_attr->comp_cntr_ext_mem.type) ||
+	    !efa_comp_cntr_mem_type_supported(efa_attr->err_cntr_ext_mem.type)) {
+		verbs_err(verbs_get_ctx(ibvctx), "Unsupported type or flag\n");
+		errno = EOPNOTSUPP;
+		return NULL;
+	}
+
+	cc = calloc(1, sizeof(*cc));
+	if (!cc) {
+		errno = ENOMEM;
+		return NULL;
+	}
+
+	if (efa_attr->flags & EFADV_COMP_CNTR_INIT_WITH_COMP_EXTERNAL_MEM) {
+		efa_fill_buffer_desc_from_mem_loc(&comp_desc, &efa_attr->comp_cntr_ext_mem,
+						  sizeof(uint64_t));
+		cc->comp_ptr = (uint64_t *)efa_attr->comp_cntr_ext_mem.ptr;
+	} else {
+		efa_fill_buffer_desc_va(&comp_desc, (uintptr_t)&cc->comp_val, sizeof(uint64_t));
+		cc->comp_ptr = &cc->comp_val;
+	}
+
+	fill_attr_in_ptr(cmdb, EFA_IB_ATTR_CREATE_COMP_CNTR_COMP_BUFFER, &comp_desc);
+
+	if (efa_attr->flags & EFADV_COMP_CNTR_INIT_WITH_ERR_EXTERNAL_MEM) {
+		efa_fill_buffer_desc_from_mem_loc(&err_desc, &efa_attr->err_cntr_ext_mem,
+						  sizeof(uint64_t));
+		cc->err_ptr = (uint64_t *)efa_attr->err_cntr_ext_mem.ptr;
+	} else {
+		efa_fill_buffer_desc_va(&err_desc, (uintptr_t)&cc->err_val, sizeof(uint64_t));
+		cc->err_ptr = &cc->err_val;
+	}
+
+	fill_attr_in_ptr(cmdb, EFA_IB_ATTR_CREATE_COMP_CNTR_ERR_BUFFER, &err_desc);
+
+	err = ibv_cmd_create_comp_cntr(ibvctx, &cc->ibv_comp_cntr, cmdb);
+	if (err) {
+		free(cc);
+		errno = err;
+		return NULL;
+	}
+
+	return &cc->ibv_comp_cntr;
+}
+
+struct ibv_comp_cntr *efa_create_comp_cntr(struct ibv_context *ibvctx,
+					   struct ibv_comp_cntr_init_attr *attr)
+{
+	struct efadv_comp_cntr_init_attr efa_attr = {};
+
+	return efa_create_comp_cntr_impl(ibvctx, attr, &efa_attr);
+}
+
+struct ibv_comp_cntr *efadv_create_comp_cntr(struct ibv_context *ibvctx,
+					     struct ibv_comp_cntr_init_attr *attr,
+					     struct efadv_comp_cntr_init_attr *efa_attr,
+					     uint32_t inlen)
+{
+	if (!is_efa_dev(ibvctx->device)) {
+		verbs_err(verbs_get_ctx(ibvctx), "Not an EFA device\n");
+		errno = EOPNOTSUPP;
+		return NULL;
+	}
+
+	if (!vext_field_avail(struct efadv_comp_cntr_init_attr, err_cntr_ext_mem, inlen) ||
+	    (inlen > sizeof(*efa_attr) && !is_ext_cleared(efa_attr, inlen))) {
+		verbs_err(verbs_get_ctx(ibvctx), "Compatibility issues\n");
+		errno = EINVAL;
+		return NULL;
+	}
+
+	return efa_create_comp_cntr_impl(ibvctx, attr, efa_attr);
+}
+
+int efa_destroy_comp_cntr(struct ibv_comp_cntr *ibvcc)
+{
+	struct efa_comp_cntr *cc = to_efa_comp_cntr(ibvcc);
+	int err;
+
+	err = ibv_cmd_destroy_comp_cntr(ibvcc);
+	if (err)
+		return err;
+
+	free(cc);
+	return 0;
+}
+
+int efa_set_comp_cntr(struct ibv_comp_cntr *ibvcc, uint64_t value)
+{
+	return ibv_cmd_set_comp_cntr(ibvcc, value);
+}
+
+int efa_set_err_comp_cntr(struct ibv_comp_cntr *ibvcc, uint64_t value)
+{
+	return ibv_cmd_set_err_comp_cntr(ibvcc, value);
+}
+
+int efa_inc_comp_cntr(struct ibv_comp_cntr *ibvcc, uint64_t amount)
+{
+	return ibv_cmd_inc_comp_cntr(ibvcc, amount);
+}
+
+int efa_inc_err_comp_cntr(struct ibv_comp_cntr *ibvcc, uint64_t amount)
+{
+	return ibv_cmd_inc_err_comp_cntr(ibvcc, amount);
+}
+
+int efa_read_comp_cntr(struct ibv_comp_cntr *ibvcc, uint64_t *value)
+{
+	struct efa_comp_cntr *cc = to_efa_comp_cntr(ibvcc);
+
+	if (!cc->comp_ptr)
+		return EOPNOTSUPP;
+
+	*value = *cc->comp_ptr;
+	return 0;
+}
+
+int efa_read_err_comp_cntr(struct ibv_comp_cntr *ibvcc, uint64_t *value)
+{
+	struct efa_comp_cntr *cc = to_efa_comp_cntr(ibvcc);
+
+	if (!cc->err_ptr)
+		return EOPNOTSUPP;
+
+	*value = *cc->err_ptr;
+	return 0;
+}
+
 static void efa_wq_terminate(struct efa_wq *wq, int pgsz)
 {
 	void *db_aligned;
@@ -2145,6 +2325,12 @@
 	return 0;
 }
 
+int efa_qp_attach_comp_cntr(struct ibv_qp *qp, struct ibv_comp_cntr *comp_cntr,
+			    struct ibv_qp_attach_comp_cntr_attr *attr)
+{
+	return ibv_cmd_qp_attach_comp_cntr(qp, comp_cntr, attr);
+}
+
 int efa_query_qp(struct ibv_qp *ibvqp, struct ibv_qp_attr *attr,
 		 int attr_mask, struct ibv_qp_init_attr *init_attr)
 {
diff --git a/providers/efa/verbs.h b/providers/efa/verbs.h
index b38b4e4..602a8d1 100644
--- a/providers/efa/verbs.h
+++ b/providers/efa/verbs.h
@@ -36,12 +36,24 @@
 int efa_arm_cq(struct ibv_cq *ibvcq, int solicited_only);
 void efa_cq_event(struct ibv_cq *ibvcq);
 
+struct ibv_comp_cntr *efa_create_comp_cntr(struct ibv_context *ibvctx,
+					   struct ibv_comp_cntr_init_attr *attr);
+int efa_destroy_comp_cntr(struct ibv_comp_cntr *ibvcc);
+int efa_set_comp_cntr(struct ibv_comp_cntr *ibvcc, uint64_t value);
+int efa_set_err_comp_cntr(struct ibv_comp_cntr *ibvcc, uint64_t value);
+int efa_inc_comp_cntr(struct ibv_comp_cntr *ibvcc, uint64_t amount);
+int efa_inc_err_comp_cntr(struct ibv_comp_cntr *ibvcc, uint64_t amount);
+int efa_read_comp_cntr(struct ibv_comp_cntr *ibvcc, uint64_t *value);
+int efa_read_err_comp_cntr(struct ibv_comp_cntr *ibvcc, uint64_t *value);
+
 struct ibv_qp *efa_create_qp(struct ibv_pd *ibvpd,
 			     struct ibv_qp_init_attr *attr);
 struct ibv_qp *efa_create_qp_ex(struct ibv_context *ibvctx,
 				struct ibv_qp_init_attr_ex *attr_ex);
 int efa_modify_qp(struct ibv_qp *ibvqp, struct ibv_qp_attr *attr,
 		  int ibv_qp_attr_mask);
+int efa_qp_attach_comp_cntr(struct ibv_qp *qp, struct ibv_comp_cntr *comp_cntr,
+			    struct ibv_qp_attach_comp_cntr_attr *attr);
 int efa_query_qp(struct ibv_qp *ibvqp, struct ibv_qp_attr *attr, int attr_mask,
 		 struct ibv_qp_init_attr *init_attr);
 int efa_query_qp_data_in_order(struct ibv_qp *ibvqp, enum ibv_wr_opcode op,
diff --git a/providers/ionic/ionic_verbs.c b/providers/ionic/ionic_verbs.c
index 5108a6f..b5abe74 100644
--- a/providers/ionic/ionic_verbs.c
+++ b/providers/ionic/ionic_verbs.c
@@ -42,7 +42,7 @@
 	/* Return highest supported cpuid input value.  */
 	__asm__ volatile ("cpuid\n\t"
 			  : "=a" (ax), "=b" (bx), "=c" (cx), "=d" (dx)
-			  : "0" (leaf));
+			  : "0" (0));
 
 	if (ax == 0 || ax < leaf)
 		return 0;
diff --git a/pyverbs/CMakeLists.txt b/pyverbs/CMakeLists.txt
index 4beb118..6399fea 100644
--- a/pyverbs/CMakeLists.txt
+++ b/pyverbs/CMakeLists.txt
@@ -22,6 +22,7 @@
   addr.pyx
   base.pyx
   cmid.pyx
+  comp_cntr.pyx
   cq.pyx
   device.pyx
   ${DMA_UTIL}
diff --git a/pyverbs/comp_cntr.pxd b/pyverbs/comp_cntr.pxd
new file mode 100644
index 0000000..8699786
--- /dev/null
+++ b/pyverbs/comp_cntr.pxd
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB)
+# Copyright Amazon.com, Inc. or its affiliates. All rights reserved.
+
+#cython: language_level=3
+
+from pyverbs.base cimport PyverbsObject, PyverbsCM
+from pyverbs.device cimport Context
+cimport pyverbs.libibverbs as v
+
+cdef class CompCntrInitAttr(PyverbsObject):
+    cdef v.ibv_comp_cntr_init_attr attr
+
+cdef class CompCntr(PyverbsCM):
+    cdef v.ibv_comp_cntr *comp_cntr
+    cdef Context ctx
+    cpdef close(self)
diff --git a/pyverbs/comp_cntr.pyx b/pyverbs/comp_cntr.pyx
new file mode 100644
index 0000000..12e812c
--- /dev/null
+++ b/pyverbs/comp_cntr.pyx
@@ -0,0 +1,96 @@
+# SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB)
+# Copyright Amazon.com, Inc. or its affiliates. All rights reserved.
+
+#cython: language_level=3
+
+from pyverbs.base import PyverbsRDMAErrno, PyverbsRDMAError
+from pyverbs.device cimport Context
+cimport pyverbs.libibverbs as v
+
+
+cdef class CompCntrInitAttr(PyverbsObject):
+    """Represents ibv_comp_cntr_init_attr struct."""
+    def __init__(self, comp_mask=0, cntr_type=0, flags=0):
+        super().__init__()
+        self.attr.comp_mask = comp_mask
+        self.attr.type = cntr_type
+        self.attr.flags = flags
+
+    @property
+    def comp_mask(self):
+        return self.attr.comp_mask
+
+    @property
+    def cntr_type(self):
+        return self.attr.type
+
+    @property
+    def flags(self):
+        return self.attr.flags
+
+
+cdef class CompCntr(PyverbsCM):
+    """Completion Counter object for tracking aggregate completions."""
+    def __init__(self, Context ctx not None, CompCntrInitAttr attr not None):
+        """Create a completion counter.
+
+        :param ctx: Device context to create the counter on.
+        :param attr: Completion counter init attributes.
+        """
+        super().__init__()
+        self.comp_cntr = v.ibv_create_comp_cntr(ctx.context, &attr.attr)
+        if self.comp_cntr == NULL:
+            raise PyverbsRDMAErrno('Failed to create comp_cntr')
+        self.ctx = ctx
+        ctx.add_ref(self)
+
+    def __dealloc__(self):
+        self.close()
+
+    cpdef close(self):
+        """Destroy the completion counter."""
+        if self.comp_cntr != NULL:
+            rc = v.ibv_destroy_comp_cntr(self.comp_cntr)
+            if rc:
+                raise PyverbsRDMAError('Failed to destroy comp_cntr', rc)
+            self.comp_cntr = NULL
+
+    def set(self, value):
+        """Set the completion count to the given value."""
+        rc = v.ibv_set_comp_cntr(self.comp_cntr, value)
+        if rc:
+            raise PyverbsRDMAError('Failed to set comp_cntr', rc)
+
+    def set_err(self, value):
+        """Set the error count to the given value."""
+        rc = v.ibv_set_err_comp_cntr(self.comp_cntr, value)
+        if rc:
+            raise PyverbsRDMAError('Failed to set_err comp_cntr', rc)
+
+    def inc(self, amount):
+        """Increment the completion count by amount."""
+        rc = v.ibv_inc_comp_cntr(self.comp_cntr, amount)
+        if rc:
+            raise PyverbsRDMAError('Failed to inc comp_cntr', rc)
+
+    def inc_err(self, amount):
+        """Increment the error count by amount."""
+        rc = v.ibv_inc_err_comp_cntr(self.comp_cntr, amount)
+        if rc:
+            raise PyverbsRDMAError('Failed to inc_err comp_cntr', rc)
+
+    def read(self):
+        """Read and return the current completion count."""
+        cdef unsigned long value = 0
+        rc = v.ibv_read_comp_cntr(self.comp_cntr, &value)
+        if rc:
+            raise PyverbsRDMAError('Failed to read comp_cntr', rc)
+        return value
+
+    def read_err(self):
+        """Read and return the current error count."""
+        cdef unsigned long value = 0
+        rc = v.ibv_read_err_comp_cntr(self.comp_cntr, &value)
+        if rc:
+            raise PyverbsRDMAError('Failed to read_err comp_cntr', rc)
+        return value
diff --git a/pyverbs/device.pxd b/pyverbs/device.pxd
index 26bbe9a..02446ce 100644
--- a/pyverbs/device.pxd
+++ b/pyverbs/device.pxd
@@ -23,6 +23,7 @@
     cdef object pps
     cdef object sched_nodes
     cdef object sched_leafs
+    cdef object comp_cntrs
     cdef object dr_domains
     cdef object wqs
     cdef object rwq_ind_tbls
@@ -63,6 +64,9 @@
 cdef class DeviceAttrEx(PyverbsObject):
     cdef v.ibv_device_attr_ex dev_attr
 
+cdef class CompCntrCaps(PyverbsObject):
+    cdef v.ibv_comp_cntr_caps caps
+
 cdef class AllocDmAttr(PyverbsObject):
     cdef v.ibv_alloc_dm_attr alloc_dm_attr
 
diff --git a/pyverbs/device.pyx b/pyverbs/device.pyx
index 25466f1..ea1f71d 100644
--- a/pyverbs/device.pyx
+++ b/pyverbs/device.pyx
@@ -9,6 +9,7 @@
 import weakref
 
 from .pyverbs_error import PyverbsRDMAError, PyverbsError
+from pyverbs.comp_cntr cimport CompCntr
 from pyverbs.cq cimport CQEX, CQ, CompChannel
 from .pyverbs_error import PyverbsUserError
 from pyverbs.base import PyverbsRDMAErrno
@@ -119,6 +120,7 @@
         self.pps = weakref.WeakSet()
         self.sched_nodes = weakref.WeakSet()
         self.sched_leafs = weakref.WeakSet()
+        self.comp_cntrs = weakref.WeakSet()
         self.dr_domains = weakref.WeakSet()
         self.wqs = weakref.WeakSet()
         self.rwq_ind_tbls = weakref.WeakSet()
@@ -185,7 +187,8 @@
                             self.crypto_logins, self.rwq_ind_tbls, self.wqs,
                             self.ccs, self.cqs, self.dms, self.pds, self.xrcds,
                             self.vars, self.sched_leafs, self.sched_nodes,
-                            self.dr_domains, self.event_channels, self.dmahs])
+                            self.dr_domains, self.event_channels, self.dmahs,
+                            self.comp_cntrs])
             rc = v.ibv_close_device(self.context)
             if rc != 0:
                 raise PyverbsRDMAErrno(f'Failed to close device {self.name}')
@@ -228,6 +231,17 @@
                                    format(name=self.name), rc)
         return dev_attr_ex
 
+    def query_comp_cntr_caps(self):
+        """
+        Query completion counter capabilities.
+        :return: CompCntrCaps object
+        """
+        caps = CompCntrCaps()
+        rc = v.ibv_query_comp_cntr_caps(self.context, &caps.caps)
+        if rc != 0:
+            raise PyverbsRDMAError('Failed to query comp_cntr caps', rc)
+        return caps
+
     def query_pkey(self, unsigned int port_num, int index):
         cdef uint16_t pkey
         rc = v.ibv_query_pkey(self.context, port_num, index, &pkey)
@@ -366,6 +380,8 @@
             self.rwq_ind_tbls.add(obj)
         elif isinstance(obj, DMAHandle):
             self.dmahs.add(obj)
+        elif isinstance(obj, CompCntr):
+            self.comp_cntrs.add(obj)
         else:
             raise PyverbsError('Unrecognized object type')
 
@@ -749,6 +765,21 @@
         return self.dev_attr.phys_port_cnt_ex
 
 
+cdef class CompCntrCaps(PyverbsObject):
+    """Completion counter capabilities."""
+    @property
+    def max_counters(self):
+        return self.caps.max_counters
+
+    @property
+    def max_value(self):
+        return self.caps.max_value
+
+    @property
+    def supported_qp_attach_ops(self):
+        return self.caps.supported_qp_attach_ops
+
+
 cdef class AllocDmAttr(PyverbsObject):
     def __init__(self, length, log_align_req = 0, comp_mask = 0):
         """
diff --git a/pyverbs/libibverbs.pxd b/pyverbs/libibverbs.pxd
index a8e942a..82bfa86 100644
--- a/pyverbs/libibverbs.pxd
+++ b/pyverbs/libibverbs.pxd
@@ -247,6 +247,22 @@
     cdef struct ibv_poll_cq_attr:
         unsigned int    comp_mask
 
+    cdef struct ibv_comp_cntr_caps:
+        unsigned long   max_value
+        unsigned int    max_counters
+        unsigned int    supported_qp_attach_ops
+
+    cdef struct ibv_comp_cntr:
+        ibv_context     *context
+        unsigned int    handle
+        unsigned long   comp_count_max_value
+        unsigned long   err_count_max_value
+
+    cdef struct ibv_comp_cntr_init_attr:
+        unsigned int    comp_mask
+        unsigned int    type
+        unsigned int    flags
+
     cdef struct ibv_wc_tm_info:
         unsigned long   tag
         unsigned int    priv
@@ -512,6 +528,10 @@
         ibv_qp_type     qp_type;
         unsigned int    events_completed;
 
+    cdef struct ibv_qp_attach_comp_cntr_attr:
+        unsigned int    comp_mask
+        unsigned int    op_mask
+
     cdef struct ibv_parent_domain_init_attr:
         ibv_pd          *pd;
         uint32_t        comp_mask;
@@ -756,6 +776,17 @@
     unsigned int ibv_wc_read_flow_tag(ibv_cq_ex *cq)
     void ibv_wc_read_tm_info(ibv_cq_ex *cq, ibv_wc_tm_info *tm_info)
     unsigned long ibv_wc_read_completion_wallclock_ns(ibv_cq_ex *cq)
+    int ibv_query_comp_cntr_caps(ibv_context *context,
+                                 ibv_comp_cntr_caps *caps)
+    ibv_comp_cntr *ibv_create_comp_cntr(ibv_context *context,
+                                        ibv_comp_cntr_init_attr *attr)
+    int ibv_destroy_comp_cntr(ibv_comp_cntr *comp_cntr)
+    int ibv_set_comp_cntr(ibv_comp_cntr *comp_cntr, unsigned long value)
+    int ibv_set_err_comp_cntr(ibv_comp_cntr *comp_cntr, unsigned long value)
+    int ibv_inc_comp_cntr(ibv_comp_cntr *comp_cntr, unsigned long amount)
+    int ibv_inc_err_comp_cntr(ibv_comp_cntr *comp_cntr, unsigned long amount)
+    int ibv_read_comp_cntr(ibv_comp_cntr *comp_cntr, unsigned long *value)
+    int ibv_read_err_comp_cntr(ibv_comp_cntr *comp_cntr, unsigned long *value)
     ibv_ah *ibv_create_ah(ibv_pd *pd, ibv_ah_attr *attr)
     int ibv_init_ah_from_wc(ibv_context *context, uint8_t port_num,
                             ibv_wc *wc, ibv_grh *grh, ibv_ah_attr *ah_attr)
@@ -770,6 +801,8 @@
     int ibv_query_qp(ibv_qp *qp, ibv_qp_attr *attr, int attr_mask,
                      ibv_qp_init_attr *init_attr)
     int ibv_destroy_qp(ibv_qp *qp)
+    int ibv_qp_attach_comp_cntr(ibv_qp *qp, ibv_comp_cntr *comp_cntr,
+                                ibv_qp_attach_comp_cntr_attr *attr)
     int ibv_post_recv(ibv_qp *qp, ibv_recv_wr *wr, ibv_recv_wr **bad_wr)
     int ibv_post_send(ibv_qp *qp, ibv_send_wr *wr, ibv_send_wr **bad_wr)
     int ibv_bind_mw(ibv_qp *qp, ibv_mw *mw, ibv_mw_bind *mw_bind)
diff --git a/pyverbs/libibverbs_enums.pxd b/pyverbs/libibverbs_enums.pxd
index 9219a36..c37f9fe 100644
--- a/pyverbs/libibverbs_enums.pxd
+++ b/pyverbs/libibverbs_enums.pxd
@@ -524,6 +524,14 @@
         IBV_REG_MR_MASK_DMAH
         IBV_REG_MR_MASK_BUF
 
+    cpdef enum ibv_qp_attach_comp_cntr_op:
+        IBV_QP_ATTACH_COMP_CNTR_OP_SEND
+        IBV_QP_ATTACH_COMP_CNTR_OP_RECV
+        IBV_QP_ATTACH_COMP_CNTR_OP_RDMA_READ
+        IBV_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_READ
+        IBV_QP_ATTACH_COMP_CNTR_OP_RDMA_WRITE
+        IBV_QP_ATTACH_COMP_CNTR_OP_REMOTE_RDMA_WRITE
+
 
 cdef extern from "<infiniband/verbs_api.h>":
     cdef unsigned long long IBV_ADVISE_MR_ADVICE_PREFETCH
diff --git a/pyverbs/providers/efa/efa_enums.pxd b/pyverbs/providers/efa/efa_enums.pxd
index 179f30c..725e486 100644
--- a/pyverbs/providers/efa/efa_enums.pxd
+++ b/pyverbs/providers/efa/efa_enums.pxd
@@ -11,6 +11,8 @@
         EFADV_DEVICE_ATTR_CAPS_CQ_WITH_SGID
         EFADV_DEVICE_ATTR_CAPS_RDMA_WRITE
         EFADV_DEVICE_ATTR_CAPS_UNSOLICITED_WRITE_RECV
+        EFADV_DEVICE_ATTR_CAPS_CQ_WITH_EXT_MEM_DMABUF
+        EFADV_DEVICE_ATTR_CAPS_COMP_CNTR
 
     cpdef enum:
         EFADV_QP_DRIVER_TYPE_SRD
@@ -30,6 +32,14 @@
         EFADV_WC_EX_WITH_IS_UNSOLICITED
 
     cpdef enum:
+        EFADV_MEMORY_LOCATION_VA
+        EFADV_MEMORY_LOCATION_DMABUF
+
+    cpdef enum:
+        EFADV_COMP_CNTR_INIT_WITH_COMP_EXTERNAL_MEM
+        EFADV_COMP_CNTR_INIT_WITH_ERR_EXTERNAL_MEM
+
+    cpdef enum:
         EFADV_MR_ATTR_VALIDITY_RECV_IC_ID
         EFADV_MR_ATTR_VALIDITY_RDMA_READ_IC_ID
         EFADV_MR_ATTR_VALIDITY_RDMA_RECV_IC_ID
diff --git a/pyverbs/providers/efa/efadv.pyx b/pyverbs/providers/efa/efadv.pyx
index ab56f38..791a3cb 100644
--- a/pyverbs/providers/efa/efadv.pyx
+++ b/pyverbs/providers/efa/efadv.pyx
@@ -6,12 +6,17 @@
 
 from pyverbs.addr cimport GID
 from pyverbs.base import PyverbsRDMAErrno, PyverbsRDMAError
+from pyverbs.base cimport PyverbsCM
+from pyverbs.comp_cntr cimport CompCntrInitAttr, CompCntr
 from pyverbs.cq cimport CQEX, CqInitAttrEx
+from pyverbs.device cimport Context
 from pyverbs.libibverbs_enums import ibv_qp_attr_mask
 cimport pyverbs.libibverbs as v
+from pyverbs.mr cimport MR
 from pyverbs.pd cimport PD
 from pyverbs.qp cimport QP, QPEx, QPInitAttr, QPInitAttrEx
-from pyverbs.mr cimport MR
+from libc.string cimport memset
+from libc.stdint cimport uintptr_t, uint8_t
 
 
 def dev_cap_to_str(flags):
@@ -21,6 +26,8 @@
             dve.EFADV_DEVICE_ATTR_CAPS_CQ_WITH_SGID: 'CQ entries with source GID',
             dve.EFADV_DEVICE_ATTR_CAPS_RDMA_WRITE: 'RDMA Write',
             dve.EFADV_DEVICE_ATTR_CAPS_UNSOLICITED_WRITE_RECV: 'Unsolicited RDMA Write receive',
+            dve.EFADV_DEVICE_ATTR_CAPS_CQ_WITH_EXT_MEM_DMABUF: 'CQ with external memory',
+            dve.EFADV_DEVICE_ATTR_CAPS_COMP_CNTR: 'Completion Counters',
     }
     return bitmask_to_str(flags, l)
 
@@ -408,3 +415,56 @@
     @max_recv_sge.setter
     def max_recv_sge(self, val):
         self.rq_depth_attr.max_recv_sge = val
+
+
+cdef class EfaCompCntrInitAttr(PyverbsObject):
+    """Represents efadv_comp_cntr_init_attr struct."""
+    cdef dv.efadv_comp_cntr_init_attr attr
+
+    def __init__(self, comp_ext_mem_ptr=None, err_ext_mem_ptr=None):
+        super().__init__()
+        memset(&self.attr, 0, sizeof(self.attr))
+        if comp_ext_mem_ptr is not None:
+            self.attr.flags |= dve.EFADV_COMP_CNTR_INIT_WITH_COMP_EXTERNAL_MEM
+            self.attr.comp_cntr_ext_mem.type = dve.EFADV_MEMORY_LOCATION_VA
+            self.attr.comp_cntr_ext_mem.ptr = <uint8_t *><uintptr_t>comp_ext_mem_ptr
+        if err_ext_mem_ptr is not None:
+            self.attr.flags |= dve.EFADV_COMP_CNTR_INIT_WITH_ERR_EXTERNAL_MEM
+            self.attr.err_cntr_ext_mem.type = dve.EFADV_MEMORY_LOCATION_VA
+            self.attr.err_cntr_ext_mem.ptr = <uint8_t *><uintptr_t>err_ext_mem_ptr
+
+    def set_comp_ext_mem_va(self, uintptr_t ptr):
+        """Set completion counter external memory VA."""
+        self.attr.flags |= dve.EFADV_COMP_CNTR_INIT_WITH_COMP_EXTERNAL_MEM
+        self.attr.comp_cntr_ext_mem.type = dve.EFADV_MEMORY_LOCATION_VA
+        self.attr.comp_cntr_ext_mem.ptr = <uint8_t *>ptr
+
+    def set_err_ext_mem_va(self, uintptr_t ptr):
+        """Set error counter external memory VA."""
+        self.attr.flags |= dve.EFADV_COMP_CNTR_INIT_WITH_ERR_EXTERNAL_MEM
+        self.attr.err_cntr_ext_mem.type = dve.EFADV_MEMORY_LOCATION_VA
+        self.attr.err_cntr_ext_mem.ptr = <uint8_t *>ptr
+
+
+cdef class EfaCompCntr(CompCntr):
+    """EFA-specific Completion Counter with external memory support."""
+    def __init__(self, Context ctx not None, CompCntrInitAttr attr not None,
+                 EfaCompCntrInitAttr efa_attr=None):
+        """Create an EFA completion counter.
+
+        :param ctx: Device context.
+        :param attr: Completion counter init attributes.
+        :param efa_attr: EFA-specific init attributes (optional).
+        """
+        PyverbsCM.__init__(self)
+        if efa_attr is None:
+            efa_attr = EfaCompCntrInitAttr()
+        self.comp_cntr = dv.efadv_create_comp_cntr(
+            ctx.context, &attr.attr, &efa_attr.attr, sizeof(efa_attr.attr))
+        if self.comp_cntr == NULL:
+            raise PyverbsRDMAErrno('Failed to create EFA comp_cntr')
+        self.ctx = ctx
+        ctx.add_ref(self)
+
+    def __dealloc__(self):
+        self.close()
diff --git a/pyverbs/providers/efa/libefa.pxd b/pyverbs/providers/efa/libefa.pxd
index 5cd4d1b..878cc5b 100644
--- a/pyverbs/providers/efa/libefa.pxd
+++ b/pyverbs/providers/efa/libefa.pxd
@@ -86,3 +86,20 @@
                                uint32_t inlen)
     int efadv_get_max_rq_depth(v.ibv_context *ibvctx, efadv_rq_depth_attr *attr,
                                uint32_t inlen);
+
+    cdef struct efadv_memory_location:
+        uint8_t *ptr
+        uint8_t type
+        uint8_t reserved[7]
+
+    cdef struct efadv_comp_cntr_init_attr:
+        uint64_t comp_mask
+        uint32_t flags
+        uint32_t reserved
+        efadv_memory_location comp_cntr_ext_mem
+        efadv_memory_location err_cntr_ext_mem
+
+    v.ibv_comp_cntr *efadv_create_comp_cntr(v.ibv_context *ibvctx,
+                                            v.ibv_comp_cntr_init_attr *attr,
+                                            efadv_comp_cntr_init_attr *efa_attr,
+                                            uint32_t inlen)
diff --git a/pyverbs/qp.pxd b/pyverbs/qp.pxd
index 25afd32..050d59d 100644
--- a/pyverbs/qp.pxd
+++ b/pyverbs/qp.pxd
@@ -30,6 +30,9 @@
 cdef class QPRateLimitAttr(PyverbsObject):
     cdef v.ibv_qp_rate_limit_attr attr
 
+cdef class QPAttachCompCntrAttr(PyverbsObject):
+    cdef v.ibv_qp_attach_comp_cntr_attr attr
+
 cdef class QP(PyverbsCM):
     cdef v.ibv_qp *qp
     cdef int type
diff --git a/pyverbs/qp.pyx b/pyverbs/qp.pyx
index b4f8403..a14fd93 100644
--- a/pyverbs/qp.pyx
+++ b/pyverbs/qp.pyx
@@ -21,6 +21,7 @@
 from pyverbs.device cimport Context
 from cpython.ref cimport PyObject
 from pyverbs.cq cimport CQ, CQEX
+from pyverbs.comp_cntr cimport CompCntr
 cimport pyverbs.libibverbs as v
 from pyverbs.xrcd cimport XRCD
 from pyverbs.srq cimport SRQ
@@ -978,6 +979,17 @@
                print_format.format('Comp mask', self.attr.comp_mask)
 
 
+cdef class QPAttachCompCntrAttr(PyverbsObject):
+    """Attributes for attaching a completion counter to a QP."""
+    def __init__(self, op_mask=0):
+        super().__init__()
+        self.attr.op_mask = op_mask
+
+    @property
+    def op_mask(self):
+        return self.attr.op_mask
+
+
 cdef class QP(PyverbsCM):
     def __init__(self, object creator not None, object init_attr not None,
                  QPAttr qp_attr=None):
@@ -1235,6 +1247,18 @@
         if rc != 0:
             raise PyverbsRDMAError('Failed to modify QP rate limit', rc)
 
+    def attach_comp_cntr(self, CompCntr comp_cntr not None,
+                         QPAttachCompCntrAttr attr not None):
+        """
+        Attach a completion counter to this QP.
+        :param comp_cntr: The completion counter to attach
+        :param attr: Attach attributes including op_mask
+        """
+        rc = v.ibv_qp_attach_comp_cntr(self.qp, comp_cntr.comp_cntr,
+                                        &attr.attr)
+        if rc != 0:
+            raise PyverbsRDMAError('Failed to attach comp cntr to QP', rc)
+
     def post_recv(self, RecvWR wr not None, RecvWR bad_wr=None):
         """
         Post a receive WR on the QP.
diff --git a/pyverbs/wr.pyx b/pyverbs/wr.pyx
index 87297d6..5f91e01 100644
--- a/pyverbs/wr.pyx
+++ b/pyverbs/wr.pyx
@@ -35,11 +35,13 @@
         self.sge.length = length
         self.sge.lkey = lkey
 
-    def __dealloc(self):
+    def __dealloc__(self):
         self.close()
 
     cpdef close(self):
-        free(self.sge)
+        if self.sge != NULL:
+            free(self.sge)
+            self.sge = NULL
 
     cpdef read(self, length, offset):
         """
@@ -105,11 +107,13 @@
         if next_wr is not None:
             self.recv_wr.next = &next_wr.recv_wr
 
-    def __dealloc(self):
+    def __dealloc__(self):
         self.close()
 
     cpdef close(self):
-        free(self.recv_wr.sg_list)
+        if self.recv_wr.sg_list != NULL:
+            free(self.recv_wr.sg_list)
+            self.recv_wr.sg_list = NULL
 
     def __str__(self):
         print_format = '{:22}: {:<20}\n'
@@ -180,11 +184,13 @@
         self.send_wr.imm_data = imm_data
         self.ah = None
 
-    def __dealloc(self):
+    def __dealloc__(self):
         self.close()
 
     cpdef close(self):
-        free(self.send_wr.sg_list)
+        if self.send_wr.sg_list != NULL:
+            free(self.send_wr.sg_list)
+            self.send_wr.sg_list = NULL
 
     def __str__(self):
         print_format = '{:22}: {:<20}\n'
diff --git a/tests/cuda_utils.py b/tests/cuda_utils.py
index a19911d..bc12cac 100644
--- a/tests/cuda_utils.py
+++ b/tests/cuda_utils.py
@@ -8,8 +8,28 @@
 
 import unittest
 
+
+def import_cuda():
+    """
+    Import the cuda-python bindings for both the cuda-python 13 and 12 layouts.
+
+    :return: A tuple of the (cuda, cudart, nvrtc) cuda-python modules.
+    """
+    try:
+        from cuda.bindings import driver as cuda, runtime as cudart, nvrtc
+    except ImportError:
+        from cuda import cuda, cudart, nvrtc
+
+    return cuda, cudart, nvrtc
+
+
 try:
-    from cuda import cuda, cudart, nvrtc
+    cuda, cudart, nvrtc = import_cuda()
+    try:
+        from cuda.bindings import __version__ as _cuda_version
+    except ImportError:
+        from cuda import __version__ as _cuda_version
+    CUDA_PYTHON_MAJOR = int(_cuda_version.split('.', 1)[0])
     CUDA_FOUND = True
 except ImportError:
     CUDA_FOUND = False
@@ -87,11 +107,26 @@
         raise unittest.SkipTest('GPU device ID must be passed')
     check_cuda_errors(cuda.cuInit(0))
     cuda_device = check_cuda_errors(cuda.cuDeviceGet(cuda_dev_id))
-    obj.cuda_ctx = check_cuda_errors(
-        cuda.cuCtxCreate(cuda.CUctx_flags.CU_CTX_MAP_HOST, cuda_device))
+    obj.cuda_ctx = create_cuda_ctx(cuda.CUctx_flags.CU_CTX_MAP_HOST, cuda_device)
     check_cuda_errors(cuda.cuCtxSetCurrent(obj.cuda_ctx))
 
 
+def create_cuda_ctx(flags, cuda_device):
+    """
+    Create a CUDA context using the calling convention for the installed
+    cuda-python version.
+    The cuCtxCreate signature is version-dependent:
+      - cuda-python 12: cuCtxCreate(flags, dev)
+      - cuda-python 13: cuCtxCreate(ctxCreateParams, flags, dev)
+    :param flags: CUDA context creation flags.
+    :param cuda_device: Target CUDA device.
+    :return: The created CUDA context.
+    """
+    if CUDA_PYTHON_MAJOR >= 13:
+        return check_cuda_errors(cuda.cuCtxCreate(None, flags, cuda_device))
+    return check_cuda_errors(cuda.cuCtxCreate(flags, cuda_device))
+
+
 def set_init_cuda_methods(cls):
     """
     Replaces the setUp and tearDown methods of any unittest.TestCase derived
diff --git a/tests/test_cuda_dmabuf.py b/tests/test_cuda_dmabuf.py
index 5b58138..24e487b 100644
--- a/tests/test_cuda_dmabuf.py
+++ b/tests/test_cuda_dmabuf.py
@@ -13,7 +13,7 @@
 import tests.utils as u
 
 try:
-    from cuda import cuda, cudart, nvrtc
+    cuda, cudart, nvrtc = cu.import_cuda()
     cu.CUDA_FOUND = True
 except ImportError:
     cu.CUDA_FOUND = False
diff --git a/tests/test_mlx5_cq.py b/tests/test_mlx5_cq.py
index 4caf461..bf82b3a 100644
--- a/tests/test_mlx5_cq.py
+++ b/tests/test_mlx5_cq.py
@@ -10,7 +10,8 @@
 from tests.mlx5_base import Mlx5DcResources
 from pyverbs.cq import CqInitAttrEx
 from tests.base import RCResources
-from pyverbs.libibverbs_enums import ibv_qp_create_send_ops_flags, ibv_wr_opcode
+from pyverbs.libibverbs_enums import ibv_qp_create_send_ops_flags, ibv_wr_opcode, \
+    ibv_qp_type, ibv_access_flags
 import tests.utils as u
 
 
@@ -83,6 +84,16 @@
         create_dv_cq(self)
 
 
+class Mlx5CqRdmaReadRes(Mlx5CQRes):
+    def create_mr(self):
+        self.mr = u.create_custom_mr(self, ibv_access_flags.IBV_ACCESS_REMOTE_READ)
+
+    def create_qps(self):
+        u.create_qp_ex(self, ibv_qp_type.IBV_QPT_RC,
+                       ibv_qp_create_send_ops_flags.IBV_QP_EX_WITH_RDMA_READ,
+                       sq_sig_all=1)
+
+
 class Mlx5DvCqDcRes(Mlx5DcResources):
     def __init__(self, dev_name, ib_port, gid_index, cqe_comp_res_format=None,
                  flags=None, cqe_size=None, create_flags=None):
@@ -188,6 +199,18 @@
             self.create_players(Mlx5CQRes, cqe_size=128, msg_size=size)
             u.traffic(**self.traffic_args, is_cq_ex=True)
 
+    def test_dv_cq_rdma_read_small_msg_128_cqe(self):
+        """
+        RDMA_READ with mlx5dv 128B CQE and payload < 64B on the requestor side.
+        Exercises scatter-to-CQE inline copy from the CQE (ibv_cq_ex poll path).
+        """
+        msg_sizes = [16, 40, 60]
+        for size in msg_sizes:
+            self.create_players(Mlx5CqRdmaReadRes, cqe_size=128, msg_size=size)
+            u.rdma_traffic(**self.traffic_args, new_send=True,
+                           send_op=ibv_wr_opcode.IBV_WR_RDMA_READ,
+                           is_cq_ex=True)
+
     def test_dv_cq_cqe_size_64(self):
         """
         Test multiple sizes of msg using CQE size of 64B.
diff --git a/tests/test_mlx5_cuda_umem.py b/tests/test_mlx5_cuda_umem.py
index 8e36b0a..0a72b58 100644
--- a/tests/test_mlx5_cuda_umem.py
+++ b/tests/test_mlx5_cuda_umem.py
@@ -10,7 +10,7 @@
 from pyverbs.libibverbs_enums import ibv_access_flags
 
 try:
-    from cuda import cuda, cudart, nvrtc
+    cuda, cudart, nvrtc = cu.import_cuda()
     cu.CUDA_FOUND = True
 except ImportError:
     cu.CUDA_FOUND = False
diff --git a/tests/test_mlx5_dmabuf.py b/tests/test_mlx5_dmabuf.py
index a4de7fa..825e9d9 100644
--- a/tests/test_mlx5_dmabuf.py
+++ b/tests/test_mlx5_dmabuf.py
@@ -17,7 +17,7 @@
 import tests.utils as u
 
 try:
-    from cuda import cuda
+    cuda, _, _ = cu.import_cuda()
     cu.CUDA_FOUND = True
 except ImportError:
     cu.CUDA_FOUND = False
diff --git a/tests/utils.py b/tests/utils.py
index 9a74e54..c4cc4b8 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -382,7 +382,7 @@
     return QPInitAttr(scq=cq, rcq=cq, cap=qp_cap, sq_sig_all=sig)
 
 
-def create_qp_ex(agr_obj, qp_type, send_flags):
+def create_qp_ex(agr_obj, qp_type, send_flags, sq_sig_all=0):
     if qp_type == ibv_qp_type.IBV_QPT_XRC_SEND:
         cap = QPCap(max_send_wr=agr_obj.num_msgs, max_recv_wr=0, max_recv_sge=0,
                     max_send_sge=1)
@@ -390,7 +390,8 @@
         cap = QPCap(max_send_wr=agr_obj.num_msgs, max_recv_wr=agr_obj.num_msgs,
                     max_recv_sge=1, max_send_sge=1)
     qia = QPInitAttrEx(cap=cap, qp_type=qp_type, scq=agr_obj.cq,
-                       rcq=agr_obj.cq, pd=agr_obj.pd, send_ops_flags=send_flags,
+                       rcq=agr_obj.cq, pd=agr_obj.pd, sq_sig_all=sq_sig_all,
+                       send_ops_flags=send_flags,
                        comp_mask=ibv_qp_init_attr_mask.IBV_QP_INIT_ATTR_PD |
                                  ibv_qp_init_attr_mask.IBV_QP_INIT_ATTR_SEND_OPS_FLAGS)
     qp_attr = QPAttr(port_num=agr_obj.ib_port)