aboutsummaryrefslogtreecommitdiff
path: root/net/wireless
diff options
context:
space:
mode:
Diffstat (limited to 'net/wireless')
-rw-r--r--net/wireless/Kconfig3
-rw-r--r--net/wireless/core.c108
-rw-r--r--net/wireless/core.h7
-rw-r--r--net/wireless/nl80211.c57
-rw-r--r--net/wireless/reg.c15
-rw-r--r--net/wireless/scan.c3
-rw-r--r--net/wireless/util.c13
-rw-r--r--net/wireless/wext-compat.c83
8 files changed, 262 insertions, 27 deletions
diff --git a/net/wireless/Kconfig b/net/wireless/Kconfig
index 45005497c63..4428dd5e911 100644
--- a/net/wireless/Kconfig
+++ b/net/wireless/Kconfig
@@ -1,5 +1,6 @@
config CFG80211
- tristate "Improved wireless configuration API"
+ tristate "Improved wireless configuration API"
+ depends on RFKILL || !RFKILL
config CFG80211_REG_DEBUG
bool "cfg80211 regulatory debugging"
diff --git a/net/wireless/core.c b/net/wireless/core.c
index a5dbea1da47..d5850292b3d 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -12,6 +12,7 @@
#include <linux/debugfs.h>
#include <linux/notifier.h>
#include <linux/device.h>
+#include <linux/rtnetlink.h>
#include <net/genetlink.h>
#include <net/cfg80211.h>
#include "nl80211.h"
@@ -227,6 +228,41 @@ int cfg80211_dev_rename(struct cfg80211_registered_device *rdev,
return 0;
}
+static void cfg80211_rfkill_poll(struct rfkill *rfkill, void *data)
+{
+ struct cfg80211_registered_device *drv = data;
+
+ drv->ops->rfkill_poll(&drv->wiphy);
+}
+
+static int cfg80211_rfkill_set_block(void *data, bool blocked)
+{
+ struct cfg80211_registered_device *drv = data;
+ struct wireless_dev *wdev;
+
+ if (!blocked)
+ return 0;
+
+ rtnl_lock();
+ mutex_lock(&drv->devlist_mtx);
+
+ list_for_each_entry(wdev, &drv->netdev_list, list)
+ dev_close(wdev->netdev);
+
+ mutex_unlock(&drv->devlist_mtx);
+ rtnl_unlock();
+
+ return 0;
+}
+
+static void cfg80211_rfkill_sync_work(struct work_struct *work)
+{
+ struct cfg80211_registered_device *drv;
+
+ drv = container_of(work, struct cfg80211_registered_device, rfkill_sync);
+ cfg80211_rfkill_set_block(drv, rfkill_blocked(drv->rfkill));
+}
+
/* exported functions */
struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv)
@@ -274,6 +310,18 @@ struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv)
drv->wiphy.dev.class = &ieee80211_class;
drv->wiphy.dev.platform_data = drv;
+ drv->rfkill_ops.set_block = cfg80211_rfkill_set_block;
+ drv->rfkill = rfkill_alloc(dev_name(&drv->wiphy.dev),
+ &drv->wiphy.dev, RFKILL_TYPE_WLAN,
+ &drv->rfkill_ops, drv);
+
+ if (!drv->rfkill) {
+ kfree(drv);
+ return NULL;
+ }
+
+ INIT_WORK(&drv->rfkill_sync, cfg80211_rfkill_sync_work);
+
/*
* Initialize wiphy parameters to IEEE 802.11 MIB default values.
* Fragmentation and RTS threshold are disabled by default with the
@@ -347,17 +395,23 @@ int wiphy_register(struct wiphy *wiphy)
/* check and set up bitrates */
ieee80211_set_bitrate_flags(wiphy);
+ res = device_add(&drv->wiphy.dev);
+ if (res)
+ return res;
+
+ res = rfkill_register(drv->rfkill);
+ if (res)
+ goto out_rm_dev;
+
mutex_lock(&cfg80211_mutex);
/* set up regulatory info */
wiphy_update_regulatory(wiphy, NL80211_REGDOM_SET_BY_CORE);
- res = device_add(&drv->wiphy.dev);
- if (res)
- goto out_unlock;
-
list_add(&drv->list, &cfg80211_drv_list);
+ mutex_unlock(&cfg80211_mutex);
+
/* add to debugfs */
drv->wiphy.debugfsdir =
debugfs_create_dir(wiphy_name(&drv->wiphy),
@@ -378,17 +432,39 @@ int wiphy_register(struct wiphy *wiphy)
cfg80211_debugfs_drv_add(drv);
- res = 0;
-out_unlock:
- mutex_unlock(&cfg80211_mutex);
+ return 0;
+
+ out_rm_dev:
+ device_del(&drv->wiphy.dev);
return res;
}
EXPORT_SYMBOL(wiphy_register);
+void wiphy_rfkill_start_polling(struct wiphy *wiphy)
+{
+ struct cfg80211_registered_device *drv = wiphy_to_dev(wiphy);
+
+ if (!drv->ops->rfkill_poll)
+ return;
+ drv->rfkill_ops.poll = cfg80211_rfkill_poll;
+ rfkill_resume_polling(drv->rfkill);
+}
+EXPORT_SYMBOL(wiphy_rfkill_start_polling);
+
+void wiphy_rfkill_stop_polling(struct wiphy *wiphy)
+{
+ struct cfg80211_registered_device *drv = wiphy_to_dev(wiphy);
+
+ rfkill_pause_polling(drv->rfkill);
+}
+EXPORT_SYMBOL(wiphy_rfkill_stop_polling);
+
void wiphy_unregister(struct wiphy *wiphy)
{
struct cfg80211_registered_device *drv = wiphy_to_dev(wiphy);
+ rfkill_unregister(drv->rfkill);
+
/* protect the device list */
mutex_lock(&cfg80211_mutex);
@@ -425,6 +501,7 @@ EXPORT_SYMBOL(wiphy_unregister);
void cfg80211_dev_free(struct cfg80211_registered_device *drv)
{
struct cfg80211_internal_bss *scan, *tmp;
+ rfkill_destroy(drv->rfkill);
mutex_destroy(&drv->mtx);
mutex_destroy(&drv->devlist_mtx);
list_for_each_entry_safe(scan, tmp, &drv->bss_list, list)
@@ -438,6 +515,15 @@ void wiphy_free(struct wiphy *wiphy)
}
EXPORT_SYMBOL(wiphy_free);
+void wiphy_rfkill_set_hw_state(struct wiphy *wiphy, bool blocked)
+{
+ struct cfg80211_registered_device *drv = wiphy_to_dev(wiphy);
+
+ if (rfkill_set_hw_state(drv->rfkill, blocked))
+ schedule_work(&drv->rfkill_sync);
+}
+EXPORT_SYMBOL(wiphy_rfkill_set_hw_state);
+
static int cfg80211_netdev_notifier_call(struct notifier_block * nb,
unsigned long state,
void *ndev)
@@ -446,7 +532,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb,
struct cfg80211_registered_device *rdev;
if (!dev->ieee80211_ptr)
- return 0;
+ return NOTIFY_DONE;
rdev = wiphy_to_dev(dev->ieee80211_ptr->wiphy);
@@ -492,9 +578,13 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb,
}
mutex_unlock(&rdev->devlist_mtx);
break;
+ case NETDEV_PRE_UP:
+ if (rfkill_blocked(rdev->rfkill))
+ return notifier_from_errno(-ERFKILL);
+ break;
}
- return 0;
+ return NOTIFY_DONE;
}
static struct notifier_block cfg80211_netdev_notifier = {
diff --git a/net/wireless/core.h b/net/wireless/core.h
index ab512bcd815..bfa340c7abb 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -11,6 +11,8 @@
#include <linux/kref.h>
#include <linux/rbtree.h>
#include <linux/debugfs.h>
+#include <linux/rfkill.h>
+#include <linux/workqueue.h>
#include <net/genetlink.h>
#include <net/cfg80211.h>
#include "reg.h"
@@ -24,6 +26,11 @@ struct cfg80211_registered_device {
* any call is in progress */
struct mutex mtx;
+ /* rfkill support */
+ struct rfkill_ops rfkill_ops;
+ struct rfkill *rfkill;
+ struct work_struct rfkill_sync;
+
/* ISO / IEC 3166 alpha2 for which this device is receiving
* country IEs on, this can help disregard country IEs from APs
* on the same alpha2 quickly. The alpha2 may differ from
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 4b4d3c8a1ae..24168560eba 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -1687,6 +1687,12 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
if (err)
goto out_rtnl;
+ if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
+ dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP_VLAN) {
+ err = -EINVAL;
+ goto out;
+ }
+
err = get_vlan(info->attrs[NL80211_ATTR_STA_VLAN], drv, &params.vlan);
if (err)
goto out;
@@ -1738,7 +1744,11 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
nla_len(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
params.listen_interval =
nla_get_u16(info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]);
+
params.aid = nla_get_u16(info->attrs[NL80211_ATTR_STA_AID]);
+ if (!params.aid || params.aid > IEEE80211_MAX_AID)
+ return -EINVAL;
+
if (info->attrs[NL80211_ATTR_HT_CAPABILITY])
params.ht_capa =
nla_data(info->attrs[NL80211_ATTR_HT_CAPABILITY]);
@@ -3559,11 +3569,43 @@ void nl80211_notify_dev_rename(struct cfg80211_registered_device *rdev)
genlmsg_multicast(msg, 0, nl80211_config_mcgrp.id, GFP_KERNEL);
}
+static int nl80211_add_scan_req(struct sk_buff *msg,
+ struct cfg80211_registered_device *rdev)
+{
+ struct cfg80211_scan_request *req = rdev->scan_req;
+ struct nlattr *nest;
+ int i;
+
+ if (WARN_ON(!req))
+ return 0;
+
+ nest = nla_nest_start(msg, NL80211_ATTR_SCAN_SSIDS);
+ if (!nest)
+ goto nla_put_failure;
+ for (i = 0; i < req->n_ssids; i++)
+ NLA_PUT(msg, i, req->ssids[i].ssid_len, req->ssids[i].ssid);
+ nla_nest_end(msg, nest);
+
+ nest = nla_nest_start(msg, NL80211_ATTR_SCAN_FREQUENCIES);
+ if (!nest)
+ goto nla_put_failure;
+ for (i = 0; i < req->n_channels; i++)
+ NLA_PUT_U32(msg, i, req->channels[i]->center_freq);
+ nla_nest_end(msg, nest);
+
+ if (req->ie)
+ NLA_PUT(msg, NL80211_ATTR_IE, req->ie_len, req->ie);
+
+ return 0;
+ nla_put_failure:
+ return -ENOBUFS;
+}
+
static int nl80211_send_scan_donemsg(struct sk_buff *msg,
- struct cfg80211_registered_device *rdev,
- struct net_device *netdev,
- u32 pid, u32 seq, int flags,
- u32 cmd)
+ struct cfg80211_registered_device *rdev,
+ struct net_device *netdev,
+ u32 pid, u32 seq, int flags,
+ u32 cmd)
{
void *hdr;
@@ -3574,7 +3616,8 @@ static int nl80211_send_scan_donemsg(struct sk_buff *msg,
NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex);
- /* XXX: we should probably bounce back the request? */
+ /* ignore errors and send incomplete event anyway */
+ nl80211_add_scan_req(msg, rdev);
return genlmsg_end(msg, hdr);
@@ -3828,7 +3871,7 @@ void nl80211_michael_mic_failure(struct cfg80211_registered_device *rdev,
struct sk_buff *msg;
void *hdr;
- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
if (!msg)
return;
@@ -3852,7 +3895,7 @@ void nl80211_michael_mic_failure(struct cfg80211_registered_device *rdev,
return;
}
- genlmsg_multicast(msg, 0, nl80211_mlme_mcgrp.id, GFP_KERNEL);
+ genlmsg_multicast(msg, 0, nl80211_mlme_mcgrp.id, GFP_ATOMIC);
return;
nla_put_failure:
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index f87ac1df2df..5e14371cda7 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -2129,7 +2129,12 @@ static int __set_regdom(const struct ieee80211_regdomain *rd)
* driver wanted to the wiphy to deal with conflicts
*/
- BUG_ON(request_wiphy->regd);
+ /*
+ * Userspace could have sent two replies with only
+ * one kernel request.
+ */
+ if (request_wiphy->regd)
+ return -EALREADY;
r = reg_copy_regd(&request_wiphy->regd, rd);
if (r)
@@ -2171,7 +2176,13 @@ static int __set_regdom(const struct ieee80211_regdomain *rd)
* the country IE rd with what CRDA believes that country should have
*/
- BUG_ON(!country_ie_regdomain);
+ /*
+ * Userspace could have sent two replies with only
+ * one kernel request. By the second reply we would have
+ * already processed and consumed the country_ie_regdomain.
+ */
+ if (!country_ie_regdomain)
+ return -EALREADY;
BUG_ON(rd == country_ie_regdomain);
/*
diff --git a/net/wireless/scan.c b/net/wireless/scan.c
index df59440290e..e95b638b919 100644
--- a/net/wireless/scan.c
+++ b/net/wireless/scan.c
@@ -29,13 +29,14 @@ void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted)
goto out;
WARN_ON(request != wiphy_to_dev(request->wiphy)->scan_req);
- wiphy_to_dev(request->wiphy)->scan_req = NULL;
if (aborted)
nl80211_send_scan_aborted(wiphy_to_dev(request->wiphy), dev);
else
nl80211_send_scan_done(wiphy_to_dev(request->wiphy), dev);
+ wiphy_to_dev(request->wiphy)->scan_req = NULL;
+
#ifdef CONFIG_WIRELESS_EXT
if (!aborted) {
memset(&wrqu, 0, sizeof(wrqu));
diff --git a/net/wireless/util.c b/net/wireless/util.c
index d072bff463a..25550692dda 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -157,26 +157,25 @@ int cfg80211_validate_key_settings(struct key_params *params, int key_idx,
params->cipher != WLAN_CIPHER_SUITE_WEP104)
return -EINVAL;
- /* TODO: add definitions for the lengths to linux/ieee80211.h */
switch (params->cipher) {
case WLAN_CIPHER_SUITE_WEP40:
- if (params->key_len != 5)
+ if (params->key_len != WLAN_KEY_LEN_WEP40)
return -EINVAL;
break;
case WLAN_CIPHER_SUITE_TKIP:
- if (params->key_len != 32)
+ if (params->key_len != WLAN_KEY_LEN_TKIP)
return -EINVAL;
break;
case WLAN_CIPHER_SUITE_CCMP:
- if (params->key_len != 16)
+ if (params->key_len != WLAN_KEY_LEN_CCMP)
return -EINVAL;
break;
case WLAN_CIPHER_SUITE_WEP104:
- if (params->key_len != 13)
+ if (params->key_len != WLAN_KEY_LEN_WEP104)
return -EINVAL;
break;
case WLAN_CIPHER_SUITE_AES_CMAC:
- if (params->key_len != 16)
+ if (params->key_len != WLAN_KEY_LEN_AES_CMAC)
return -EINVAL;
break;
default:
@@ -259,7 +258,7 @@ unsigned int ieee80211_get_hdrlen_from_skb(const struct sk_buff *skb)
}
EXPORT_SYMBOL(ieee80211_get_hdrlen_from_skb);
-int ieee80211_get_mesh_hdrlen(struct ieee80211s_hdr *meshhdr)
+static int ieee80211_get_mesh_hdrlen(struct ieee80211s_hdr *meshhdr)
{
int ae = meshhdr->flags & MESH_FLAGS_AE;
/* 7.1.3.5a.2 */
diff --git a/net/wireless/wext-compat.c b/net/wireless/wext-compat.c
index 711e00a0c9b..d030c531567 100644
--- a/net/wireless/wext-compat.c
+++ b/net/wireless/wext-compat.c
@@ -744,3 +744,86 @@ int cfg80211_wext_giwencode(struct net_device *dev,
return err;
}
EXPORT_SYMBOL_GPL(cfg80211_wext_giwencode);
+
+int cfg80211_wext_siwtxpower(struct net_device *dev,
+ struct iw_request_info *info,
+ union iwreq_data *data, char *extra)
+{
+ struct wireless_dev *wdev = dev->ieee80211_ptr;
+ struct cfg80211_registered_device *rdev = wiphy_to_dev(wdev->wiphy);
+ enum tx_power_setting type;
+ int dbm = 0;
+
+ if ((data->txpower.flags & IW_TXPOW_TYPE) != IW_TXPOW_DBM)
+ return -EINVAL;
+ if (data->txpower.flags & IW_TXPOW_RANGE)
+ return -EINVAL;
+
+ if (!rdev->ops->set_tx_power)
+ return -EOPNOTSUPP;
+
+ /* only change when not disabling */
+ if (!data->txpower.disabled) {
+ rfkill_set_sw_state(rdev->rfkill, false);
+
+ if (data->txpower.fixed) {
+ /*
+ * wext doesn't support negative values, see
+ * below where it's for automatic
+ */
+ if (data->txpower.value < 0)
+ return -EINVAL;
+ dbm = data->txpower.value;
+ type = TX_POWER_FIXED;
+ /* TODO: do regulatory check! */
+ } else {
+ /*
+ * Automatic power level setting, max being the value
+ * passed in from userland.
+ */
+ if (data->txpower.value < 0) {
+ type = TX_POWER_AUTOMATIC;
+ } else {
+ dbm = data->txpower.value;
+ type = TX_POWER_LIMITED;
+ }
+ }
+ } else {
+ rfkill_set_sw_state(rdev->rfkill, true);
+ schedule_work(&rdev->rfkill_sync);
+ return 0;
+ }
+
+ return rdev->ops->set_tx_power(wdev->wiphy, type, dbm);;
+}
+EXPORT_SYMBOL_GPL(cfg80211_wext_siwtxpower);
+
+int cfg80211_wext_giwtxpower(struct net_device *dev,
+ struct iw_request_info *info,
+ union iwreq_data *data, char *extra)
+{
+ struct wireless_dev *wdev = dev->ieee80211_ptr;
+ struct cfg80211_registered_device *rdev = wiphy_to_dev(wdev->wiphy);
+ int err, val;
+
+ if ((data->txpower.flags & IW_TXPOW_TYPE) != IW_TXPOW_DBM)
+ return -EINVAL;
+ if (data->txpower.flags & IW_TXPOW_RANGE)
+ return -EINVAL;
+
+ if (!rdev->ops->get_tx_power)
+ return -EOPNOTSUPP;
+
+ err = rdev->ops->get_tx_power(wdev->wiphy, &val);
+ if (err)
+ return err;
+
+ /* well... oh well */
+ data->txpower.fixed = 1;
+ data->txpower.disabled = rfkill_blocked(rdev->rfkill);
+ data->txpower.value = val;
+ data->txpower.flags = IW_TXPOW_DBM;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cfg80211_wext_giwtxpower);