diff options
Diffstat (limited to 'net/wireless')
-rw-r--r-- | net/wireless/Kconfig | 3 | ||||
-rw-r--r-- | net/wireless/core.c | 108 | ||||
-rw-r--r-- | net/wireless/core.h | 7 | ||||
-rw-r--r-- | net/wireless/nl80211.c | 57 | ||||
-rw-r--r-- | net/wireless/reg.c | 15 | ||||
-rw-r--r-- | net/wireless/scan.c | 3 | ||||
-rw-r--r-- | net/wireless/util.c | 13 | ||||
-rw-r--r-- | net/wireless/wext-compat.c | 83 |
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, ¶ms.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); |