From 0af5d7d021bb899d9c3880415267e178a20fb7a9 Mon Sep 17 00:00:00 2001
From: Mans Rullgard <mans@mansr.com>
Date: Thu, 24 Nov 2016 18:46:41 +0000
Subject: [PATCH] ivshmem-net: fix race in state machine

(cherry picked from commit 5d663baed6a89d09bae4446f6509f9957c780bc7)
---
 drivers/net/ivshmem-net.c | 60 ++++++++++++++++++++++++-----------------------
 1 file changed, 31 insertions(+), 29 deletions(-)

--- a/drivers/net/ivshmem-net.c
+++ b/drivers/net/ivshmem-net.c
@@ -36,6 +36,8 @@
 #define IVSHM_NET_STATE_READY	2
 #define IVSHM_NET_STATE_RUN	3
 
+#define IVSHM_NET_FLAG_RUN	0
+
 #define IVSHM_NET_MTU_MIN 256
 #define IVSHM_NET_MTU_MAX 65535
 #define IVSHM_NET_MTU_DEF 16384
@@ -96,6 +98,8 @@ struct ivshm_net {
 	u32 lstate;
 	u32 rstate;
 
+	unsigned long flags;
+
 	struct workqueue_struct *state_wq;
 	struct work_struct state_work;
 
@@ -529,12 +533,32 @@ static void ivshm_net_run(struct net_dev
 {
 	struct ivshm_net *in = netdev_priv(ndev);
 
+	if (in->lstate < IVSHM_NET_STATE_READY)
+		return;
+
+	if (!netif_running(ndev))
+		return;
+
+	if (test_and_set_bit(IVSHM_NET_FLAG_RUN, &in->flags))
+		return;
+
 	netif_start_queue(ndev);
 	napi_enable(&in->napi);
 	napi_schedule(&in->napi);
 	ivshm_net_set_state(in, IVSHM_NET_STATE_RUN);
 }
 
+static void ivshm_net_do_stop(struct net_device *ndev)
+{
+	struct ivshm_net *in = netdev_priv(ndev);
+
+	if (!test_and_clear_bit(IVSHM_NET_FLAG_RUN, &in->flags))
+		return;
+
+	netif_stop_queue(ndev);
+	napi_disable(&in->napi);
+}
+
 static void ivshm_net_state_change(struct work_struct *work)
 {
 	struct ivshm_net *in = container_of(work, struct ivshm_net, state_work);
@@ -560,21 +584,13 @@ static void ivshm_net_state_change(struc
 		break;
 
 	case IVSHM_NET_STATE_READY:
+	case IVSHM_NET_STATE_RUN:
 		if (rstate >= IVSHM_NET_STATE_READY) {
 			netif_carrier_on(ndev);
-			if (ndev->flags & IFF_UP)
-				ivshm_net_run(ndev);
+			ivshm_net_run(ndev);
 		} else {
 			netif_carrier_off(ndev);
-			ivshm_net_set_state(in, IVSHM_NET_STATE_RESET);
-		}
-		break;
-
-	case IVSHM_NET_STATE_RUN:
-		if (rstate < IVSHM_NET_STATE_READY) {
-			netif_stop_queue(ndev);
-			napi_disable(&in->napi);
-			netif_carrier_off(ndev);
+			ivshm_net_do_stop(ndev);
 			ivshm_net_set_state(in, IVSHM_NET_STATE_RESET);
 		}
 		break;
@@ -584,18 +600,13 @@ static void ivshm_net_state_change(struc
 	WRITE_ONCE(in->rstate, rstate);
 }
 
-static bool ivshm_net_check_state(struct net_device *ndev)
+static void ivshm_net_check_state(struct net_device *ndev)
 {
 	struct ivshm_net *in = netdev_priv(ndev);
 	u32 rstate = readl(&in->ivshm_regs->rstate);
 
-	if (rstate != READ_ONCE(in->rstate) ||
-	    in->lstate != IVSHM_NET_STATE_RUN) {
+	if (rstate != in->rstate || !test_bit(IVSHM_NET_FLAG_RUN, &in->flags))
 		queue_work(in->state_wq, &in->state_work);
-		return false;
-	}
-
-	return true;
 }
 
 static irqreturn_t ivshm_net_int(int irq, void *data)
@@ -617,24 +628,15 @@ static int ivshm_net_open(struct net_dev
 
 	netdev_reset_queue(ndev);
 	ndev->operstate = IF_OPER_UP;
-
-	if (in->lstate == IVSHM_NET_STATE_READY)
-		ivshm_net_run(ndev);
+	ivshm_net_run(ndev);
 
 	return 0;
 }
 
 static int ivshm_net_stop(struct net_device *ndev)
 {
-	struct ivshm_net *in = netdev_priv(ndev);
-
 	ndev->operstate = IF_OPER_DOWN;
-
-	if (in->lstate == IVSHM_NET_STATE_RUN) {
-		napi_disable(&in->napi);
-		netif_stop_queue(ndev);
-		ivshm_net_set_state(in, IVSHM_NET_STATE_READY);
-	}
+	ivshm_net_do_stop(ndev);
 
 	return 0;
 }