aboutsummaryrefslogtreecommitdiffstats
path: root/net/core
diff options
context:
space:
mode:
authorNeil Horman <nhorman@tuxdriver.com>2013-02-11 05:25:30 -0500
committerDavid S. Miller <davem@davemloft.net>2013-02-11 19:19:33 -0500
commit2cde6acd49daca58b96f1fbc697492825511ad31 (patch)
tree77353244a904fbb41e8658cceedb4b5120c5552a /net/core
parentf05de73bf82fbbc00265c06d12efb7273f7dc54a (diff)
netpoll: Fix __netpoll_rcu_free so that it can hold the rtnl lock
__netpoll_rcu_free is used to free netpoll structures when the rtnl_lock is already held. The mechanism is used to asynchronously call __netpoll_cleanup outside of the holding of the rtnl_lock, so as to avoid deadlock. Unfortunately, __netpoll_cleanup modifies pointers (dev->np), which means the rtnl_lock must be held while calling it. Further, it cannot be held, because rcu callbacks may be issued in softirq contexts, which cannot sleep. Fix this by converting the rcu callback to a work queue that is guaranteed to get scheduled in process context, so that we can hold the rtnl properly while calling __netpoll_cleanup Tested successfully by myself. Signed-off-by: Neil Horman <nhorman@tuxdriver.com> CC: "David S. Miller" <davem@davemloft.net> CC: Cong Wang <amwang@redhat.com> CC: Eric Dumazet <eric.dumazet@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/core')
-rw-r--r--net/core/netpoll.c16
1 files changed, 10 insertions, 6 deletions
diff --git a/net/core/netpoll.c b/net/core/netpoll.c
index edcd9ad95304..c536474e2260 100644
--- a/net/core/netpoll.c
+++ b/net/core/netpoll.c
@@ -61,6 +61,7 @@ static struct srcu_struct netpoll_srcu;
61 61
62static void zap_completion_queue(void); 62static void zap_completion_queue(void);
63static void netpoll_neigh_reply(struct sk_buff *skb, struct netpoll_info *npinfo); 63static void netpoll_neigh_reply(struct sk_buff *skb, struct netpoll_info *npinfo);
64static void netpoll_async_cleanup(struct work_struct *work);
64 65
65static unsigned int carrier_timeout = 4; 66static unsigned int carrier_timeout = 4;
66module_param(carrier_timeout, uint, 0644); 67module_param(carrier_timeout, uint, 0644);
@@ -1020,6 +1021,7 @@ int __netpoll_setup(struct netpoll *np, struct net_device *ndev, gfp_t gfp)
1020 1021
1021 np->dev = ndev; 1022 np->dev = ndev;
1022 strlcpy(np->dev_name, ndev->name, IFNAMSIZ); 1023 strlcpy(np->dev_name, ndev->name, IFNAMSIZ);
1024 INIT_WORK(&np->cleanup_work, netpoll_async_cleanup);
1023 1025
1024 if ((ndev->priv_flags & IFF_DISABLE_NETPOLL) || 1026 if ((ndev->priv_flags & IFF_DISABLE_NETPOLL) ||
1025 !ndev->netdev_ops->ndo_poll_controller) { 1027 !ndev->netdev_ops->ndo_poll_controller) {
@@ -1255,25 +1257,27 @@ void __netpoll_cleanup(struct netpoll *np)
1255 if (ops->ndo_netpoll_cleanup) 1257 if (ops->ndo_netpoll_cleanup)
1256 ops->ndo_netpoll_cleanup(np->dev); 1258 ops->ndo_netpoll_cleanup(np->dev);
1257 1259
1258 RCU_INIT_POINTER(np->dev->npinfo, NULL); 1260 rcu_assign_pointer(np->dev->npinfo, NULL);
1259 call_rcu_bh(&npinfo->rcu, rcu_cleanup_netpoll_info); 1261 call_rcu_bh(&npinfo->rcu, rcu_cleanup_netpoll_info);
1260 } 1262 }
1261} 1263}
1262EXPORT_SYMBOL_GPL(__netpoll_cleanup); 1264EXPORT_SYMBOL_GPL(__netpoll_cleanup);
1263 1265
1264static void rcu_cleanup_netpoll(struct rcu_head *rcu_head) 1266static void netpoll_async_cleanup(struct work_struct *work)
1265{ 1267{
1266 struct netpoll *np = container_of(rcu_head, struct netpoll, rcu); 1268 struct netpoll *np = container_of(work, struct netpoll, cleanup_work);
1267 1269
1270 rtnl_lock();
1268 __netpoll_cleanup(np); 1271 __netpoll_cleanup(np);
1272 rtnl_unlock();
1269 kfree(np); 1273 kfree(np);
1270} 1274}
1271 1275
1272void __netpoll_free_rcu(struct netpoll *np) 1276void __netpoll_free_async(struct netpoll *np)
1273{ 1277{
1274 call_rcu_bh(&np->rcu, rcu_cleanup_netpoll); 1278 schedule_work(&np->cleanup_work);
1275} 1279}
1276EXPORT_SYMBOL_GPL(__netpoll_free_rcu); 1280EXPORT_SYMBOL_GPL(__netpoll_free_async);
1277 1281
1278void netpoll_cleanup(struct netpoll *np) 1282void netpoll_cleanup(struct netpoll *np)
1279{ 1283{