net: stmmac: Rework and fix TX Timeout code

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>
This commit is contained in:
Jose Abreu 2018-03-29 10:40:18 +01:00 committed by David S. Miller
parent 02281a3525
commit 34877a15f7
2 changed files with 73 additions and 5 deletions

View 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);

View 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 @@ int stmmac_dvr_probe(struct device *device,
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;