#include <linux/debugfs.h>
#include <linux/notifier.h>
#include <linux/device.h>
+#include <linux/etherdevice.h>
#include <linux/rtnetlink.h>
+#include <linux/sched.h>
#include <net/genetlink.h>
#include <net/cfg80211.h>
#include "nl80211.h"
#include "sysfs.h"
#include "debugfs.h"
#include "wext-compat.h"
+#include "ethtool.h"
/* name for sysfs, %d is appended */
#define PHY_NAME "phy"
/* for debugfs */
static struct dentry *ieee80211_debugfs_dir;
+/* for the cleanup, scan and event works */
+struct workqueue_struct *cfg80211_wq;
+
/* requires cfg80211_mutex to be held! */
struct cfg80211_registered_device *cfg80211_rdev_by_wiphy_idx(int wiphy_idx)
{
struct wireless_dev *wdev;
int err = 0;
- if (!rdev->wiphy.netnsok)
+ if (!(rdev->wiphy.flags & WIPHY_FLAG_NETNS_OK))
return -EOPNOTSUPP;
list_for_each_entry(wdev, &rdev->netdev_list, list) {
cfg80211_rfkill_set_block(rdev, rfkill_blocked(rdev->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,
- NULL);
- 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_process_rdev_events(rdev);
cfg80211_unlock_rdev(rdev);
rtnl_unlock();
}
INIT_LIST_HEAD(&rdev->bss_list);
INIT_WORK(&rdev->scan_done_wk, __cfg80211_scan_done);
+#ifdef CONFIG_CFG80211_WEXT
+ rdev->wiphy.wext = &cfg80211_wext_handler;
+#endif
+
device_initialize(&rdev->wiphy.dev);
rdev->wiphy.dev.class = &ieee80211_class;
rdev->wiphy.dev.platform_data = rdev;
+#ifdef CONFIG_CFG80211_DEFAULT_PS
+ rdev->wiphy.flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT;
+#endif
+
wiphy_net_set(&rdev->wiphy, &init_net);
rdev->rfkill_ops.set_block = cfg80211_rfkill_set_block;
INIT_WORK(&rdev->conn_work, cfg80211_conn_work);
INIT_WORK(&rdev->event_work, cfg80211_event_work);
+ init_waitqueue_head(&rdev->dev_wait);
+
/*
* Initialize wiphy parameters to IEEE 802.11 MIB default values.
* Fragmentation and RTS threshold are disabled by default with the
if (IS_ERR(rdev->wiphy.debugfsdir))
rdev->wiphy.debugfsdir = NULL;
- if (wiphy->custom_regulatory) {
+ if (wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY) {
struct regulatory_request request;
request.wiphy_idx = get_wiphy_idx(wiphy);
/* protect the device list */
mutex_lock(&cfg80211_mutex);
+ wait_event(rdev->dev_wait, ({
+ int __count;
+ mutex_lock(&rdev->devlist_mtx);
+ __count = rdev->opencount;
+ mutex_unlock(&rdev->devlist_mtx);
+ __count == 0;}));
+
+ mutex_lock(&rdev->devlist_mtx);
BUG_ON(!list_empty(&rdev->netdev_list));
+ mutex_unlock(&rdev->devlist_mtx);
+
+ /*
+ * First remove the hardware from everywhere, this makes
+ * it impossible to find from userspace.
+ */
+ debugfs_remove_recursive(rdev->wiphy.debugfsdir);
+ list_del(&rdev->list);
/*
* Try to grab rdev->mtx. If a command is still in progress,
* down the device already. We wait for this command to complete
* before unlinking the item from the list.
* Note: as codified by the BUG_ON above we cannot get here if
- * a virtual interface is still associated. Hence, we can only
- * get to lock contention here if userspace issues a command
- * that identified the hardware by wiphy index.
+ * a virtual interface is still present. Hence, we can only get
+ * to lock contention here if userspace issues a command that
+ * identified the hardware by wiphy index.
*/
- mutex_lock(&rdev->mtx);
- /* unlock again before freeing */
- mutex_unlock(&rdev->mtx);
-
- cfg80211_debugfs_rdev_del(rdev);
+ cfg80211_lock_rdev(rdev);
+ /* nothing */
+ cfg80211_unlock_rdev(rdev);
/* If this device got a regulatory hint tell core its
* free to listen now to a new shiny device regulatory hint */
reg_device_remove(wiphy);
- list_del(&rdev->list);
cfg80211_rdev_list_generation++;
device_del(&rdev->wiphy.dev);
- debugfs_remove(rdev->wiphy.debugfsdir);
mutex_unlock(&cfg80211_mutex);
+ flush_work(&rdev->scan_done_wk);
cancel_work_sync(&rdev->conn_work);
- cancel_work_sync(&rdev->scan_done_wk);
- kfree(rdev->scan_req);
flush_work(&rdev->event_work);
}
EXPORT_SYMBOL(wiphy_unregister);
}
EXPORT_SYMBOL(wiphy_rfkill_set_hw_state);
+static void wdev_cleanup_work(struct work_struct *work)
+{
+ struct wireless_dev *wdev;
+ struct cfg80211_registered_device *rdev;
+
+ wdev = container_of(work, struct wireless_dev, cleanup_work);
+ rdev = wiphy_to_dev(wdev->wiphy);
+
+ cfg80211_lock_rdev(rdev);
+
+ if (WARN_ON(rdev->scan_req && rdev->scan_req->dev == wdev->netdev)) {
+ rdev->scan_req->aborted = true;
+ ___cfg80211_scan_done(rdev, true);
+ }
+
+ cfg80211_unlock_rdev(rdev);
+
+ mutex_lock(&rdev->devlist_mtx);
+ rdev->opencount--;
+ mutex_unlock(&rdev->devlist_mtx);
+ wake_up(&rdev->dev_wait);
+
+ dev_put(wdev->netdev);
+}
+
+static struct device_type wiphy_type = {
+ .name = "wlan",
+};
+
static int cfg80211_netdev_notifier_call(struct notifier_block * nb,
unsigned long state,
void *ndev)
WARN_ON(wdev->iftype == NL80211_IFTYPE_UNSPECIFIED);
switch (state) {
+ case NETDEV_POST_INIT:
+ SET_NETDEV_DEVTYPE(dev, &wiphy_type);
+ break;
case NETDEV_REGISTER:
+ /*
+ * NB: cannot take rdev->mtx here because this may be
+ * called within code protected by it when interfaces
+ * are added with nl80211.
+ */
mutex_init(&wdev->mtx);
+ INIT_WORK(&wdev->cleanup_work, wdev_cleanup_work);
INIT_LIST_HEAD(&wdev->event_list);
spin_lock_init(&wdev->event_lock);
mutex_lock(&rdev->devlist_mtx);
wdev->netdev = dev;
wdev->sme_state = CFG80211_SME_IDLE;
mutex_unlock(&rdev->devlist_mtx);
-#ifdef CONFIG_WIRELESS_EXT
- if (!dev->wireless_handlers)
- dev->wireless_handlers = &cfg80211_wext_handler;
+#ifdef CONFIG_CFG80211_WEXT
wdev->wext.default_key = -1;
wdev->wext.default_mgmt_key = -1;
wdev->wext.connect.auth_type = NL80211_AUTHTYPE_AUTOMATIC;
- wdev->wext.ps = CONFIG_CFG80211_DEFAULT_PS_VALUE;
+ if (wdev->wiphy->flags & WIPHY_FLAG_PS_ON_BY_DEFAULT)
+ wdev->wext.ps = true;
+ else
+ wdev->wext.ps = false;
wdev->wext.ps_timeout = 100;
if (rdev->ops->set_power_mgmt)
if (rdev->ops->set_power_mgmt(wdev->wiphy, dev,
wdev->wext.ps = false;
}
#endif
+ if (!dev->ethtool_ops)
+ dev->ethtool_ops = &cfg80211_ethtool_ops;
+
+ if ((wdev->iftype == NL80211_IFTYPE_STATION ||
+ wdev->iftype == NL80211_IFTYPE_ADHOC) && !wdev->use_4addr)
+ dev->priv_flags |= IFF_DONT_BRIDGE;
break;
case NETDEV_GOING_DOWN:
switch (wdev->iftype) {
break;
case NL80211_IFTYPE_STATION:
wdev_lock(wdev);
-#ifdef CONFIG_WIRELESS_EXT
+#ifdef CONFIG_CFG80211_WEXT
kfree(wdev->wext.ie);
wdev->wext.ie = NULL;
wdev->wext.ie_len = 0;
break;
}
break;
+ case NETDEV_DOWN:
+ dev_hold(dev);
+ queue_work(cfg80211_wq, &wdev->cleanup_work);
+ break;
case NETDEV_UP:
-#ifdef CONFIG_WIRELESS_EXT
+ /*
+ * If we have a really quick DOWN/UP succession we may
+ * have this work still pending ... cancel it and see
+ * if it was pending, in which case we need to account
+ * for some of the work it would have done.
+ */
+ if (cancel_work_sync(&wdev->cleanup_work)) {
+ mutex_lock(&rdev->devlist_mtx);
+ rdev->opencount--;
+ mutex_unlock(&rdev->devlist_mtx);
+ dev_put(dev);
+ }
+#ifdef CONFIG_CFG80211_WEXT
cfg80211_lock_rdev(rdev);
mutex_lock(&rdev->devlist_mtx);
wdev_lock(wdev);
break;
}
wdev_unlock(wdev);
+ rdev->opencount++;
mutex_unlock(&rdev->devlist_mtx);
cfg80211_unlock_rdev(rdev);
#endif
break;
case NETDEV_UNREGISTER:
+ /*
+ * NB: cannot take rdev->mtx here because this may be
+ * called within code protected by it when interfaces
+ * are removed with nl80211.
+ */
mutex_lock(&rdev->devlist_mtx);
/*
* It is possible to get NETDEV_UNREGISTER
sysfs_remove_link(&dev->dev.kobj, "phy80211");
list_del_init(&wdev->list);
rdev->devlist_generation++;
- mutex_destroy(&wdev->mtx);
-#ifdef CONFIG_WIRELESS_EXT
+#ifdef CONFIG_CFG80211_WEXT
kfree(wdev->wext.keys);
#endif
}
if (err)
goto out_fail_reg;
+ cfg80211_wq = create_singlethread_workqueue("cfg80211");
+ if (!cfg80211_wq)
+ goto out_fail_wq;
+
return 0;
+out_fail_wq:
+ regulatory_exit();
out_fail_reg:
debugfs_remove(ieee80211_debugfs_dir);
out_fail_nl80211:
wiphy_sysfs_exit();
regulatory_exit();
unregister_pernet_device(&cfg80211_pernet_ops);
+ destroy_workqueue(cfg80211_wq);
}
module_exit(cfg80211_exit);