diff options
author | Johannes Berg <johannes@sipsolutions.net> | 2009-07-07 03:56:11 +0200 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2009-07-10 15:02:32 -0400 |
commit | 667503ddcb96f3b10211f997fe55907fa7509841 (patch) | |
tree | 5e2559e94a716bb81bfc7566e3e3a05267810c31 /net/wireless/core.c | |
parent | 4f5dadcebb55fccef34722bbbf6401d39124c8a4 (diff) |
cfg80211: fix locking
Over time, a lot of locking issues have crept into
the smarts of cfg80211, so e.g. scan completion can
race against a new scan, IBSS join can race against
leaving an IBSS, etc.
Introduce a new per-interface lock that protects
most of the per-interface data that we need to keep
track of, and sprinkle assertions about that lock
everywhere. Some things now need to be offloaded to
work structs so that we don't require being able to
sleep in functions the drivers call. The exception
to that are the MLME callbacks (rx_auth etc.) that
currently only mac80211 calls because it was easier
to do that there instead of in cfg80211, and future
drivers implementing those calls will, if they ever
exist, probably need to use a similar scheme like
mac80211 anyway...
In order to be able to handle _deauth and _disassoc
properly, introduce a cookie passed to it that will
determine locking requirements.
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'net/wireless/core.c')
-rw-r--r-- | net/wireless/core.c | 92 |
1 files changed, 86 insertions, 6 deletions
diff --git a/net/wireless/core.c b/net/wireless/core.c index c6813beded0..9c73769440a 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -257,6 +257,71 @@ static void cfg80211_rfkill_sync_work(struct work_struct *work) cfg80211_rfkill_set_block(drv, rfkill_blocked(drv->rfkill)); } +static void cfg80211_process_events(struct wireless_dev *wdev) +{ + struct cfg80211_event *ev; + unsigned long flags; + + spin_lock_irqsave(&wdev->event_lock, flags); + while (!list_empty(&wdev->event_list)) { + ev = list_first_entry(&wdev->event_list, + struct cfg80211_event, list); + list_del(&ev->list); + spin_unlock_irqrestore(&wdev->event_lock, flags); + + wdev_lock(wdev); + switch (ev->type) { + case EVENT_CONNECT_RESULT: + __cfg80211_connect_result( + wdev->netdev, ev->cr.bssid, + ev->cr.req_ie, ev->cr.req_ie_len, + ev->cr.resp_ie, ev->cr.resp_ie_len, + ev->cr.status, + ev->cr.status == WLAN_STATUS_SUCCESS); + break; + case EVENT_ROAMED: + __cfg80211_roamed(wdev, ev->rm.bssid, + ev->rm.req_ie, ev->rm.req_ie_len, + ev->rm.resp_ie, ev->rm.resp_ie_len); + break; + case EVENT_DISCONNECTED: + __cfg80211_disconnected(wdev->netdev, + ev->dc.ie, ev->dc.ie_len, + ev->dc.reason, true); + break; + case EVENT_IBSS_JOINED: + __cfg80211_ibss_joined(wdev->netdev, ev->ij.bssid); + break; + } + wdev_unlock(wdev); + + kfree(ev); + + spin_lock_irqsave(&wdev->event_lock, flags); + } + spin_unlock_irqrestore(&wdev->event_lock, flags); +} + +static void cfg80211_event_work(struct work_struct *work) +{ + struct cfg80211_registered_device *rdev; + struct wireless_dev *wdev; + + rdev = container_of(work, struct cfg80211_registered_device, + event_work); + + rtnl_lock(); + cfg80211_lock_rdev(rdev); + mutex_lock(&rdev->devlist_mtx); + + list_for_each_entry(wdev, &rdev->netdev_list, list) + cfg80211_process_events(wdev); + + mutex_unlock(&rdev->devlist_mtx); + cfg80211_unlock_rdev(rdev); + rtnl_unlock(); +} + /* exported functions */ struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv) @@ -299,6 +364,7 @@ struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv) INIT_LIST_HEAD(&drv->netdev_list); spin_lock_init(&drv->bss_lock); INIT_LIST_HEAD(&drv->bss_list); + INIT_WORK(&drv->scan_done_wk, __cfg80211_scan_done); device_initialize(&drv->wiphy.dev); drv->wiphy.dev.class = &ieee80211_class; @@ -316,6 +382,7 @@ struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv) INIT_WORK(&drv->rfkill_sync, cfg80211_rfkill_sync_work); INIT_WORK(&drv->conn_work, cfg80211_conn_work); + INIT_WORK(&drv->event_work, cfg80211_event_work); /* * Initialize wiphy parameters to IEEE 802.11 MIB default values. @@ -477,6 +544,9 @@ void wiphy_unregister(struct wiphy *wiphy) mutex_unlock(&drv->mtx); cancel_work_sync(&drv->conn_work); + cancel_work_sync(&drv->scan_done_wk); + kfree(drv->scan_req); + flush_work(&drv->event_work); cfg80211_debugfs_drv_del(drv); @@ -535,6 +605,9 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, switch (state) { case NETDEV_REGISTER: + mutex_init(&wdev->mtx); + INIT_LIST_HEAD(&wdev->event_list); + spin_lock_init(&wdev->event_lock); mutex_lock(&rdev->devlist_mtx); list_add(&wdev->list, &rdev->netdev_list); if (sysfs_create_link(&dev->dev.kobj, &rdev->wiphy.dev.kobj, @@ -566,15 +639,17 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, cfg80211_leave_ibss(rdev, dev, true); break; case NL80211_IFTYPE_STATION: + wdev_lock(wdev); #ifdef CONFIG_WIRELESS_EXT kfree(wdev->wext.ie); wdev->wext.ie = NULL; wdev->wext.ie_len = 0; wdev->wext.connect.auth_type = NL80211_AUTHTYPE_AUTOMATIC; #endif - cfg80211_disconnect(rdev, dev, - WLAN_REASON_DEAUTH_LEAVING, true); + __cfg80211_disconnect(rdev, dev, + WLAN_REASON_DEAUTH_LEAVING, true); cfg80211_mlme_down(rdev, dev); + wdev_unlock(wdev); break; default: break; @@ -582,20 +657,24 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, break; case NETDEV_UP: #ifdef CONFIG_WIRELESS_EXT + cfg80211_lock_rdev(rdev); + wdev_lock(wdev); switch (wdev->iftype) { case NL80211_IFTYPE_ADHOC: if (wdev->wext.ibss.ssid_len) - cfg80211_join_ibss(rdev, dev, - &wdev->wext.ibss); + __cfg80211_join_ibss(rdev, dev, + &wdev->wext.ibss); break; case NL80211_IFTYPE_STATION: if (wdev->wext.connect.ssid_len) - cfg80211_connect(rdev, dev, - &wdev->wext.connect); + __cfg80211_connect(rdev, dev, + &wdev->wext.connect); break; default: break; } + wdev_unlock(wdev); + cfg80211_unlock_rdev(rdev); #endif break; case NETDEV_UNREGISTER: @@ -605,6 +684,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, list_del_init(&wdev->list); } mutex_unlock(&rdev->devlist_mtx); + mutex_destroy(&wdev->mtx); break; case NETDEV_PRE_UP: if (rfkill_blocked(rdev->rfkill)) |