diff options
author | Juuso Oikarinen <juuso.oikarinen@nokia.com> | 2010-06-09 06:43:26 -0400 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2010-06-14 15:42:31 -0400 |
commit | 685429623f88d84f98bd5daffc3c427c408740d4 (patch) | |
tree | d50f119094dd3fb7668f837dbe50857d11bb4fa3 /net | |
parent | 5ea096c0c85e80335889539899af9a4717976e0b (diff) |
mac80211: Fix circular locking dependency in ARP filter handling
There is a circular locking dependency when configuring the
hardware ARP filters on association, occurring when flushing the mac80211
workqueue. This is what happens:
[ 92.026800] =======================================================
[ 92.030507] [ INFO: possible circular locking dependency detected ]
[ 92.030507] 2.6.34-04781-g2b2c009 #85
[ 92.030507] -------------------------------------------------------
[ 92.030507] modprobe/5225 is trying to acquire lock:
[ 92.030507] ((wiphy_name(local->hw.wiphy))){+.+.+.}, at: [<ffffffff8105b5c0>] flush_workq
ueue+0x0/0xb0
[ 92.030507]
[ 92.030507] but task is already holding lock:
[ 92.030507] (rtnl_mutex){+.+.+.}, at: [<ffffffff812b9ce2>] rtnl_lock+0x12/0x20
[ 92.030507]
[ 92.030507] which lock already depends on the new lock.
[ 92.030507]
[ 92.030507]
[ 92.030507] the existing dependency chain (in reverse order) is:
[ 92.030507]
[ 92.030507] -> #2 (rtnl_mutex){+.+.+.}:
[ 92.030507] [<ffffffff810761fb>] lock_acquire+0xdb/0x110
[ 92.030507] [<ffffffff81341754>] mutex_lock_nested+0x44/0x300
[ 92.030507] [<ffffffff812b9ce2>] rtnl_lock+0x12/0x20
[ 92.030507] [<ffffffffa022d47c>] ieee80211_assoc_done+0x6c/0xe0 [mac80211]
[ 92.030507] [<ffffffffa022f2ad>] ieee80211_work_work+0x31d/0x1280 [mac80211]
[ 92.030507] -> #1 ((&local->work_work)){+.+.+.}:
[ 92.030507] [<ffffffff810761fb>] lock_acquire+0xdb/0x110
[ 92.030507] [<ffffffff8105a51a>] worker_thread+0x22a/0x370
[ 92.030507] [<ffffffff8105ecc6>] kthread+0x96/0xb0
[ 92.030507] [<ffffffff81003a94>] kernel_thread_helper+0x4/0x10
[ 92.030507]
[ 92.030507] -> #0 ((wiphy_name(local->hw.wiphy))){+.+.+.}:
[ 92.030507] [<ffffffff81075fdc>] __lock_acquire+0x1c0c/0x1d50
[ 92.030507] [<ffffffff810761fb>] lock_acquire+0xdb/0x110
[ 92.030507] [<ffffffff8105b60e>] flush_workqueue+0x4e/0xb0
[ 92.030507] [<ffffffffa023ff7b>] ieee80211_stop_device+0x2b/0xb0 [mac80211]
[ 92.030507] [<ffffffffa0231635>] ieee80211_stop+0x3e5/0x680 [mac80211]
The locking in this case is quite complex. Fix the problem by rewriting the
way the hardware ARP filter list is handled - i.e. make a copy of the address
list to the bss_conf struct, and provide that list to the hardware driver
when needed.
The current patch will enable filtering also in promiscuous mode. This may need
to be changed in the future.
Reported-by: Reinette Chatre <reinette.chatre@intel.com>
Signed-off-by: Juuso Oikarinen <juuso.oikarinen@nokia.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'net')
-rw-r--r-- | net/mac80211/driver-ops.h | 17 | ||||
-rw-r--r-- | net/mac80211/driver-trace.h | 22 | ||||
-rw-r--r-- | net/mac80211/ieee80211_i.h | 2 | ||||
-rw-r--r-- | net/mac80211/iface.c | 3 | ||||
-rw-r--r-- | net/mac80211/main.c | 54 | ||||
-rw-r--r-- | net/mac80211/mlme.c | 34 |
6 files changed, 58 insertions, 74 deletions
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h index 965d64f68567..c33317320eee 100644 --- a/net/mac80211/driver-ops.h +++ b/net/mac80211/driver-ops.h | |||
@@ -89,23 +89,6 @@ static inline void drv_bss_info_changed(struct ieee80211_local *local, | |||
89 | trace_drv_return_void(local); | 89 | trace_drv_return_void(local); |
90 | } | 90 | } |
91 | 91 | ||
92 | struct in_ifaddr; | ||
93 | static inline int drv_configure_arp_filter(struct ieee80211_local *local, | ||
94 | struct ieee80211_vif *vif, | ||
95 | struct in_ifaddr *ifa_list) | ||
96 | { | ||
97 | int ret = 0; | ||
98 | |||
99 | might_sleep(); | ||
100 | |||
101 | trace_drv_configure_arp_filter(local, vif_to_sdata(vif)); | ||
102 | if (local->ops->configure_arp_filter) | ||
103 | ret = local->ops->configure_arp_filter(&local->hw, vif, | ||
104 | ifa_list); | ||
105 | trace_drv_return_int(local, ret); | ||
106 | return ret; | ||
107 | } | ||
108 | |||
109 | static inline u64 drv_prepare_multicast(struct ieee80211_local *local, | 92 | static inline u64 drv_prepare_multicast(struct ieee80211_local *local, |
110 | struct netdev_hw_addr_list *mc_list) | 93 | struct netdev_hw_addr_list *mc_list) |
111 | { | 94 | { |
diff --git a/net/mac80211/driver-trace.h b/net/mac80211/driver-trace.h index 06444ea67bc4..8da31caff931 100644 --- a/net/mac80211/driver-trace.h +++ b/net/mac80211/driver-trace.h | |||
@@ -251,28 +251,6 @@ TRACE_EVENT(drv_bss_info_changed, | |||
251 | ) | 251 | ) |
252 | ); | 252 | ); |
253 | 253 | ||
254 | TRACE_EVENT(drv_configure_arp_filter, | ||
255 | TP_PROTO(struct ieee80211_local *local, | ||
256 | struct ieee80211_sub_if_data *sdata), | ||
257 | |||
258 | TP_ARGS(local, sdata), | ||
259 | |||
260 | TP_STRUCT__entry( | ||
261 | LOCAL_ENTRY | ||
262 | VIF_ENTRY | ||
263 | ), | ||
264 | |||
265 | TP_fast_assign( | ||
266 | LOCAL_ASSIGN; | ||
267 | VIF_ASSIGN; | ||
268 | ), | ||
269 | |||
270 | TP_printk( | ||
271 | VIF_PR_FMT LOCAL_PR_FMT, | ||
272 | VIF_PR_ARG, LOCAL_PR_ARG | ||
273 | ) | ||
274 | ); | ||
275 | |||
276 | TRACE_EVENT(drv_prepare_multicast, | 254 | TRACE_EVENT(drv_prepare_multicast, |
277 | TP_PROTO(struct ieee80211_local *local, int mc_count), | 255 | TP_PROTO(struct ieee80211_local *local, int mc_count), |
278 | 256 | ||
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index c3c2be3f8a2c..9b3c3f971d28 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h | |||
@@ -514,6 +514,8 @@ struct ieee80211_sub_if_data { | |||
514 | struct work_struct work; | 514 | struct work_struct work; |
515 | struct sk_buff_head skb_queue; | 515 | struct sk_buff_head skb_queue; |
516 | 516 | ||
517 | bool arp_filter_state; | ||
518 | |||
517 | /* | 519 | /* |
518 | * AP this belongs to: self in AP mode and | 520 | * AP this belongs to: self in AP mode and |
519 | * corresponding AP in VLAN mode, NULL for | 521 | * corresponding AP in VLAN mode, NULL for |
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 490be2f3af27..910729fc18cd 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c | |||
@@ -1076,6 +1076,9 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name, | |||
1076 | sdata->wdev.wiphy = local->hw.wiphy; | 1076 | sdata->wdev.wiphy = local->hw.wiphy; |
1077 | sdata->local = local; | 1077 | sdata->local = local; |
1078 | sdata->dev = ndev; | 1078 | sdata->dev = ndev; |
1079 | #ifdef CONFIG_INET | ||
1080 | sdata->arp_filter_state = true; | ||
1081 | #endif | ||
1079 | 1082 | ||
1080 | for (i = 0; i < IEEE80211_FRAGMENT_MAX; i++) | 1083 | for (i = 0; i < IEEE80211_FRAGMENT_MAX; i++) |
1081 | skb_queue_head_init(&sdata->fragments[i].skb_list); | 1084 | skb_queue_head_init(&sdata->fragments[i].skb_list); |
diff --git a/net/mac80211/main.c b/net/mac80211/main.c index c2e46e88f3c9..a1bf46c64b93 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c | |||
@@ -20,6 +20,7 @@ | |||
20 | #include <linux/rtnetlink.h> | 20 | #include <linux/rtnetlink.h> |
21 | #include <linux/bitmap.h> | 21 | #include <linux/bitmap.h> |
22 | #include <linux/pm_qos_params.h> | 22 | #include <linux/pm_qos_params.h> |
23 | #include <linux/inetdevice.h> | ||
23 | #include <net/net_namespace.h> | 24 | #include <net/net_namespace.h> |
24 | #include <net/cfg80211.h> | 25 | #include <net/cfg80211.h> |
25 | 26 | ||
@@ -317,23 +318,6 @@ static void ieee80211_recalc_smps_work(struct work_struct *work) | |||
317 | } | 318 | } |
318 | 319 | ||
319 | #ifdef CONFIG_INET | 320 | #ifdef CONFIG_INET |
320 | int ieee80211_set_arp_filter(struct ieee80211_sub_if_data *sdata) | ||
321 | { | ||
322 | struct in_device *idev; | ||
323 | int ret = 0; | ||
324 | |||
325 | BUG_ON(!sdata); | ||
326 | ASSERT_RTNL(); | ||
327 | |||
328 | idev = sdata->dev->ip_ptr; | ||
329 | if (!idev) | ||
330 | return 0; | ||
331 | |||
332 | ret = drv_configure_arp_filter(sdata->local, &sdata->vif, | ||
333 | idev->ifa_list); | ||
334 | return ret; | ||
335 | } | ||
336 | |||
337 | static int ieee80211_ifa_changed(struct notifier_block *nb, | 321 | static int ieee80211_ifa_changed(struct notifier_block *nb, |
338 | unsigned long data, void *arg) | 322 | unsigned long data, void *arg) |
339 | { | 323 | { |
@@ -343,8 +327,11 @@ static int ieee80211_ifa_changed(struct notifier_block *nb, | |||
343 | ifa_notifier); | 327 | ifa_notifier); |
344 | struct net_device *ndev = ifa->ifa_dev->dev; | 328 | struct net_device *ndev = ifa->ifa_dev->dev; |
345 | struct wireless_dev *wdev = ndev->ieee80211_ptr; | 329 | struct wireless_dev *wdev = ndev->ieee80211_ptr; |
330 | struct in_device *idev; | ||
346 | struct ieee80211_sub_if_data *sdata; | 331 | struct ieee80211_sub_if_data *sdata; |
332 | struct ieee80211_bss_conf *bss_conf; | ||
347 | struct ieee80211_if_managed *ifmgd; | 333 | struct ieee80211_if_managed *ifmgd; |
334 | int c = 0; | ||
348 | 335 | ||
349 | if (!netif_running(ndev)) | 336 | if (!netif_running(ndev)) |
350 | return NOTIFY_DONE; | 337 | return NOTIFY_DONE; |
@@ -356,17 +343,44 @@ static int ieee80211_ifa_changed(struct notifier_block *nb, | |||
356 | if (wdev->wiphy != local->hw.wiphy) | 343 | if (wdev->wiphy != local->hw.wiphy) |
357 | return NOTIFY_DONE; | 344 | return NOTIFY_DONE; |
358 | 345 | ||
359 | /* We are concerned about IP addresses only when associated */ | ||
360 | sdata = IEEE80211_DEV_TO_SUB_IF(ndev); | 346 | sdata = IEEE80211_DEV_TO_SUB_IF(ndev); |
347 | bss_conf = &sdata->vif.bss_conf; | ||
361 | 348 | ||
362 | /* ARP filtering is only supported in managed mode */ | 349 | /* ARP filtering is only supported in managed mode */ |
363 | if (sdata->vif.type != NL80211_IFTYPE_STATION) | 350 | if (sdata->vif.type != NL80211_IFTYPE_STATION) |
364 | return NOTIFY_DONE; | 351 | return NOTIFY_DONE; |
365 | 352 | ||
353 | idev = sdata->dev->ip_ptr; | ||
354 | if (!idev) | ||
355 | return NOTIFY_DONE; | ||
356 | |||
366 | ifmgd = &sdata->u.mgd; | 357 | ifmgd = &sdata->u.mgd; |
367 | mutex_lock(&ifmgd->mtx); | 358 | mutex_lock(&ifmgd->mtx); |
368 | if (ifmgd->associated) | 359 | |
369 | ieee80211_set_arp_filter(sdata); | 360 | /* Copy the addresses to the bss_conf list */ |
361 | ifa = idev->ifa_list; | ||
362 | while (c < IEEE80211_BSS_ARP_ADDR_LIST_LEN && ifa) { | ||
363 | bss_conf->arp_addr_list[c] = ifa->ifa_address; | ||
364 | ifa = ifa->ifa_next; | ||
365 | c++; | ||
366 | } | ||
367 | |||
368 | /* If not all addresses fit the list, disable filtering */ | ||
369 | if (ifa) { | ||
370 | sdata->arp_filter_state = false; | ||
371 | c = 0; | ||
372 | } else { | ||
373 | sdata->arp_filter_state = true; | ||
374 | } | ||
375 | bss_conf->arp_addr_cnt = c; | ||
376 | |||
377 | /* Configure driver only if associated */ | ||
378 | if (ifmgd->associated) { | ||
379 | bss_conf->arp_filter_enabled = sdata->arp_filter_state; | ||
380 | ieee80211_bss_info_change_notify(sdata, | ||
381 | BSS_CHANGED_ARP_FILTER); | ||
382 | } | ||
383 | |||
370 | mutex_unlock(&ifmgd->mtx); | 384 | mutex_unlock(&ifmgd->mtx); |
371 | 385 | ||
372 | return NOTIFY_DONE; | 386 | return NOTIFY_DONE; |
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 583b34686a26..74479c2d12d4 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c | |||
@@ -806,11 +806,12 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata, | |||
806 | { | 806 | { |
807 | struct ieee80211_bss *bss = (void *)cbss->priv; | 807 | struct ieee80211_bss *bss = (void *)cbss->priv; |
808 | struct ieee80211_local *local = sdata->local; | 808 | struct ieee80211_local *local = sdata->local; |
809 | struct ieee80211_bss_conf *bss_conf = &sdata->vif.bss_conf; | ||
809 | 810 | ||
810 | bss_info_changed |= BSS_CHANGED_ASSOC; | 811 | bss_info_changed |= BSS_CHANGED_ASSOC; |
811 | /* set timing information */ | 812 | /* set timing information */ |
812 | sdata->vif.bss_conf.beacon_int = cbss->beacon_interval; | 813 | bss_conf->beacon_int = cbss->beacon_interval; |
813 | sdata->vif.bss_conf.timestamp = cbss->tsf; | 814 | bss_conf->timestamp = cbss->tsf; |
814 | 815 | ||
815 | bss_info_changed |= BSS_CHANGED_BEACON_INT; | 816 | bss_info_changed |= BSS_CHANGED_BEACON_INT; |
816 | bss_info_changed |= ieee80211_handle_bss_capability(sdata, | 817 | bss_info_changed |= ieee80211_handle_bss_capability(sdata, |
@@ -835,7 +836,7 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata, | |||
835 | 836 | ||
836 | ieee80211_led_assoc(local, 1); | 837 | ieee80211_led_assoc(local, 1); |
837 | 838 | ||
838 | sdata->vif.bss_conf.assoc = 1; | 839 | bss_conf->assoc = 1; |
839 | /* | 840 | /* |
840 | * For now just always ask the driver to update the basic rateset | 841 | * For now just always ask the driver to update the basic rateset |
841 | * when we have associated, we aren't checking whether it actually | 842 | * when we have associated, we aren't checking whether it actually |
@@ -848,9 +849,15 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata, | |||
848 | 849 | ||
849 | /* Tell the driver to monitor connection quality (if supported) */ | 850 | /* Tell the driver to monitor connection quality (if supported) */ |
850 | if ((local->hw.flags & IEEE80211_HW_SUPPORTS_CQM_RSSI) && | 851 | if ((local->hw.flags & IEEE80211_HW_SUPPORTS_CQM_RSSI) && |
851 | sdata->vif.bss_conf.cqm_rssi_thold) | 852 | bss_conf->cqm_rssi_thold) |
852 | bss_info_changed |= BSS_CHANGED_CQM; | 853 | bss_info_changed |= BSS_CHANGED_CQM; |
853 | 854 | ||
855 | /* Enable ARP filtering */ | ||
856 | if (bss_conf->arp_filter_enabled != sdata->arp_filter_state) { | ||
857 | bss_conf->arp_filter_enabled = sdata->arp_filter_state; | ||
858 | bss_info_changed |= BSS_CHANGED_ARP_FILTER; | ||
859 | } | ||
860 | |||
854 | ieee80211_bss_info_change_notify(sdata, bss_info_changed); | 861 | ieee80211_bss_info_change_notify(sdata, bss_info_changed); |
855 | 862 | ||
856 | mutex_lock(&local->iflist_mtx); | 863 | mutex_lock(&local->iflist_mtx); |
@@ -932,6 +939,12 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata, | |||
932 | 939 | ||
933 | ieee80211_hw_config(local, config_changed); | 940 | ieee80211_hw_config(local, config_changed); |
934 | 941 | ||
942 | /* Disable ARP filtering */ | ||
943 | if (sdata->vif.bss_conf.arp_filter_enabled) { | ||
944 | sdata->vif.bss_conf.arp_filter_enabled = false; | ||
945 | changed |= BSS_CHANGED_ARP_FILTER; | ||
946 | } | ||
947 | |||
935 | /* The BSSID (not really interesting) and HT changed */ | 948 | /* The BSSID (not really interesting) and HT changed */ |
936 | changed |= BSS_CHANGED_BSSID | BSS_CHANGED_HT; | 949 | changed |= BSS_CHANGED_BSSID | BSS_CHANGED_HT; |
937 | ieee80211_bss_info_change_notify(sdata, changed); | 950 | ieee80211_bss_info_change_notify(sdata, changed); |
@@ -2018,18 +2031,9 @@ static enum work_done_result ieee80211_assoc_done(struct ieee80211_work *wk, | |||
2018 | cfg80211_send_assoc_timeout(wk->sdata->dev, | 2031 | cfg80211_send_assoc_timeout(wk->sdata->dev, |
2019 | wk->filter_ta); | 2032 | wk->filter_ta); |
2020 | return WORK_DONE_DESTROY; | 2033 | return WORK_DONE_DESTROY; |
2021 | } else { | ||
2022 | mutex_unlock(&wk->sdata->u.mgd.mtx); | ||
2023 | #ifdef CONFIG_INET | ||
2024 | /* | ||
2025 | * configure ARP filter IP addresses to the driver, | ||
2026 | * intentionally outside the mgd mutex. | ||
2027 | */ | ||
2028 | rtnl_lock(); | ||
2029 | ieee80211_set_arp_filter(wk->sdata); | ||
2030 | rtnl_unlock(); | ||
2031 | #endif | ||
2032 | } | 2034 | } |
2035 | |||
2036 | mutex_unlock(&wk->sdata->u.mgd.mtx); | ||
2033 | } | 2037 | } |
2034 | 2038 | ||
2035 | cfg80211_send_rx_assoc(wk->sdata->dev, skb->data, skb->len); | 2039 | cfg80211_send_rx_assoc(wk->sdata->dev, skb->data, skb->len); |