Merge tag 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mst/vhost
[linux-2.6-microblaze.git] / drivers / vdpa / mlx5 / net / mlx5_vnet.c
index 5906cad..294ba05 100644 (file)
@@ -45,6 +45,8 @@ MODULE_LICENSE("Dual BSD/GPL");
        (VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER | VIRTIO_CONFIG_S_DRIVER_OK |        \
         VIRTIO_CONFIG_S_FEATURES_OK | VIRTIO_CONFIG_S_NEEDS_RESET | VIRTIO_CONFIG_S_FAILED)
 
+#define MLX5_FEATURE(_mvdev, _feature) (!!((_mvdev)->actual_features & BIT_ULL(_feature)))
+
 struct mlx5_vdpa_net_resources {
        u32 tisn;
        u32 tdn;
@@ -90,7 +92,6 @@ struct mlx5_vq_restore_info {
        u16 avail_index;
        u16 used_index;
        bool ready;
-       struct vdpa_callback cb;
        bool restore;
 };
 
@@ -100,7 +101,6 @@ struct mlx5_vdpa_virtqueue {
        u64 device_addr;
        u64 driver_addr;
        u32 num_ent;
-       struct vdpa_callback event_cb;
 
        /* Resources for implementing the notification channel from the device
         * to the driver. fwqp is the firmware end of an RC connection; the
@@ -135,11 +135,20 @@ struct mlx5_vdpa_virtqueue {
  */
 #define MLX5_MAX_SUPPORTED_VQS 16
 
+static bool is_index_valid(struct mlx5_vdpa_dev *mvdev, u16 idx)
+{
+       if (unlikely(idx > mvdev->max_idx))
+               return false;
+
+       return true;
+}
+
 struct mlx5_vdpa_net {
        struct mlx5_vdpa_dev mvdev;
        struct mlx5_vdpa_net_resources res;
        struct virtio_net_config config;
        struct mlx5_vdpa_virtqueue vqs[MLX5_MAX_SUPPORTED_VQS];
+       struct vdpa_callback event_cbs[MLX5_MAX_SUPPORTED_VQS + 1];
 
        /* Serialize vq resources creation and destruction. This is required
         * since memory map might change and we need to destroy and create
@@ -151,15 +160,18 @@ struct mlx5_vdpa_net {
        struct mlx5_flow_handle *rx_rule;
        bool setup;
        u16 mtu;
+       u32 cur_num_vqs;
 };
 
 static void free_resources(struct mlx5_vdpa_net *ndev);
 static void init_mvqs(struct mlx5_vdpa_net *ndev);
-static int setup_driver(struct mlx5_vdpa_net *ndev);
+static int setup_driver(struct mlx5_vdpa_dev *mvdev);
 static void teardown_driver(struct mlx5_vdpa_net *ndev);
 
 static bool mlx5_vdpa_debug;
 
+#define MLX5_CVQ_MAX_ENT 16
+
 #define MLX5_LOG_VIO_FLAG(_feature)                                                                \
        do {                                                                                       \
                if (features & BIT_ULL(_feature))                                                  \
@@ -172,11 +184,41 @@ static bool mlx5_vdpa_debug;
                        mlx5_vdpa_info(mvdev, "%s\n", #_status);                                   \
        } while (0)
 
+/* TODO: cross-endian support */
+static inline bool mlx5_vdpa_is_little_endian(struct mlx5_vdpa_dev *mvdev)
+{
+       return virtio_legacy_is_little_endian() ||
+               (mvdev->actual_features & BIT_ULL(VIRTIO_F_VERSION_1));
+}
+
+static u16 mlx5vdpa16_to_cpu(struct mlx5_vdpa_dev *mvdev, __virtio16 val)
+{
+       return __virtio16_to_cpu(mlx5_vdpa_is_little_endian(mvdev), val);
+}
+
+static __virtio16 cpu_to_mlx5vdpa16(struct mlx5_vdpa_dev *mvdev, u16 val)
+{
+       return __cpu_to_virtio16(mlx5_vdpa_is_little_endian(mvdev), val);
+}
+
 static inline u32 mlx5_vdpa_max_qps(int max_vqs)
 {
        return max_vqs / 2;
 }
 
+static u16 ctrl_vq_idx(struct mlx5_vdpa_dev *mvdev)
+{
+       if (!(mvdev->actual_features & BIT_ULL(VIRTIO_NET_F_MQ)))
+               return 2;
+
+       return 2 * mlx5_vdpa_max_qps(mvdev->max_vqs);
+}
+
+static bool is_ctrl_vq_idx(struct mlx5_vdpa_dev *mvdev, u16 idx)
+{
+       return idx == ctrl_vq_idx(mvdev);
+}
+
 static void print_status(struct mlx5_vdpa_dev *mvdev, u8 status, bool set)
 {
        if (status & ~VALID_STATUS_MASK)
@@ -481,6 +523,10 @@ static int mlx5_vdpa_poll_one(struct mlx5_vdpa_cq *vcq)
 
 static void mlx5_vdpa_handle_completions(struct mlx5_vdpa_virtqueue *mvq, int num)
 {
+       struct mlx5_vdpa_net *ndev = mvq->ndev;
+       struct vdpa_callback *event_cb;
+
+       event_cb = &ndev->event_cbs[mvq->index];
        mlx5_cq_set_ci(&mvq->cq.mcq);
 
        /* make sure CQ cosumer update is visible to the hardware before updating
@@ -488,8 +534,8 @@ static void mlx5_vdpa_handle_completions(struct mlx5_vdpa_virtqueue *mvq, int nu
         */
        dma_wmb();
        rx_post(&mvq->vqqp, num);
-       if (mvq->event_cb.callback)
-               mvq->event_cb.callback(mvq->event_cb.private);
+       if (event_cb->callback)
+               event_cb->callback(event_cb->private);
 }
 
 static void mlx5_vdpa_cq_comp(struct mlx5_core_cq *mcq, struct mlx5_eqe *eqe)
@@ -1100,10 +1146,8 @@ static int setup_vq(struct mlx5_vdpa_net *ndev, struct mlx5_vdpa_virtqueue *mvq)
        if (!mvq->num_ent)
                return 0;
 
-       if (mvq->initialized) {
-               mlx5_vdpa_warn(&ndev->mvdev, "attempt re init\n");
-               return -EINVAL;
-       }
+       if (mvq->initialized)
+               return 0;
 
        err = cq_create(ndev, idx, mvq->num_ent);
        if (err)
@@ -1190,19 +1234,20 @@ static void teardown_vq(struct mlx5_vdpa_net *ndev, struct mlx5_vdpa_virtqueue *
 
 static int create_rqt(struct mlx5_vdpa_net *ndev)
 {
-       int log_max_rqt;
        __be32 *list;
+       int max_rqt;
        void *rqtc;
        int inlen;
        void *in;
        int i, j;
        int err;
 
-       log_max_rqt = min_t(int, 1, MLX5_CAP_GEN(ndev->mvdev.mdev, log_max_rqt_size));
-       if (log_max_rqt < 1)
+       max_rqt = min_t(int, MLX5_MAX_SUPPORTED_VQS / 2,
+                       1 << MLX5_CAP_GEN(ndev->mvdev.mdev, log_max_rqt_size));
+       if (max_rqt < 1)
                return -EOPNOTSUPP;
 
-       inlen = MLX5_ST_SZ_BYTES(create_rqt_in) + (1 << log_max_rqt) * MLX5_ST_SZ_BYTES(rq_num);
+       inlen = MLX5_ST_SZ_BYTES(create_rqt_in) + max_rqt * MLX5_ST_SZ_BYTES(rq_num);
        in = kzalloc(inlen, GFP_KERNEL);
        if (!in)
                return -ENOMEM;
@@ -1211,10 +1256,9 @@ static int create_rqt(struct mlx5_vdpa_net *ndev)
        rqtc = MLX5_ADDR_OF(create_rqt_in, in, rqt_context);
 
        MLX5_SET(rqtc, rqtc, list_q_type, MLX5_RQTC_LIST_Q_TYPE_VIRTIO_NET_Q);
-       MLX5_SET(rqtc, rqtc, rqt_max_size, 1 << log_max_rqt);
-       MLX5_SET(rqtc, rqtc, rqt_actual_size, 1);
+       MLX5_SET(rqtc, rqtc, rqt_max_size, max_rqt);
        list = MLX5_ADDR_OF(rqtc, rqtc, rq_num[0]);
-       for (i = 0, j = 0; j < ndev->mvdev.max_vqs; j++) {
+       for (i = 0, j = 0; j < max_rqt; j++) {
                if (!ndev->vqs[j].initialized)
                        continue;
 
@@ -1223,6 +1267,7 @@ static int create_rqt(struct mlx5_vdpa_net *ndev)
                        i++;
                }
        }
+       MLX5_SET(rqtc, rqtc, rqt_actual_size, i);
 
        err = mlx5_vdpa_create_rqt(&ndev->mvdev, in, inlen, &ndev->res.rqtn);
        kfree(in);
@@ -1232,6 +1277,52 @@ static int create_rqt(struct mlx5_vdpa_net *ndev)
        return 0;
 }
 
+#define MLX5_MODIFY_RQT_NUM_RQS ((u64)1)
+
+static int modify_rqt(struct mlx5_vdpa_net *ndev, int num)
+{
+       __be32 *list;
+       int max_rqt;
+       void *rqtc;
+       int inlen;
+       void *in;
+       int i, j;
+       int err;
+
+       max_rqt = min_t(int, ndev->cur_num_vqs / 2,
+                       1 << MLX5_CAP_GEN(ndev->mvdev.mdev, log_max_rqt_size));
+       if (max_rqt < 1)
+               return -EOPNOTSUPP;
+
+       inlen = MLX5_ST_SZ_BYTES(modify_rqt_in) + max_rqt * MLX5_ST_SZ_BYTES(rq_num);
+       in = kzalloc(inlen, GFP_KERNEL);
+       if (!in)
+               return -ENOMEM;
+
+       MLX5_SET(modify_rqt_in, in, uid, ndev->mvdev.res.uid);
+       MLX5_SET64(modify_rqt_in, in, bitmask, MLX5_MODIFY_RQT_NUM_RQS);
+       rqtc = MLX5_ADDR_OF(modify_rqt_in, in, ctx);
+       MLX5_SET(rqtc, rqtc, list_q_type, MLX5_RQTC_LIST_Q_TYPE_VIRTIO_NET_Q);
+
+       list = MLX5_ADDR_OF(rqtc, rqtc, rq_num[0]);
+       for (i = 0, j = 0; j < num; j++) {
+               if (!ndev->vqs[j].initialized)
+                       continue;
+
+               if (!vq_is_tx(ndev->vqs[j].index)) {
+                       list[i] = cpu_to_be32(ndev->vqs[j].virtq_id);
+                       i++;
+               }
+       }
+       MLX5_SET(rqtc, rqtc, rqt_actual_size, i);
+       err = mlx5_vdpa_modify_rqt(&ndev->mvdev, in, inlen, ndev->res.rqtn);
+       kfree(in);
+       if (err)
+               return err;
+
+       return 0;
+}
+
 static void destroy_rqt(struct mlx5_vdpa_net *ndev)
 {
        mlx5_vdpa_destroy_rqt(&ndev->mvdev, ndev->res.rqtn);
@@ -1345,12 +1436,206 @@ static void remove_fwd_to_tir(struct mlx5_vdpa_net *ndev)
        ndev->rx_rule = NULL;
 }
 
+static virtio_net_ctrl_ack handle_ctrl_mac(struct mlx5_vdpa_dev *mvdev, u8 cmd)
+{
+       struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
+       struct mlx5_control_vq *cvq = &mvdev->cvq;
+       virtio_net_ctrl_ack status = VIRTIO_NET_ERR;
+       struct mlx5_core_dev *pfmdev;
+       size_t read;
+       u8 mac[ETH_ALEN];
+
+       pfmdev = pci_get_drvdata(pci_physfn(mvdev->mdev->pdev));
+       switch (cmd) {
+       case VIRTIO_NET_CTRL_MAC_ADDR_SET:
+               read = vringh_iov_pull_iotlb(&cvq->vring, &cvq->riov, (void *)mac, ETH_ALEN);
+               if (read != ETH_ALEN)
+                       break;
+
+               if (!memcmp(ndev->config.mac, mac, 6)) {
+                       status = VIRTIO_NET_OK;
+                       break;
+               }
+
+               if (!is_zero_ether_addr(ndev->config.mac)) {
+                       if (mlx5_mpfs_del_mac(pfmdev, ndev->config.mac)) {
+                               mlx5_vdpa_warn(mvdev, "failed to delete old MAC %pM from MPFS table\n",
+                                              ndev->config.mac);
+                               break;
+                       }
+               }
+
+               if (mlx5_mpfs_add_mac(pfmdev, mac)) {
+                       mlx5_vdpa_warn(mvdev, "failed to insert new MAC %pM into MPFS table\n",
+                                      mac);
+                       break;
+               }
+
+               memcpy(ndev->config.mac, mac, ETH_ALEN);
+               status = VIRTIO_NET_OK;
+               break;
+
+       default:
+               break;
+       }
+
+       return status;
+}
+
+static int change_num_qps(struct mlx5_vdpa_dev *mvdev, int newqps)
+{
+       struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
+       int cur_qps = ndev->cur_num_vqs / 2;
+       int err;
+       int i;
+
+       if (cur_qps > newqps) {
+               err = modify_rqt(ndev, 2 * newqps);
+               if (err)
+                       return err;
+
+               for (i = ndev->cur_num_vqs - 1; i >= 2 * newqps; i--)
+                       teardown_vq(ndev, &ndev->vqs[i]);
+
+               ndev->cur_num_vqs = 2 * newqps;
+       } else {
+               ndev->cur_num_vqs = 2 * newqps;
+               for (i = cur_qps * 2; i < 2 * newqps; i++) {
+                       err = setup_vq(ndev, &ndev->vqs[i]);
+                       if (err)
+                               goto clean_added;
+               }
+               err = modify_rqt(ndev, 2 * newqps);
+               if (err)
+                       goto clean_added;
+       }
+       return 0;
+
+clean_added:
+       for (--i; i >= cur_qps; --i)
+               teardown_vq(ndev, &ndev->vqs[i]);
+
+       return err;
+}
+
+static virtio_net_ctrl_ack handle_ctrl_mq(struct mlx5_vdpa_dev *mvdev, u8 cmd)
+{
+       struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
+       virtio_net_ctrl_ack status = VIRTIO_NET_ERR;
+       struct mlx5_control_vq *cvq = &mvdev->cvq;
+       struct virtio_net_ctrl_mq mq;
+       size_t read;
+       u16 newqps;
+
+       switch (cmd) {
+       case VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET:
+               read = vringh_iov_pull_iotlb(&cvq->vring, &cvq->riov, (void *)&mq, sizeof(mq));
+               if (read != sizeof(mq))
+                       break;
+
+               newqps = mlx5vdpa16_to_cpu(mvdev, mq.virtqueue_pairs);
+               if (ndev->cur_num_vqs == 2 * newqps) {
+                       status = VIRTIO_NET_OK;
+                       break;
+               }
+
+               if (newqps & (newqps - 1))
+                       break;
+
+               if (!change_num_qps(mvdev, newqps))
+                       status = VIRTIO_NET_OK;
+
+               break;
+       default:
+               break;
+       }
+
+       return status;
+}
+
+static void mlx5_cvq_kick_handler(struct work_struct *work)
+{
+       virtio_net_ctrl_ack status = VIRTIO_NET_ERR;
+       struct virtio_net_ctrl_hdr ctrl;
+       struct mlx5_ctrl_wq_ent *wqent;
+       struct mlx5_vdpa_dev *mvdev;
+       struct mlx5_control_vq *cvq;
+       struct mlx5_vdpa_net *ndev;
+       size_t read, write;
+       int err;
+
+       wqent = container_of(work, struct mlx5_ctrl_wq_ent, work);
+       mvdev = wqent->mvdev;
+       ndev = to_mlx5_vdpa_ndev(mvdev);
+       cvq = &mvdev->cvq;
+       if (!(ndev->mvdev.actual_features & BIT_ULL(VIRTIO_NET_F_CTRL_VQ)))
+               goto out;
+
+       if (!cvq->ready)
+               goto out;
+
+       while (true) {
+               err = vringh_getdesc_iotlb(&cvq->vring, &cvq->riov, &cvq->wiov, &cvq->head,
+                                          GFP_ATOMIC);
+               if (err <= 0)
+                       break;
+
+               read = vringh_iov_pull_iotlb(&cvq->vring, &cvq->riov, &ctrl, sizeof(ctrl));
+               if (read != sizeof(ctrl))
+                       break;
+
+               switch (ctrl.class) {
+               case VIRTIO_NET_CTRL_MAC:
+                       status = handle_ctrl_mac(mvdev, ctrl.cmd);
+                       break;
+               case VIRTIO_NET_CTRL_MQ:
+                       status = handle_ctrl_mq(mvdev, ctrl.cmd);
+                       break;
+
+               default:
+                       break;
+               }
+
+               /* Make sure data is written before advancing index */
+               smp_wmb();
+
+               write = vringh_iov_push_iotlb(&cvq->vring, &cvq->wiov, &status, sizeof(status));
+               vringh_complete_iotlb(&cvq->vring, cvq->head, write);
+               vringh_kiov_cleanup(&cvq->riov);
+               vringh_kiov_cleanup(&cvq->wiov);
+
+               if (vringh_need_notify_iotlb(&cvq->vring))
+                       vringh_notify(&cvq->vring);
+       }
+out:
+       kfree(wqent);
+}
+
 static void mlx5_vdpa_kick_vq(struct vdpa_device *vdev, u16 idx)
 {
        struct mlx5_vdpa_dev *mvdev = to_mvdev(vdev);
        struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
-       struct mlx5_vdpa_virtqueue *mvq = &ndev->vqs[idx];
+       struct mlx5_vdpa_virtqueue *mvq;
+       struct mlx5_ctrl_wq_ent *wqent;
+
+       if (!is_index_valid(mvdev, idx))
+               return;
+
+       if (unlikely(is_ctrl_vq_idx(mvdev, idx))) {
+               if (!mvdev->cvq.ready)
+                       return;
+
+               wqent = kzalloc(sizeof(*wqent), GFP_ATOMIC);
+               if (!wqent)
+                       return;
 
+               wqent->mvdev = mvdev;
+               INIT_WORK(&wqent->work, mlx5_cvq_kick_handler);
+               queue_work(mvdev->wq, &wqent->work);
+               return;
+       }
+
+       mvq = &ndev->vqs[idx];
        if (unlikely(!mvq->ready))
                return;
 
@@ -1362,8 +1647,19 @@ static int mlx5_vdpa_set_vq_address(struct vdpa_device *vdev, u16 idx, u64 desc_
 {
        struct mlx5_vdpa_dev *mvdev = to_mvdev(vdev);
        struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
-       struct mlx5_vdpa_virtqueue *mvq = &ndev->vqs[idx];
+       struct mlx5_vdpa_virtqueue *mvq;
+
+       if (!is_index_valid(mvdev, idx))
+               return -EINVAL;
 
+       if (is_ctrl_vq_idx(mvdev, idx)) {
+               mvdev->cvq.desc_addr = desc_area;
+               mvdev->cvq.device_addr = device_area;
+               mvdev->cvq.driver_addr = driver_area;
+               return 0;
+       }
+
+       mvq = &ndev->vqs[idx];
        mvq->desc_addr = desc_area;
        mvq->device_addr = device_area;
        mvq->driver_addr = driver_area;
@@ -1376,6 +1672,9 @@ static void mlx5_vdpa_set_vq_num(struct vdpa_device *vdev, u16 idx, u32 num)
        struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
        struct mlx5_vdpa_virtqueue *mvq;
 
+       if (!is_index_valid(mvdev, idx) || is_ctrl_vq_idx(mvdev, idx))
+               return;
+
        mvq = &ndev->vqs[idx];
        mvq->num_ent = num;
 }
@@ -1384,17 +1683,46 @@ static void mlx5_vdpa_set_vq_cb(struct vdpa_device *vdev, u16 idx, struct vdpa_c
 {
        struct mlx5_vdpa_dev *mvdev = to_mvdev(vdev);
        struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
-       struct mlx5_vdpa_virtqueue *vq = &ndev->vqs[idx];
 
-       vq->event_cb = *cb;
+       ndev->event_cbs[idx] = *cb;
+}
+
+static void mlx5_cvq_notify(struct vringh *vring)
+{
+       struct mlx5_control_vq *cvq = container_of(vring, struct mlx5_control_vq, vring);
+
+       if (!cvq->event_cb.callback)
+               return;
+
+       cvq->event_cb.callback(cvq->event_cb.private);
+}
+
+static void set_cvq_ready(struct mlx5_vdpa_dev *mvdev, bool ready)
+{
+       struct mlx5_control_vq *cvq = &mvdev->cvq;
+
+       cvq->ready = ready;
+       if (!ready)
+               return;
+
+       cvq->vring.notify = mlx5_cvq_notify;
 }
 
 static void mlx5_vdpa_set_vq_ready(struct vdpa_device *vdev, u16 idx, bool ready)
 {
        struct mlx5_vdpa_dev *mvdev = to_mvdev(vdev);
        struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
-       struct mlx5_vdpa_virtqueue *mvq = &ndev->vqs[idx];
+       struct mlx5_vdpa_virtqueue *mvq;
+
+       if (!is_index_valid(mvdev, idx))
+               return;
+
+       if (is_ctrl_vq_idx(mvdev, idx)) {
+               set_cvq_ready(mvdev, ready);
+               return;
+       }
 
+       mvq = &ndev->vqs[idx];
        if (!ready)
                suspend_vq(ndev, mvq);
 
@@ -1405,9 +1733,14 @@ static bool mlx5_vdpa_get_vq_ready(struct vdpa_device *vdev, u16 idx)
 {
        struct mlx5_vdpa_dev *mvdev = to_mvdev(vdev);
        struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
-       struct mlx5_vdpa_virtqueue *mvq = &ndev->vqs[idx];
 
-       return mvq->ready;
+       if (!is_index_valid(mvdev, idx))
+               return false;
+
+       if (is_ctrl_vq_idx(mvdev, idx))
+               return mvdev->cvq.ready;
+
+       return ndev->vqs[idx].ready;
 }
 
 static int mlx5_vdpa_set_vq_state(struct vdpa_device *vdev, u16 idx,
@@ -1415,8 +1748,17 @@ static int mlx5_vdpa_set_vq_state(struct vdpa_device *vdev, u16 idx,
 {
        struct mlx5_vdpa_dev *mvdev = to_mvdev(vdev);
        struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
-       struct mlx5_vdpa_virtqueue *mvq = &ndev->vqs[idx];
+       struct mlx5_vdpa_virtqueue *mvq;
 
+       if (!is_index_valid(mvdev, idx))
+               return -EINVAL;
+
+       if (is_ctrl_vq_idx(mvdev, idx)) {
+               mvdev->cvq.vring.last_avail_idx = state->split.avail_index;
+               return 0;
+       }
+
+       mvq = &ndev->vqs[idx];
        if (mvq->fw_state == MLX5_VIRTIO_NET_Q_OBJECT_STATE_RDY) {
                mlx5_vdpa_warn(mvdev, "can't modify available index\n");
                return -EINVAL;
@@ -1431,10 +1773,19 @@ static int mlx5_vdpa_get_vq_state(struct vdpa_device *vdev, u16 idx, struct vdpa
 {
        struct mlx5_vdpa_dev *mvdev = to_mvdev(vdev);
        struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
-       struct mlx5_vdpa_virtqueue *mvq = &ndev->vqs[idx];
+       struct mlx5_vdpa_virtqueue *mvq;
        struct mlx5_virtq_attr attr;
        int err;
 
+       if (!is_index_valid(mvdev, idx))
+               return -EINVAL;
+
+       if (is_ctrl_vq_idx(mvdev, idx)) {
+               state->split.avail_index = mvdev->cvq.vring.last_avail_idx;
+               return 0;
+       }
+
+       mvq = &ndev->vqs[idx];
        /* If the virtq object was destroyed, use the value saved at
         * the last minute of suspend_vq. This caters for userspace
         * that cares about emulating the index after vq is stopped.
@@ -1491,10 +1842,14 @@ static u64 mlx5_vdpa_get_features(struct vdpa_device *vdev)
        u16 dev_features;
 
        dev_features = MLX5_CAP_DEV_VDPA_EMULATION(mvdev->mdev, device_features_bits_mask);
-       ndev->mvdev.mlx_features = mlx_to_vritio_features(dev_features);
+       ndev->mvdev.mlx_features |= mlx_to_vritio_features(dev_features);
        if (MLX5_CAP_DEV_VDPA_EMULATION(mvdev->mdev, virtio_version_1_0))
                ndev->mvdev.mlx_features |= BIT_ULL(VIRTIO_F_VERSION_1);
        ndev->mvdev.mlx_features |= BIT_ULL(VIRTIO_F_ACCESS_PLATFORM);
+       ndev->mvdev.mlx_features |= BIT_ULL(VIRTIO_NET_F_CTRL_VQ);
+       ndev->mvdev.mlx_features |= BIT_ULL(VIRTIO_NET_F_CTRL_MAC_ADDR);
+       ndev->mvdev.mlx_features |= BIT_ULL(VIRTIO_NET_F_MQ);
+
        print_features(mvdev, ndev->mvdev.mlx_features, false);
        return ndev->mvdev.mlx_features;
 }
@@ -1507,17 +1862,29 @@ static int verify_min_features(struct mlx5_vdpa_dev *mvdev, u64 features)
        return 0;
 }
 
-static int setup_virtqueues(struct mlx5_vdpa_net *ndev)
+static int setup_virtqueues(struct mlx5_vdpa_dev *mvdev)
 {
+       struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
+       struct mlx5_control_vq *cvq = &mvdev->cvq;
        int err;
        int i;
 
-       for (i = 0; i < 2 * mlx5_vdpa_max_qps(ndev->mvdev.max_vqs); i++) {
+       for (i = 0; i < 2 * mlx5_vdpa_max_qps(mvdev->max_vqs); i++) {
                err = setup_vq(ndev, &ndev->vqs[i]);
                if (err)
                        goto err_vq;
        }
 
+       if (mvdev->actual_features & BIT_ULL(VIRTIO_NET_F_CTRL_VQ)) {
+               err = vringh_init_iotlb(&cvq->vring, mvdev->actual_features,
+                                       MLX5_CVQ_MAX_ENT, false,
+                                       (struct vring_desc *)(uintptr_t)cvq->desc_addr,
+                                       (struct vring_avail *)(uintptr_t)cvq->driver_addr,
+                                       (struct vring_used *)(uintptr_t)cvq->device_addr);
+               if (err)
+                       goto err_vq;
+       }
+
        return 0;
 
 err_vq:
@@ -1541,16 +1908,22 @@ static void teardown_virtqueues(struct mlx5_vdpa_net *ndev)
        }
 }
 
-/* TODO: cross-endian support */
-static inline bool mlx5_vdpa_is_little_endian(struct mlx5_vdpa_dev *mvdev)
-{
-       return virtio_legacy_is_little_endian() ||
-               (mvdev->actual_features & BIT_ULL(VIRTIO_F_VERSION_1));
-}
-
-static __virtio16 cpu_to_mlx5vdpa16(struct mlx5_vdpa_dev *mvdev, u16 val)
+static void update_cvq_info(struct mlx5_vdpa_dev *mvdev)
 {
-       return __cpu_to_virtio16(mlx5_vdpa_is_little_endian(mvdev), val);
+       if (MLX5_FEATURE(mvdev, VIRTIO_NET_F_CTRL_VQ)) {
+               if (MLX5_FEATURE(mvdev, VIRTIO_NET_F_MQ)) {
+                       /* MQ supported. CVQ index is right above the last data virtqueue's */
+                       mvdev->max_idx = mvdev->max_vqs;
+               } else {
+                       /* Only CVQ supportted. data virtqueues occupy indices 0 and 1.
+                        * CVQ gets index 2
+                        */
+                       mvdev->max_idx = 2;
+               }
+       } else {
+               /* Two data virtqueues only: one for rx and one for tx */
+               mvdev->max_idx = 1;
+       }
 }
 
 static int mlx5_vdpa_set_features(struct vdpa_device *vdev, u64 features)
@@ -1568,6 +1941,7 @@ static int mlx5_vdpa_set_features(struct vdpa_device *vdev, u64 features)
        ndev->mvdev.actual_features = features & ndev->mvdev.mlx_features;
        ndev->config.mtu = cpu_to_mlx5vdpa16(mvdev, ndev->mtu);
        ndev->config.status |= cpu_to_mlx5vdpa16(mvdev, VIRTIO_NET_S_LINK_UP);
+       update_cvq_info(mvdev);
        return err;
 }
 
@@ -1605,15 +1979,14 @@ static u8 mlx5_vdpa_get_status(struct vdpa_device *vdev)
 static int save_channel_info(struct mlx5_vdpa_net *ndev, struct mlx5_vdpa_virtqueue *mvq)
 {
        struct mlx5_vq_restore_info *ri = &mvq->ri;
-       struct mlx5_virtq_attr attr;
+       struct mlx5_virtq_attr attr = {};
        int err;
 
-       if (!mvq->initialized)
-               return 0;
-
-       err = query_virtqueue(ndev, mvq, &attr);
-       if (err)
-               return err;
+       if (mvq->initialized) {
+               err = query_virtqueue(ndev, mvq, &attr);
+               if (err)
+                       return err;
+       }
 
        ri->avail_index = attr.available_index;
        ri->used_index = attr.used_index;
@@ -1622,7 +1995,6 @@ static int save_channel_info(struct mlx5_vdpa_net *ndev, struct mlx5_vdpa_virtqu
        ri->desc_addr = mvq->desc_addr;
        ri->device_addr = mvq->device_addr;
        ri->driver_addr = mvq->driver_addr;
-       ri->cb = mvq->event_cb;
        ri->restore = true;
        return 0;
 }
@@ -1667,12 +2039,12 @@ static void restore_channels_info(struct mlx5_vdpa_net *ndev)
                mvq->desc_addr = ri->desc_addr;
                mvq->device_addr = ri->device_addr;
                mvq->driver_addr = ri->driver_addr;
-               mvq->event_cb = ri->cb;
        }
 }
 
-static int mlx5_vdpa_change_map(struct mlx5_vdpa_net *ndev, struct vhost_iotlb *iotlb)
+static int mlx5_vdpa_change_map(struct mlx5_vdpa_dev *mvdev, struct vhost_iotlb *iotlb)
 {
+       struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
        int err;
 
        suspend_vqs(ndev);
@@ -1681,58 +2053,59 @@ static int mlx5_vdpa_change_map(struct mlx5_vdpa_net *ndev, struct vhost_iotlb *
                goto err_mr;
 
        teardown_driver(ndev);
-       mlx5_vdpa_destroy_mr(&ndev->mvdev);
-       err = mlx5_vdpa_create_mr(&ndev->mvdev, iotlb);
+       mlx5_vdpa_destroy_mr(mvdev);
+       err = mlx5_vdpa_create_mr(mvdev, iotlb);
        if (err)
                goto err_mr;
 
-       if (!(ndev->mvdev.status & VIRTIO_CONFIG_S_DRIVER_OK))
+       if (!(mvdev->status & VIRTIO_CONFIG_S_DRIVER_OK))
                return 0;
 
        restore_channels_info(ndev);
-       err = setup_driver(ndev);
+       err = setup_driver(mvdev);
        if (err)
                goto err_setup;
 
        return 0;
 
 err_setup:
-       mlx5_vdpa_destroy_mr(&ndev->mvdev);
+       mlx5_vdpa_destroy_mr(mvdev);
 err_mr:
        return err;
 }
 
-static int setup_driver(struct mlx5_vdpa_net *ndev)
+static int setup_driver(struct mlx5_vdpa_dev *mvdev)
 {
+       struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
        int err;
 
        mutex_lock(&ndev->reslock);
        if (ndev->setup) {
-               mlx5_vdpa_warn(&ndev->mvdev, "setup driver called for already setup driver\n");
+               mlx5_vdpa_warn(mvdev, "setup driver called for already setup driver\n");
                err = 0;
                goto out;
        }
-       err = setup_virtqueues(ndev);
+       err = setup_virtqueues(mvdev);
        if (err) {
-               mlx5_vdpa_warn(&ndev->mvdev, "setup_virtqueues\n");
+               mlx5_vdpa_warn(mvdev, "setup_virtqueues\n");
                goto out;
        }
 
        err = create_rqt(ndev);
        if (err) {
-               mlx5_vdpa_warn(&ndev->mvdev, "create_rqt\n");
+               mlx5_vdpa_warn(mvdev, "create_rqt\n");
                goto err_rqt;
        }
 
        err = create_tir(ndev);
        if (err) {
-               mlx5_vdpa_warn(&ndev->mvdev, "create_tir\n");
+               mlx5_vdpa_warn(mvdev, "create_tir\n");
                goto err_tir;
        }
 
        err = add_fwd_to_tir(ndev);
        if (err) {
-               mlx5_vdpa_warn(&ndev->mvdev, "add_fwd_to_tir\n");
+               mlx5_vdpa_warn(mvdev, "add_fwd_to_tir\n");
                goto err_fwd;
        }
        ndev->setup = true;
@@ -1781,24 +2154,10 @@ static void mlx5_vdpa_set_status(struct vdpa_device *vdev, u8 status)
        int err;
 
        print_status(mvdev, status, true);
-       if (!status) {
-               mlx5_vdpa_info(mvdev, "performing device reset\n");
-               teardown_driver(ndev);
-               clear_vqs_ready(ndev);
-               mlx5_vdpa_destroy_mr(&ndev->mvdev);
-               ndev->mvdev.status = 0;
-               ndev->mvdev.mlx_features = 0;
-               ++mvdev->generation;
-               if (MLX5_CAP_GEN(mvdev->mdev, umem_uid_0)) {
-                       if (mlx5_vdpa_create_mr(mvdev, NULL))
-                               mlx5_vdpa_warn(mvdev, "create MR failed\n");
-               }
-               return;
-       }
 
        if ((status ^ ndev->mvdev.status) & VIRTIO_CONFIG_S_DRIVER_OK) {
                if (status & VIRTIO_CONFIG_S_DRIVER_OK) {
-                       err = setup_driver(ndev);
+                       err = setup_driver(mvdev);
                        if (err) {
                                mlx5_vdpa_warn(mvdev, "failed to setup driver\n");
                                goto err_setup;
@@ -1817,6 +2176,29 @@ err_setup:
        ndev->mvdev.status |= VIRTIO_CONFIG_S_FAILED;
 }
 
+static int mlx5_vdpa_reset(struct vdpa_device *vdev)
+{
+       struct mlx5_vdpa_dev *mvdev = to_mvdev(vdev);
+       struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
+
+       print_status(mvdev, 0, true);
+       mlx5_vdpa_info(mvdev, "performing device reset\n");
+       teardown_driver(ndev);
+       clear_vqs_ready(ndev);
+       mlx5_vdpa_destroy_mr(&ndev->mvdev);
+       ndev->mvdev.status = 0;
+       ndev->mvdev.mlx_features = 0;
+       memset(ndev->event_cbs, 0, sizeof(ndev->event_cbs));
+       ndev->mvdev.actual_features = 0;
+       ++mvdev->generation;
+       if (MLX5_CAP_GEN(mvdev->mdev, umem_uid_0)) {
+               if (mlx5_vdpa_create_mr(mvdev, NULL))
+                       mlx5_vdpa_warn(mvdev, "create MR failed\n");
+       }
+
+       return 0;
+}
+
 static size_t mlx5_vdpa_get_config_size(struct vdpa_device *vdev)
 {
        return sizeof(struct virtio_net_config);
@@ -1848,7 +2230,6 @@ static u32 mlx5_vdpa_get_generation(struct vdpa_device *vdev)
 static int mlx5_vdpa_set_map(struct vdpa_device *vdev, struct vhost_iotlb *iotlb)
 {
        struct mlx5_vdpa_dev *mvdev = to_mvdev(vdev);
-       struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev);
        bool change_map;
        int err;
 
@@ -1859,7 +2240,7 @@ static int mlx5_vdpa_set_map(struct vdpa_device *vdev, struct vhost_iotlb *iotlb
        }
 
        if (change_map)
-               return mlx5_vdpa_change_map(ndev, iotlb);
+               return mlx5_vdpa_change_map(mvdev, iotlb);
 
        return 0;
 }
@@ -1889,6 +2270,9 @@ static struct vdpa_notification_area mlx5_get_vq_notification(struct vdpa_device
        struct mlx5_vdpa_net *ndev;
        phys_addr_t addr;
 
+       if (!is_index_valid(mvdev, idx) || is_ctrl_vq_idx(mvdev, idx))
+               return ret;
+
        /* If SF BAR size is smaller than PAGE_SIZE, do not use direct
         * notification to avoid the risk of mapping pages that contain BAR of more
         * than one SF
@@ -1928,6 +2312,7 @@ static const struct vdpa_config_ops mlx5_vdpa_ops = {
        .get_vendor_id = mlx5_vdpa_get_vendor_id,
        .get_status = mlx5_vdpa_get_status,
        .set_status = mlx5_vdpa_set_status,
+       .reset = mlx5_vdpa_reset,
        .get_config_size = mlx5_vdpa_get_config_size,
        .get_config = mlx5_vdpa_get_config,
        .set_config = mlx5_vdpa_set_config,
@@ -2040,7 +2425,7 @@ static int mlx5_vdpa_dev_add(struct vdpa_mgmt_dev *v_mdev, const char *name)
        max_vqs = min_t(u32, max_vqs, MLX5_MAX_SUPPORTED_VQS);
 
        ndev = vdpa_alloc_device(struct mlx5_vdpa_net, mvdev.vdev, mdev->device, &mlx5_vdpa_ops,
-                                name);
+                                name, false);
        if (IS_ERR(ndev))
                return PTR_ERR(ndev);
 
@@ -2063,8 +2448,11 @@ static int mlx5_vdpa_dev_add(struct vdpa_mgmt_dev *v_mdev, const char *name)
                err = mlx5_mpfs_add_mac(pfmdev, config->mac);
                if (err)
                        goto err_mtu;
+
+               ndev->mvdev.mlx_features |= BIT_ULL(VIRTIO_NET_F_MAC);
        }
 
+       config->max_virtqueue_pairs = cpu_to_mlx5vdpa16(mvdev, mlx5_vdpa_max_qps(max_vqs));
        mvdev->vdev.dma_dev = &mdev->pdev->dev;
        err = mlx5_vdpa_alloc_resources(&ndev->mvdev);
        if (err)
@@ -2080,8 +2468,15 @@ static int mlx5_vdpa_dev_add(struct vdpa_mgmt_dev *v_mdev, const char *name)
        if (err)
                goto err_mr;
 
+       mvdev->wq = create_singlethread_workqueue("mlx5_vdpa_ctrl_wq");
+       if (!mvdev->wq) {
+               err = -ENOMEM;
+               goto err_res2;
+       }
+
+       ndev->cur_num_vqs = 2 * mlx5_vdpa_max_qps(max_vqs);
        mvdev->vdev.mdev = &mgtdev->mgtdev;
-       err = _vdpa_register_device(&mvdev->vdev, 2 * mlx5_vdpa_max_qps(max_vqs));
+       err = _vdpa_register_device(&mvdev->vdev, ndev->cur_num_vqs + 1);
        if (err)
                goto err_reg;
 
@@ -2089,6 +2484,8 @@ static int mlx5_vdpa_dev_add(struct vdpa_mgmt_dev *v_mdev, const char *name)
        return 0;
 
 err_reg:
+       destroy_workqueue(mvdev->wq);
+err_res2:
        free_resources(ndev);
 err_mr:
        mlx5_vdpa_destroy_mr(mvdev);
@@ -2106,7 +2503,9 @@ err_mtu:
 static void mlx5_vdpa_dev_del(struct vdpa_mgmt_dev *v_mdev, struct vdpa_device *dev)
 {
        struct mlx5_vdpa_mgmtdev *mgtdev = container_of(v_mdev, struct mlx5_vdpa_mgmtdev, mgtdev);
+       struct mlx5_vdpa_dev *mvdev = to_mvdev(dev);
 
+       destroy_workqueue(mvdev->wq);
        _vdpa_unregister_device(dev);
        mgtdev->ndev = NULL;
 }