net: axienet: Convert DMA error handler to a work queue
authorAndre Przywara <andre.przywara@arm.com>
Tue, 24 Mar 2020 13:23:35 +0000 (13:23 +0000)
committerDavid S. Miller <davem@davemloft.net>
Tue, 24 Mar 2020 23:33:04 +0000 (16:33 -0700)
The DMA error handler routine is currently a tasklet, scheduled to run
after the DMA error IRQ was handled.
However it needs to take the MDIO mutex, which is not allowed to do in a
tasklet. A kernel (with debug options) complains consequently:
[  614.050361] net eth0: DMA Tx error 0x174019
[  614.064002] net eth0: Current BD is at: 0x8f84aa0ce
[  614.080195] BUG: sleeping function called from invalid context at kernel/locking/mutex.c:935
[  614.109484] in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 40, name: kworker/u4:4
[  614.135428] 3 locks held by kworker/u4:4/40:
[  614.149075]  #0: ffff000879863328 ((wq_completion)rpciod){....}, at: process_one_work+0x1f0/0x6a8
[  614.177528]  #1: ffff80001251bdf8 ((work_completion)(&task->u.tk_work)){....}, at: process_one_work+0x1f0/0x6a8
[  614.209033]  #2: ffff0008784e0110 (sk_lock-AF_INET-RPC){....}, at: tcp_sendmsg+0x24/0x58
[  614.235429] CPU: 0 PID: 40 Comm: kworker/u4:4 Not tainted 5.6.0-rc3-00926-g4a165a9d5921 #26
[  614.260854] Hardware name: ARM Test FPGA (DT)
[  614.274734] Workqueue: rpciod rpc_async_schedule
[  614.289022] Call trace:
[  614.296871]  dump_backtrace+0x0/0x1a0
[  614.308311]  show_stack+0x14/0x20
[  614.318751]  dump_stack+0xbc/0x100
[  614.329403]  ___might_sleep+0xf0/0x140
[  614.341018]  __might_sleep+0x4c/0x80
[  614.352201]  __mutex_lock+0x5c/0x8a8
[  614.363348]  mutex_lock_nested+0x1c/0x28
[  614.375654]  axienet_dma_err_handler+0x38/0x388
[  614.389999]  tasklet_action_common.isra.15+0x160/0x1a8
[  614.405894]  tasklet_action+0x24/0x30
[  614.417297]  efi_header_end+0xe0/0x494
[  614.429020]  irq_exit+0xd0/0xd8
[  614.439047]  __handle_domain_irq+0x60/0xb0
[  614.451877]  gic_handle_irq+0xdc/0x2d0
[  614.463486]  el1_irq+0xcc/0x180
[  614.473451]  __tcp_transmit_skb+0x41c/0xb58
[  614.486513]  tcp_write_xmit+0x224/0x10a0
[  614.498792]  __tcp_push_pending_frames+0x38/0xc8
[  614.513126]  tcp_rcv_established+0x41c/0x820
[  614.526301]  tcp_v4_do_rcv+0x8c/0x218
[  614.537784]  __release_sock+0x5c/0x108
[  614.549466]  release_sock+0x34/0xa0
[  614.560318]  tcp_sendmsg+0x40/0x58
[  614.571053]  inet_sendmsg+0x40/0x68
[  614.582061]  sock_sendmsg+0x18/0x30
[  614.593074]  xs_sendpages+0x218/0x328
[  614.604506]  xs_tcp_send_request+0xa0/0x1b8
[  614.617461]  xprt_transmit+0xc8/0x4f0
[  614.628943]  call_transmit+0x8c/0xa0
[  614.640028]  __rpc_execute+0xbc/0x6f8
[  614.651380]  rpc_async_schedule+0x28/0x48
[  614.663846]  process_one_work+0x298/0x6a8
[  614.676299]  worker_thread+0x40/0x490
[  614.687687]  kthread+0x134/0x138
[  614.697804]  ret_from_fork+0x10/0x18
[  614.717319] xilinx_axienet 7fe00000.ethernet eth0: Link is Down
[  615.748343] xilinx_axienet 7fe00000.ethernet eth0: Link is Up - 1Gbps/Full - flow control off

Since tasklets are not really popular anymore anyway, lets convert this
over to a work queue, which can sleep and thus can take the MDIO mutex.

Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/xilinx/xilinx_axienet.h
drivers/net/ethernet/xilinx/xilinx_axienet_main.c

index 2dacfc8..04e51af 100644 (file)
@@ -435,7 +435,7 @@ struct axienet_local {
        void __iomem *regs;
        void __iomem *dma_regs;
 
-       struct tasklet_struct dma_err_tasklet;
+       struct work_struct dma_err_task;
 
        int tx_irq;
        int rx_irq;
index e2f3e2b..d4c8724 100644 (file)
@@ -806,7 +806,7 @@ static irqreturn_t axienet_tx_irq(int irq, void *_ndev)
                /* Write to the Rx channel control register */
                axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, cr);
 
-               tasklet_schedule(&lp->dma_err_tasklet);
+               schedule_work(&lp->dma_err_task);
                axienet_dma_out32(lp, XAXIDMA_TX_SR_OFFSET, status);
        }
 out:
@@ -855,7 +855,7 @@ static irqreturn_t axienet_rx_irq(int irq, void *_ndev)
                /* write to the Rx channel control register */
                axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, cr);
 
-               tasklet_schedule(&lp->dma_err_tasklet);
+               schedule_work(&lp->dma_err_task);
                axienet_dma_out32(lp, XAXIDMA_RX_SR_OFFSET, status);
        }
 out:
@@ -891,7 +891,7 @@ static irqreturn_t axienet_eth_irq(int irq, void *_ndev)
        return IRQ_HANDLED;
 }
 
-static void axienet_dma_err_handler(unsigned long data);
+static void axienet_dma_err_handler(struct work_struct *work);
 
 /**
  * axienet_open - Driver open routine.
@@ -935,9 +935,8 @@ static int axienet_open(struct net_device *ndev)
 
        phylink_start(lp->phylink);
 
-       /* Enable tasklets for Axi DMA error handling */
-       tasklet_init(&lp->dma_err_tasklet, axienet_dma_err_handler,
-                    (unsigned long) lp);
+       /* Enable worker thread for Axi DMA error handling */
+       INIT_WORK(&lp->dma_err_task, axienet_dma_err_handler);
 
        /* Enable interrupts for Axi DMA Tx */
        ret = request_irq(lp->tx_irq, axienet_tx_irq, IRQF_SHARED,
@@ -966,7 +965,7 @@ err_rx_irq:
 err_tx_irq:
        phylink_stop(lp->phylink);
        phylink_disconnect_phy(lp->phylink);
-       tasklet_kill(&lp->dma_err_tasklet);
+       cancel_work_sync(&lp->dma_err_task);
        dev_err(lp->dev, "request_irq() failed\n");
        return ret;
 }
@@ -1025,7 +1024,7 @@ static int axienet_stop(struct net_device *ndev)
        axienet_mdio_enable(lp);
        mutex_unlock(&lp->mii_bus->mdio_lock);
 
-       tasklet_kill(&lp->dma_err_tasklet);
+       cancel_work_sync(&lp->dma_err_task);
 
        if (lp->eth_irq > 0)
                free_irq(lp->eth_irq, ndev);
@@ -1484,17 +1483,18 @@ static const struct phylink_mac_ops axienet_phylink_ops = {
 };
 
 /**
- * axienet_dma_err_handler - Tasklet handler for Axi DMA Error
- * @data:      Data passed
+ * axienet_dma_err_handler - Work queue task for Axi DMA Error
+ * @work:      pointer to work_struct
  *
  * Resets the Axi DMA and Axi Ethernet devices, and reconfigures the
  * Tx/Rx BDs.
  */
-static void axienet_dma_err_handler(unsigned long data)
+static void axienet_dma_err_handler(struct work_struct *work)
 {
        u32 axienet_status;
        u32 cr, i;
-       struct axienet_local *lp = (struct axienet_local *) data;
+       struct axienet_local *lp = container_of(work, struct axienet_local,
+                                               dma_err_task);
        struct net_device *ndev = lp->ndev;
        struct axidma_bd *cur_p;