[PATCH 11/13] staging: most: net: fix race between create/destroy device

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Andrey Shvetsov <andrey.shvetsov@xxxxxx>

This introduces the kref for the net_dev_context to prevent the
destruction of the network devices that are in use.

Each get_net_dev_context is completed with the put_net_dev_context,
except the function aim_probe_channel that calls one more
get_net_dev_context or kref_get and the function aim_disconnect_channel
that calls one more put_net_dev_context.

Signed-off-by: Andrey Shvetsov <andrey.shvetsov@xxxxxx>
Signed-off-by: Christian Gromm <christian.gromm@xxxxxxxxxxxxx>
---
 drivers/staging/most/aim-network/networking.c | 92 +++++++++++++++++++++------
 1 file changed, 72 insertions(+), 20 deletions(-)

diff --git a/drivers/staging/most/aim-network/networking.c b/drivers/staging/most/aim-network/networking.c
index cbd9500..8cf1c81 100644
--- a/drivers/staging/most/aim-network/networking.c
+++ b/drivers/staging/most/aim-network/networking.c
@@ -21,6 +21,7 @@
 #include <linux/list.h>
 #include <linux/wait.h>
 #include <linux/kobject.h>
+#include <linux/kref.h>
 #include "mostcore.h"
 
 #define MEP_HDR_LEN 8
@@ -69,6 +70,7 @@ struct net_dev_context {
 	struct net_dev_channel rx;
 	struct net_dev_channel tx;
 	struct list_head list;
+	struct kref kref;
 };
 
 static struct list_head net_devices = LIST_HEAD_INIT(net_devices);
@@ -268,6 +270,26 @@ static void most_nd_setup(struct net_device *dev)
 	dev->netdev_ops = &most_nd_ops;
 }
 
+static void release_nd(struct kref *kref)
+{
+	struct net_dev_context *nd;
+
+	nd = container_of(kref, struct net_dev_context, kref);
+	list_del(&nd->list);
+}
+
+static inline void put_net_dev_context(struct net_dev_context *nd)
+{
+	unsigned long flags;
+	int released;
+
+	spin_lock_irqsave(&list_lock, flags);
+	released = kref_put(&nd->kref, release_nd);
+	spin_unlock_irqrestore(&list_lock, flags);
+	if (released)
+		free_netdev(nd->dev);
+}
+
 static struct net_dev_context *get_net_dev_context(
 	struct most_interface *iface)
 {
@@ -277,6 +299,7 @@ static struct net_dev_context *get_net_dev_context(
 	spin_lock_irqsave(&list_lock, flags);
 	list_for_each_entry(nd, &net_devices, list) {
 		if (nd->iface == iface) {
+			kref_get(&nd->kref);
 			spin_unlock_irqrestore(&list_lock, flags);
 			return nd;
 		}
@@ -300,7 +323,6 @@ static int aim_probe_channel(struct most_interface *iface, int channel_idx,
 		return -EINVAL;
 
 	nd = get_net_dev_context(iface);
-
 	if (!nd) {
 		struct net_device *dev;
 
@@ -309,19 +331,34 @@ static int aim_probe_channel(struct most_interface *iface, int channel_idx,
 		if (!dev)
 			return -ENOMEM;
 
+		/*
+		 * The network device for the given iface may be added with use
+		 * of the other channel just after the get_net_dev_context
+		 * call.  Free our duplicate of net_device in this case.
+		 */
+		spin_lock_irqsave(&list_lock, flags);
+		list_for_each_entry(nd, &net_devices, list) {
+			if (nd->iface == iface) {
+				kref_get(&nd->kref);
+				spin_unlock_irqrestore(&list_lock, flags);
+				free_netdev(dev);
+				goto ok;
+			}
+		}
+
 		nd = netdev_priv(dev);
+		kref_init(&nd->kref);
 		nd->iface = iface;
 		nd->dev = dev;
-
-		spin_lock_irqsave(&list_lock, flags);
 		list_add(&nd->list, &net_devices);
 		spin_unlock_irqrestore(&list_lock, flags);
 	}
 
+ok:
 	ch = ccfg->direction == MOST_CH_TX ? &nd->tx : &nd->rx;
 	if (ch->linked) {
 		pr_err("only one channel per instance & direction allowed\n");
-		return -EINVAL;
+		goto err;
 	}
 
 	ch->ch_id = channel_idx;
@@ -329,10 +366,14 @@ static int aim_probe_channel(struct most_interface *iface, int channel_idx,
 	if (nd->tx.linked && nd->rx.linked && register_netdev(nd->dev)) {
 		pr_err("register_netdev() failed\n");
 		ch->linked = false;
-		return -EINVAL;
+		goto err;
 	}
 
 	return 0;
+
+err:
+	put_net_dev_context(nd);
+	return -EINVAL;
 }
 
 static int aim_disconnect_channel(struct most_interface *iface,
@@ -340,7 +381,7 @@ static int aim_disconnect_channel(struct most_interface *iface,
 {
 	struct net_dev_context *nd;
 	struct net_dev_channel *ch;
-	unsigned long flags;
+	int ret = -EINVAL;
 
 	nd = get_net_dev_context(iface);
 	if (!nd)
@@ -351,7 +392,7 @@ static int aim_disconnect_channel(struct most_interface *iface,
 	else if (nd->tx.linked && channel_idx == nd->tx.ch_id)
 		ch = &nd->tx;
 	else
-		return -EINVAL;
+		goto put_nd;
 
 	/*
 	 * do not call most_stop_channel() here, because channels are
@@ -361,14 +402,12 @@ static int aim_disconnect_channel(struct most_interface *iface,
 		unregister_netdev(nd->dev);
 
 	ch->linked = false;
-	if (!nd->rx.linked && !nd->tx.linked) {
-		spin_lock_irqsave(&list_lock, flags);
-		list_del(&nd->list);
-		spin_unlock_irqrestore(&list_lock, flags);
-		free_netdev(nd->dev);
-	}
+	put_net_dev_context(nd);
+	ret = 0;
 
-	return 0;
+put_nd:
+	put_net_dev_context(nd);
+	return ret;
 }
 
 static int aim_resume_tx_channel(struct most_interface *iface,
@@ -377,10 +416,13 @@ static int aim_resume_tx_channel(struct most_interface *iface,
 	struct net_dev_context *nd;
 
 	nd = get_net_dev_context(iface);
-	if (!nd || nd->tx.ch_id != channel_idx)
+	if (!nd)
 		return 0;
 
-	netif_wake_queue(nd->dev);
+	if (nd->tx.ch_id == channel_idx)
+		netif_wake_queue(nd->dev);
+
+	put_net_dev_context(nd);
 	return 0;
 }
 
@@ -393,25 +435,30 @@ static int aim_rx_data(struct mbo *mbo)
 	struct sk_buff *skb;
 	struct net_device *dev;
 	unsigned int skb_len;
+	int ret = -EIO;
 
 	nd = get_net_dev_context(mbo->ifp);
-	if (!nd || nd->rx.ch_id != mbo->hdm_channel_id)
+	if (!nd)
 		return -EIO;
 
+	if (nd->rx.ch_id != mbo->hdm_channel_id)
+		goto put_nd;
+
 	dev = nd->dev;
 
 	if (nd->is_mamac) {
 		if (!PMS_IS_MAMAC(buf, len))
-			return -EIO;
+			goto put_nd;
 
 		skb = dev_alloc_skb(len - MDP_HDR_LEN + 2 * ETH_ALEN + 2);
 	} else {
 		if (!PMS_IS_MEP(buf, len))
-			return -EIO;
+			goto put_nd;
 
 		skb = dev_alloc_skb(len - MEP_HDR_LEN);
 	}
 
+	ret = 0;
 	if (!skb) {
 		dev->stats.rx_dropped++;
 		pr_err_once("drop packet: no memory for skb\n");
@@ -450,7 +497,10 @@ static int aim_rx_data(struct mbo *mbo)
 
 out:
 	most_put_mbo(mbo);
-	return 0;
+
+put_nd:
+	put_net_dev_context(nd);
+	return ret;
 }
 
 static struct most_aim aim = {
@@ -509,6 +559,8 @@ static void on_netinfo(struct most_interface *iface,
 				    m[0], m[1], m[2], m[3], m[4], m[5]);
 		}
 	}
+
+	put_net_dev_context(nd);
 }
 
 module_init(most_net_init);
-- 
1.9.1

_______________________________________________
devel mailing list
devel@xxxxxxxxxxxxxxxxxxxxxx
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel



[Index of Archives]     [Linux Driver Backports]     [DMA Engine]     [Linux GPIO]     [Linux SPI]     [Video for Linux]     [Linux USB Devel]     [Linux Coverity]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux