diff options
author | Luciano Coelho <coelho@ti.com> | 2011-06-30 01:32:41 -0400 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2011-07-05 14:42:36 -0400 |
commit | c10841ca722a0bc960dc541c51582773f9a24f98 (patch) | |
tree | f1e5901db0a530cfd4008f718a908a86bddcb333 | |
parent | 37000b305bff81bb1ee2f7f37b1319b670a08f76 (diff) |
cfg80211: fix deadlock with rfkill/sched_scan by adding new mutex
There was a deadlock when rfkill-blocking a wireless interface,
because we were locking the rdev mutex on NETDEV_GOING_DOWN to stop
sched_scans that were eventually running. The rfkill block code was
already holding a mutex under rdev:
kernel: =======================================================
kernel: [ INFO: possible circular locking dependency detected ]
kernel: 3.0.0-rc1-00049-g1fa7b6a #57
kernel: -------------------------------------------------------
kernel: kworker/0:1/4525 is trying to acquire lock:
kernel: (&rdev->mtx){+.+.+.}, at: [<ffffffff8164c831>] cfg80211_netdev_notifier_call+0x131/0x5b0
kernel:
kernel: but task is already holding lock:
kernel: (&rdev->devlist_mtx){+.+.+.}, at: [<ffffffff8164dcef>] cfg80211_rfkill_set_block+0x4f/0xa0
kernel:
kernel: which lock already depends on the new lock.
To fix this, add a new mutex specifically for sched_scan, to protect
the sched_scan_req element in the rdev struct, instead of using the
global rdev mutex.
Reported-by: Duane Griffin <duaneg@dghda.com>
Signed-off-by: Luciano Coelho <coelho@ti.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
-rw-r--r-- | net/wireless/core.c | 12 | ||||
-rw-r--r-- | net/wireless/core.h | 2 | ||||
-rw-r--r-- | net/wireless/nl80211.c | 24 | ||||
-rw-r--r-- | net/wireless/scan.c | 10 |
4 files changed, 34 insertions, 14 deletions
diff --git a/net/wireless/core.c b/net/wireless/core.c index c22ef3492ee6..880dbe2e6f94 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c | |||
@@ -366,6 +366,7 @@ struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv) | |||
366 | 366 | ||
367 | mutex_init(&rdev->mtx); | 367 | mutex_init(&rdev->mtx); |
368 | mutex_init(&rdev->devlist_mtx); | 368 | mutex_init(&rdev->devlist_mtx); |
369 | mutex_init(&rdev->sched_scan_mtx); | ||
369 | INIT_LIST_HEAD(&rdev->netdev_list); | 370 | INIT_LIST_HEAD(&rdev->netdev_list); |
370 | spin_lock_init(&rdev->bss_lock); | 371 | spin_lock_init(&rdev->bss_lock); |
371 | INIT_LIST_HEAD(&rdev->bss_list); | 372 | INIT_LIST_HEAD(&rdev->bss_list); |
@@ -701,6 +702,7 @@ void cfg80211_dev_free(struct cfg80211_registered_device *rdev) | |||
701 | rfkill_destroy(rdev->rfkill); | 702 | rfkill_destroy(rdev->rfkill); |
702 | mutex_destroy(&rdev->mtx); | 703 | mutex_destroy(&rdev->mtx); |
703 | mutex_destroy(&rdev->devlist_mtx); | 704 | mutex_destroy(&rdev->devlist_mtx); |
705 | mutex_destroy(&rdev->sched_scan_mtx); | ||
704 | list_for_each_entry_safe(scan, tmp, &rdev->bss_list, list) | 706 | list_for_each_entry_safe(scan, tmp, &rdev->bss_list, list) |
705 | cfg80211_put_bss(&scan->pub); | 707 | cfg80211_put_bss(&scan->pub); |
706 | cfg80211_rdev_free_wowlan(rdev); | 708 | cfg80211_rdev_free_wowlan(rdev); |
@@ -737,12 +739,16 @@ static void wdev_cleanup_work(struct work_struct *work) | |||
737 | ___cfg80211_scan_done(rdev, true); | 739 | ___cfg80211_scan_done(rdev, true); |
738 | } | 740 | } |
739 | 741 | ||
742 | cfg80211_unlock_rdev(rdev); | ||
743 | |||
744 | mutex_lock(&rdev->sched_scan_mtx); | ||
745 | |||
740 | if (WARN_ON(rdev->sched_scan_req && | 746 | if (WARN_ON(rdev->sched_scan_req && |
741 | rdev->sched_scan_req->dev == wdev->netdev)) { | 747 | rdev->sched_scan_req->dev == wdev->netdev)) { |
742 | __cfg80211_stop_sched_scan(rdev, false); | 748 | __cfg80211_stop_sched_scan(rdev, false); |
743 | } | 749 | } |
744 | 750 | ||
745 | cfg80211_unlock_rdev(rdev); | 751 | mutex_unlock(&rdev->sched_scan_mtx); |
746 | 752 | ||
747 | mutex_lock(&rdev->devlist_mtx); | 753 | mutex_lock(&rdev->devlist_mtx); |
748 | rdev->opencount--; | 754 | rdev->opencount--; |
@@ -830,9 +836,9 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, | |||
830 | break; | 836 | break; |
831 | case NL80211_IFTYPE_P2P_CLIENT: | 837 | case NL80211_IFTYPE_P2P_CLIENT: |
832 | case NL80211_IFTYPE_STATION: | 838 | case NL80211_IFTYPE_STATION: |
833 | cfg80211_lock_rdev(rdev); | 839 | mutex_lock(&rdev->sched_scan_mtx); |
834 | __cfg80211_stop_sched_scan(rdev, false); | 840 | __cfg80211_stop_sched_scan(rdev, false); |
835 | cfg80211_unlock_rdev(rdev); | 841 | mutex_unlock(&rdev->sched_scan_mtx); |
836 | 842 | ||
837 | wdev_lock(wdev); | 843 | wdev_lock(wdev); |
838 | #ifdef CONFIG_CFG80211_WEXT | 844 | #ifdef CONFIG_CFG80211_WEXT |
diff --git a/net/wireless/core.h b/net/wireless/core.h index 3dce1f167eba..a570ff9214ec 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h | |||
@@ -65,6 +65,8 @@ struct cfg80211_registered_device { | |||
65 | struct work_struct scan_done_wk; | 65 | struct work_struct scan_done_wk; |
66 | struct work_struct sched_scan_results_wk; | 66 | struct work_struct sched_scan_results_wk; |
67 | 67 | ||
68 | struct mutex sched_scan_mtx; | ||
69 | |||
68 | #ifdef CONFIG_NL80211_TESTMODE | 70 | #ifdef CONFIG_NL80211_TESTMODE |
69 | struct genl_info *testmode_info; | 71 | struct genl_info *testmode_info; |
70 | #endif | 72 | #endif |
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index f07602d7bf68..cea338150d05 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c | |||
@@ -3461,9 +3461,6 @@ static int nl80211_start_sched_scan(struct sk_buff *skb, | |||
3461 | if (!is_valid_ie_attr(info->attrs[NL80211_ATTR_IE])) | 3461 | if (!is_valid_ie_attr(info->attrs[NL80211_ATTR_IE])) |
3462 | return -EINVAL; | 3462 | return -EINVAL; |
3463 | 3463 | ||
3464 | if (rdev->sched_scan_req) | ||
3465 | return -EINPROGRESS; | ||
3466 | |||
3467 | if (!info->attrs[NL80211_ATTR_SCHED_SCAN_INTERVAL]) | 3464 | if (!info->attrs[NL80211_ATTR_SCHED_SCAN_INTERVAL]) |
3468 | return -EINVAL; | 3465 | return -EINVAL; |
3469 | 3466 | ||
@@ -3502,12 +3499,21 @@ static int nl80211_start_sched_scan(struct sk_buff *skb, | |||
3502 | if (ie_len > wiphy->max_scan_ie_len) | 3499 | if (ie_len > wiphy->max_scan_ie_len) |
3503 | return -EINVAL; | 3500 | return -EINVAL; |
3504 | 3501 | ||
3502 | mutex_lock(&rdev->sched_scan_mtx); | ||
3503 | |||
3504 | if (rdev->sched_scan_req) { | ||
3505 | err = -EINPROGRESS; | ||
3506 | goto out; | ||
3507 | } | ||
3508 | |||
3505 | request = kzalloc(sizeof(*request) | 3509 | request = kzalloc(sizeof(*request) |
3506 | + sizeof(*request->ssids) * n_ssids | 3510 | + sizeof(*request->ssids) * n_ssids |
3507 | + sizeof(*request->channels) * n_channels | 3511 | + sizeof(*request->channels) * n_channels |
3508 | + ie_len, GFP_KERNEL); | 3512 | + ie_len, GFP_KERNEL); |
3509 | if (!request) | 3513 | if (!request) { |
3510 | return -ENOMEM; | 3514 | err = -ENOMEM; |
3515 | goto out; | ||
3516 | } | ||
3511 | 3517 | ||
3512 | if (n_ssids) | 3518 | if (n_ssids) |
3513 | request->ssids = (void *)&request->channels[n_channels]; | 3519 | request->ssids = (void *)&request->channels[n_channels]; |
@@ -3605,6 +3611,7 @@ static int nl80211_start_sched_scan(struct sk_buff *skb, | |||
3605 | out_free: | 3611 | out_free: |
3606 | kfree(request); | 3612 | kfree(request); |
3607 | out: | 3613 | out: |
3614 | mutex_unlock(&rdev->sched_scan_mtx); | ||
3608 | return err; | 3615 | return err; |
3609 | } | 3616 | } |
3610 | 3617 | ||
@@ -3612,12 +3619,17 @@ static int nl80211_stop_sched_scan(struct sk_buff *skb, | |||
3612 | struct genl_info *info) | 3619 | struct genl_info *info) |
3613 | { | 3620 | { |
3614 | struct cfg80211_registered_device *rdev = info->user_ptr[0]; | 3621 | struct cfg80211_registered_device *rdev = info->user_ptr[0]; |
3622 | int err; | ||
3615 | 3623 | ||
3616 | if (!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) || | 3624 | if (!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) || |
3617 | !rdev->ops->sched_scan_stop) | 3625 | !rdev->ops->sched_scan_stop) |
3618 | return -EOPNOTSUPP; | 3626 | return -EOPNOTSUPP; |
3619 | 3627 | ||
3620 | return __cfg80211_stop_sched_scan(rdev, false); | 3628 | mutex_lock(&rdev->sched_scan_mtx); |
3629 | err = __cfg80211_stop_sched_scan(rdev, false); | ||
3630 | mutex_unlock(&rdev->sched_scan_mtx); | ||
3631 | |||
3632 | return err; | ||
3621 | } | 3633 | } |
3622 | 3634 | ||
3623 | static int nl80211_send_bss(struct sk_buff *msg, u32 pid, u32 seq, int flags, | 3635 | static int nl80211_send_bss(struct sk_buff *msg, u32 pid, u32 seq, int flags, |
diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 7a6c67667d70..ae0c2256ba3b 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c | |||
@@ -100,14 +100,14 @@ void __cfg80211_sched_scan_results(struct work_struct *wk) | |||
100 | rdev = container_of(wk, struct cfg80211_registered_device, | 100 | rdev = container_of(wk, struct cfg80211_registered_device, |
101 | sched_scan_results_wk); | 101 | sched_scan_results_wk); |
102 | 102 | ||
103 | cfg80211_lock_rdev(rdev); | 103 | mutex_lock(&rdev->sched_scan_mtx); |
104 | 104 | ||
105 | /* we don't have sched_scan_req anymore if the scan is stopping */ | 105 | /* we don't have sched_scan_req anymore if the scan is stopping */ |
106 | if (rdev->sched_scan_req) | 106 | if (rdev->sched_scan_req) |
107 | nl80211_send_sched_scan_results(rdev, | 107 | nl80211_send_sched_scan_results(rdev, |
108 | rdev->sched_scan_req->dev); | 108 | rdev->sched_scan_req->dev); |
109 | 109 | ||
110 | cfg80211_unlock_rdev(rdev); | 110 | mutex_unlock(&rdev->sched_scan_mtx); |
111 | } | 111 | } |
112 | 112 | ||
113 | void cfg80211_sched_scan_results(struct wiphy *wiphy) | 113 | void cfg80211_sched_scan_results(struct wiphy *wiphy) |
@@ -123,9 +123,9 @@ void cfg80211_sched_scan_stopped(struct wiphy *wiphy) | |||
123 | { | 123 | { |
124 | struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy); | 124 | struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy); |
125 | 125 | ||
126 | cfg80211_lock_rdev(rdev); | 126 | mutex_lock(&rdev->sched_scan_mtx); |
127 | __cfg80211_stop_sched_scan(rdev, true); | 127 | __cfg80211_stop_sched_scan(rdev, true); |
128 | cfg80211_unlock_rdev(rdev); | 128 | mutex_unlock(&rdev->sched_scan_mtx); |
129 | } | 129 | } |
130 | EXPORT_SYMBOL(cfg80211_sched_scan_stopped); | 130 | EXPORT_SYMBOL(cfg80211_sched_scan_stopped); |
131 | 131 | ||
@@ -135,7 +135,7 @@ int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev, | |||
135 | int err; | 135 | int err; |
136 | struct net_device *dev; | 136 | struct net_device *dev; |
137 | 137 | ||
138 | ASSERT_RDEV_LOCK(rdev); | 138 | lockdep_assert_held(&rdev->sched_scan_mtx); |
139 | 139 | ||
140 | if (!rdev->sched_scan_req) | 140 | if (!rdev->sched_scan_req) |
141 | return 0; | 141 | return 0; |