diff --git a/drivers/vdpa/mlx5/mlx5_vdpa.c b/drivers/vdpa/mlx5/mlx5_vdpa.c
index a8f3e4b1de..ba779c10ee 100644
--- a/drivers/vdpa/mlx5/mlx5_vdpa.c
+++ b/drivers/vdpa/mlx5/mlx5_vdpa.c
@@ -283,6 +283,7 @@ mlx5_vdpa_dev_close(int vid)
 	}
 	if (priv->configured)
 		ret |= mlx5_vdpa_lm_log(priv);
+	mlx5_vdpa_err_event_unset(priv);
 	mlx5_vdpa_cqe_event_unset(priv);
 	mlx5_vdpa_steer_unset(priv);
 	mlx5_vdpa_virtqs_release(priv);
@@ -318,6 +319,7 @@ mlx5_vdpa_dev_config(int vid)
 		DRV_LOG(WARNING, "MTU cannot be set on device %s.",
 				vdev->device->name);
 	if (mlx5_vdpa_pd_create(priv) || mlx5_vdpa_mem_register(priv) ||
+	    mlx5_vdpa_err_event_setup(priv) ||
 	    mlx5_vdpa_virtqs_prepare(priv) || mlx5_vdpa_steer_setup(priv) ||
 	    mlx5_vdpa_cqe_event_setup(priv)) {
 		mlx5_vdpa_dev_close(vid);
diff --git a/drivers/vdpa/mlx5/mlx5_vdpa.h b/drivers/vdpa/mlx5/mlx5_vdpa.h
index fcbc12ab0c..0d6886c52c 100644
--- a/drivers/vdpa/mlx5/mlx5_vdpa.h
+++ b/drivers/vdpa/mlx5/mlx5_vdpa.h
@@ -87,6 +87,7 @@ struct mlx5_vdpa_virtq {
 	uint16_t vq_size;
 	uint8_t notifier_state;
 	bool stopped;
+	uint32_t version;
 	struct mlx5_vdpa_priv *priv;
 	struct mlx5_devx_obj *virtq;
 	struct mlx5_devx_obj *counters;
@@ -97,6 +98,8 @@ struct mlx5_vdpa_virtq {
 		uint32_t size;
 	} umems[3];
 	struct rte_intr_handle intr_handle;
+	uint64_t err_time[3]; /* RDTSC time of recent errors. */
+	uint32_t n_retry;
 	struct mlx5_devx_virtio_q_couners_attr reset;
 };
 
@@ -143,8 +146,10 @@ struct mlx5_vdpa_priv {
 	struct rte_vhost_memory *vmem;
 	uint32_t eqn;
 	struct mlx5dv_devx_event_channel *eventc;
+	struct mlx5dv_devx_event_channel *err_chnl;
 	struct mlx5dv_devx_uar *uar;
 	struct rte_intr_handle intr_handle;
+	struct rte_intr_handle err_intr_handle;
 	struct mlx5_devx_obj *td;
 	struct mlx5_devx_obj *tis;
 	uint16_t nr_virtqs;
@@ -259,6 +264,25 @@ int mlx5_vdpa_cqe_event_setup(struct mlx5_vdpa_priv *priv);
  */
 void mlx5_vdpa_cqe_event_unset(struct mlx5_vdpa_priv *priv);
 
+/**
+ * Setup error interrupt handler.
+ *
+ * @param[in] priv
+ *   The vdpa driver private structure.
+ *
+ * @return
+ *   0 on success, a negative errno value otherwise and rte_errno is set.
+ */
+int mlx5_vdpa_err_event_setup(struct mlx5_vdpa_priv *priv);
+
+/**
+ * Unset error event handler.
+ *
+ * @param[in] priv
+ *   The vdpa driver private structure.
+ */
+void mlx5_vdpa_err_event_unset(struct mlx5_vdpa_priv *priv);
+
 /**
  * Release a virtq and all its related resources.
  *
@@ -392,6 +416,19 @@ int mlx5_vdpa_virtq_modify(struct mlx5_vdpa_virtq *virtq, int state);
  */
 int mlx5_vdpa_virtq_stop(struct mlx5_vdpa_priv *priv, int index);
 
+/**
+ * Query virtq information.
+ *
+ * @param[in] priv
+ *   The vdpa driver private structure.
+ * @param[in] index
+ *   The virtq index.
+ *
+ * @return
+ *   0 on success, a negative value otherwise.
+ */
+int mlx5_vdpa_virtq_query(struct mlx5_vdpa_priv *priv, int index);
+
 /**
  * Get virtq statistics.
  *
diff --git a/drivers/vdpa/mlx5/mlx5_vdpa_event.c b/drivers/vdpa/mlx5/mlx5_vdpa_event.c
index 8a01e42794..187a8d8d41 100644
--- a/drivers/vdpa/mlx5/mlx5_vdpa_event.c
+++ b/drivers/vdpa/mlx5/mlx5_vdpa_event.c
@@ -15,11 +15,14 @@
 #include <rte_alarm.h>
 
 #include <mlx5_common.h>
+#include <mlx5_glue.h>
 
 #include "mlx5_vdpa_utils.h"
 #include "mlx5_vdpa.h"
 
 
+#define MLX5_VDPA_ERROR_TIME_SEC 3u
+
 void
 mlx5_vdpa_event_qp_global_release(struct mlx5_vdpa_priv *priv)
 {
@@ -378,6 +381,146 @@ mlx5_vdpa_interrupt_handler(void *cb_arg)
 	pthread_mutex_unlock(&priv->vq_config_lock);
 }
 
+static void
+mlx5_vdpa_err_interrupt_handler(void *cb_arg __rte_unused)
+{
+#ifdef HAVE_IBV_DEVX_EVENT
+	struct mlx5_vdpa_priv *priv = cb_arg;
+	union {
+		struct mlx5dv_devx_async_event_hdr event_resp;
+		uint8_t buf[sizeof(struct mlx5dv_devx_async_event_hdr) + 128];
+	} out;
+	uint32_t vq_index, i, version;
+	struct mlx5_vdpa_virtq *virtq;
+	uint64_t sec;
+
+	pthread_mutex_lock(&priv->vq_config_lock);
+	while (mlx5_glue->devx_get_event(priv->err_chnl, &out.event_resp,
+					 sizeof(out.buf)) >=
+				       (ssize_t)sizeof(out.event_resp.cookie)) {
+		vq_index = out.event_resp.cookie & UINT32_MAX;
+		version = out.event_resp.cookie >> 32;
+		if (vq_index >= priv->nr_virtqs) {
+			DRV_LOG(ERR, "Invalid device %s error event virtq %d.",
+				priv->vdev->device->name, vq_index);
+			continue;
+		}
+		virtq = &priv->virtqs[vq_index];
+		if (!virtq->enable || virtq->version != version)
+			continue;
+		if (rte_rdtsc() / rte_get_tsc_hz() < MLX5_VDPA_ERROR_TIME_SEC)
+			continue;
+		virtq->stopped = true;
+		/* Query error info. */
+		if (mlx5_vdpa_virtq_query(priv, vq_index))
+			goto log;
+		/* Disable vq. */
+		if (mlx5_vdpa_virtq_enable(priv, vq_index, 0)) {
+			DRV_LOG(ERR, "Failed to disable virtq %d.", vq_index);
+			goto log;
+		}
+		/* Retry if error happens less than N times in 3 seconds. */
+		sec = (rte_rdtsc() - virtq->err_time[0]) / rte_get_tsc_hz();
+		if (sec > MLX5_VDPA_ERROR_TIME_SEC) {
+			/* Retry. */
+			if (mlx5_vdpa_virtq_enable(priv, vq_index, 1))
+				DRV_LOG(ERR, "Failed to enable virtq %d.",
+					vq_index);
+			else
+				DRV_LOG(WARNING, "Recover virtq %d: %u.",
+					vq_index, ++virtq->n_retry);
+		} else {
+			/* Retry timeout, give up. */
+			DRV_LOG(ERR, "Device %s virtq %d failed to recover.",
+				priv->vdev->device->name, vq_index);
+		}
+log:
+		/* Shift in current time to error time log end. */
+		for (i = 1; i < RTE_DIM(virtq->err_time); i++)
+			virtq->err_time[i - 1] = virtq->err_time[i];
+		virtq->err_time[RTE_DIM(virtq->err_time) - 1] = rte_rdtsc();
+	}
+	pthread_mutex_unlock(&priv->vq_config_lock);
+#endif
+}
+
+int
+mlx5_vdpa_err_event_setup(struct mlx5_vdpa_priv *priv)
+{
+	int ret;
+	int flags;
+
+	/* Setup device event channel. */
+	priv->err_chnl = mlx5_glue->devx_create_event_channel(priv->ctx, 0);
+	if (!priv->err_chnl) {
+		rte_errno = errno;
+		DRV_LOG(ERR, "Failed to create device event channel %d.",
+			rte_errno);
+		goto error;
+	}
+	flags = fcntl(priv->err_chnl->fd, F_GETFL);
+	ret = fcntl(priv->err_chnl->fd, F_SETFL, flags | O_NONBLOCK);
+	if (ret) {
+		DRV_LOG(ERR, "Failed to change device event channel FD.");
+		goto error;
+	}
+	priv->err_intr_handle.fd = priv->err_chnl->fd;
+	priv->err_intr_handle.type = RTE_INTR_HANDLE_EXT;
+	if (rte_intr_callback_register(&priv->err_intr_handle,
+				       mlx5_vdpa_err_interrupt_handler,
+				       priv)) {
+		priv->err_intr_handle.fd = 0;
+		DRV_LOG(ERR, "Failed to register error interrupt for device %d.",
+			priv->vid);
+		goto error;
+	} else {
+		DRV_LOG(DEBUG, "Registered error interrupt for device%d.",
+			priv->vid);
+	}
+	return 0;
+error:
+	mlx5_vdpa_err_event_unset(priv);
+	return -1;
+}
+
+void
+mlx5_vdpa_err_event_unset(struct mlx5_vdpa_priv *priv)
+{
+	int retries = MLX5_VDPA_INTR_RETRIES;
+	int ret = -EAGAIN;
+
+	if (!priv->err_intr_handle.fd)
+		return;
+	while (retries-- && ret == -EAGAIN) {
+		ret = rte_intr_callback_unregister(&priv->err_intr_handle,
+					    mlx5_vdpa_err_interrupt_handler,
+					    priv);
+		if (ret == -EAGAIN) {
+			DRV_LOG(DEBUG, "Try again to unregister fd %d "
+				"of error interrupt, retries = %d.",
+				priv->err_intr_handle.fd, retries);
+			rte_pause();
+		}
+	}
+	memset(&priv->err_intr_handle, 0, sizeof(priv->err_intr_handle));
+	if (priv->err_chnl) {
+#ifdef HAVE_IBV_DEVX_EVENT
+		union {
+			struct mlx5dv_devx_async_event_hdr event_resp;
+			uint8_t buf[sizeof(struct mlx5dv_devx_async_event_hdr) + 128];
+		} out;
+
+		/* Clean all pending events. */
+		while (mlx5_glue->devx_get_event(priv->err_chnl,
+		       &out.event_resp, sizeof(out.buf)) >=
+		       (ssize_t)sizeof(out.event_resp.cookie))
+			;
+#endif
+		mlx5_glue->devx_destroy_event_channel(priv->err_chnl);
+		priv->err_chnl = NULL;
+	}
+}
+
 int
 mlx5_vdpa_cqe_event_setup(struct mlx5_vdpa_priv *priv)
 {
diff --git a/drivers/vdpa/mlx5/mlx5_vdpa_virtq.c b/drivers/vdpa/mlx5/mlx5_vdpa_virtq.c
index 17e71cf4f4..d5ac040544 100644
--- a/drivers/vdpa/mlx5/mlx5_vdpa_virtq.c
+++ b/drivers/vdpa/mlx5/mlx5_vdpa_virtq.c
@@ -88,11 +88,6 @@ mlx5_vdpa_virtq_unset(struct mlx5_vdpa_virtq *virtq)
 			rte_free(virtq->umems[i].buf);
 	}
 	memset(&virtq->umems, 0, sizeof(virtq->umems));
-	if (virtq->counters) {
-		claim_zero(mlx5_devx_cmd_destroy(virtq->counters));
-		virtq->counters = NULL;
-	}
-	memset(&virtq->reset, 0, sizeof(virtq->reset));
 	if (virtq->eqp.fw_qp)
 		mlx5_vdpa_event_qp_destroy(&virtq->eqp);
 	virtq->notifier_state = MLX5_VDPA_NOTIFIER_STATE_DISABLED;
@@ -103,9 +98,19 @@ void
 mlx5_vdpa_virtqs_release(struct mlx5_vdpa_priv *priv)
 {
 	int i;
+	struct mlx5_vdpa_virtq *virtq;
 
-	for (i = 0; i < priv->nr_virtqs; i++)
-		mlx5_vdpa_virtq_unset(&priv->virtqs[i]);
+	for (i = 0; i < priv->nr_virtqs; i++) {
+		virtq = &priv->virtqs[i];
+		mlx5_vdpa_virtq_unset(virtq);
+		if (virtq->counters) {
+			claim_zero(mlx5_devx_cmd_destroy(virtq->counters));
+			virtq->counters = NULL;
+			memset(&virtq->reset, 0, sizeof(virtq->reset));
+		}
+		memset(virtq->err_time, 0, sizeof(virtq->err_time));
+		virtq->n_retry = 0;
+	}
 	if (priv->tis) {
 		claim_zero(mlx5_devx_cmd_destroy(priv->tis));
 		priv->tis = NULL;
@@ -138,7 +143,6 @@ mlx5_vdpa_virtq_modify(struct mlx5_vdpa_virtq *virtq, int state)
 int
 mlx5_vdpa_virtq_stop(struct mlx5_vdpa_priv *priv, int index)
 {
-	struct mlx5_devx_virtq_attr attr = {0};
 	struct mlx5_vdpa_virtq *virtq = &priv->virtqs[index];
 	int ret;
 
@@ -148,6 +152,17 @@ mlx5_vdpa_virtq_stop(struct mlx5_vdpa_priv *priv, int index)
 	if (ret)
 		return -1;
 	virtq->stopped = true;
+	DRV_LOG(DEBUG, "vid %u virtq %u was stopped.", priv->vid, index);
+	return mlx5_vdpa_virtq_query(priv, index);
+}
+
+int
+mlx5_vdpa_virtq_query(struct mlx5_vdpa_priv *priv, int index)
+{
+	struct mlx5_devx_virtq_attr attr = {0};
+	struct mlx5_vdpa_virtq *virtq = &priv->virtqs[index];
+	int ret;
+
 	if (mlx5_devx_cmd_query_virtq(virtq->virtq, &attr)) {
 		DRV_LOG(ERR, "Failed to query virtq %d.", index);
 		return -1;
@@ -162,7 +177,9 @@ mlx5_vdpa_virtq_stop(struct mlx5_vdpa_priv *priv, int index)
 		DRV_LOG(ERR, "Failed to set virtq %d base.", index);
 		return -1;
 	}
-	DRV_LOG(DEBUG, "vid %u virtq %u was stopped.", priv->vid, index);
+	if (attr.state == MLX5_VIRTQ_STATE_ERROR)
+		DRV_LOG(WARNING, "vid %d vring %d hw error=%hhu",
+			priv->vid, index, attr.error_type);
 	return 0;
 }
 
@@ -195,6 +212,8 @@ mlx5_vdpa_virtq_setup(struct mlx5_vdpa_priv *priv, int index)
 	unsigned int i;
 	uint16_t last_avail_idx;
 	uint16_t last_used_idx;
+	uint16_t event_num = MLX5_EVENT_TYPE_OBJECT_CHANGE;
+	uint64_t cookie;
 
 	ret = rte_vhost_get_vhost_vring(priv->vid, index, &vq);
 	if (ret)
@@ -231,8 +250,9 @@ mlx5_vdpa_virtq_setup(struct mlx5_vdpa_priv *priv, int index)
 			" need event QPs and event mechanism.", index);
 	}
 	if (priv->caps.queue_counters_valid) {
-		virtq->counters = mlx5_devx_cmd_create_virtio_q_counters
-								    (priv->ctx);
+		if (!virtq->counters)
+			virtq->counters = mlx5_devx_cmd_create_virtio_q_counters
+								(priv->ctx);
 		if (!virtq->counters) {
 			DRV_LOG(ERR, "Failed to create virtq couners for virtq"
 				" %d.", index);
@@ -332,6 +352,19 @@ mlx5_vdpa_virtq_setup(struct mlx5_vdpa_priv *priv, int index)
 				virtq->intr_handle.fd, index);
 		}
 	}
+	/* Subscribe virtq error event. */
+	virtq->version++;
+	cookie = ((uint64_t)virtq->version << 32) + index;
+	ret = mlx5_glue->devx_subscribe_devx_event(priv->err_chnl,
+						   virtq->virtq->obj,
+						   sizeof(event_num),
+						   &event_num, cookie);
+	if (ret) {
+		DRV_LOG(ERR, "Failed to subscribe device %d virtq %d error event.",
+			priv->vid, index);
+		rte_errno = errno;
+		goto error;
+	}
 	virtq->stopped = false;
 	DRV_LOG(DEBUG, "vid %u virtq %u was created successfully.", priv->vid,
 		index);
@@ -526,12 +559,11 @@ mlx5_vdpa_virtq_stats_get(struct mlx5_vdpa_priv *priv, int qid,
 	struct mlx5_devx_virtio_q_couners_attr attr = {0};
 	int ret;
 
-	if (!virtq->virtq || !virtq->enable) {
+	if (!virtq->counters) {
 		DRV_LOG(ERR, "Failed to read virtq %d statistics - virtq "
 			"is invalid.", qid);
 		return -EINVAL;
 	}
-	MLX5_ASSERT(virtq->counters);
 	ret = mlx5_devx_cmd_query_virtio_q_counters(virtq->counters, &attr);
 	if (ret) {
 		DRV_LOG(ERR, "Failed to read virtq %d stats from HW.", qid);
@@ -583,12 +615,11 @@ mlx5_vdpa_virtq_stats_reset(struct mlx5_vdpa_priv *priv, int qid)
 	struct mlx5_vdpa_virtq *virtq = &priv->virtqs[qid];
 	int ret;
 
-	if (!virtq->virtq || !virtq->enable) {
+	if (!virtq->counters) {
 		DRV_LOG(ERR, "Failed to read virtq %d statistics - virtq "
 			"is invalid.", qid);
 		return -EINVAL;
 	}
-	MLX5_ASSERT(virtq->counters);
 	ret = mlx5_devx_cmd_query_virtio_q_counters(virtq->counters,
 						    &virtq->reset);
 	if (ret)
