From 775efb78d9b0bf438a6a97c13b798e04f5de4be2 Mon Sep 17 00:00:00 2001
From: Grygorii Strashko <grygorii.strashko@ti.com>
Date: Wed, 7 Jul 2021 14:43:12 +0300
Subject: [PATCH] net: ethernet: icssg-prueth: fix bug 'scheduling while
 atomic' from emac_ndo_set_rx_mode()

The .ndo_set_rx_mode() is called with BH disabled and should be atomic, but
current ICSSG SR2.0 driver calls non-atomic emac_set_port_state() from
emac_ndo_set_rx_mode() which causes "BUG: scheduling while atomic:
ifconfig/1739/0x00000202".

Fix it by adding workqueue and scheduling work in emac_ndo_set_rx_mode() to
reconfigure port instead of calling emac_set_port_state() directly.

Signed-off-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: Vignesh Raghavendra <vigneshr@ti.com>
---
 drivers/net/ethernet/ti/icssg_prueth.c | 50 ++++++++++++++++++--------
 drivers/net/ethernet/ti/icssg_prueth.h |  2 ++
 2 files changed, 37 insertions(+), 15 deletions(-)

diff --git a/drivers/net/ethernet/ti/icssg_prueth.c b/drivers/net/ethernet/ti/icssg_prueth.c
index 3b1769a5e2b3..f212e5904afc 100644
--- a/drivers/net/ethernet/ti/icssg_prueth.c
+++ b/drivers/net/ethernet/ti/icssg_prueth.c
@@ -1790,6 +1790,7 @@ static int emac_ndo_stop(struct net_device *ndev)
 
 	prueth->iep_initialized--;
 
+	cancel_work_sync(&emac->rx_mode_work);
 	/* stop PRUs */
 	prueth_emac_stop(emac);
 
@@ -1856,25 +1857,17 @@ static void emac_ndo_set_rx_mode_sr1(struct net_device *ndev)
 	}
 }
 
-/**
- * emac_ndo_set_rx_mode - EMAC set receive mode function
- * @ndev: The EMAC network adapter
- *
- * Called when system wants to set the receive mode of the device.
- *
- */
-static void emac_ndo_set_rx_mode(struct net_device *ndev)
+static void emac_ndo_set_rx_mode_work(struct work_struct *work)
 {
-	struct prueth_emac *emac = netdev_priv(ndev);
-	struct prueth *prueth = emac->prueth;
-	bool promisc = ndev->flags & IFF_PROMISC;
-	bool allmulti = ndev->flags & IFF_ALLMULTI;
+	struct prueth_emac *emac = container_of(work, struct prueth_emac, rx_mode_work);
+	struct net_device *ndev = emac->ndev;
+	bool promisc, allmulti;
 
-	if (prueth->is_sr1) {
-		emac_ndo_set_rx_mode_sr1(ndev);
+	if (!(ndev->flags & IFF_UP))
 		return;
-	}
 
+	promisc = ndev->flags & IFF_PROMISC;
+	allmulti = ndev->flags & IFF_ALLMULTI;
 	emac_set_port_state(emac, ICSSG_EMAC_PORT_UC_FLOODING_DISABLE);
 	emac_set_port_state(emac, ICSSG_EMAC_PORT_MC_FLOODING_DISABLE);
 
@@ -1896,6 +1889,26 @@ static void emac_ndo_set_rx_mode(struct net_device *ndev)
 	}
 }
 
+/**
+ * emac_ndo_set_rx_mode - EMAC set receive mode function
+ * @ndev: The EMAC network adapter
+ *
+ * Called when system wants to set the receive mode of the device.
+ *
+ */
+static void emac_ndo_set_rx_mode(struct net_device *ndev)
+{
+	struct prueth_emac *emac = netdev_priv(ndev);
+	struct prueth *prueth = emac->prueth;
+
+	if (prueth->is_sr1) {
+		emac_ndo_set_rx_mode_sr1(ndev);
+		return;
+	}
+
+	queue_work(emac->cmd_wq, &emac->rx_mode_work);
+}
+
 static int emac_set_ts_config(struct net_device *ndev, struct ifreq *ifr)
 {
 	struct prueth_emac *emac = netdev_priv(ndev);
@@ -2085,6 +2098,10 @@ static int prueth_netdev_init(struct prueth *prueth,
 	emac->prueth = prueth;
 	emac->ndev = ndev;
 	emac->port_id = port;
+	emac->cmd_wq = create_singlethread_workqueue("icssg_cmd_wq");
+	if (!emac->cmd_wq)
+		goto free_ndev;
+	INIT_WORK(&emac->rx_mode_work, emac_ndo_set_rx_mode_work);
 
 	ret = pruss_request_mem_region(prueth->pruss,
 				       port == PRUETH_PORT_MII0 ?
@@ -2184,6 +2201,8 @@ static int prueth_netdev_init(struct prueth *prueth,
 
 free:
 	pruss_release_mem_region(prueth->pruss, &emac->dram);
+	destroy_workqueue(emac->cmd_wq);
+free_ndev:
 	free_netdev(ndev);
 	prueth->emac[mac] = NULL;
 
@@ -2212,6 +2231,7 @@ static void prueth_netdev_exit(struct prueth *prueth,
 	netif_napi_del(&emac->napi_rx);
 
 	pruss_release_mem_region(prueth->pruss, &emac->dram);
+	destroy_workqueue(emac->cmd_wq);
 	free_netdev(emac->ndev);
 	prueth->emac[mac] = NULL;
 }
diff --git a/drivers/net/ethernet/ti/icssg_prueth.h b/drivers/net/ethernet/ti/icssg_prueth.h
index 7cc81c67bf72..59dec45a3c86 100644
--- a/drivers/net/ethernet/ti/icssg_prueth.h
+++ b/drivers/net/ethernet/ti/icssg_prueth.h
@@ -166,6 +166,8 @@ struct prueth_emac {
 	struct completion cmd_complete;
 	/* Mutex to serialize access to firmware command interface */
 	struct mutex cmd_lock;
+	struct work_struct rx_mode_work;
+	struct workqueue_struct	*cmd_wq;
 
 	struct pruss_mem_region dram;
 };
