providers/efa: Add processing hints work request setter

Introduce efadv_wr_set_processing_hints(), an EFA direct verbs work
request setter that allows applications to communicate intended usage
patterns to the device for processing optimization. Hints are passed
as a bitmask, allowing multiple hints to be set on a single work
request.

The setter is exposed through efadv_qp and gated by
EFADV_WR_EX_WITH_PROCESSING_HINTS in efadv_qp_init_attr.wr_flags,
ensuring graceful failure when an application is dynamically linked with
older libraries.

Reviewed-by: Anas Mousa <anasmous@amazon.com>
Reviewed-by: Yonatan Nachum <ynachum@amazon.com>
Signed-off-by: Michael Margolin <mrgolin@amazon.com>
diff --git a/debian/ibverbs-providers.symbols b/debian/ibverbs-providers.symbols
index 931b970..3697696 100644
--- a/debian/ibverbs-providers.symbols
+++ b/debian/ibverbs-providers.symbols
@@ -187,6 +187,7 @@
  EFA_1.3@EFA_1.3 50
  EFA_1.4@EFA_1.4 59
  EFA_1.5@EFA_1.5 63
+ EFA_1.6@EFA_1.6 64
  efadv_create_driver_qp@EFA_1.0 24
  efadv_create_qp_ex@EFA_1.1 26
  efadv_query_device@EFA_1.1 26
@@ -198,6 +199,7 @@
  efadv_query_cq@EFA_1.4 59
  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
 libhns.so.1 ibverbs-providers #MINVER#
 * Build-Depends-Package: libibverbs-dev
  HNS_1.0@HNS_1.0 51
diff --git a/providers/efa/CMakeLists.txt b/providers/efa/CMakeLists.txt
index ea082f0..1f3c32c 100644
--- a/providers/efa/CMakeLists.txt
+++ b/providers/efa/CMakeLists.txt
@@ -3,7 +3,7 @@
 endif()
 
 rdma_shared_provider(efa libefa.map
-	1 1.5.${PACKAGE_VERSION}
+	1 1.6.${PACKAGE_VERSION}
 	${TRACE_FILE}
 	efa.c
 	verbs.c
diff --git a/providers/efa/efa.h b/providers/efa/efa.h
index 0685dfa..7a596c9 100644
--- a/providers/efa/efa.h
+++ b/providers/efa/efa.h
@@ -175,6 +175,7 @@
 
 struct efa_qp {
 	struct verbs_qp verbs_qp;
+	struct efadv_qp dv_qp;
 	struct efa_sq sq;
 	struct efa_rq rq;
 	int page_size;
@@ -239,6 +240,11 @@
 	return container_of(ibvqpx, struct efa_qp, verbs_qp.qp_ex);
 }
 
+static inline struct efa_qp *efadv_qp_to_efa_qp(struct efadv_qp *efadv_qp)
+{
+	return container_of(efadv_qp, struct efa_qp, dv_qp);
+}
+
 static inline struct efa_ah *to_efa_ah(struct ibv_ah *ibvah)
 {
 	return container_of(ibvah, struct efa_ah, ibvah);
diff --git a/providers/efa/efa_io_defs.h b/providers/efa/efa_io_defs.h
index fccb217..4059cc8 100644
--- a/providers/efa/efa_io_defs.h
+++ b/providers/efa/efa_io_defs.h
@@ -65,6 +65,11 @@
 	EFA_IO_COMP_STATUS_REMOTE_ERROR_FEATURE_MISMATCH = 18,
 };
 
+enum efa_io_processing_hint {
+	/* Optimize for throughput */
+	EFA_IO_PROCESSING_HINT_BURST_PPS_SENSITIVE  = 1 << 0,
+};
+
 struct efa_io_tx_meta_desc {
 	/* Verbs-generated Request ID */
 	uint16_t req_id;
@@ -114,7 +119,15 @@
 
 	uint16_t ah;
 
-	uint16_t reserved;
+	/*
+	 * control flags
+	 * 1:0 : processing_hints - Bitmask of enum
+	 *    efa_io_processing_hint
+	 * 7:2 : reserved - MBZ
+	 */
+	uint8_t ctrl3;
+
+	uint8_t reserved;
 
 	/* Queue key */
 	uint32_t qkey;
@@ -327,6 +340,7 @@
 #define EFA_IO_TX_META_DESC_FIRST_MASK                      BIT(2)
 #define EFA_IO_TX_META_DESC_LAST_MASK                       BIT(3)
 #define EFA_IO_TX_META_DESC_COMP_REQ_MASK                   BIT(4)
+#define EFA_IO_TX_META_DESC_PROCESSING_HINTS_MASK           GENMASK(1, 0)
 
 /* tx_buf_desc */
 #define EFA_IO_TX_BUF_DESC_LKEY_MASK                        GENMASK(23, 0)
diff --git a/providers/efa/efadv.h b/providers/efa/efadv.h
index bb2f928..8745988 100644
--- a/providers/efa/efadv.h
+++ b/providers/efa/efadv.h
@@ -87,12 +87,17 @@
 	EFADV_QP_FLAGS_INLINE_WRITE = 1 << 1,
 };
 
+enum {
+	EFADV_WR_EX_WITH_PROCESSING_HINTS = 1 << 0,
+};
+
 struct efadv_qp_init_attr {
 	uint64_t comp_mask;
 	uint32_t driver_qp_type;
 	uint16_t flags;
 	uint8_t sl;
 	uint8_t reserved;
+	uint64_t wr_flags;
 };
 
 struct ibv_qp *efadv_create_qp_ex(struct ibv_context *ibvctx,
@@ -113,12 +118,22 @@
 int efadv_query_qp_wqs(struct ibv_qp *ibvqp, struct efadv_wq_attr *sq_attr,
 		       struct efadv_wq_attr *rq_attr, uint32_t inlen);
 
-struct efadv_cq {
-	uint64_t comp_mask;
-	int (*wc_read_sgid)(struct efadv_cq *efadv_cq, union ibv_gid *sgid);
-	bool (*wc_is_unsolicited)(struct efadv_cq *efadv_cq);
+enum efadv_wr_processing_hint {
+	EFADV_WR_PROCESSING_HINT_BURST_PPS_SENSITIVE = 1 << 0,
 };
 
+struct efadv_qp {
+	uint64_t comp_mask;
+	void (*wr_set_processing_hints)(struct efadv_qp *efadv_qp, uint32_t hints);
+};
+
+struct efadv_qp *efadv_qp_from_ibv_qp_ex(struct ibv_qp_ex *ibvqpx);
+
+static inline void efadv_wr_set_processing_hints(struct efadv_qp *efadv_qp, uint32_t hints)
+{
+	efadv_qp->wr_set_processing_hints(efadv_qp, hints);
+}
+
 enum {
 	EFADV_WC_EX_WITH_SGID = 1 << 0,
 	EFADV_WC_EX_WITH_IS_UNSOLICITED = 1 << 1,
@@ -156,6 +171,12 @@
 
 int efadv_query_cq(struct ibv_cq *ibvcq, struct efadv_cq_attr *attr, uint32_t inlen);
 
+struct efadv_cq {
+	uint64_t comp_mask;
+	int (*wc_read_sgid)(struct efadv_cq *efadv_cq, union ibv_gid *sgid);
+	bool (*wc_is_unsolicited)(struct efadv_cq *efadv_cq);
+};
+
 struct efadv_cq *efadv_cq_from_ibv_cq_ex(struct ibv_cq_ex *ibvcqx);
 
 static inline int efadv_wc_read_sgid(struct efadv_cq *efadv_cq,
diff --git a/providers/efa/libefa.map b/providers/efa/libefa.map
index 03a6d8e..3f51e5e 100644
--- a/providers/efa/libefa.map
+++ b/providers/efa/libefa.map
@@ -35,3 +35,8 @@
 		efadv_get_max_sq_depth;
 		efadv_get_max_rq_depth;
 } EFA_1.4;
+
+EFA_1.6 {
+	global:
+		efadv_qp_from_ibv_qp_ex;
+} EFA_1.5;
diff --git a/providers/efa/man/CMakeLists.txt b/providers/efa/man/CMakeLists.txt
index 07ca589..1879aae 100644
--- a/providers/efa/man/CMakeLists.txt
+++ b/providers/efa/man/CMakeLists.txt
@@ -7,4 +7,5 @@
   efadv_query_device.3.md
   efadv_query_mr.3.md
   efadv_query_qp_wqs.3.md
+  efadv_wr_set_processing_hints.3.md
 )
diff --git a/providers/efa/man/efadv_create_qp_ex.3.md b/providers/efa/man/efadv_create_qp_ex.3.md
index 8617fe3..c5f0043 100644
--- a/providers/efa/man/efadv_create_qp_ex.3.md
+++ b/providers/efa/man/efadv_create_qp_ex.3.md
@@ -46,7 +46,8 @@
 	uint32_t driver_qp_type;
 	uint16_t flags;
 	uint8_t sl;
-	uint8_t reserved[1];
+	uint8_t reserved;
+	uint64_t wr_flags;
 };
 ```
 
@@ -74,6 +75,15 @@
 *sl*
 :	Service Level - 0 value implies default level.
 
+*wr_flags*
+:	A bitwise OR of the values described below. Controls which
+	EFA-specific work request setter functions are available on
+	the QP. Use **efadv_qp_from_ibv_qp_ex()** to get the
+	efadv_qp for accessing the work request setter interface.
+
+	EFADV_WR_EX_WITH_PROCESSING_HINTS:
+		Enable **efadv_wr_set_processing_hints()** on this QP.
+
 # RETURN VALUE
 
 efadv_create_qp_ex() returns a pointer to the created QP, or NULL if the request fails.
diff --git a/providers/efa/man/efadv_wr_set_processing_hints.3.md b/providers/efa/man/efadv_wr_set_processing_hints.3.md
new file mode 100644
index 0000000..930fb06
--- /dev/null
+++ b/providers/efa/man/efadv_wr_set_processing_hints.3.md
@@ -0,0 +1,62 @@
+---
+layout: page
+title: EFADV_WR_SET_PROCESSING_HINTS
+section: 3
+tagline: Verbs
+date: 2026-05-05
+header: "EFA Direct Verbs Manual"
+footer: efa
+---
+
+# NAME
+
+efadv_wr_set_processing_hints - Set processing hints on the current
+work request
+
+# SYNOPSIS
+
+```c
+#include <infiniband/efadv.h>
+
+static inline void efadv_wr_set_processing_hints(
+	struct efadv_qp *efadv_qp,
+	uint32_t hints);
+```
+
+# DESCRIPTION
+
+**efadv_wr_set_processing_hints()** sets processing hints on the
+current work request being built. Hints allow the application to
+communicate intended usage patterns to the device, which may use them
+to optimize processing.
+
+This function is a work request setter and must be called after the
+work request opcode function (e.g. **ibv_wr_send()**) and before
+**ibv_wr_complete()** or the next work request opcode call.
+
+Use **efadv_qp_from_ibv_qp_ex()** to get the efadv_qp for accessing
+this interface.
+
+The QP must be created with **EFADV_WR_EX_WITH_PROCESSING_HINTS** set
+in *efadv_qp_init_attr.wr_flags* to use this function.
+
+The *hints* argument is a bitmask of **efadv_wr_processing_hint**
+values:
+
+```c
+enum efadv_wr_processing_hint {
+	EFADV_WR_PROCESSING_HINT_BURST_PPS_SENSITIVE = 1 << 0,
+};
+```
+
+*EFADV_WR_PROCESSING_HINT_BURST_PPS_SENSITIVE*
+:	Optimize for throughput in bursty, packet-rate sensitive
+	workloads.
+
+# SEE ALSO
+
+**efadv**(7), **efadv_create_qp_ex**(3), **ibv_wr_start**(3)
+
+# AUTHORS
+
+Michael Margolin <mrgolin@amazon.com>
diff --git a/providers/efa/verbs.c b/providers/efa/verbs.c
index f8fd2c5..09604a4 100644
--- a/providers/efa/verbs.c
+++ b/providers/efa/verbs.c
@@ -1777,8 +1777,9 @@
 	}
 }
 
-static void efa_qp_fill_wr_pfns(struct ibv_qp_ex *ibvqpx,
+static void efa_qp_fill_wr_pfns(struct efa_qp *qp,
 				struct ibv_qp_init_attr_ex *attr_ex,
+				struct efadv_qp_init_attr *efa_attr,
 				uint16_t wqe_size);
 
 static int efa_check_qp_attr(struct efa_context *ctx,
@@ -1853,6 +1854,12 @@
 		}
 	}
 
+	if (!check_comp_mask(efa_attr->wr_flags, EFADV_WR_EX_WITH_PROCESSING_HINTS)) {
+		verbs_err(&ctx->ibvctx, "Unsupported wr_flags[%" PRIx64 "]\n",
+			  efa_attr->wr_flags);
+		return EOPNOTSUPP;
+	}
+
 	if (!attr->recv_cq || !attr->send_cq) {
 		verbs_err(&ctx->ibvctx, "Send/Receive CQ not provided\n");
 		return EINVAL;
@@ -1983,7 +1990,7 @@
 	pthread_spin_unlock(&ctx->qp_table_lock);
 
 	if (attr->comp_mask & IBV_QP_INIT_ATTR_SEND_OPS_FLAGS) {
-		efa_qp_fill_wr_pfns(&qp->verbs_qp.qp_ex, attr, qp->sq.wqe_size);
+		efa_qp_fill_wr_pfns(qp, attr, efa_attr, qp->sq.wqe_size);
 		qp->verbs_qp.comp_mask |= VERBS_QP_EX;
 	}
 
@@ -2185,6 +2192,13 @@
 	return caps;
 }
 
+struct efadv_qp *efadv_qp_from_ibv_qp_ex(struct ibv_qp_ex *ibvqpx)
+{
+	struct efa_qp *qp = to_efa_qp_ex(ibvqpx);
+
+	return &qp->dv_qp;
+}
+
 int efa_destroy_qp(struct ibv_qp *ibvqp)
 {
 	struct efa_context *ctx = to_efa_context(ibvqp->context);
@@ -2912,6 +2926,20 @@
 			efa_wqe_get_data_length(qp->sq));
 }
 
+static void efa_send_wr_set_processing_hints(struct efadv_qp *efadv_qp, uint32_t hints)
+{
+	struct efa_qp *qp = efadv_qp_to_efa_qp(efadv_qp);
+	uint8_t wqe_hints = 0;
+
+	if (unlikely(qp->wr_session_err))
+		return;
+
+	if (hints & EFADV_WR_PROCESSING_HINT_BURST_PPS_SENSITIVE)
+		wqe_hints |= EFA_IO_PROCESSING_HINT_BURST_PPS_SENSITIVE;
+
+	EFA_SET(&qp->sq.curr_tx_wqe.md->ctrl3, EFA_IO_TX_META_DESC_PROCESSING_HINTS, wqe_hints);
+}
+
 static void efa_send_wr_start(struct ibv_qp_ex *ibvqpx)
 {
 	struct efa_qp *qp = to_efa_qp_ex(ibvqpx);
@@ -3012,11 +3040,13 @@
 		pthread_spin_unlock(&sq->wq.wqlock);
 }
 
-static void efa_qp_fill_wr_pfns(struct ibv_qp_ex *ibvqpx,
+static void efa_qp_fill_wr_pfns(struct efa_qp *qp,
 				struct ibv_qp_init_attr_ex *attr_ex,
+				struct efadv_qp_init_attr *efa_attr,
 				uint16_t wqe_size)
 {
 	bool use_64 = wqe_size == EFA_IO_TX_DESC_SIZE_64;
+	struct ibv_qp_ex *ibvqpx = &qp->verbs_qp.qp_ex;
 
 	ibvqpx->wr_start = efa_send_wr_start;
 	ibvqpx->wr_complete = efa_send_wr_complete;
@@ -3045,6 +3075,9 @@
 	ibvqpx->wr_set_sge = efa_send_wr_set_sge;
 	ibvqpx->wr_set_sge_list = efa_send_wr_set_sge_list;
 	ibvqpx->wr_set_ud_addr = efa_send_wr_set_addr;
+
+	if (efa_attr->wr_flags & EFADV_WR_EX_WITH_PROCESSING_HINTS)
+		qp->dv_qp.wr_set_processing_hints = efa_send_wr_set_processing_hints;
 }
 
 static int efa_post_recv_validate(struct efa_qp *qp, struct ibv_recv_wr *wr)