diff --git a/libibverbs/cmd_fallback.c b/libibverbs/cmd_fallback.c
index 0e50503e003484..38193a186a8ccb 100644
--- a/libibverbs/cmd_fallback.c
+++ b/libibverbs/cmd_fallback.c
@@ -206,11 +206,43 @@ void *_write_get_resp_ex(struct ibv_command_buffer *link,
 	return resp_start;
 }
 
+static int ioctl_write(struct ibv_context *ctx, unsigned int write_method,
+		       const void *req, size_t core_req_size, size_t req_size,
+		       void *resp, size_t core_resp_size, size_t resp_size)
+{
+	DECLARE_COMMAND_BUFFER(cmdb, UVERBS_OBJECT_DEVICE,
+			       UVERBS_METHOD_INVOKE_WRITE, 5);
+
+	fill_attr_const_in(cmdb, UVERBS_ATTR_WRITE_CMD, write_method);
+
+	if (core_req_size)
+		fill_attr_in(cmdb, UVERBS_ATTR_CORE_IN, req, core_req_size);
+	if (core_resp_size)
+		fill_attr_out(cmdb, UVERBS_ATTR_CORE_OUT, resp, core_resp_size);
+
+	if (req_size - core_req_size)
+		fill_attr_in(cmdb, UVERBS_ATTR_UHW_IN, req + core_req_size,
+			     req_size - core_req_size);
+	if (resp_size - core_resp_size)
+		fill_attr_out(cmdb, UVERBS_ATTR_UHW_OUT, resp + core_resp_size,
+			     resp_size - core_resp_size);
+
+	return execute_ioctl(ctx, cmdb);
+}
+
 int _execute_cmd_write(struct ibv_context *ctx, unsigned int write_method,
 		       struct ib_uverbs_cmd_hdr *req, size_t core_req_size,
 		       size_t req_size, void *resp, size_t core_resp_size,
 		       size_t resp_size)
 {
+	struct verbs_ex_private *priv = get_priv(ctx);
+
+	if (!VERBS_WRITE_ONLY && (VERBS_IOCTL_ONLY || priv->use_ioctl_write))
+		return ioctl_write(ctx, write_method, req + 1,
+				   core_req_size - sizeof(*req),
+				   req_size - sizeof(*req), resp,
+				   core_resp_size, resp_size);
+
 	req->command = write_method;
 	req->in_words = __check_divide(req_size, 4);
 	req->out_words = __check_divide(resp_size, 4);
@@ -232,6 +264,15 @@ int _execute_cmd_write_ex(struct ibv_context *ctx, unsigned int write_method,
 		       size_t req_size, void *resp, size_t core_resp_size,
 		       size_t resp_size)
 {
+	struct verbs_ex_private *priv = get_priv(ctx);
+
+	if (!VERBS_WRITE_ONLY && (VERBS_IOCTL_ONLY || priv->use_ioctl_write))
+		return ioctl_write(
+			ctx, IB_USER_VERBS_CMD_FLAG_EXTENDED | write_method,
+			req + 1, core_req_size - sizeof(*req),
+			req_size - sizeof(*req), resp, core_resp_size,
+			resp_size);
+
 	req->hdr.command = IB_USER_VERBS_CMD_FLAG_EXTENDED | write_method;
 	req->hdr.in_words =
 		__check_divide(core_req_size - sizeof(struct ex_hdr), 8);
@@ -245,7 +286,8 @@ int _execute_cmd_write_ex(struct ibv_context *ctx, unsigned int write_method,
 
 	/*
 	 * Users assumes the stack buffer is zeroed before passing to the
-	 * kernel for writing.
+	 * kernel for writing. New kernels with the ioctl path do this
+	 * automatically for us.
 	 */
 	if (resp)
 		memset(resp, 0, resp_size);
diff --git a/libibverbs/device.c b/libibverbs/device.c
index e6e23e718d9fca..1fed29b17ec31d 100644
--- a/libibverbs/device.c
+++ b/libibverbs/device.c
@@ -43,6 +43,7 @@
 #include <alloca.h>
 #include <errno.h>
 
+#include <rdma/ib_user_ioctl_cmds.h>
 #include <util/symver.h>
 #include "ibverbs.h"
 
@@ -193,6 +194,31 @@ __lib_ibv_create_cq_ex(struct ibv_context *context,
 	return cq;
 }
 
+static bool has_ioctl_write(struct ibv_context *ctx)
+{
+	int rc;
+	DECLARE_COMMAND_BUFFER(cmdb, UVERBS_OBJECT_DEVICE,
+			       UVERBS_METHOD_INVOKE_WRITE, 1);
+
+	if (VERBS_IOCTL_ONLY)
+		return true;
+	if (VERBS_WRITE_ONLY)
+		return false;
+
+	/*
+	 * This command should return ENOSPC since the request length is too
+	 * small.
+	 */
+	fill_attr_const_in(cmdb, UVERBS_ATTR_WRITE_CMD,
+			   IB_USER_VERBS_CMD_QUERY_DEVICE);
+	rc = execute_ioctl(ctx, cmdb);
+	if (rc == EPROTONOSUPPORT)
+		return false;
+	if (rc == ENOTTY)
+		return false;
+	return true;
+}
+
 /*
  * Ownership of cmd_fd is transferred into this function, and it will either
  * be released during the matching call to verbs_uninit_contxt or during the
@@ -240,6 +266,7 @@ int verbs_init_context(struct verbs_context *context_ex,
 
 	context_ex->priv->driver_id = driver_id;
 	verbs_set_ops(context_ex, &verbs_dummy_ops);
+	context_ex->priv->use_ioctl_write = has_ioctl_write(context);
 
 	return 0;
 }
diff --git a/libibverbs/ibverbs.h b/libibverbs/ibverbs.h
index 24843b49fdb3c7..36e29a6b14d87b 100644
--- a/libibverbs/ibverbs.h
+++ b/libibverbs/ibverbs.h
@@ -70,6 +70,7 @@ void load_drivers(void);
 struct verbs_ex_private {
 	BITMAP_DECLARE(unsupported_ioctls, VERBS_OPS_NUM);
 	uint32_t driver_id;
+	bool use_ioctl_write;
 	struct verbs_context_ops ops;
 };
 
