cfg80211: check for and abort dangling scan requests

If you trigger a scan request on an interface and then
take it down, or rmmod the module or unplug the device
the driver might "forget" to cancel the scan request.
That is a bug in the driver, but the current behaviour
is that we just hang endlessly waiting for the netdev
refcount to become 0 which it never will. To improve
robustness, check for this situation in cfg80211, warn
about it and clean up behind the driver. I don't just
clean up silently because it's likely that the driver
also has some internal state it has now leaked.

Additionally, this fixes a locking bug, clearing the
scan_req pointer should be done under the rdev lock.

Finally, we also need to _wait_ for the scan work and
not just abort it since it might be pending and wanting
to do a cleanup.

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
Johannes Berg 2009-08-12 22:21:21 +02:00 committed by John W. Linville
parent 70bdb6b275
commit 36e6fea849
3 changed files with 26 additions and 11 deletions

View File

@ -601,8 +601,8 @@ void wiphy_unregister(struct wiphy *wiphy)
mutex_unlock(&cfg80211_mutex); mutex_unlock(&cfg80211_mutex);
flush_work(&rdev->scan_done_wk);
cancel_work_sync(&rdev->conn_work); cancel_work_sync(&rdev->conn_work);
cancel_work_sync(&rdev->scan_done_wk);
kfree(rdev->scan_req); kfree(rdev->scan_req);
flush_work(&rdev->event_work); flush_work(&rdev->event_work);
} }
@ -728,6 +728,13 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb,
#endif #endif
break; break;
case NETDEV_UNREGISTER: case NETDEV_UNREGISTER:
cfg80211_lock_rdev(rdev);
if (WARN_ON(rdev->scan_req && rdev->scan_req->dev == dev)) {
rdev->scan_req->aborted = true;
___cfg80211_scan_done(rdev);
}
mutex_lock(&rdev->devlist_mtx); mutex_lock(&rdev->devlist_mtx);
/* /*
* It is possible to get NETDEV_UNREGISTER * It is possible to get NETDEV_UNREGISTER
@ -746,6 +753,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb,
#endif #endif
} }
mutex_unlock(&rdev->devlist_mtx); mutex_unlock(&rdev->devlist_mtx);
cfg80211_unlock_rdev(rdev);
break; break;
case NETDEV_PRE_UP: case NETDEV_PRE_UP:
if (!(wdev->wiphy->interface_modes & BIT(wdev->iftype))) if (!(wdev->wiphy->interface_modes & BIT(wdev->iftype)))

View File

@ -368,6 +368,7 @@ void cfg80211_sme_scan_done(struct net_device *dev);
void cfg80211_sme_rx_auth(struct net_device *dev, const u8 *buf, size_t len); void cfg80211_sme_rx_auth(struct net_device *dev, const u8 *buf, size_t len);
void cfg80211_sme_disassoc(struct net_device *dev, int idx); void cfg80211_sme_disassoc(struct net_device *dev, int idx);
void __cfg80211_scan_done(struct work_struct *wk); void __cfg80211_scan_done(struct work_struct *wk);
void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev);
void cfg80211_upload_connect_keys(struct wireless_dev *wdev); void cfg80211_upload_connect_keys(struct wireless_dev *wdev);
struct ieee80211_channel * struct ieee80211_channel *

View File

@ -18,19 +18,14 @@
#define IEEE80211_SCAN_RESULT_EXPIRE (15 * HZ) #define IEEE80211_SCAN_RESULT_EXPIRE (15 * HZ)
void __cfg80211_scan_done(struct work_struct *wk) void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev)
{ {
struct cfg80211_registered_device *rdev;
struct cfg80211_scan_request *request; struct cfg80211_scan_request *request;
struct net_device *dev; struct net_device *dev;
#ifdef CONFIG_WIRELESS_EXT #ifdef CONFIG_WIRELESS_EXT
union iwreq_data wrqu; union iwreq_data wrqu;
#endif #endif
rdev = container_of(wk, struct cfg80211_registered_device,
scan_done_wk);
mutex_lock(&rdev->mtx);
request = rdev->scan_req; request = rdev->scan_req;
dev = request->dev; dev = request->dev;
@ -43,9 +38,9 @@ void __cfg80211_scan_done(struct work_struct *wk)
cfg80211_sme_scan_done(dev); cfg80211_sme_scan_done(dev);
if (request->aborted) if (request->aborted)
nl80211_send_scan_aborted(wiphy_to_dev(request->wiphy), dev); nl80211_send_scan_aborted(rdev, dev);
else else
nl80211_send_scan_done(wiphy_to_dev(request->wiphy), dev); nl80211_send_scan_done(rdev, dev);
#ifdef CONFIG_WIRELESS_EXT #ifdef CONFIG_WIRELESS_EXT
if (!request->aborted) { if (!request->aborted) {
@ -57,11 +52,22 @@ void __cfg80211_scan_done(struct work_struct *wk)
dev_put(dev); dev_put(dev);
cfg80211_unlock_rdev(rdev); rdev->scan_req = NULL;
wiphy_to_dev(request->wiphy)->scan_req = NULL;
kfree(request); kfree(request);
} }
void __cfg80211_scan_done(struct work_struct *wk)
{
struct cfg80211_registered_device *rdev;
rdev = container_of(wk, struct cfg80211_registered_device,
scan_done_wk);
cfg80211_lock_rdev(rdev);
___cfg80211_scan_done(rdev);
cfg80211_unlock_rdev(rdev);
}
void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted) void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted)
{ {
WARN_ON(request != wiphy_to_dev(request->wiphy)->scan_req); WARN_ON(request != wiphy_to_dev(request->wiphy)->scan_req);