virtio: fix crash when releasing null queue
[dpdk.git] / drivers / net / virtio / virtio_ethdev.c
index 57ae6c4..147aca1 100644 (file)
@@ -1,7 +1,7 @@
 /*-
  *   BSD LICENSE
  *
- *   Copyright(c) 2010-2014 Intel Corporation. All rights reserved.
+ *   Copyright(c) 2010-2015 Intel Corporation. All rights reserved.
  *   All rights reserved.
  *
  *   Redistribution and use in source and binary forms, with or without
@@ -51,6 +51,7 @@
 #include <rte_pci.h>
 #include <rte_ether.h>
 #include <rte_common.h>
+#include <rte_errno.h>
 
 #include <rte_memory.h>
 #include <rte_eal.h>
@@ -63,6 +64,7 @@
 
 
 static int eth_virtio_dev_init(struct rte_eth_dev *eth_dev);
+static int eth_virtio_dev_uninit(struct rte_eth_dev *eth_dev);
 static int  virtio_dev_configure(struct rte_eth_dev *dev);
 static int  virtio_dev_start(struct rte_eth_dev *dev);
 static void virtio_dev_stop(struct rte_eth_dev *dev);
@@ -78,9 +80,6 @@ static int virtio_dev_link_update(struct rte_eth_dev *dev,
 static void virtio_set_hwaddr(struct virtio_hw *hw);
 static void virtio_get_hwaddr(struct virtio_hw *hw);
 
-static void virtio_dev_rx_queue_release(__rte_unused void *rxq);
-static void virtio_dev_tx_queue_release(__rte_unused void *txq);
-
 static void virtio_dev_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats);
 static void virtio_dev_stats_reset(struct rte_eth_dev *dev);
 static void virtio_dev_free_mbufs(struct rte_eth_dev *dev);
@@ -114,19 +113,20 @@ static int
 virtio_send_command(struct virtqueue *vq, struct virtio_pmd_ctrl *ctrl,
                int *dlen, int pkt_num)
 {
-       uint16_t head = vq->vq_desc_head_idx, i;
+       uint32_t head, i;
        int k, sum = 0;
        virtio_net_ctrl_ack status = ~0;
        struct virtio_pmd_ctrl result;
 
        ctrl->status = status;
 
-       if (!vq->hw->cvq) {
+       if (!(vq && vq->hw->cvq)) {
                PMD_INIT_LOG(ERR,
                             "%s(): Control queue is not supported.",
                             __func__);
                return -1;
        }
+       head = vq->vq_desc_head_idx;
 
        PMD_INIT_LOG(DEBUG, "vq->vq_desc_head_idx = %d, status = %d, "
                "vq->hw->cvq = %p vq = %p",
@@ -237,10 +237,25 @@ virtio_set_multiple_queues(struct rte_eth_dev *dev, uint16_t nb_queues)
        return 0;
 }
 
+void
+virtio_dev_queue_release(struct virtqueue *vq) {
+       struct virtio_hw *hw;
+
+       if (vq) {
+               hw = vq->hw;
+               /* Select and deactivate the queue */
+               VIRTIO_WRITE_REG_2(hw, VIRTIO_PCI_QUEUE_SEL, vq->queue_id);
+               VIRTIO_WRITE_REG_4(hw, VIRTIO_PCI_QUEUE_PFN, 0);
+
+               rte_free(vq);
+               vq = NULL;
+       }
+}
+
 int virtio_dev_queue_setup(struct rte_eth_dev *dev,
                        int queue_type,
                        uint16_t queue_idx,
-                       uint16_t  vtpci_queue_idx,
+                       uint16_t vtpci_queue_idx,
                        uint16_t nb_desc,
                        unsigned int socket_id,
                        struct virtqueue **pvq)
@@ -250,7 +265,7 @@ int virtio_dev_queue_setup(struct rte_eth_dev *dev,
        uint16_t vq_size;
        int size;
        struct virtio_hw *hw = dev->data->dev_private;
-       struct virtqueue  *vq = NULL;
+       struct virtqueue *vq = NULL;
 
        /* Write the virtqueue index to the Queue Select Field */
        VIRTIO_WRITE_REG_2(hw, VIRTIO_PCI_QUEUE_SEL, vtpci_queue_idx);
@@ -262,18 +277,14 @@ int virtio_dev_queue_setup(struct rte_eth_dev *dev,
         */
        vq_size = VIRTIO_READ_REG_2(hw, VIRTIO_PCI_QUEUE_NUM);
        PMD_INIT_LOG(DEBUG, "vq_size: %d nb_desc:%d", vq_size, nb_desc);
-       if (nb_desc == 0)
-               nb_desc = vq_size;
        if (vq_size == 0) {
                PMD_INIT_LOG(ERR, "%s: virtqueue does not exist", __func__);
                return -EINVAL;
-       } else if (!rte_is_power_of_2(vq_size)) {
+       }
+
+       if (!rte_is_power_of_2(vq_size)) {
                PMD_INIT_LOG(ERR, "%s: virtqueue size is not powerof 2", __func__);
                return -EINVAL;
-       } else if (nb_desc != vq_size) {
-               PMD_INIT_LOG(ERR, "Warning: nb_desc(%d) is not equal to vq size (%d), fall to vq size",
-                       nb_desc, vq_size);
-               nb_desc = vq_size;
        }
 
        if (queue_type == VTNET_RQ) {
@@ -303,7 +314,10 @@ int virtio_dev_queue_setup(struct rte_eth_dev *dev,
        vq->queue_id = queue_idx;
        vq->vq_queue_index = vtpci_queue_idx;
        vq->vq_nentries = vq_size;
-       vq->vq_free_cnt = vq_size;
+
+       if (nb_desc == 0 || nb_desc > vq_size)
+               nb_desc = vq_size;
+       vq->vq_free_cnt = nb_desc;
 
        /*
         * Reserve a memzone for vring elements
@@ -315,8 +329,12 @@ int virtio_dev_queue_setup(struct rte_eth_dev *dev,
        mz = rte_memzone_reserve_aligned(vq_name, vq->vq_ring_size,
                socket_id, 0, VIRTIO_PCI_VRING_ALIGN);
        if (mz == NULL) {
-               rte_free(vq);
-               return -ENOMEM;
+               if (rte_errno == EEXIST)
+                       mz = rte_memzone_lookup(vq_name);
+               if (mz == NULL) {
+                       rte_free(vq);
+                       return -ENOMEM;
+               }
        }
 
        /*
@@ -349,8 +367,13 @@ int virtio_dev_queue_setup(struct rte_eth_dev *dev,
                        vq_size * hw->vtnet_hdr_size,
                        socket_id, 0, RTE_CACHE_LINE_SIZE);
                if (vq->virtio_net_hdr_mz == NULL) {
-                       rte_free(vq);
-                       return -ENOMEM;
+                       if (rte_errno == EEXIST)
+                               vq->virtio_net_hdr_mz =
+                                       rte_memzone_lookup(vq_name);
+                       if (vq->virtio_net_hdr_mz == NULL) {
+                               rte_free(vq);
+                               return -ENOMEM;
+                       }
                }
                vq->virtio_net_hdr_mem =
                        vq->virtio_net_hdr_mz->phys_addr;
@@ -363,8 +386,13 @@ int virtio_dev_queue_setup(struct rte_eth_dev *dev,
                vq->virtio_net_hdr_mz = rte_memzone_reserve_aligned(vq_name,
                        PAGE_SIZE, socket_id, 0, RTE_CACHE_LINE_SIZE);
                if (vq->virtio_net_hdr_mz == NULL) {
-                       rte_free(vq);
-                       return -ENOMEM;
+                       if (rte_errno == EEXIST)
+                               vq->virtio_net_hdr_mz =
+                                       rte_memzone_lookup(vq_name);
+                       if (vq->virtio_net_hdr_mz == NULL) {
+                               rte_free(vq);
+                               return -ENOMEM;
+                       }
                }
                vq->virtio_net_hdr_mem =
                        vq->virtio_net_hdr_mz->phys_addr;
@@ -386,13 +414,12 @@ virtio_dev_cq_queue_setup(struct rte_eth_dev *dev, uint16_t vtpci_queue_idx,
                uint32_t socket_id)
 {
        struct virtqueue *vq;
-       uint16_t nb_desc = 0;
        int ret;
        struct virtio_hw *hw = dev->data->dev_private;
 
        PMD_INIT_FUNC_TRACE();
        ret = virtio_dev_queue_setup(dev, VTNET_CQ, VTNET_SQ_CQ_QUEUE_IDX,
-                       vtpci_queue_idx, nb_desc, socket_id, &vq);
+                       vtpci_queue_idx, 0, socket_id, &vq);
        if (ret < 0) {
                PMD_INIT_LOG(ERR, "control vq initialization failed");
                return ret;
@@ -402,6 +429,22 @@ virtio_dev_cq_queue_setup(struct rte_eth_dev *dev, uint16_t vtpci_queue_idx,
        return 0;
 }
 
+static void
+virtio_free_queues(struct rte_eth_dev *dev)
+{
+       unsigned int i;
+
+       for (i = 0; i < dev->data->nb_rx_queues; i++)
+               virtio_dev_rx_queue_release(dev->data->rx_queues[i]);
+
+       dev->data->nb_rx_queues = 0;
+
+       for (i = 0; i < dev->data->nb_tx_queues; i++)
+               virtio_dev_tx_queue_release(dev->data->tx_queues[i]);
+
+       dev->data->nb_tx_queues = 0;
+}
+
 static void
 virtio_dev_close(struct rte_eth_dev *dev)
 {
@@ -416,6 +459,7 @@ virtio_dev_close(struct rte_eth_dev *dev)
        vtpci_reset(hw);
        hw->started = 0;
        virtio_dev_free_mbufs(dev);
+       virtio_free_queues(dev);
 }
 
 static void
@@ -426,6 +470,11 @@ virtio_dev_promiscuous_enable(struct rte_eth_dev *dev)
        int dlen[1];
        int ret;
 
+       if (!vtpci_with_feature(hw, VIRTIO_NET_F_CTRL_RX)) {
+               PMD_INIT_LOG(INFO, "host does not support rx control\n");
+               return;
+       }
+
        ctrl.hdr.class = VIRTIO_NET_CTRL_RX;
        ctrl.hdr.cmd = VIRTIO_NET_CTRL_RX_PROMISC;
        ctrl.data[0] = 1;
@@ -444,6 +493,11 @@ virtio_dev_promiscuous_disable(struct rte_eth_dev *dev)
        int dlen[1];
        int ret;
 
+       if (!vtpci_with_feature(hw, VIRTIO_NET_F_CTRL_RX)) {
+               PMD_INIT_LOG(INFO, "host does not support rx control\n");
+               return;
+       }
+
        ctrl.hdr.class = VIRTIO_NET_CTRL_RX;
        ctrl.hdr.cmd = VIRTIO_NET_CTRL_RX_PROMISC;
        ctrl.data[0] = 0;
@@ -462,6 +516,11 @@ virtio_dev_allmulticast_enable(struct rte_eth_dev *dev)
        int dlen[1];
        int ret;
 
+       if (!vtpci_with_feature(hw, VIRTIO_NET_F_CTRL_RX)) {
+               PMD_INIT_LOG(INFO, "host does not support rx control\n");
+               return;
+       }
+
        ctrl.hdr.class = VIRTIO_NET_CTRL_RX;
        ctrl.hdr.cmd = VIRTIO_NET_CTRL_RX_ALLMULTI;
        ctrl.data[0] = 1;
@@ -480,6 +539,11 @@ virtio_dev_allmulticast_disable(struct rte_eth_dev *dev)
        int dlen[1];
        int ret;
 
+       if (!vtpci_with_feature(hw, VIRTIO_NET_F_CTRL_RX)) {
+               PMD_INIT_LOG(INFO, "host does not support rx control\n");
+               return;
+       }
+
        ctrl.hdr.class = VIRTIO_NET_CTRL_RX;
        ctrl.hdr.cmd = VIRTIO_NET_CTRL_RX_ALLMULTI;
        ctrl.data[0] = 0;
@@ -508,10 +572,8 @@ static const struct eth_dev_ops virtio_eth_dev_ops = {
        .stats_reset             = virtio_dev_stats_reset,
        .link_update             = virtio_dev_link_update,
        .rx_queue_setup          = virtio_dev_rx_queue_setup,
-       /* meaningfull only to multiple queue */
        .rx_queue_release        = virtio_dev_rx_queue_release,
        .tx_queue_setup          = virtio_dev_tx_queue_setup,
-       /* meaningfull only to multiple queue */
        .tx_queue_release        = virtio_dev_tx_queue_release,
        /* collect stats per queue */
        .queue_stats_mapping_set = virtio_dev_queue_stats_mapping_set,
@@ -648,7 +710,7 @@ virtio_get_hwaddr(struct virtio_hw *hw)
        }
 }
 
-static int
+static void
 virtio_mac_table_set(struct virtio_hw *hw,
                     const struct virtio_net_ctrl_mac *uc,
                     const struct virtio_net_ctrl_mac *mc)
@@ -656,6 +718,11 @@ virtio_mac_table_set(struct virtio_hw *hw,
        struct virtio_pmd_ctrl ctrl;
        int err, len[2];
 
+       if (!vtpci_with_feature(hw, VIRTIO_NET_F_CTRL_MAC_ADDR)) {
+               PMD_DRV_LOG(INFO, "host does not support mac table\n");
+               return;
+       }
+
        ctrl.hdr.class = VIRTIO_NET_CTRL_MAC;
        ctrl.hdr.cmd = VIRTIO_NET_CTRL_MAC_TABLE_SET;
 
@@ -668,8 +735,6 @@ virtio_mac_table_set(struct virtio_hw *hw,
        err = virtio_send_command(hw->cvq, &ctrl, len, 2);
        if (err != 0)
                PMD_DRV_LOG(NOTICE, "mac table set failed: %d", err);
-
-       return err;
 }
 
 static void
@@ -776,23 +841,10 @@ virtio_vlan_filter_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
 static void
 virtio_negotiate_features(struct virtio_hw *hw)
 {
-       uint32_t host_features, mask;
-
-       /* checksum offload not implemented */
-       mask = VIRTIO_NET_F_CSUM | VIRTIO_NET_F_GUEST_CSUM;
-
-       /* TSO and LRO are only available when their corresponding
-        * checksum offload feature is also negotiated.
-        */
-       mask |= VIRTIO_NET_F_HOST_TSO4 | VIRTIO_NET_F_HOST_TSO6 | VIRTIO_NET_F_HOST_ECN;
-       mask |= VIRTIO_NET_F_GUEST_TSO4 | VIRTIO_NET_F_GUEST_TSO6 | VIRTIO_NET_F_GUEST_ECN;
-       mask |= VTNET_LRO_FEATURES;
-
-       /* not negotiating INDIRECT descriptor table support */
-       mask |= VIRTIO_RING_F_INDIRECT_DESC;
+       uint32_t host_features;
 
        /* Prepare guest_features: feature that driver wants to support */
-       hw->guest_features = VTNET_FEATURES & ~mask;
+       hw->guest_features = VIRTIO_PMD_GUEST_FEATURES;
        PMD_INIT_LOG(DEBUG, "guest_features before negotiate = %x",
                hw->guest_features);
 
@@ -1231,12 +1283,51 @@ eth_virtio_dev_init(struct rte_eth_dev *eth_dev)
        return 0;
 }
 
+static int
+eth_virtio_dev_uninit(struct rte_eth_dev *eth_dev)
+{
+       struct rte_pci_device *pci_dev;
+       struct virtio_hw *hw = eth_dev->data->dev_private;
+
+       PMD_INIT_FUNC_TRACE();
+
+       if (rte_eal_process_type() == RTE_PROC_SECONDARY)
+               return -EPERM;
+
+       if (hw->started == 1) {
+               virtio_dev_stop(eth_dev);
+               virtio_dev_close(eth_dev);
+       }
+       pci_dev = eth_dev->pci_dev;
+
+       eth_dev->dev_ops = NULL;
+       eth_dev->tx_pkt_burst = NULL;
+       eth_dev->rx_pkt_burst = NULL;
+
+       virtio_dev_queue_release(hw->cvq);
+
+       rte_free(eth_dev->data->mac_addrs);
+       eth_dev->data->mac_addrs = NULL;
+
+       /* reset interrupt callback  */
+       if (pci_dev->driver->drv_flags & RTE_PCI_DRV_INTR_LSC)
+               rte_intr_callback_unregister(&pci_dev->intr_handle,
+                                               virtio_interrupt_handler,
+                                               eth_dev);
+
+       PMD_INIT_LOG(DEBUG, "dev_uninit completed");
+
+       return 0;
+}
+
 static struct eth_driver rte_virtio_pmd = {
        .pci_drv = {
                .name = "rte_virtio_pmd",
                .id_table = pci_id_virtio_map,
+               .drv_flags = RTE_PCI_DRV_DETACHABLE,
        },
        .eth_dev_init = eth_virtio_dev_init,
+       .eth_dev_uninit = eth_virtio_dev_uninit,
        .dev_private_size = sizeof(struct virtio_hw),
 };
 
@@ -1259,19 +1350,6 @@ rte_virtio_pmd_init(const char *name __rte_unused,
        return 0;
 }
 
-/*
- * Only 1 queue is supported, no queue release related operation
- */
-static void
-virtio_dev_rx_queue_release(__rte_unused void *rxq)
-{
-}
-
-static void
-virtio_dev_tx_queue_release(__rte_unused void *txq)
-{
-}
-
 /*
  * Configure virtio device
  * It returns 0 on success.
@@ -1379,6 +1457,8 @@ static void virtio_dev_free_mbufs(struct rte_eth_dev *dev)
                             "Before freeing rxq[%d] used and unused buf", i);
                VIRTQUEUE_DUMP((struct virtqueue *)dev->data->rx_queues[i]);
 
+               PMD_INIT_LOG(DEBUG, "rx_queues[%d]=%p",
+                               i, dev->data->rx_queues[i]);
                while ((buf = (struct rte_mbuf *)virtqueue_detatch_unused(
                                        dev->data->rx_queues[i])) != NULL) {
                        rte_pktmbuf_free(buf);