]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
gve: Add workqueue and reset support
authorCatherine Sullivan <csully@google.com>
Mon, 1 Jul 2019 22:57:54 +0000 (15:57 -0700)
committerDavid S. Miller <davem@davemloft.net>
Tue, 2 Jul 2019 02:36:35 +0000 (19:36 -0700)
Add support for the workqueue to handle management interrupts and
support for resets.

Signed-off-by: Catherine Sullivan <csully@google.com>
Signed-off-by: Sagi Shahar <sagis@google.com>
Signed-off-by: Jon Olson <jonolson@google.com>
Acked-by: Willem de Bruijn <willemb@google.com>
Reviewed-by: Luigi Rizzo <lrizzo@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Documentation/networking/device_drivers/google/gve.rst
drivers/net/ethernet/google/gve/gve.h
drivers/net/ethernet/google/gve/gve_main.c
drivers/net/ethernet/google/gve/gve_rx.c

index df8974fb3270ce2a5c376cb34a5c3c75be3ab5ec..793693cef6e36fb8debbfa2ccd7f98315145e86e 100644 (file)
@@ -40,6 +40,8 @@ The driver interacts with the device in the following ways:
     - See gve_register.h for more detail
  - Admin Queue
     - See description below
+ - Reset
+    - At any time the device can be reset
  - Interrupts
     - See supported interrupts below
  - Transmit and Receive Queues
@@ -69,6 +71,12 @@ the following (with proper locking):
 The device will update the status field in each AQ command reported as
 executed through the ADMIN_QUEUE_EVENT_COUNTER register.
 
+Device Resets
+-------------
+A device reset is triggered by writing 0x0 to the AQ PFN register.
+This causes the device to release all resources allocated by the
+driver, including the AQ itself.
+
 Interrupts
 ----------
 The following interrupts are supported by the driver:
@@ -78,6 +86,9 @@ Management Interrupt
 The management interrupt is used by the device to tell the driver to
 look at the GVE_DEVICE_STATUS register.
 
+The handler for the management irq simply queues the service task in
+the workqueue to check the register and acks the irq.
+
 Notification Block Interrupts
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 The notification block interrupts are used to tell the driver to poll
index 110ae46adc69f346caa27cb80e9f5016e222d57e..7960d553207866bf1c09b670c775fb8ccd0048cc 100644 (file)
@@ -205,9 +205,18 @@ struct gve_priv {
        u32 adminq_mask; /* masks prod_cnt to adminq size */
        u32 adminq_prod_cnt; /* free-running count of AQ cmds executed */
 
+       struct workqueue_struct *gve_wq;
+       struct work_struct service_task;
+       unsigned long service_task_flags;
        unsigned long state_flags;
 };
 
+enum gve_service_task_flags {
+       GVE_PRIV_FLAGS_DO_RESET                 = BIT(1),
+       GVE_PRIV_FLAGS_RESET_IN_PROGRESS        = BIT(2),
+       GVE_PRIV_FLAGS_PROBE_IN_PROGRESS        = BIT(3),
+};
+
 enum gve_state_flags {
        GVE_PRIV_FLAGS_ADMIN_QUEUE_OK           = BIT(1),
        GVE_PRIV_FLAGS_DEVICE_RESOURCES_OK      = BIT(2),
@@ -215,6 +224,53 @@ enum gve_state_flags {
        GVE_PRIV_FLAGS_NAPI_ENABLED             = BIT(4),
 };
 
+static inline bool gve_get_do_reset(struct gve_priv *priv)
+{
+       return test_bit(GVE_PRIV_FLAGS_DO_RESET, &priv->service_task_flags);
+}
+
+static inline void gve_set_do_reset(struct gve_priv *priv)
+{
+       set_bit(GVE_PRIV_FLAGS_DO_RESET, &priv->service_task_flags);
+}
+
+static inline void gve_clear_do_reset(struct gve_priv *priv)
+{
+       clear_bit(GVE_PRIV_FLAGS_DO_RESET, &priv->service_task_flags);
+}
+
+static inline bool gve_get_reset_in_progress(struct gve_priv *priv)
+{
+       return test_bit(GVE_PRIV_FLAGS_RESET_IN_PROGRESS,
+                       &priv->service_task_flags);
+}
+
+static inline void gve_set_reset_in_progress(struct gve_priv *priv)
+{
+       set_bit(GVE_PRIV_FLAGS_RESET_IN_PROGRESS, &priv->service_task_flags);
+}
+
+static inline void gve_clear_reset_in_progress(struct gve_priv *priv)
+{
+       clear_bit(GVE_PRIV_FLAGS_RESET_IN_PROGRESS, &priv->service_task_flags);
+}
+
+static inline bool gve_get_probe_in_progress(struct gve_priv *priv)
+{
+       return test_bit(GVE_PRIV_FLAGS_PROBE_IN_PROGRESS,
+                       &priv->service_task_flags);
+}
+
+static inline void gve_set_probe_in_progress(struct gve_priv *priv)
+{
+       set_bit(GVE_PRIV_FLAGS_PROBE_IN_PROGRESS, &priv->service_task_flags);
+}
+
+static inline void gve_clear_probe_in_progress(struct gve_priv *priv)
+{
+       clear_bit(GVE_PRIV_FLAGS_PROBE_IN_PROGRESS, &priv->service_task_flags);
+}
+
 static inline bool gve_get_admin_queue_ok(struct gve_priv *priv)
 {
        return test_bit(GVE_PRIV_FLAGS_ADMIN_QUEUE_OK, &priv->state_flags);
@@ -390,4 +446,10 @@ int gve_rx_alloc_rings(struct gve_priv *priv);
 void gve_rx_free_rings(struct gve_priv *priv);
 bool gve_clean_rx_done(struct gve_rx_ring *rx, int budget,
                       netdev_features_t feat);
+/* Reset */
+void gve_schedule_reset(struct gve_priv *priv);
+int gve_reset(struct gve_priv *priv, bool attempt_teardown);
+int gve_adjust_queues(struct gve_priv *priv,
+                     struct gve_queue_config new_rx_config,
+                     struct gve_queue_config new_tx_config);
 #endif /* _GVE_H_ */
index 44462dce0254dfee64416bdff79bcad0835836ac..126d6533b96520686bc15d6c54d430978f1b0503 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/pci.h>
 #include <linux/sched.h>
 #include <linux/timer.h>
+#include <linux/workqueue.h>
 #include <net/sch_generic.h>
 #include "gve.h"
 #include "gve_adminq.h"
@@ -77,6 +78,9 @@ static void gve_free_counter_array(struct gve_priv *priv)
 
 static irqreturn_t gve_mgmnt_intr(int irq, void *arg)
 {
+       struct gve_priv *priv = arg;
+
+       queue_work(priv->gve_wq, &priv->service_task);
        return IRQ_HANDLED;
 }
 
@@ -284,6 +288,8 @@ static int gve_setup_device_resources(struct gve_priv *priv)
        return err;
 }
 
+static void gve_trigger_reset(struct gve_priv *priv);
+
 static void gve_teardown_device_resources(struct gve_priv *priv)
 {
        int err;
@@ -295,7 +301,7 @@ static void gve_teardown_device_resources(struct gve_priv *priv)
                        dev_err(&priv->pdev->dev,
                                "Could not deconfigure device resources: err=%d\n",
                                err);
-                       return;
+                       gve_trigger_reset(priv);
                }
        }
        gve_free_counter_array(priv);
@@ -330,6 +336,9 @@ static int gve_register_qpls(struct gve_priv *priv)
                        netif_err(priv, drv, priv->dev,
                                  "failed to register queue page list %d\n",
                                  priv->qpls[i].id);
+                       /* This failure will trigger a reset - no need to clean
+                        * up
+                        */
                        return err;
                }
        }
@@ -344,6 +353,7 @@ static int gve_unregister_qpls(struct gve_priv *priv)
 
        for (i = 0; i < num_qpls; i++) {
                err = gve_adminq_unregister_page_list(priv, priv->qpls[i].id);
+               /* This failure will trigger a reset - no need to clean up */
                if (err) {
                        netif_err(priv, drv, priv->dev,
                                  "Failed to unregister queue page list %d\n",
@@ -364,6 +374,9 @@ static int gve_create_rings(struct gve_priv *priv)
                if (err) {
                        netif_err(priv, drv, priv->dev, "failed to create tx queue %d\n",
                                  i);
+                       /* This failure will trigger a reset - no need to clean
+                        * up
+                        */
                        return err;
                }
                netif_dbg(priv, drv, priv->dev, "created tx queue %d\n", i);
@@ -373,6 +386,9 @@ static int gve_create_rings(struct gve_priv *priv)
                if (err) {
                        netif_err(priv, drv, priv->dev, "failed to create rx queue %d\n",
                                  i);
+                       /* This failure will trigger a reset - no need to clean
+                        * up
+                        */
                        return err;
                }
                /* Rx data ring has been prefilled with packet buffers at
@@ -448,6 +464,9 @@ static int gve_destroy_rings(struct gve_priv *priv)
                        netif_err(priv, drv, priv->dev,
                                  "failed to destroy tx queue %d\n",
                                  i);
+                       /* This failure will trigger a reset - no need to clean
+                        * up
+                        */
                        return err;
                }
                netif_dbg(priv, drv, priv->dev, "destroyed tx queue %d\n", i);
@@ -458,6 +477,9 @@ static int gve_destroy_rings(struct gve_priv *priv)
                        netif_err(priv, drv, priv->dev,
                                  "failed to destroy rx queue %d\n",
                                  i);
+                       /* This failure will trigger a reset - no need to clean
+                        * up
+                        */
                        return err;
                }
                netif_dbg(priv, drv, priv->dev, "destroyed rx queue %d\n", i);
@@ -626,6 +648,18 @@ static void gve_free_qpls(struct gve_priv *priv)
        kfree(priv->qpls);
 }
 
+/* Use this to schedule a reset when the device is capable of continuing
+ * to handle other requests in its current state. If it is not, do a reset
+ * in thread instead.
+ */
+void gve_schedule_reset(struct gve_priv *priv)
+{
+       gve_set_do_reset(priv);
+       queue_work(priv->gve_wq, &priv->service_task);
+}
+
+static void gve_reset_and_teardown(struct gve_priv *priv, bool was_up);
+static int gve_reset_recovery(struct gve_priv *priv, bool was_up);
 static void gve_turndown(struct gve_priv *priv);
 static void gve_turnup(struct gve_priv *priv);
 
@@ -650,10 +684,10 @@ static int gve_open(struct net_device *dev)
 
        err = gve_register_qpls(priv);
        if (err)
-               return err;
+               goto reset;
        err = gve_create_rings(priv);
        if (err)
-               return err;
+               goto reset;
        gve_set_device_rings_ok(priv);
 
        gve_turnup(priv);
@@ -665,6 +699,19 @@ static int gve_open(struct net_device *dev)
 free_qpls:
        gve_free_qpls(priv);
        return err;
+
+reset:
+       /* This must have been called from a reset due to the rtnl lock
+        * so just return at this point.
+        */
+       if (gve_get_reset_in_progress(priv))
+               return err;
+       /* Otherwise reset before returning */
+       gve_reset_and_teardown(priv, true);
+       /* if this fails there is nothing we can do so just ignore the return */
+       gve_reset_recovery(priv, false);
+       /* return the original error */
+       return err;
 }
 
 static int gve_close(struct net_device *dev)
@@ -677,16 +724,26 @@ static int gve_close(struct net_device *dev)
                gve_turndown(priv);
                err = gve_destroy_rings(priv);
                if (err)
-                       return err;
+                       goto err;
                err = gve_unregister_qpls(priv);
                if (err)
-                       return err;
+                       goto err;
                gve_clear_device_rings_ok(priv);
        }
 
        gve_free_rings(priv);
        gve_free_qpls(priv);
        return 0;
+
+err:
+       /* This must have been called from a reset due to the rtnl lock
+        * so just return at this point.
+        */
+       if (gve_get_reset_in_progress(priv))
+               return err;
+       /* Otherwise reset before returning */
+       gve_reset_and_teardown(priv, true);
+       return gve_reset_recovery(priv, false);
 }
 
 static void gve_turndown(struct gve_priv *priv)
@@ -749,6 +806,7 @@ static void gve_tx_timeout(struct net_device *dev)
 {
        struct gve_priv *priv = netdev_priv(dev);
 
+       gve_schedule_reset(priv);
        priv->tx_timeo_cnt++;
 }
 
@@ -760,6 +818,42 @@ static const struct net_device_ops gve_netdev_ops = {
        .ndo_tx_timeout         =       gve_tx_timeout,
 };
 
+static void gve_handle_status(struct gve_priv *priv, u32 status)
+{
+       if (GVE_DEVICE_STATUS_RESET_MASK & status) {
+               dev_info(&priv->pdev->dev, "Device requested reset.\n");
+               gve_set_do_reset(priv);
+       }
+}
+
+static void gve_handle_reset(struct gve_priv *priv)
+{
+       /* A service task will be scheduled at the end of probe to catch any
+        * resets that need to happen, and we don't want to reset until
+        * probe is done.
+        */
+       if (gve_get_probe_in_progress(priv))
+               return;
+
+       if (gve_get_do_reset(priv)) {
+               rtnl_lock();
+               gve_reset(priv, false);
+               rtnl_unlock();
+       }
+}
+
+/* Handle NIC status register changes and reset requests */
+static void gve_service_task(struct work_struct *work)
+{
+       struct gve_priv *priv = container_of(work, struct gve_priv,
+                                            service_task);
+
+       gve_handle_status(priv,
+                         ioread32be(&priv->reg_bar0->device_status));
+
+       gve_handle_reset(priv);
+}
+
 static int gve_init_priv(struct gve_priv *priv, bool skip_describe_device)
 {
        int num_ntfy;
@@ -847,6 +941,72 @@ static void gve_teardown_priv_resources(struct gve_priv *priv)
        gve_adminq_free(&priv->pdev->dev, priv);
 }
 
+static void gve_trigger_reset(struct gve_priv *priv)
+{
+       /* Reset the device by releasing the AQ */
+       gve_adminq_release(priv);
+}
+
+static void gve_reset_and_teardown(struct gve_priv *priv, bool was_up)
+{
+       gve_trigger_reset(priv);
+       /* With the reset having already happened, close cannot fail */
+       if (was_up)
+               gve_close(priv->dev);
+       gve_teardown_priv_resources(priv);
+}
+
+static int gve_reset_recovery(struct gve_priv *priv, bool was_up)
+{
+       int err;
+
+       err = gve_init_priv(priv, true);
+       if (err)
+               goto err;
+       if (was_up) {
+               err = gve_open(priv->dev);
+               if (err)
+                       goto err;
+       }
+       return 0;
+err:
+       dev_err(&priv->pdev->dev, "Reset failed! !!! DISABLING ALL QUEUES !!!\n");
+       gve_turndown(priv);
+       return err;
+}
+
+int gve_reset(struct gve_priv *priv, bool attempt_teardown)
+{
+       bool was_up = netif_carrier_ok(priv->dev);
+       int err;
+
+       dev_info(&priv->pdev->dev, "Performing reset\n");
+       gve_clear_do_reset(priv);
+       gve_set_reset_in_progress(priv);
+       /* If we aren't attempting to teardown normally, just go turndown and
+        * reset right away.
+        */
+       if (!attempt_teardown) {
+               gve_turndown(priv);
+               gve_reset_and_teardown(priv, was_up);
+       } else {
+               /* Otherwise attempt to close normally */
+               if (was_up) {
+                       err = gve_close(priv->dev);
+                       /* If that fails reset as we did above */
+                       if (err)
+                               gve_reset_and_teardown(priv, was_up);
+               }
+               /* Clean up any remaining resources */
+               gve_teardown_priv_resources(priv);
+       }
+
+       /* Set it all back up */
+       err = gve_reset_recovery(priv, was_up);
+       gve_clear_reset_in_progress(priv);
+       return err;
+}
+
 static void gve_write_version(u8 __iomem *driver_version_register)
 {
        const char *c = gve_version_prefix;
@@ -943,21 +1103,36 @@ static int gve_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
        priv->msg_enable = DEFAULT_MSG_LEVEL;
        priv->reg_bar0 = reg_bar;
        priv->db_bar2 = db_bar;
+       priv->service_task_flags = 0x0;
        priv->state_flags = 0x0;
+
+       gve_set_probe_in_progress(priv);
+       priv->gve_wq = alloc_ordered_workqueue("gve", 0);
+       if (!priv->gve_wq) {
+               dev_err(&pdev->dev, "Could not allocate workqueue");
+               err = -ENOMEM;
+               goto abort_with_netdev;
+       }
+       INIT_WORK(&priv->service_task, gve_service_task);
        priv->tx_cfg.max_queues = max_tx_queues;
        priv->rx_cfg.max_queues = max_rx_queues;
 
        err = gve_init_priv(priv, false);
        if (err)
-               goto abort_with_netdev;
+               goto abort_with_wq;
 
        err = register_netdev(dev);
        if (err)
-               goto abort_with_netdev;
+               goto abort_with_wq;
 
        dev_info(&pdev->dev, "GVE version %s\n", gve_version_str);
+       gve_clear_probe_in_progress(priv);
+       queue_work(priv->gve_wq, &priv->service_task);
        return 0;
 
+abort_with_wq:
+       destroy_workqueue(priv->gve_wq);
+
 abort_with_netdev:
        free_netdev(dev);
 
@@ -985,6 +1160,7 @@ static void gve_remove(struct pci_dev *pdev)
 
        unregister_netdev(netdev);
        gve_teardown_priv_resources(priv);
+       destroy_workqueue(priv->gve_wq);
        free_netdev(netdev);
        pci_iounmap(pdev, db_bar);
        pci_iounmap(pdev, reg_bar);
index 2254857eac504b4d7f3a7a9f28eced2fafd40fc1..84e0ecce14c42059e9461d91f80d67023166c4fc 100644 (file)
@@ -334,6 +334,9 @@ static bool gve_rx(struct gve_rx_ring *rx, struct gve_rx_desc *rx_desc,
 #endif
 
 have_skb:
+       /* We didn't manage to allocate an skb but we haven't had any
+        * reset worthy failures.
+        */
        if (!skb)
                return true;
 
@@ -399,7 +402,7 @@ bool gve_clean_rx_done(struct gve_rx_ring *rx, int budget,
                           rx->desc.seqno);
                bytes += be16_to_cpu(desc->len) - GVE_RX_PAD;
                if (!gve_rx(rx, desc, feat))
-                       return false;
+                       gve_schedule_reset(priv);
                cnt++;
                idx = cnt & rx->desc.mask;
                desc = rx->desc.desc_ring + idx;