net: stmmac: Rework and fix TX Timeout code
authorJose Abreu <Jose.Abreu@synopsys.com>
Thu, 29 Mar 2018 09:40:18 +0000 (10:40 +0100)
committerDavid S. Miller <davem@davemloft.net>
Fri, 30 Mar 2018 16:31:59 +0000 (12:31 -0400)
Currently TX Timeout handler does not behaves as expected and leads to
an unrecoverable state. Rework current implementation of TX Timeout
handling to actually perform a complete reset of the driver state and IP.

We use deferred work to init a task which will be responsible for
resetting the system.

Signed-off-by: Jose Abreu <joabreu@synopsys.com>
Cc: David S. Miller <davem@davemloft.net>
Cc: Joao Pinto <jpinto@synopsys.com>
Cc: Giuseppe Cavallaro <peppe.cavallaro@st.com>
Cc: Alexandre Torgue <alexandre.torgue@st.com>
Cc: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/stmicro/stmmac/stmmac.h
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c

index 75161e1b7e558ce0e90d234b40f0532c4bd2fbd8..1485d8fcfaa9bea01a5e498b8162cd4677176467 100644 (file)
@@ -145,6 +145,17 @@ struct stmmac_priv {
        struct dentry *dbgfs_rings_status;
        struct dentry *dbgfs_dma_cap;
 #endif
+
+       unsigned long state;
+       struct workqueue_struct *wq;
+       struct work_struct service_task;
+};
+
+enum stmmac_state {
+       STMMAC_DOWN,
+       STMMAC_RESET_REQUESTED,
+       STMMAC_RESETING,
+       STMMAC_SERVICE_SCHED,
 };
 
 int stmmac_mdio_unregister(struct net_device *ndev);
index 9f983dd069d5f35affe0db976bf6c679a9d63021..b75ecf3d19fe855bf777a55e18db6fb92ab526b0 100644 (file)
@@ -196,6 +196,20 @@ static void stmmac_start_all_queues(struct stmmac_priv *priv)
                netif_tx_start_queue(netdev_get_tx_queue(priv->dev, queue));
 }
 
+static void stmmac_service_event_schedule(struct stmmac_priv *priv)
+{
+       if (!test_bit(STMMAC_DOWN, &priv->state) &&
+           !test_and_set_bit(STMMAC_SERVICE_SCHED, &priv->state))
+               queue_work(priv->wq, &priv->service_task);
+}
+
+static void stmmac_global_err(struct stmmac_priv *priv)
+{
+       netif_carrier_off(priv->dev);
+       set_bit(STMMAC_RESET_REQUESTED, &priv->state);
+       stmmac_service_event_schedule(priv);
+}
+
 /**
  * stmmac_clk_csr_set - dynamically set the MDC clock
  * @priv: driver private structure
@@ -3587,12 +3601,8 @@ static int stmmac_poll(struct napi_struct *napi, int budget)
 static void stmmac_tx_timeout(struct net_device *dev)
 {
        struct stmmac_priv *priv = netdev_priv(dev);
-       u32 tx_count = priv->plat->tx_queues_to_use;
-       u32 chan;
 
-       /* Clear Tx resources and restart transmitting again */
-       for (chan = 0; chan < tx_count; chan++)
-               stmmac_tx_err(priv, chan);
+       stmmac_global_err(priv);
 }
 
 /**
@@ -3716,6 +3726,10 @@ static irqreturn_t stmmac_interrupt(int irq, void *dev_id)
                return IRQ_NONE;
        }
 
+       /* Check if adapter is up */
+       if (test_bit(STMMAC_DOWN, &priv->state))
+               return IRQ_HANDLED;
+
        /* To handle GMAC own interrupts */
        if ((priv->plat->has_gmac) || (priv->plat->has_gmac4)) {
                int status = priv->hw->mac->host_irq_status(priv->hw,
@@ -4051,6 +4065,37 @@ static const struct net_device_ops stmmac_netdev_ops = {
        .ndo_set_mac_address = stmmac_set_mac_address,
 };
 
+static void stmmac_reset_subtask(struct stmmac_priv *priv)
+{
+       if (!test_and_clear_bit(STMMAC_RESET_REQUESTED, &priv->state))
+               return;
+       if (test_bit(STMMAC_DOWN, &priv->state))
+               return;
+
+       netdev_err(priv->dev, "Reset adapter.\n");
+
+       rtnl_lock();
+       netif_trans_update(priv->dev);
+       while (test_and_set_bit(STMMAC_RESETING, &priv->state))
+               usleep_range(1000, 2000);
+
+       set_bit(STMMAC_DOWN, &priv->state);
+       dev_close(priv->dev);
+       dev_open(priv->dev);
+       clear_bit(STMMAC_DOWN, &priv->state);
+       clear_bit(STMMAC_RESETING, &priv->state);
+       rtnl_unlock();
+}
+
+static void stmmac_service_task(struct work_struct *work)
+{
+       struct stmmac_priv *priv = container_of(work, struct stmmac_priv,
+                       service_task);
+
+       stmmac_reset_subtask(priv);
+       clear_bit(STMMAC_SERVICE_SCHED, &priv->state);
+}
+
 /**
  *  stmmac_hw_init - Init the MAC device
  *  @priv: driver private structure
@@ -4212,6 +4257,15 @@ int stmmac_dvr_probe(struct device *device,
        /* Verify driver arguments */
        stmmac_verify_args();
 
+       /* Allocate workqueue */
+       priv->wq = create_singlethread_workqueue("stmmac_wq");
+       if (!priv->wq) {
+               dev_err(priv->device, "failed to create workqueue\n");
+               goto error_wq;
+       }
+
+       INIT_WORK(&priv->service_task, stmmac_service_task);
+
        /* Override with kernel parameters if supplied XXX CRS XXX
         * this needs to have multiple instances
         */
@@ -4342,6 +4396,8 @@ error_mdio_register:
                netif_napi_del(&rx_q->napi);
        }
 error_hw_init:
+       destroy_workqueue(priv->wq);
+error_wq:
        free_netdev(ndev);
 
        return ret;
@@ -4374,6 +4430,7 @@ int stmmac_dvr_remove(struct device *dev)
            priv->hw->pcs != STMMAC_PCS_TBI &&
            priv->hw->pcs != STMMAC_PCS_RTBI)
                stmmac_mdio_unregister(ndev);
+       destroy_workqueue(priv->wq);
        free_netdev(ndev);
 
        return 0;