net/virtio: make interrupt handling more generic
[dpdk.git] / drivers / net / virtio / virtio_user_ethdev.c
index c54698a..add9774 100644 (file)
@@ -6,30 +6,33 @@
 #include <sys/types.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <linux/major.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
 #include <sys/socket.h>
 
 #include <rte_malloc.h>
 #include <rte_kvargs.h>
-#include <rte_ethdev_vdev.h>
+#include <ethdev_vdev.h>
 #include <rte_bus_vdev.h>
 #include <rte_alarm.h>
 #include <rte_cycles.h>
 
 #include "virtio_ethdev.h"
 #include "virtio_logs.h"
-#include "virtio_pci.h"
+#include "virtio.h"
 #include "virtqueue.h"
 #include "virtio_rxtx.h"
 #include "virtio_user/virtio_user_dev.h"
 #include "virtio_user/vhost.h"
 
-#define virtio_user_get_dev(hw) \
-       ((struct virtio_user_dev *)(hw)->virtio_user_dev)
+#define virtio_user_get_dev(hwp) container_of(hwp, struct virtio_user_dev, hw)
 
 static void
-virtio_user_reset_queues_packed(struct rte_eth_dev *dev)
+virtio_user_reset_queues_packed(struct rte_eth_dev *eth_dev)
 {
-       struct virtio_hw *hw = dev->data->dev_private;
+       struct virtio_user_dev *dev = eth_dev->data->dev_private;
+       struct virtio_hw *hw = &dev->hw;
        struct virtnet_rx *rxvq;
        struct virtnet_tx *txvq;
        uint16_t i;
@@ -45,14 +48,14 @@ virtio_user_reset_queues_packed(struct rte_eth_dev *dev)
        rte_delay_ms(1);
 
        /* Vring reset for each Tx queue and Rx queue. */
-       for (i = 0; i < dev->data->nb_rx_queues; i++) {
-               rxvq = dev->data->rx_queues[i];
+       for (i = 0; i < eth_dev->data->nb_rx_queues; i++) {
+               rxvq = eth_dev->data->rx_queues[i];
                virtqueue_rxvq_reset_packed(rxvq->vq);
-               virtio_dev_rx_queue_setup_finish(dev, i);
+               virtio_dev_rx_queue_setup_finish(eth_dev, i);
        }
 
-       for (i = 0; i < dev->data->nb_tx_queues; i++) {
-               txvq = dev->data->tx_queues[i];
+       for (i = 0; i < eth_dev->data->nb_tx_queues; i++) {
+               txvq = eth_dev->data->tx_queues[i];
                virtqueue_txvq_reset_packed(txvq->vq);
        }
 
@@ -64,16 +67,24 @@ virtio_user_reset_queues_packed(struct rte_eth_dev *dev)
 static int
 virtio_user_server_reconnect(struct virtio_user_dev *dev)
 {
-       int ret;
-       int connectfd;
+       int ret, connectfd, old_status;
        struct rte_eth_dev *eth_dev = &rte_eth_devices[dev->port_id];
-       struct virtio_hw *hw = eth_dev->data->dev_private;
+       struct virtio_hw *hw = &dev->hw;
+       uint64_t protocol_features;
 
        connectfd = accept(dev->listenfd, NULL, NULL);
        if (connectfd < 0)
                return -1;
 
        dev->vhostfd = connectfd;
+       old_status = dev->status;
+
+       virtio_reset(hw);
+
+       virtio_set_status(hw, VIRTIO_CONFIG_STATUS_ACK);
+
+       virtio_set_status(hw, VIRTIO_CONFIG_STATUS_DRIVER);
+
        if (dev->ops->send_request(dev, VHOST_USER_GET_FEATURES,
                                   &dev->device_features) < 0) {
                PMD_INIT_LOG(ERR, "get_features failed: %s",
@@ -81,6 +92,28 @@ virtio_user_server_reconnect(struct virtio_user_dev *dev)
                return -1;
        }
 
+       if (dev->device_features &
+                       (1ULL << VHOST_USER_F_PROTOCOL_FEATURES)) {
+               if (dev->ops->send_request(dev,
+                                       VHOST_USER_GET_PROTOCOL_FEATURES,
+                                       &protocol_features))
+                       return -1;
+
+               /* Offer VHOST_USER_PROTOCOL_F_STATUS */
+               dev->protocol_features |=
+                       (1ULL << VHOST_USER_PROTOCOL_F_STATUS);
+               dev->protocol_features &= protocol_features;
+
+               if (dev->ops->send_request(dev,
+                                       VHOST_USER_SET_PROTOCOL_FEATURES,
+                                       &dev->protocol_features))
+                       return -1;
+
+               if (!(dev->protocol_features &
+                               (1ULL << VHOST_USER_PROTOCOL_F_MQ)))
+                       dev->unsupported_features |= (1ull << VIRTIO_NET_F_MQ);
+       }
+
        dev->device_features |= dev->frontend_features;
 
        /* umask vhost-user unsupported features */
@@ -89,14 +122,18 @@ virtio_user_server_reconnect(struct virtio_user_dev *dev)
        dev->features &= dev->device_features;
 
        /* For packed ring, resetting queues is required in reconnection. */
-       if (vtpci_packed_queue(hw)) {
+       if (virtio_with_packed_queue(hw) &&
+          (old_status & VIRTIO_CONFIG_STATUS_DRIVER_OK)) {
                PMD_INIT_LOG(NOTICE, "Packets on the fly will be dropped"
                                " when packed ring reconnecting.");
                virtio_user_reset_queues_packed(eth_dev);
        }
 
-       ret = virtio_user_start_device(dev);
-       if (ret < 0)
+       virtio_set_status(hw, VIRTIO_CONFIG_STATUS_FEATURES_OK);
+
+       /* Start the device */
+       virtio_set_status(hw, VIRTIO_CONFIG_STATUS_DRIVER_OK);
+       if (!dev->started)
                return -1;
 
        if (dev->queue_pairs > 1) {
@@ -144,6 +181,11 @@ virtio_user_delayed_handler(void *param)
                if (dev->vhostfd >= 0) {
                        close(dev->vhostfd);
                        dev->vhostfd = -1;
+                       /* Until the featuers are negotiated again, don't assume
+                        * the backend supports VHOST_USER_PROTOCOL_F_STATUS
+                        */
+                       dev->protocol_features &=
+                               ~(1ULL << VHOST_USER_PROTOCOL_F_STATUS);
                }
                eth_dev->intr_handle->fd = dev->listenfd;
                rte_intr_callback_register(eth_dev->intr_handle,
@@ -184,7 +226,7 @@ virtio_user_read_dev_config(struct virtio_hw *hw, size_t offset,
                        }
                        r = recv(dev->vhostfd, buf, 128, MSG_PEEK);
                        if (r == 0 || (r < 0 && errno != EAGAIN)) {
-                               dev->status &= (~VIRTIO_NET_S_LINK_UP);
+                               dev->net_status &= (~VIRTIO_NET_S_LINK_UP);
                                PMD_DRV_LOG(ERR, "virtio-user port %u is down",
                                            hw->port_id);
 
@@ -196,7 +238,7 @@ virtio_user_read_dev_config(struct virtio_hw *hw, size_t offset,
                                                  virtio_user_delayed_handler,
                                                  (void *)hw);
                        } else {
-                               dev->status |= VIRTIO_NET_S_LINK_UP;
+                               dev->net_status |= VIRTIO_NET_S_LINK_UP;
                        }
                        if (fcntl(dev->vhostfd, F_SETFL,
                                        flags & ~O_NONBLOCK) == -1) {
@@ -204,12 +246,12 @@ virtio_user_read_dev_config(struct virtio_hw *hw, size_t offset,
                                return;
                        }
                } else if (dev->is_server) {
-                       dev->status &= (~VIRTIO_NET_S_LINK_UP);
+                       dev->net_status &= (~VIRTIO_NET_S_LINK_UP);
                        if (virtio_user_server_reconnect(dev) >= 0)
-                               dev->status |= VIRTIO_NET_S_LINK_UP;
+                               dev->net_status |= VIRTIO_NET_S_LINK_UP;
                }
 
-               *(uint16_t *)dst = dev->status;
+               *(uint16_t *)dst = dev->net_status;
        }
 
        if (offset == offsetof(struct virtio_net_config, max_virtqueue_pairs))
@@ -245,12 +287,17 @@ static void
 virtio_user_set_status(struct virtio_hw *hw, uint8_t status)
 {
        struct virtio_user_dev *dev = virtio_user_get_dev(hw);
+       uint8_t old_status = dev->status;
 
+       if (status & VIRTIO_CONFIG_STATUS_FEATURES_OK &&
+                       ~old_status & VIRTIO_CONFIG_STATUS_FEATURES_OK)
+               virtio_user_dev_set_features(dev);
        if (status & VIRTIO_CONFIG_STATUS_DRIVER_OK)
                virtio_user_start_device(dev);
        else if (status == VIRTIO_CONFIG_STATUS_RESET)
                virtio_user_reset(hw);
-       dev->status = status;
+
+       virtio_user_dev_set_status(dev, status);
 }
 
 static uint8_t
@@ -258,6 +305,8 @@ virtio_user_get_status(struct virtio_hw *hw)
 {
        struct virtio_user_dev *dev = virtio_user_get_dev(hw);
 
+       virtio_user_dev_update_status(dev);
+
        return dev->status;
 }
 
@@ -278,13 +327,19 @@ virtio_user_set_features(struct virtio_hw *hw, uint64_t features)
        dev->features = features & dev->device_features;
 }
 
+static int
+virtio_user_features_ok(struct virtio_hw *hw __rte_unused)
+{
+       return 0;
+}
+
 static uint8_t
 virtio_user_get_isr(struct virtio_hw *hw __rte_unused)
 {
        /* rxq interrupts and config interrupt are separated in virtio-user,
         * here we only report config change.
         */
-       return VIRTIO_PCI_ISR_CONFIG;
+       return VIRTIO_ISR_CONFIG;
 }
 
 static uint16_t
@@ -368,7 +423,7 @@ virtio_user_setup_queue(struct virtio_hw *hw, struct virtqueue *vq)
 {
        struct virtio_user_dev *dev = virtio_user_get_dev(hw);
 
-       if (vtpci_packed_queue(hw))
+       if (virtio_with_packed_queue(hw))
                virtio_user_setup_queue_packed(vq, dev);
        else
                virtio_user_setup_queue_split(vq, dev);
@@ -401,7 +456,7 @@ virtio_user_notify_queue(struct virtio_hw *hw, struct virtqueue *vq)
        struct virtio_user_dev *dev = virtio_user_get_dev(hw);
 
        if (hw->cvq && (hw->cvq->vq == vq)) {
-               if (vtpci_packed_queue(vq->hw))
+               if (virtio_with_packed_queue(vq->hw))
                        virtio_user_handle_cq_packed(dev, vq->vq_queue_index);
                else
                        virtio_user_handle_cq(dev, vq->vq_queue_index);
@@ -413,13 +468,24 @@ virtio_user_notify_queue(struct virtio_hw *hw, struct virtqueue *vq)
                            strerror(errno));
 }
 
-const struct virtio_pci_ops virtio_user_ops = {
+static int
+virtio_user_dev_close(struct virtio_hw *hw)
+{
+       struct virtio_user_dev *dev = virtio_user_get_dev(hw);
+
+       virtio_user_dev_uninit(dev);
+
+       return 0;
+}
+
+const struct virtio_ops virtio_user_ops = {
        .read_dev_cfg   = virtio_user_read_dev_config,
        .write_dev_cfg  = virtio_user_write_dev_config,
        .get_status     = virtio_user_get_status,
        .set_status     = virtio_user_set_status,
        .get_features   = virtio_user_get_features,
        .set_features   = virtio_user_set_features,
+       .features_ok    = virtio_user_features_ok,
        .get_isr        = virtio_user_get_isr,
        .set_config_irq = virtio_user_set_config_irq,
        .set_queue_irq  = virtio_user_set_queue_irq,
@@ -427,6 +493,7 @@ const struct virtio_pci_ops virtio_user_ops = {
        .setup_queue    = virtio_user_setup_queue,
        .del_queue      = virtio_user_del_queue,
        .notify_queue   = virtio_user_notify_queue,
+       .dev_close      = virtio_user_dev_close,
 };
 
 static const char *valid_args[] = {
@@ -494,6 +561,60 @@ get_integer_arg(const char *key __rte_unused,
        return -errno;
 }
 
+static uint32_t
+vdpa_dynamic_major_num(void)
+{
+       FILE *fp;
+       char *line = NULL;
+       size_t size;
+       char name[11];
+       bool found = false;
+       uint32_t num;
+
+       fp = fopen("/proc/devices", "r");
+       if (fp == NULL) {
+               PMD_INIT_LOG(ERR, "Cannot open /proc/devices: %s",
+                            strerror(errno));
+               return UNNAMED_MAJOR;
+       }
+
+       while (getline(&line, &size, fp) > 0) {
+               char *stripped = line + strspn(line, " ");
+               if ((sscanf(stripped, "%u %10s", &num, name) == 2) &&
+                   (strncmp(name, "vhost-vdpa", 10) == 0)) {
+                       found = true;
+                       break;
+               }
+       }
+       fclose(fp);
+       return found ? num : UNNAMED_MAJOR;
+}
+
+static enum virtio_user_backend_type
+virtio_user_backend_type(const char *path)
+{
+       struct stat sb;
+
+       if (stat(path, &sb) == -1) {
+               if (errno == ENOENT)
+                       return VIRTIO_USER_BACKEND_VHOST_USER;
+
+               PMD_INIT_LOG(ERR, "Stat fails: %s (%s)\n", path,
+                            strerror(errno));
+               return VIRTIO_USER_BACKEND_UNKNOWN;
+       }
+
+       if (S_ISSOCK(sb.st_mode)) {
+               return VIRTIO_USER_BACKEND_VHOST_USER;
+       } else if (S_ISCHR(sb.st_mode)) {
+               if (major(sb.st_rdev) == MISC_MAJOR)
+                       return VIRTIO_USER_BACKEND_VHOST_KERNEL;
+               if (major(sb.st_rdev) == vdpa_dynamic_major_num())
+                       return VIRTIO_USER_BACKEND_VHOST_VDPA;
+       }
+       return VIRTIO_USER_BACKEND_UNKNOWN;
+}
+
 static struct rte_eth_dev *
 virtio_user_eth_dev_alloc(struct rte_vdev_device *vdev)
 {
@@ -502,46 +623,32 @@ virtio_user_eth_dev_alloc(struct rte_vdev_device *vdev)
        struct virtio_hw *hw;
        struct virtio_user_dev *dev;
 
-       eth_dev = rte_eth_vdev_allocate(vdev, sizeof(*hw));
+       eth_dev = rte_eth_vdev_allocate(vdev, sizeof(*dev));
        if (!eth_dev) {
                PMD_INIT_LOG(ERR, "cannot alloc rte_eth_dev");
                return NULL;
        }
 
        data = eth_dev->data;
-       hw = eth_dev->data->dev_private;
-
-       dev = rte_zmalloc(NULL, sizeof(*dev), 0);
-       if (!dev) {
-               PMD_INIT_LOG(ERR, "malloc virtio_user_dev failed");
-               rte_eth_dev_release_port(eth_dev);
-               return NULL;
-       }
+       dev = eth_dev->data->dev_private;
+       hw = &dev->hw;
 
        hw->port_id = data->port_id;
        dev->port_id = data->port_id;
-       virtio_hw_internal[hw->port_id].vtpci_ops = &virtio_user_ops;
-       /*
-        * MSIX is required to enable LSC (see virtio_init_device).
-        * Here just pretend that we support msix.
-        */
-       hw->use_msix = 1;
-       hw->modern   = 0;
+       VIRTIO_OPS(hw) = &virtio_user_ops;
+
+       hw->intr_lsc = 1;
        hw->use_vec_rx = 0;
        hw->use_vec_tx = 0;
        hw->use_inorder_rx = 0;
        hw->use_inorder_tx = 0;
-       hw->virtio_user_dev = dev;
+
        return eth_dev;
 }
 
 static void
 virtio_user_eth_dev_free(struct rte_eth_dev *eth_dev)
 {
-       struct rte_eth_dev_data *data = eth_dev->data;
-       struct virtio_hw *hw = data->dev_private;
-
-       rte_free(hw->virtio_user_dev);
        rte_eth_dev_release_port(eth_dev);
 }
 
@@ -550,11 +657,13 @@ virtio_user_eth_dev_free(struct rte_eth_dev *eth_dev)
  * Returns 0 on success.
  */
 static int
-virtio_user_pmd_probe(struct rte_vdev_device *dev)
+virtio_user_pmd_probe(struct rte_vdev_device *vdev)
 {
        struct rte_kvargs *kvlist = NULL;
        struct rte_eth_dev *eth_dev;
        struct virtio_hw *hw;
+       struct virtio_user_dev *dev;
+       enum virtio_user_backend_type backend_type = VIRTIO_USER_BACKEND_UNKNOWN;
        uint64_t queues = VIRTIO_USER_DEF_Q_NUM;
        uint64_t cq = VIRTIO_USER_DEF_CQ_EN;
        uint64_t queue_size = VIRTIO_USER_DEF_Q_SZ;
@@ -568,14 +677,20 @@ virtio_user_pmd_probe(struct rte_vdev_device *dev)
        char *mac_addr = NULL;
        int ret = -1;
 
+       RTE_BUILD_BUG_ON(offsetof(struct virtio_user_dev, hw) != 0);
+
        if (rte_eal_process_type() == RTE_PROC_SECONDARY) {
-               const char *name = rte_vdev_device_name(dev);
+               const char *name = rte_vdev_device_name(vdev);
                eth_dev = rte_eth_dev_attach_secondary(name);
                if (!eth_dev) {
                        PMD_INIT_LOG(ERR, "Failed to probe %s", name);
                        return -1;
                }
 
+               dev = eth_dev->data->dev_private;
+               hw = &dev->hw;
+               VIRTIO_OPS(hw) = &virtio_user_ops;
+
                if (eth_virtio_dev_init(eth_dev) < 0) {
                        PMD_INIT_LOG(ERR, "eth_virtio_dev_init fails");
                        rte_eth_dev_release_port(eth_dev);
@@ -583,12 +698,12 @@ virtio_user_pmd_probe(struct rte_vdev_device *dev)
                }
 
                eth_dev->dev_ops = &virtio_user_secondary_eth_dev_ops;
-               eth_dev->device = &dev->device;
+               eth_dev->device = &vdev->device;
                rte_eth_dev_probing_finish(eth_dev);
                return 0;
        }
 
-       kvlist = rte_kvargs_parse(rte_vdev_device_args(dev), valid_args);
+       kvlist = rte_kvargs_parse(rte_vdev_device_args(vdev), valid_args);
        if (!kvlist) {
                PMD_INIT_LOG(ERR, "error when parsing param");
                goto end;
@@ -607,8 +722,18 @@ virtio_user_pmd_probe(struct rte_vdev_device *dev)
                goto end;
        }
 
+       backend_type = virtio_user_backend_type(path);
+       if (backend_type == VIRTIO_USER_BACKEND_UNKNOWN) {
+               PMD_INIT_LOG(ERR,
+                            "unable to determine backend type for path %s",
+                       path);
+               goto end;
+       }
+       PMD_INIT_LOG(INFO, "Backend type detected: %s",
+                    virtio_user_backend_strings[backend_type]);
+
        if (rte_kvargs_count(kvlist, VIRTIO_USER_ARG_INTERFACE_NAME) == 1) {
-               if (is_vhost_user_by_type(path)) {
+               if (backend_type != VIRTIO_USER_BACKEND_VHOST_KERNEL) {
                        PMD_INIT_LOG(ERR,
                                "arg %s applies only to vhost-kernel backend",
                                VIRTIO_USER_ARG_INTERFACE_NAME);
@@ -718,22 +843,23 @@ virtio_user_pmd_probe(struct rte_vdev_device *dev)
                }
        }
 
-       eth_dev = virtio_user_eth_dev_alloc(dev);
+       eth_dev = virtio_user_eth_dev_alloc(vdev);
        if (!eth_dev) {
                PMD_INIT_LOG(ERR, "virtio_user fails to alloc device");
                goto end;
        }
 
-       hw = eth_dev->data->dev_private;
-       if (virtio_user_dev_init(hw->virtio_user_dev, path, queues, cq,
+       dev = eth_dev->data->dev_private;
+       hw = &dev->hw;
+       if (virtio_user_dev_init(dev, path, queues, cq,
                         queue_size, mac_addr, &ifname, server_mode,
-                        mrg_rxbuf, in_order, packed_vq) < 0) {
+                        mrg_rxbuf, in_order, packed_vq, backend_type) < 0) {
                PMD_INIT_LOG(ERR, "virtio_user_dev_init fails");
                virtio_user_eth_dev_free(eth_dev);
                goto end;
        }
 
-       /* previously called by rte_pci_probe() for physical dev */
+       /* previously called by pci probing for physical dev */
        if (eth_virtio_dev_init(eth_dev) < 0) {
                PMD_INIT_LOG(ERR, "eth_virtio_dev_init fails");
                virtio_user_eth_dev_free(eth_dev);
@@ -742,7 +868,7 @@ virtio_user_pmd_probe(struct rte_vdev_device *dev)
 
        if (vectorized) {
                if (packed_vq) {
-#if defined(CC_AVX512_SUPPORT)
+#if defined(CC_AVX512_SUPPORT) || defined(RTE_ARCH_ARM)
                        hw->use_vec_rx = 1;
                        hw->use_vec_tx = 1;
 #else
@@ -789,7 +915,53 @@ virtio_user_pmd_remove(struct rte_vdev_device *vdev)
                return rte_eth_dev_release_port(eth_dev);
 
        /* make sure the device is stopped, queues freed */
-       rte_eth_dev_close(eth_dev->data->port_id);
+       return rte_eth_dev_close(eth_dev->data->port_id);
+}
+
+static int virtio_user_pmd_dma_map(struct rte_vdev_device *vdev, void *addr,
+               uint64_t iova, size_t len)
+{
+       const char *name;
+       struct rte_eth_dev *eth_dev;
+       struct virtio_user_dev *dev;
+
+       if (!vdev)
+               return -EINVAL;
+
+       name = rte_vdev_device_name(vdev);
+       eth_dev = rte_eth_dev_allocated(name);
+       /* Port has already been released by close. */
+       if (!eth_dev)
+               return 0;
+
+       dev = eth_dev->data->dev_private;
+
+       if (dev->ops->dma_map)
+               return dev->ops->dma_map(dev, addr, iova, len);
+
+       return 0;
+}
+
+static int virtio_user_pmd_dma_unmap(struct rte_vdev_device *vdev, void *addr,
+               uint64_t iova, size_t len)
+{
+       const char *name;
+       struct rte_eth_dev *eth_dev;
+       struct virtio_user_dev *dev;
+
+       if (!vdev)
+               return -EINVAL;
+
+       name = rte_vdev_device_name(vdev);
+       eth_dev = rte_eth_dev_allocated(name);
+       /* Port has already been released by close. */
+       if (!eth_dev)
+               return 0;
+
+       dev = eth_dev->data->dev_private;
+
+       if (dev->ops->dma_unmap)
+               return dev->ops->dma_unmap(dev, addr, iova, len);
 
        return 0;
 }
@@ -797,6 +969,9 @@ virtio_user_pmd_remove(struct rte_vdev_device *vdev)
 static struct rte_vdev_driver virtio_user_driver = {
        .probe = virtio_user_pmd_probe,
        .remove = virtio_user_pmd_remove,
+       .dma_map = virtio_user_pmd_dma_map,
+       .dma_unmap = virtio_user_pmd_dma_unmap,
+       .drv_flags = RTE_VDEV_DRV_NEED_IOVA_AS_VA,
 };
 
 RTE_PMD_REGISTER_VDEV(net_virtio_user, virtio_user_driver);