summaryrefslogtreecommitdiffstats
path: root/net/wireless
diff options
context:
space:
mode:
Diffstat (limited to 'net/wireless')
-rw-r--r--net/wireless/Kconfig30
-rw-r--r--net/wireless/Makefile7
-rw-r--r--net/wireless/core.c14
-rw-r--r--net/wireless/core.h13
-rw-r--r--net/wireless/lib80211.c284
-rw-r--r--net/wireless/lib80211_crypt_ccmp.c492
-rw-r--r--net/wireless/lib80211_crypt_tkip.c784
-rw-r--r--net/wireless/lib80211_crypt_wep.c296
-rw-r--r--net/wireless/nl80211.c417
-rw-r--r--net/wireless/reg.c1005
-rw-r--r--net/wireless/reg.h33
-rw-r--r--net/wireless/sysfs.c25
-rw-r--r--net/wireless/util.c19
-rw-r--r--net/wireless/wext-compat.c139
-rw-r--r--net/wireless/wext.c2
15 files changed, 3281 insertions, 279 deletions
diff --git a/net/wireless/Kconfig b/net/wireless/Kconfig
index 646c7121dbc0..e28e2b8fa436 100644
--- a/net/wireless/Kconfig
+++ b/net/wireless/Kconfig
@@ -1,6 +1,15 @@
config CFG80211
tristate "Improved wireless configuration API"
+config CFG80211_REG_DEBUG
+ bool "cfg80211 regulatory debugging"
+ depends on CFG80211
+ default n
+ ---help---
+ You can enable this if you want to debug regulatory changes.
+
+ If unsure, say N.
+
config NL80211
bool "nl80211 new netlink interface support"
depends on CFG80211
@@ -40,6 +49,8 @@ config WIRELESS_OLD_REGULATORY
ieee80211_regdom module parameter. This is being phased out and you
should stop using them ASAP.
+ Note: You will need CRDA if you want 802.11d support
+
Say Y unless you have installed a new userspace application.
Also say Y if have one currently depending on the ieee80211_regdom
module parameter and cannot port it to use the new userspace
@@ -72,3 +83,22 @@ config WIRELESS_EXT_SYSFS
Say Y if you have programs using it, like old versions of
hal.
+
+config LIB80211
+ tristate "Common routines for IEEE802.11 drivers"
+ default n
+ help
+ This options enables a library of common routines used
+ by IEEE802.11 wireless LAN drivers.
+
+ Drivers should select this themselves if needed. Say Y if
+ you want this built into your kernel.
+
+config LIB80211_CRYPT_WEP
+ tristate
+
+config LIB80211_CRYPT_CCMP
+ tristate
+
+config LIB80211_CRYPT_TKIP
+ tristate
diff --git a/net/wireless/Makefile b/net/wireless/Makefile
index b9f943c45f3b..938a334c8dbc 100644
--- a/net/wireless/Makefile
+++ b/net/wireless/Makefile
@@ -1,5 +1,12 @@
obj-$(CONFIG_WIRELESS_EXT) += wext.o
obj-$(CONFIG_CFG80211) += cfg80211.o
+obj-$(CONFIG_LIB80211) += lib80211.o
+obj-$(CONFIG_LIB80211_CRYPT_WEP) += lib80211_crypt_wep.o
+obj-$(CONFIG_LIB80211_CRYPT_CCMP) += lib80211_crypt_ccmp.o
+obj-$(CONFIG_LIB80211_CRYPT_TKIP) += lib80211_crypt_tkip.o
cfg80211-y += core.o sysfs.o radiotap.o util.o reg.o
+cfg80211-$(CONFIG_WIRELESS_EXT) += wext-compat.o
cfg80211-$(CONFIG_NL80211) += nl80211.o
+
+ccflags-y += -D__CHECK_ENDIAN__
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 5031db7b275b..b96fc0c3f1c4 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -19,7 +19,6 @@
#include "nl80211.h"
#include "core.h"
#include "sysfs.h"
-#include "reg.h"
/* name for sysfs, %d is appended */
#define PHY_NAME "phy"
@@ -236,8 +235,7 @@ struct wiphy *wiphy_new(struct cfg80211_ops *ops, int sizeof_priv)
mutex_unlock(&cfg80211_drv_mutex);
/* give it a proper name */
- snprintf(drv->wiphy.dev.bus_id, BUS_ID_SIZE,
- PHY_NAME "%d", drv->idx);
+ dev_set_name(&drv->wiphy.dev, PHY_NAME "%d", drv->idx);
mutex_init(&drv->mtx);
mutex_init(&drv->devlist_mtx);
@@ -301,12 +299,10 @@ int wiphy_register(struct wiphy *wiphy)
/* check and set up bitrates */
ieee80211_set_bitrate_flags(wiphy);
+ mutex_lock(&cfg80211_drv_mutex);
+
/* set up regulatory info */
- mutex_lock(&cfg80211_reg_mutex);
wiphy_update_regulatory(wiphy, REGDOM_SET_BY_CORE);
- mutex_unlock(&cfg80211_reg_mutex);
-
- mutex_lock(&cfg80211_drv_mutex);
res = device_add(&drv->wiphy.dev);
if (res)
@@ -351,6 +347,10 @@ void wiphy_unregister(struct wiphy *wiphy)
/* unlock again before freeing */
mutex_unlock(&drv->mtx);
+ /* 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(&drv->list);
device_del(&drv->wiphy.dev);
debugfs_remove(drv->wiphy.debugfsdir);
diff --git a/net/wireless/core.h b/net/wireless/core.h
index 771cc5cc7658..f7fb9f413028 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -11,6 +11,7 @@
#include <net/genetlink.h>
#include <net/wireless.h>
#include <net/cfg80211.h>
+#include "reg.h"
struct cfg80211_registered_device {
struct cfg80211_ops *ops;
@@ -21,6 +22,18 @@ struct cfg80211_registered_device {
* any call is in progress */
struct mutex mtx;
+ /* 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
+ * cfg80211_regdomain's alpha2 when an intersection has occurred.
+ * If the AP is reconfigured this can also be used to tell us if
+ * the country on the country IE changed. */
+ char country_ie_alpha2[2];
+
+ /* If a Country IE has been received this tells us the environment
+ * which its telling us its in. This defaults to ENVIRON_ANY */
+ enum environment_cap env;
+
/* wiphy index, internal only */
int idx;
diff --git a/net/wireless/lib80211.c b/net/wireless/lib80211.c
new file mode 100644
index 000000000000..97d411f74507
--- /dev/null
+++ b/net/wireless/lib80211.c
@@ -0,0 +1,284 @@
+/*
+ * lib80211 -- common bits for IEEE802.11 drivers
+ *
+ * Copyright(c) 2008 John W. Linville <linville@tuxdriver.com>
+ *
+ * Portions copied from old ieee80211 component, w/ original copyright
+ * notices below:
+ *
+ * Host AP crypto routines
+ *
+ * Copyright (c) 2002-2003, Jouni Malinen <j@w1.fi>
+ * Portions Copyright (C) 2004, Intel Corporation <jketreno@linux.intel.com>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/ctype.h>
+#include <linux/ieee80211.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include <net/lib80211.h>
+
+#define DRV_NAME "lib80211"
+
+#define DRV_DESCRIPTION "common routines for IEEE802.11 drivers"
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_AUTHOR("John W. Linville <linville@tuxdriver.com>");
+MODULE_LICENSE("GPL");
+
+struct lib80211_crypto_alg {
+ struct list_head list;
+ struct lib80211_crypto_ops *ops;
+};
+
+static LIST_HEAD(lib80211_crypto_algs);
+static DEFINE_SPINLOCK(lib80211_crypto_lock);
+
+const char *print_ssid(char *buf, const char *ssid, u8 ssid_len)
+{
+ const char *s = ssid;
+ char *d = buf;
+
+ ssid_len = min_t(u8, ssid_len, IEEE80211_MAX_SSID_LEN);
+ while (ssid_len--) {
+ if (isprint(*s)) {
+ *d++ = *s++;
+ continue;
+ }
+
+ *d++ = '\\';
+ if (*s == '\0')
+ *d++ = '0';
+ else if (*s == '\n')
+ *d++ = 'n';
+ else if (*s == '\r')
+ *d++ = 'r';
+ else if (*s == '\t')
+ *d++ = 't';
+ else if (*s == '\\')
+ *d++ = '\\';
+ else
+ d += snprintf(d, 3, "%03o", *s);
+ s++;
+ }
+ *d = '\0';
+ return buf;
+}
+EXPORT_SYMBOL(print_ssid);
+
+int lib80211_crypt_info_init(struct lib80211_crypt_info *info, char *name,
+ spinlock_t *lock)
+{
+ memset(info, 0, sizeof(*info));
+
+ info->name = name;
+ info->lock = lock;
+
+ INIT_LIST_HEAD(&info->crypt_deinit_list);
+ setup_timer(&info->crypt_deinit_timer, lib80211_crypt_deinit_handler,
+ (unsigned long)info);
+
+ return 0;
+}
+EXPORT_SYMBOL(lib80211_crypt_info_init);
+
+void lib80211_crypt_info_free(struct lib80211_crypt_info *info)
+{
+ int i;
+
+ lib80211_crypt_quiescing(info);
+ del_timer_sync(&info->crypt_deinit_timer);
+ lib80211_crypt_deinit_entries(info, 1);
+
+ for (i = 0; i < NUM_WEP_KEYS; i++) {
+ struct lib80211_crypt_data *crypt = info->crypt[i];
+ if (crypt) {
+ if (crypt->ops) {
+ crypt->ops->deinit(crypt->priv);
+ module_put(crypt->ops->owner);
+ }
+ kfree(crypt);
+ info->crypt[i] = NULL;
+ }
+ }
+}
+EXPORT_SYMBOL(lib80211_crypt_info_free);
+
+void lib80211_crypt_deinit_entries(struct lib80211_crypt_info *info, int force)
+{
+ struct lib80211_crypt_data *entry, *next;
+ unsigned long flags;
+
+ spin_lock_irqsave(info->lock, flags);
+ list_for_each_entry_safe(entry, next, &info->crypt_deinit_list, list) {
+ if (atomic_read(&entry->refcnt) != 0 && !force)
+ continue;
+
+ list_del(&entry->list);
+
+ if (entry->ops) {
+ entry->ops->deinit(entry->priv);
+ module_put(entry->ops->owner);
+ }
+ kfree(entry);
+ }
+ spin_unlock_irqrestore(info->lock, flags);
+}
+EXPORT_SYMBOL(lib80211_crypt_deinit_entries);
+
+/* After this, crypt_deinit_list won't accept new members */
+void lib80211_crypt_quiescing(struct lib80211_crypt_info *info)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(info->lock, flags);
+ info->crypt_quiesced = 1;
+ spin_unlock_irqrestore(info->lock, flags);
+}
+EXPORT_SYMBOL(lib80211_crypt_quiescing);
+
+void lib80211_crypt_deinit_handler(unsigned long data)
+{
+ struct lib80211_crypt_info *info = (struct lib80211_crypt_info *)data;
+ unsigned long flags;
+
+ lib80211_crypt_deinit_entries(info, 0);
+
+ spin_lock_irqsave(info->lock, flags);
+ if (!list_empty(&info->crypt_deinit_list) && !info->crypt_quiesced) {
+ printk(KERN_DEBUG "%s: entries remaining in delayed crypt "
+ "deletion list\n", info->name);
+ info->crypt_deinit_timer.expires = jiffies + HZ;
+ add_timer(&info->crypt_deinit_timer);
+ }
+ spin_unlock_irqrestore(info->lock, flags);
+}
+EXPORT_SYMBOL(lib80211_crypt_deinit_handler);
+
+void lib80211_crypt_delayed_deinit(struct lib80211_crypt_info *info,
+ struct lib80211_crypt_data **crypt)
+{
+ struct lib80211_crypt_data *tmp;
+ unsigned long flags;
+
+ if (*crypt == NULL)
+ return;
+
+ tmp = *crypt;
+ *crypt = NULL;
+
+ /* must not run ops->deinit() while there may be pending encrypt or
+ * decrypt operations. Use a list of delayed deinits to avoid needing
+ * locking. */
+
+ spin_lock_irqsave(info->lock, flags);
+ if (!info->crypt_quiesced) {
+ list_add(&tmp->list, &info->crypt_deinit_list);
+ if (!timer_pending(&info->crypt_deinit_timer)) {
+ info->crypt_deinit_timer.expires = jiffies + HZ;
+ add_timer(&info->crypt_deinit_timer);
+ }
+ }
+ spin_unlock_irqrestore(info->lock, flags);
+}
+EXPORT_SYMBOL(lib80211_crypt_delayed_deinit);
+
+int lib80211_register_crypto_ops(struct lib80211_crypto_ops *ops)
+{
+ unsigned long flags;
+ struct lib80211_crypto_alg *alg;
+
+ alg = kzalloc(sizeof(*alg), GFP_KERNEL);
+ if (alg == NULL)
+ return -ENOMEM;
+
+ alg->ops = ops;
+
+ spin_lock_irqsave(&lib80211_crypto_lock, flags);
+ list_add(&alg->list, &lib80211_crypto_algs);
+ spin_unlock_irqrestore(&lib80211_crypto_lock, flags);
+
+ printk(KERN_DEBUG "lib80211_crypt: registered algorithm '%s'\n",
+ ops->name);
+
+ return 0;
+}
+EXPORT_SYMBOL(lib80211_register_crypto_ops);
+
+int lib80211_unregister_crypto_ops(struct lib80211_crypto_ops *ops)
+{
+ struct lib80211_crypto_alg *alg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&lib80211_crypto_lock, flags);
+ list_for_each_entry(alg, &lib80211_crypto_algs, list) {
+ if (alg->ops == ops)
+ goto found;
+ }
+ spin_unlock_irqrestore(&lib80211_crypto_lock, flags);
+ return -EINVAL;
+
+ found:
+ printk(KERN_DEBUG "lib80211_crypt: unregistered algorithm "
+ "'%s'\n", ops->name);
+ list_del(&alg->list);
+ spin_unlock_irqrestore(&lib80211_crypto_lock, flags);
+ kfree(alg);
+ return 0;
+}
+EXPORT_SYMBOL(lib80211_unregister_crypto_ops);
+
+struct lib80211_crypto_ops *lib80211_get_crypto_ops(const char *name)
+{
+ struct lib80211_crypto_alg *alg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&lib80211_crypto_lock, flags);
+ list_for_each_entry(alg, &lib80211_crypto_algs, list) {
+ if (strcmp(alg->ops->name, name) == 0)
+ goto found;
+ }
+ spin_unlock_irqrestore(&lib80211_crypto_lock, flags);
+ return NULL;
+
+ found:
+ spin_unlock_irqrestore(&lib80211_crypto_lock, flags);
+ return alg->ops;
+}
+EXPORT_SYMBOL(lib80211_get_crypto_ops);
+
+static void *lib80211_crypt_null_init(int keyidx)
+{
+ return (void *)1;
+}
+
+static void lib80211_crypt_null_deinit(void *priv)
+{
+}
+
+static struct lib80211_crypto_ops lib80211_crypt_null = {
+ .name = "NULL",
+ .init = lib80211_crypt_null_init,
+ .deinit = lib80211_crypt_null_deinit,
+ .owner = THIS_MODULE,
+};
+
+static int __init lib80211_init(void)
+{
+ printk(KERN_INFO DRV_NAME ": " DRV_DESCRIPTION "\n");
+ return lib80211_register_crypto_ops(&lib80211_crypt_null);
+}
+
+static void __exit lib80211_exit(void)
+{
+ lib80211_unregister_crypto_ops(&lib80211_crypt_null);
+ BUG_ON(!list_empty(&lib80211_crypto_algs));
+}
+
+module_init(lib80211_init);
+module_exit(lib80211_exit);
diff --git a/net/wireless/lib80211_crypt_ccmp.c b/net/wireless/lib80211_crypt_ccmp.c
new file mode 100644
index 000000000000..db428194c16a
--- /dev/null
+++ b/net/wireless/lib80211_crypt_ccmp.c
@@ -0,0 +1,492 @@
+/*
+ * lib80211 crypt: host-based CCMP encryption implementation for lib80211
+ *
+ * Copyright (c) 2003-2004, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2008, John W. Linville <linville@tuxdriver.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. See README and COPYING for
+ * more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/if_ether.h>
+#include <linux/if_arp.h>
+#include <asm/string.h>
+#include <linux/wireless.h>
+
+#include <linux/ieee80211.h>
+
+#include <linux/crypto.h>
+
+#include <net/lib80211.h>
+
+MODULE_AUTHOR("Jouni Malinen");
+MODULE_DESCRIPTION("Host AP crypt: CCMP");
+MODULE_LICENSE("GPL");
+
+#define AES_BLOCK_LEN 16
+#define CCMP_HDR_LEN 8
+#define CCMP_MIC_LEN 8
+#define CCMP_TK_LEN 16
+#define CCMP_PN_LEN 6
+
+struct lib80211_ccmp_data {
+ u8 key[CCMP_TK_LEN];
+ int key_set;
+
+ u8 tx_pn[CCMP_PN_LEN];
+ u8 rx_pn[CCMP_PN_LEN];
+
+ u32 dot11RSNAStatsCCMPFormatErrors;
+ u32 dot11RSNAStatsCCMPReplays;
+ u32 dot11RSNAStatsCCMPDecryptErrors;
+
+ int key_idx;
+
+ struct crypto_cipher *tfm;
+
+ /* scratch buffers for virt_to_page() (crypto API) */
+ u8 tx_b0[AES_BLOCK_LEN], tx_b[AES_BLOCK_LEN],
+ tx_e[AES_BLOCK_LEN], tx_s0[AES_BLOCK_LEN];
+ u8 rx_b0[AES_BLOCK_LEN], rx_b[AES_BLOCK_LEN], rx_a[AES_BLOCK_LEN];
+};
+
+static inline void lib80211_ccmp_aes_encrypt(struct crypto_cipher *tfm,
+ const u8 pt[16], u8 ct[16])
+{
+ crypto_cipher_encrypt_one(tfm, ct, pt);
+}
+
+static void *lib80211_ccmp_init(int key_idx)
+{
+ struct lib80211_ccmp_data *priv;
+
+ priv = kzalloc(sizeof(*priv), GFP_ATOMIC);
+ if (priv == NULL)
+ goto fail;
+ priv->key_idx = key_idx;
+
+ priv->tfm = crypto_alloc_cipher("aes", 0, CRYPTO_ALG_ASYNC);
+ if (IS_ERR(priv->tfm)) {
+ printk(KERN_DEBUG "lib80211_crypt_ccmp: could not allocate "
+ "crypto API aes\n");
+ priv->tfm = NULL;
+ goto fail;
+ }
+
+ return priv;
+
+ fail:
+ if (priv) {
+ if (priv->tfm)
+ crypto_free_cipher(priv->tfm);
+ kfree(priv);
+ }
+
+ return NULL;
+}
+
+static void lib80211_ccmp_deinit(void *priv)
+{
+ struct lib80211_ccmp_data *_priv = priv;
+ if (_priv && _priv->tfm)
+ crypto_free_cipher(_priv->tfm);
+ kfree(priv);
+}
+
+static inline void xor_block(u8 * b, u8 * a, size_t len)
+{
+ int i;
+ for (i = 0; i < len; i++)
+ b[i] ^= a[i];
+}
+
+static void ccmp_init_blocks(struct crypto_cipher *tfm,
+ struct ieee80211_hdr *hdr,
+ u8 * pn, size_t dlen, u8 * b0, u8 * auth, u8 * s0)
+{
+ u8 *pos, qc = 0;
+ size_t aad_len;
+ int a4_included, qc_included;
+ u8 aad[2 * AES_BLOCK_LEN];
+
+ a4_included = ieee80211_has_a4(hdr->frame_control);
+ qc_included = ieee80211_is_data_qos(hdr->frame_control);
+
+ aad_len = 22;
+ if (a4_included)
+ aad_len += 6;
+ if (qc_included) {
+ pos = (u8 *) & hdr->addr4;
+ if (a4_included)
+ pos += 6;
+ qc = *pos & 0x0f;
+ aad_len += 2;
+ }
+
+ /* CCM Initial Block:
+ * Flag (Include authentication header, M=3 (8-octet MIC),
+ * L=1 (2-octet Dlen))
+ * Nonce: 0x00 | A2 | PN
+ * Dlen */
+ b0[0] = 0x59;
+ b0[1] = qc;
+ memcpy(b0 + 2, hdr->addr2, ETH_ALEN);
+ memcpy(b0 + 8, pn, CCMP_PN_LEN);
+ b0[14] = (dlen >> 8) & 0xff;
+ b0[15] = dlen & 0xff;
+
+ /* AAD:
+ * FC with bits 4..6 and 11..13 masked to zero; 14 is always one
+ * A1 | A2 | A3
+ * SC with bits 4..15 (seq#) masked to zero
+ * A4 (if present)
+ * QC (if present)
+ */
+ pos = (u8 *) hdr;
+ aad[0] = 0; /* aad_len >> 8 */
+ aad[1] = aad_len & 0xff;
+ aad[2] = pos[0] & 0x8f;
+ aad[3] = pos[1] & 0xc7;
+ memcpy(aad + 4, hdr->addr1, 3 * ETH_ALEN);
+ pos = (u8 *) & hdr->seq_ctrl;
+ aad[22] = pos[0] & 0x0f;
+ aad[23] = 0; /* all bits masked */
+ memset(aad + 24, 0, 8);
+ if (a4_included)
+ memcpy(aad + 24, hdr->addr4, ETH_ALEN);
+ if (qc_included) {
+ aad[a4_included ? 30 : 24] = qc;
+ /* rest of QC masked */
+ }
+
+ /* Start with the first block and AAD */
+ lib80211_ccmp_aes_encrypt(tfm, b0, auth);
+ xor_block(auth, aad, AES_BLOCK_LEN);
+ lib80211_ccmp_aes_encrypt(tfm, auth, auth);
+ xor_block(auth, &aad[AES_BLOCK_LEN], AES_BLOCK_LEN);
+ lib80211_ccmp_aes_encrypt(tfm, auth, auth);
+ b0[0] &= 0x07;
+ b0[14] = b0[15] = 0;
+ lib80211_ccmp_aes_encrypt(tfm, b0, s0);
+}
+
+static int lib80211_ccmp_hdr(struct sk_buff *skb, int hdr_len,
+ u8 *aeskey, int keylen, void *priv)
+{
+ struct lib80211_ccmp_data *key = priv;
+ int i;
+ u8 *pos;
+
+ if (skb_headroom(skb) < CCMP_HDR_LEN || skb->len < hdr_len)
+ return -1;
+
+ if (aeskey != NULL && keylen >= CCMP_TK_LEN)
+ memcpy(aeskey, key->key, CCMP_TK_LEN);
+
+ pos = skb_push(skb, CCMP_HDR_LEN);
+ memmove(pos, pos + CCMP_HDR_LEN, hdr_len);
+ pos += hdr_len;
+
+ i = CCMP_PN_LEN - 1;
+ while (i >= 0) {
+ key->tx_pn[i]++;
+ if (key->tx_pn[i] != 0)
+ break;
+ i--;
+ }
+
+ *pos++ = key->tx_pn[5];
+ *pos++ = key->tx_pn[4];
+ *pos++ = 0;
+ *pos++ = (key->key_idx << 6) | (1 << 5) /* Ext IV included */ ;
+ *pos++ = key->tx_pn[3];
+ *pos++ = key->tx_pn[2];
+ *pos++ = key->tx_pn[1];
+ *pos++ = key->tx_pn[0];
+
+ return CCMP_HDR_LEN;
+}
+
+static int lib80211_ccmp_encrypt(struct sk_buff *skb, int hdr_len, void *priv)
+{
+ struct lib80211_ccmp_data *key = priv;
+ int data_len, i, blocks, last, len;
+ u8 *pos, *mic;
+ struct ieee80211_hdr *hdr;
+ u8 *b0 = key->tx_b0;
+ u8 *b = key->tx_b;
+ u8 *e = key->tx_e;
+ u8 *s0 = key->tx_s0;
+
+ if (skb_tailroom(skb) < CCMP_MIC_LEN || skb->len < hdr_len)
+ return -1;
+
+ data_len = skb->len - hdr_len;
+ len = lib80211_ccmp_hdr(skb, hdr_len, NULL, 0, priv);
+ if (len < 0)
+ return -1;
+
+ pos = skb->data + hdr_len + CCMP_HDR_LEN;
+ mic = skb_put(skb, CCMP_MIC_LEN);
+ hdr = (struct ieee80211_hdr *)skb->data;
+ ccmp_init_blocks(key->tfm, hdr, key->tx_pn, data_len, b0, b, s0);
+
+ blocks = DIV_ROUND_UP(data_len, AES_BLOCK_LEN);
+ last = data_len % AES_BLOCK_LEN;
+
+ for (i = 1; i <= blocks; i++) {
+ len = (i == blocks && last) ? last : AES_BLOCK_LEN;
+ /* Authentication */
+ xor_block(b, pos, len);
+ lib80211_ccmp_aes_encrypt(key->tfm, b, b);
+ /* Encryption, with counter */
+ b0[14] = (i >> 8) & 0xff;
+ b0[15] = i & 0xff;
+ lib80211_ccmp_aes_encrypt(key->tfm, b0, e);
+ xor_block(pos, e, len);
+ pos += len;
+ }
+
+ for (i = 0; i < CCMP_MIC_LEN; i++)
+ mic[i] = b[i] ^ s0[i];
+
+ return 0;
+}
+
+/*
+ * deal with seq counter wrapping correctly.
+ * refer to timer_after() for jiffies wrapping handling
+ */
+static inline int ccmp_replay_check(u8 *pn_n, u8 *pn_o)
+{
+ u32 iv32_n, iv16_n;
+ u32 iv32_o, iv16_o;
+
+ iv32_n = (pn_n[0] << 24) | (pn_n[1] << 16) | (pn_n[2] << 8) | pn_n[3];
+ iv16_n = (pn_n[4] << 8) | pn_n[5];
+
+ iv32_o = (pn_o[0] << 24) | (pn_o[1] << 16) | (pn_o[2] << 8) | pn_o[3];
+ iv16_o = (pn_o[4] << 8) | pn_o[5];
+
+ if ((s32)iv32_n - (s32)iv32_o < 0 ||
+ (iv32_n == iv32_o && iv16_n <= iv16_o))
+ return 1;
+ return 0;
+}
+
+static int lib80211_ccmp_decrypt(struct sk_buff *skb, int hdr_len, void *priv)
+{
+ struct lib80211_ccmp_data *key = priv;
+ u8 keyidx, *pos;
+ struct ieee80211_hdr *hdr;
+ u8 *b0 = key->rx_b0;
+ u8 *b = key->rx_b;
+ u8 *a = key->rx_a;
+ u8 pn[6];
+ int i, blocks, last, len;
+ size_t data_len = skb->len - hdr_len - CCMP_HDR_LEN - CCMP_MIC_LEN;
+ u8 *mic = skb->data + skb->len - CCMP_MIC_LEN;
+
+ if (skb->len < hdr_len + CCMP_HDR_LEN + CCMP_MIC_LEN) {
+ key->dot11RSNAStatsCCMPFormatErrors++;
+ return -1;
+ }
+
+ hdr = (struct ieee80211_hdr *)skb->data;
+ pos = skb->data + hdr_len;
+ keyidx = pos[3];
+ if (!(keyidx & (1 << 5))) {
+ if (net_ratelimit()) {
+ printk(KERN_DEBUG "CCMP: received packet without ExtIV"
+ " flag from %pM\n", hdr->addr2);
+ }
+ key->dot11RSNAStatsCCMPFormatErrors++;
+ return -2;
+ }
+ keyidx >>= 6;
+ if (key->key_idx != keyidx) {
+ printk(KERN_DEBUG "CCMP: RX tkey->key_idx=%d frame "
+ "keyidx=%d priv=%p\n", key->key_idx, keyidx, priv);
+ return -6;
+ }
+ if (!key->key_set) {
+ if (net_ratelimit()) {
+ printk(KERN_DEBUG "CCMP: received packet from %pM"
+ " with keyid=%d that does not have a configured"
+ " key\n", hdr->addr2, keyidx);
+ }
+ return -3;
+ }
+
+ pn[0] = pos[7];
+ pn[1] = pos[6];
+ pn[2] = pos[5];
+ pn[3] = pos[4];
+ pn[4] = pos[1];
+ pn[5] = pos[0];
+ pos += 8;
+
+ if (ccmp_replay_check(pn, key->rx_pn)) {
+ if (net_ratelimit()) {
+ printk(KERN_DEBUG "CCMP: replay detected: STA=%pM "
+ "previous PN %02x%02x%02x%02x%02x%02x "
+ "received PN %02x%02x%02x%02x%02x%02x\n",
+ hdr->addr2,
+ key->rx_pn[0], key->rx_pn[1], key->rx_pn[2],
+ key->rx_pn[3], key->rx_pn[4], key->rx_pn[5],
+ pn[0], pn[1], pn[2], pn[3], pn[4], pn[5]);
+ }
+ key->dot11RSNAStatsCCMPReplays++;
+ return -4;
+ }
+
+ ccmp_init_blocks(key->tfm, hdr, pn, data_len, b0, a, b);
+ xor_block(mic, b, CCMP_MIC_LEN);
+
+ blocks = DIV_ROUND_UP(data_len, AES_BLOCK_LEN);
+ last = data_len % AES_BLOCK_LEN;
+
+ for (i = 1; i <= blocks; i++) {
+ len = (i == blocks && last) ? last : AES_BLOCK_LEN;
+ /* Decrypt, with counter */
+ b0[14] = (i >> 8) & 0xff;
+ b0[15] = i & 0xff;
+ lib80211_ccmp_aes_encrypt(key->tfm, b0, b);
+ xor_block(pos, b, len);
+ /* Authentication */
+ xor_block(a, pos, len);
+ lib80211_ccmp_aes_encrypt(key->tfm, a, a);
+ pos += len;
+ }
+
+ if (memcmp(mic, a, CCMP_MIC_LEN) != 0) {
+ if (net_ratelimit()) {
+ printk(KERN_DEBUG "CCMP: decrypt failed: STA="
+ "%pM\n", hdr->addr2);
+ }
+ key->dot11RSNAStatsCCMPDecryptErrors++;
+ return -5;
+ }
+
+ memcpy(key->rx_pn, pn, CCMP_PN_LEN);
+
+ /* Remove hdr and MIC */
+ memmove(skb->data + CCMP_HDR_LEN, skb->data, hdr_len);
+ skb_pull(skb, CCMP_HDR_LEN);
+ skb_trim(skb, skb->len - CCMP_MIC_LEN);
+
+ return keyidx;
+}
+
+static int lib80211_ccmp_set_key(void *key, int len, u8 * seq, void *priv)
+{
+ struct lib80211_ccmp_data *data = priv;
+ int keyidx;
+ struct crypto_cipher *tfm = data->tfm;
+
+ keyidx = data->key_idx;
+ memset(data, 0, sizeof(*data));
+ data->key_idx = keyidx;
+ data->tfm = tfm;
+ if (len == CCMP_TK_LEN) {
+ memcpy(data->key, key, CCMP_TK_LEN);
+ data->key_set = 1;
+ if (seq) {
+ data->rx_pn[0] = seq[5];
+ data->rx_pn[1] = seq[4];
+ data->rx_pn[2] = seq[3];
+ data->rx_pn[3] = seq[2];
+ data->rx_pn[4] = seq[1];
+ data->rx_pn[5] = seq[0];
+ }
+ crypto_cipher_setkey(data->tfm, data->key, CCMP_TK_LEN);
+ } else if (len == 0)
+ data->key_set = 0;
+ else
+ return -1;
+
+ return 0;
+}
+
+static int lib80211_ccmp_get_key(void *key, int len, u8 * seq, void *priv)
+{
+ struct lib80211_ccmp_data *data = priv;
+
+ if (len < CCMP_TK_LEN)
+ return -1;
+
+ if (!data->key_set)
+ return 0;
+ memcpy(key, data->key, CCMP_TK_LEN);
+
+ if (seq) {
+ seq[0] = data->tx_pn[5];
+ seq[1] = data->tx_pn[4];
+ seq[2] = data->tx_pn[3];
+ seq[3] = data->tx_pn[2];
+ seq[4] = data->tx_pn[1];
+ seq[5] = data->tx_pn[0];
+ }
+
+ return CCMP_TK_LEN;
+}
+
+static char *lib80211_ccmp_print_stats(char *p, void *priv)
+{
+ struct lib80211_ccmp_data *ccmp = priv;
+
+ p += sprintf(p, "key[%d] alg=CCMP key_set=%d "
+ "tx_pn=%02x%02x%02x%02x%02x%02x "
+ "rx_pn=%02x%02x%02x%02x%02x%02x "
+ "format_errors=%d replays=%d decrypt_errors=%d\n",
+ ccmp->key_idx, ccmp->key_set,
+ ccmp->tx_pn[0], ccmp->tx_pn[1], ccmp->tx_pn[2],
+ ccmp->tx_pn[3], ccmp->tx_pn[4], ccmp->tx_pn[5],
+ ccmp->rx_pn[0], ccmp->rx_pn[1], ccmp->rx_pn[2],
+ ccmp->rx_pn[3], ccmp->rx_pn[4], ccmp->rx_pn[5],
+ ccmp->dot11RSNAStatsCCMPFormatErrors,
+ ccmp->dot11RSNAStatsCCMPReplays,
+ ccmp->dot11RSNAStatsCCMPDecryptErrors);
+
+ return p;
+}
+
+static struct lib80211_crypto_ops lib80211_crypt_ccmp = {
+ .name = "CCMP",
+ .init = lib80211_ccmp_init,
+ .deinit = lib80211_ccmp_deinit,
+ .build_iv = lib80211_ccmp_hdr,
+ .encrypt_mpdu = lib80211_ccmp_encrypt,
+ .decrypt_mpdu = lib80211_ccmp_decrypt,
+ .encrypt_msdu = NULL,
+ .decrypt_msdu = NULL,
+ .set_key = lib80211_ccmp_set_key,
+ .get_key = lib80211_ccmp_get_key,
+ .print_stats = lib80211_ccmp_print_stats,
+ .extra_mpdu_prefix_len = CCMP_HDR_LEN,
+ .extra_mpdu_postfix_len = CCMP_MIC_LEN,
+ .owner = THIS_MODULE,
+};
+
+static int __init lib80211_crypto_ccmp_init(void)
+{
+ return lib80211_register_crypto_ops(&lib80211_crypt_ccmp);
+}
+
+static void __exit lib80211_crypto_ccmp_exit(void)
+{
+ lib80211_unregister_crypto_ops(&lib80211_crypt_ccmp);
+}
+
+module_init(lib80211_crypto_ccmp_init);
+module_exit(lib80211_crypto_ccmp_exit);
diff --git a/net/wireless/lib80211_crypt_tkip.c b/net/wireless/lib80211_crypt_tkip.c
new file mode 100644
index 000000000000..7e8e22bfed90
--- /dev/null
+++ b/net/wireless/lib80211_crypt_tkip.c
@@ -0,0 +1,784 @@
+/*
+ * lib80211 crypt: host-based TKIP encryption implementation for lib80211
+ *
+ * Copyright (c) 2003-2004, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2008, John W. Linville <linville@tuxdriver.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. See README and COPYING for
+ * more details.
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <linux/scatterlist.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/mm.h>
+#include <linux/if_ether.h>
+#include <linux/if_arp.h>
+#include <asm/string.h>
+
+#include <linux/wireless.h>
+#include <linux/ieee80211.h>
+#include <net/iw_handler.h>
+
+#include <linux/crypto.h>
+#include <linux/crc32.h>
+
+#include <net/lib80211.h>
+
+MODULE_AUTHOR("Jouni Malinen");
+MODULE_DESCRIPTION("lib80211 crypt: TKIP");
+MODULE_LICENSE("GPL");
+
+struct lib80211_tkip_data {
+#define TKIP_KEY_LEN 32
+ u8 key[TKIP_KEY_LEN];
+ int key_set;
+
+ u32 tx_iv32;
+ u16 tx_iv16;
+ u16 tx_ttak[5];
+ int tx_phase1_done;
+
+ u32 rx_iv32;
+ u16 rx_iv16;
+ u16 rx_ttak[5];
+ int rx_phase1_done;
+ u32 rx_iv32_new;
+ u16 rx_iv16_new;
+
+ u32 dot11RSNAStatsTKIPReplays;
+ u32 dot11RSNAStatsTKIPICVErrors;
+ u32 dot11RSNAStatsTKIPLocalMICFailures;
+
+ int key_idx;
+
+ struct crypto_blkcipher *rx_tfm_arc4;
+ struct crypto_hash *rx_tfm_michael;
+ struct crypto_blkcipher *tx_tfm_arc4;
+ struct crypto_hash *tx_tfm_michael;
+
+ /* scratch buffers for virt_to_page() (crypto API) */
+ u8 rx_hdr[16], tx_hdr[16];
+
+ unsigned long flags;
+};
+
+static unsigned long lib80211_tkip_set_flags(unsigned long flags, void *priv)
+{
+ struct lib80211_tkip_data *_priv = priv;
+ unsigned long old_flags = _priv->flags;
+ _priv->flags = flags;
+ return old_flags;
+}
+
+static unsigned long lib80211_tkip_get_flags(void *priv)
+{
+ struct lib80211_tkip_data *_priv = priv;
+ return _priv->flags;
+}
+
+static void *lib80211_tkip_init(int key_idx)
+{
+ struct lib80211_tkip_data *priv;
+
+ priv = kzalloc(sizeof(*priv), GFP_ATOMIC);
+ if (priv == NULL)
+ goto fail;
+
+ priv->key_idx = key_idx;
+
+ priv->tx_tfm_arc4 = crypto_alloc_blkcipher("ecb(arc4)", 0,
+ CRYPTO_ALG_ASYNC);
+ if (IS_ERR(priv->tx_tfm_arc4)) {
+ printk(KERN_DEBUG "lib80211_crypt_tkip: could not allocate "
+ "crypto API arc4\n");
+ priv->tx_tfm_arc4 = NULL;
+ goto fail;
+ }
+
+ priv->tx_tfm_michael = crypto_alloc_hash("michael_mic", 0,
+ CRYPTO_ALG_ASYNC);
+ if (IS_ERR(priv->tx_tfm_michael)) {
+ printk(KERN_DEBUG "lib80211_crypt_tkip: could not allocate "
+ "crypto API michael_mic\n");
+ priv->tx_tfm_michael = NULL;
+ goto fail;
+ }
+
+ priv->rx_tfm_arc4 = crypto_alloc_blkcipher("ecb(arc4)", 0,
+ CRYPTO_ALG_ASYNC);
+ if (IS_ERR(priv->rx_tfm_arc4)) {
+ printk(KERN_DEBUG "lib80211_crypt_tkip: could not allocate "
+ "crypto API arc4\n");
+ priv->rx_tfm_arc4 = NULL;
+ goto fail;
+ }
+
+ priv->rx_tfm_michael = crypto_alloc_hash("michael_mic", 0,
+ CRYPTO_ALG_ASYNC);
+ if (IS_ERR(priv->rx_tfm_michael)) {
+ printk(KERN_DEBUG "lib80211_crypt_tkip: could not allocate "
+ "crypto API michael_mic\n");
+ priv->rx_tfm_michael = NULL;
+ goto fail;
+ }
+
+ return priv;
+
+ fail:
+ if (priv) {
+ if (priv->tx_tfm_michael)
+ crypto_free_hash(priv->tx_tfm_michael);
+ if (priv->tx_tfm_arc4)
+ crypto_free_blkcipher(priv->tx_tfm_arc4);
+ if (priv->rx_tfm_michael)
+ crypto_free_hash(priv->rx_tfm_michael);
+ if (priv->rx_tfm_arc4)
+ crypto_free_blkcipher(priv->rx_tfm_arc4);
+ kfree(priv);
+ }
+
+ return NULL;
+}
+
+static void lib80211_tkip_deinit(void *priv)
+{
+ struct lib80211_tkip_data *_priv = priv;
+ if (_priv) {
+ if (_priv->tx_tfm_michael)
+ crypto_free_hash(_priv->tx_tfm_michael);
+ if (_priv->tx_tfm_arc4)
+ crypto_free_blkcipher(_priv->tx_tfm_arc4);
+ if (_priv->rx_tfm_michael)
+ crypto_free_hash(_priv->rx_tfm_michael);
+ if (_priv->rx_tfm_arc4)
+ crypto_free_blkcipher(_priv->rx_tfm_arc4);
+ }
+ kfree(priv);
+}
+
+static inline u16 RotR1(u16 val)
+{
+ return (val >> 1) | (val << 15);
+}
+
+static inline u8 Lo8(u16 val)
+{
+ return val & 0xff;
+}
+
+static inline u8 Hi8(u16 val)
+{
+ return val >> 8;
+}
+
+static inline u16 Lo16(u32 val)
+{
+ return val & 0xffff;
+}
+
+static inline u16 Hi16(u32 val)
+{
+ return val >> 16;
+}
+
+static inline u16 Mk16(u8 hi, u8 lo)
+{
+ return lo | (((u16) hi) << 8);
+}
+
+static inline u16 Mk16_le(__le16 * v)
+{
+ return le16_to_cpu(*v);
+}
+
+static const u16 Sbox[256] = {
+ 0xC6A5, 0xF884, 0xEE99, 0xF68D, 0xFF0D, 0xD6BD, 0xDEB1, 0x9154,
+ 0x6050, 0x0203, 0xCEA9, 0x567D, 0xE719, 0xB562, 0x4DE6, 0xEC9A,
+ 0x8F45, 0x1F9D, 0x8940, 0xFA87, 0xEF15, 0xB2EB, 0x8EC9, 0xFB0B,
+ 0x41EC, 0xB367, 0x5FFD, 0x45EA, 0x23BF, 0x53F7, 0xE496, 0x9B5B,
+ 0x75C2, 0xE11C, 0x3DAE, 0x4C6A, 0x6C5A, 0x7E41, 0xF502, 0x834F,
+ 0x685C, 0x51F4, 0xD134, 0xF908, 0xE293, 0xAB73, 0x6253, 0x2A3F,
+ 0x080C, 0x9552, 0x4665, 0x9D5E, 0x3028, 0x37A1, 0x0A0F, 0x2FB5,
+ 0x0E09, 0x2436, 0x1B9B, 0xDF3D, 0xCD26, 0x4E69, 0x7FCD, 0xEA9F,
+ 0x121B, 0x1D9E, 0x5874, 0x342E, 0x362D, 0xDCB2, 0xB4EE, 0x5BFB,
+ 0xA4F6, 0x764D, 0xB761, 0x7DCE, 0x527B, 0xDD3E, 0x5E71, 0x1397,
+ 0xA6F5, 0xB968, 0x0000, 0xC12C, 0x4060, 0xE31F, 0x79C8, 0xB6ED,
+ 0xD4BE, 0x8D46, 0x67D9, 0x724B, 0x94DE, 0x98D4, 0xB0E8, 0x854A,
+ 0xBB6B, 0xC52A, 0x4FE5, 0xED16, 0x86C5, 0x9AD7, 0x6655, 0x1194,
+ 0x8ACF, 0xE910, 0x0406, 0xFE81, 0xA0F0, 0x7844, 0x25BA, 0x4BE3,
+ 0xA2F3, 0x5DFE, 0x80C0, 0x058A, 0x3FAD, 0x21BC, 0x7048, 0xF104,
+ 0x63DF, 0x77C1, 0xAF75, 0x4263, 0x2030, 0xE51A, 0xFD0E, 0xBF6D,
+ 0x814C, 0x1814, 0x2635, 0xC32F, 0xBEE1, 0x35A2, 0x88CC, 0x2E39,
+ 0x9357, 0x55F2, 0xFC82, 0x7A47, 0xC8AC, 0xBAE7, 0x322B, 0xE695,
+ 0xC0A0, 0x1998, 0x9ED1, 0xA37F, 0x4466, 0x547E, 0x3BAB, 0x0B83,
+ 0x8CCA, 0xC729, 0x6BD3, 0x283C, 0xA779, 0xBCE2, 0x161D, 0xAD76,
+ 0xDB3B, 0x6456, 0x744E, 0x141E, 0x92DB, 0x0C0A, 0x486C, 0xB8E4,
+ 0x9F5D, 0xBD6E, 0x43EF, 0xC4A6, 0x39A8, 0x31A4, 0xD337, 0xF28B,
+ 0xD532, 0x8B43, 0x6E59, 0xDAB7, 0x018C, 0xB164, 0x9CD2, 0x49E0,
+ 0xD8B4, 0xACFA, 0xF307, 0xCF25, 0xCAAF, 0xF48E, 0x47E9, 0x1018,
+ 0x6FD5, 0xF088, 0x4A6F, 0x5C72, 0x3824, 0x57F1, 0x73C7, 0x9751,
+ 0xCB23, 0xA17C, 0xE89C, 0x3E21, 0x96DD, 0x61DC, 0x0D86, 0x0F85,
+ 0xE090, 0x7C42, 0x71C4, 0xCCAA, 0x90D8, 0x0605, 0xF701, 0x1C12,
+ 0xC2A3, 0x6A5F, 0xAEF9, 0x69D0, 0x1791, 0x9958, 0x3A27, 0x27B9,
+ 0xD938, 0xEB13, 0x2BB3, 0x2233, 0xD2BB, 0xA970, 0x0789, 0x33A7,
+ 0x2DB6, 0x3C22, 0x1592, 0xC920, 0x8749, 0xAAFF, 0x5078, 0xA57A,
+ 0x038F, 0x59F8, 0x0980, 0x1A17, 0x65DA, 0xD731, 0x84C6, 0xD0B8,
+ 0x82C3, 0x29B0, 0x5A77, 0x1E11, 0x7BCB, 0xA8FC, 0x6DD6, 0x2C3A,
+};
+
+static inline u16 _S_(u16 v)
+{
+ u16 t = Sbox[Hi8(v)];
+ return Sbox[Lo8(v)] ^ ((t << 8) | (t >> 8));
+}
+
+#define PHASE1_LOOP_COUNT 8
+
+static void tkip_mixing_phase1(u16 * TTAK, const u8 * TK, const u8 * TA,
+ u32 IV32)
+{
+ int i, j;
+
+ /* Initialize the 80-bit TTAK from TSC (IV32) and TA[0..5] */
+ TTAK[0] = Lo16(IV32);
+ TTAK[1] = Hi16(IV32);
+ TTAK[2] = Mk16(TA[1], TA[0]);
+ TTAK[3] = Mk16(TA[3], TA[2]);
+ TTAK[4] = Mk16(TA[5], TA[4]);
+
+ for (i = 0; i < PHASE1_LOOP_COUNT; i++) {
+ j = 2 * (i & 1);
+ TTAK[0] += _S_(TTAK[4] ^ Mk16(TK[1 + j], TK[0 + j]));
+ TTAK[1] += _S_(TTAK[0] ^ Mk16(TK[5 + j], TK[4 + j]));
+ TTAK[2] += _S_(TTAK[1] ^ Mk16(TK[9 + j], TK[8 + j]));
+ TTAK[3] += _S_(TTAK[2] ^ Mk16(TK[13 + j], TK[12 + j]));
+ TTAK[4] += _S_(TTAK[3] ^ Mk16(TK[1 + j], TK[0 + j])) + i;
+ }
+}
+
+static void tkip_mixing_phase2(u8 * WEPSeed, const u8 * TK, const u16 * TTAK,
+ u16 IV16)
+{
+ /* Make temporary area overlap WEP seed so that the final copy can be
+ * avoided on little endian hosts. */
+ u16 *PPK = (u16 *) & WEPSeed[4];
+
+ /* Step 1 - make copy of TTAK and bring in TSC */
+ PPK[0] = TTAK[0];
+ PPK[1] = TTAK[1];
+ PPK[2] = TTAK[2];
+ PPK[3] = TTAK[3];
+ PPK[4] = TTAK[4];
+ PPK[5] = TTAK[4] + IV16;
+
+ /* Step 2 - 96-bit bijective mixing using S-box */
+ PPK[0] += _S_(PPK[5] ^ Mk16_le((__le16 *) & TK[0]));
+ PPK[1] += _S_(PPK[0] ^ Mk16_le((__le16 *) & TK[2]));
+ PPK[2] += _S_(PPK[1] ^ Mk16_le((__le16 *) & TK[4]));
+ PPK[3] += _S_(PPK[2] ^ Mk16_le((__le16 *) & TK[6]));
+ PPK[4] += _S_(PPK[3] ^ Mk16_le((__le16 *) & TK[8]));
+ PPK[5] += _S_(PPK[4] ^ Mk16_le((__le16 *) & TK[10]));
+
+ PPK[0] += RotR1(PPK[5] ^ Mk16_le((__le16 *) & TK[12]));
+ PPK[1] += RotR1(PPK[0] ^ Mk16_le((__le16 *) & TK[14]));
+ PPK[2] += RotR1(PPK[1]);
+ PPK[3] += RotR1(PPK[2]);
+ PPK[4] += RotR1(PPK[3]);
+ PPK[5] += RotR1(PPK[4]);
+
+ /* Step 3 - bring in last of TK bits, assign 24-bit WEP IV value
+ * WEPSeed[0..2] is transmitted as WEP IV */
+ WEPSeed[0] = Hi8(IV16);
+ WEPSeed[1] = (Hi8(IV16) | 0x20) & 0x7F;
+ WEPSeed[2] = Lo8(IV16);
+ WEPSeed[3] = Lo8((PPK[5] ^ Mk16_le((__le16 *) & TK[0])) >> 1);
+
+#ifdef __BIG_ENDIAN
+ {
+ int i;
+ for (i = 0; i < 6; i++)
+ PPK[i] = (PPK[i] << 8) | (PPK[i] >> 8);
+ }
+#endif
+}
+
+static int lib80211_tkip_hdr(struct sk_buff *skb, int hdr_len,
+ u8 * rc4key, int keylen, void *priv)
+{
+ struct lib80211_tkip_data *tkey = priv;
+ int len;
+ u8 *pos;
+ struct ieee80211_hdr *hdr;
+
+ hdr = (struct ieee80211_hdr *)skb->data;
+
+ if (skb_headroom(skb) < 8 || skb->len < hdr_len)
+ return -1;
+
+ if (rc4key == NULL || keylen < 16)
+ return -1;
+
+ if (!tkey->tx_phase1_done) {
+ tkip_mixing_phase1(tkey->tx_ttak, tkey->key, hdr->addr2,
+ tkey->tx_iv32);
+ tkey->tx_phase1_done = 1;
+ }
+ tkip_mixing_phase2(rc4key, tkey->key, tkey->tx_ttak, tkey->tx_iv16);
+
+ len = skb->len - hdr_len;
+ pos = skb_push(skb, 8);
+ memmove(pos, pos + 8, hdr_len);
+ pos += hdr_len;
+
+ *pos++ = *rc4key;
+ *pos++ = *(rc4key + 1);
+ *pos++ = *(rc4key + 2);
+ *pos++ = (tkey->key_idx << 6) | (1 << 5) /* Ext IV included */ ;
+ *pos++ = tkey->tx_iv32 & 0xff;
+ *pos++ = (tkey->tx_iv32 >> 8) & 0xff;
+ *pos++ = (tkey->tx_iv32 >> 16) & 0xff;
+ *pos++ = (tkey->tx_iv32 >> 24) & 0xff;
+
+ tkey->tx_iv16++;
+ if (tkey->tx_iv16 == 0) {
+ tkey->tx_phase1_done = 0;
+ tkey->tx_iv32++;
+ }
+
+ return 8;
+}
+
+static int lib80211_tkip_encrypt(struct sk_buff *skb, int hdr_len, void *priv)
+{
+ struct lib80211_tkip_data *tkey = priv;
+ struct blkcipher_desc desc = { .tfm = tkey->tx_tfm_arc4 };
+ int len;
+ u8 rc4key[16], *pos, *icv;
+ u32 crc;
+ struct scatterlist sg;
+
+ if (tkey->flags & IEEE80211_CRYPTO_TKIP_COUNTERMEASURES) {
+ if (net_ratelimit()) {
+ struct ieee80211_hdr *hdr =
+ (struct ieee80211_hdr *)skb->data;
+ printk(KERN_DEBUG ": TKIP countermeasures: dropped "
+ "TX packet to %pM\n", hdr->addr1);
+ }
+ return -1;
+ }
+
+ if (skb_tailroom(skb) < 4 || skb->len < hdr_len)
+ return -1;
+
+ len = skb->len - hdr_len;
+ pos = skb->data + hdr_len;
+
+ if ((lib80211_tkip_hdr(skb, hdr_len, rc4key, 16, priv)) < 0)
+ return -1;
+
+ icv = skb_put(skb, 4);
+
+ crc = ~crc32_le(~0, pos, len);
+ icv[0] = crc;
+ icv[1] = crc >> 8;
+ icv[2] = crc >> 16;
+ icv[3] = crc >> 24;
+
+ crypto_blkcipher_setkey(tkey->tx_tfm_arc4, rc4key, 16);
+ sg_init_one(&sg, pos, len + 4);
+ return crypto_blkcipher_encrypt(&desc, &sg, &sg, len + 4);
+}
+
+/*
+ * deal with seq counter wrapping correctly.
+ * refer to timer_after() for jiffies wrapping handling
+ */
+static inline int tkip_replay_check(u32 iv32_n, u16 iv16_n,
+ u32 iv32_o, u16 iv16_o)
+{
+ if ((s32)iv32_n - (s32)iv32_o < 0 ||
+ (iv32_n == iv32_o && iv16_n <= iv16_o))
+ return 1;
+ return 0;
+}
+
+static int lib80211_tkip_decrypt(struct sk_buff *skb, int hdr_len, void *priv)
+{
+ struct lib80211_tkip_data *tkey = priv;
+ struct blkcipher_desc desc = { .tfm = tkey->rx_tfm_arc4 };
+ u8 rc4key[16];
+ u8 keyidx, *pos;
+ u32 iv32;
+ u16 iv16;
+ struct ieee80211_hdr *hdr;
+ u8 icv[4];
+ u32 crc;
+ struct scatterlist sg;
+ int plen;
+
+ hdr = (struct ieee80211_hdr *)skb->data;
+
+ if (tkey->flags & IEEE80211_CRYPTO_TKIP_COUNTERMEASURES) {
+ if (net_ratelimit()) {
+ printk(KERN_DEBUG ": TKIP countermeasures: dropped "
+ "received packet from %pM\n", hdr->addr2);
+ }
+ return -1;
+ }
+
+ if (skb->len < hdr_len + 8 + 4)
+ return -1;
+
+ pos = skb->data + hdr_len;
+ keyidx = pos[3];
+ if (!(keyidx & (1 << 5))) {
+ if (net_ratelimit()) {
+ printk(KERN_DEBUG "TKIP: received packet without ExtIV"
+ " flag from %pM\n", hdr->addr2);
+ }
+ return -2;
+ }
+ keyidx >>= 6;
+ if (tkey->key_idx != keyidx) {
+ printk(KERN_DEBUG "TKIP: RX tkey->key_idx=%d frame "
+ "keyidx=%d priv=%p\n", tkey->key_idx, keyidx, priv);
+ return -6;
+ }
+ if (!tkey->key_set) {
+ if (net_ratelimit()) {
+ printk(KERN_DEBUG "TKIP: received packet from %pM"
+ " with keyid=%d that does not have a configured"
+ " key\n", hdr->addr2, keyidx);
+ }
+ return -3;
+ }
+ iv16 = (pos[0] << 8) | pos[2];
+ iv32 = pos[4] | (pos[5] << 8) | (pos[6] << 16) | (pos[7] << 24);
+ pos += 8;
+
+ if (tkip_replay_check(iv32, iv16, tkey->rx_iv32, tkey->rx_iv16)) {
+ if (net_ratelimit()) {
+ printk(KERN_DEBUG "TKIP: replay detected: STA=%pM"
+ " previous TSC %08x%04x received TSC "
+ "%08x%04x\n", hdr->addr2,
+ tkey->rx_iv32, tkey->rx_iv16, iv32, iv16);
+ }
+ tkey->dot11RSNAStatsTKIPReplays++;
+ return -4;
+ }
+
+ if (iv32 != tkey->rx_iv32 || !tkey->rx_phase1_done) {
+ tkip_mixing_phase1(tkey->rx_ttak, tkey->key, hdr->addr2, iv32);
+ tkey->rx_phase1_done = 1;
+ }
+ tkip_mixing_phase2(rc4key, tkey->key, tkey->rx_ttak, iv16);
+
+ plen = skb->len - hdr_len - 12;
+
+ crypto_blkcipher_setkey(tkey->rx_tfm_arc4, rc4key, 16);
+ sg_init_one(&sg, pos, plen + 4);
+ if (crypto_blkcipher_decrypt(&desc, &sg, &sg, plen + 4)) {
+ if (net_ratelimit()) {
+ printk(KERN_DEBUG ": TKIP: failed to decrypt "
+ "received packet from %pM\n",
+ hdr->addr2);
+ }
+ return -7;
+ }
+
+ crc = ~crc32_le(~0, pos, plen);
+ icv[0] = crc;
+ icv[1] = crc >> 8;
+ icv[2] = crc >> 16;
+ icv[3] = crc >> 24;
+ if (memcmp(icv, pos + plen, 4) != 0) {
+ if (iv32 != tkey->rx_iv32) {
+ /* Previously cached Phase1 result was already lost, so
+ * it needs to be recalculated for the next packet. */
+ tkey->rx_phase1_done = 0;
+ }
+ if (net_ratelimit()) {
+ printk(KERN_DEBUG "TKIP: ICV error detected: STA="
+ "%pM\n", hdr->addr2);
+ }
+ tkey->dot11RSNAStatsTKIPICVErrors++;
+ return -5;
+ }
+
+ /* Update real counters only after Michael MIC verification has
+ * completed */
+ tkey->rx_iv32_new = iv32;
+ tkey->rx_iv16_new = iv16;
+
+ /* Remove IV and ICV */
+ memmove(skb->data + 8, skb->data, hdr_len);
+ skb_pull(skb, 8);
+ skb_trim(skb, skb->len - 4);
+
+ return keyidx;
+}
+
+static int michael_mic(struct crypto_hash *tfm_michael, u8 * key, u8 * hdr,
+ u8 * data, size_t data_len, u8 * mic)
+{
+ struct hash_desc desc;
+ struct scatterlist sg[2];
+
+ if (tfm_michael == NULL) {
+ printk(KERN_WARNING "michael_mic: tfm_michael == NULL\n");
+ return -1;
+ }
+ sg_init_table(sg, 2);
+ sg_set_buf(&sg[0], hdr, 16);
+ sg_set_buf(&sg[1], data, data_len);
+
+ if (crypto_hash_setkey(tfm_michael, key, 8))
+ return -1;
+
+ desc.tfm = tfm_michael;
+ desc.flags = 0;
+ return crypto_hash_digest(&desc, sg, data_len + 16, mic);
+}
+
+static void michael_mic_hdr(struct sk_buff *skb, u8 * hdr)
+{
+ struct ieee80211_hdr *hdr11;
+
+ hdr11 = (struct ieee80211_hdr *)skb->data;
+
+ switch (le16_to_cpu(hdr11->frame_control) &
+ (IEEE80211_FCTL_FROMDS | IEEE80211_FCTL_TODS)) {
+ case IEEE80211_FCTL_TODS:
+ memcpy(hdr, hdr11->addr3, ETH_ALEN); /* DA */
+ memcpy(hdr + ETH_ALEN, hdr11->addr2, ETH_ALEN); /* SA */
+ break;
+ case IEEE80211_FCTL_FROMDS:
+ memcpy(hdr, hdr11->addr1, ETH_ALEN); /* DA */
+ memcpy(hdr + ETH_ALEN, hdr11->addr3, ETH_ALEN); /* SA */
+ break;
+ case IEEE80211_FCTL_FROMDS | IEEE80211_FCTL_TODS:
+ memcpy(hdr, hdr11->addr3, ETH_ALEN); /* DA */
+ memcpy(hdr + ETH_ALEN, hdr11->addr4, ETH_ALEN); /* SA */
+ break;
+ case 0:
+ memcpy(hdr, hdr11->addr1, ETH_ALEN); /* DA */
+ memcpy(hdr + ETH_ALEN, hdr11->addr2, ETH_ALEN); /* SA */
+ break;
+ }
+
+ if (ieee80211_is_data_qos(hdr11->frame_control)) {
+ hdr[12] = le16_to_cpu(*ieee80211_get_qos_ctl(hdr11))
+ & IEEE80211_QOS_CTL_TID_MASK;
+ } else
+ hdr[12] = 0; /* priority */
+
+ hdr[13] = hdr[14] = hdr[15] = 0; /* reserved */
+}
+
+static int lib80211_michael_mic_add(struct sk_buff *skb, int hdr_len,
+ void *priv)
+{
+ struct lib80211_tkip_data *tkey = priv;
+ u8 *pos;
+
+ if (skb_tailroom(skb) < 8 || skb->len < hdr_len) {
+ printk(KERN_DEBUG "Invalid packet for Michael MIC add "
+ "(tailroom=%d hdr_len=%d skb->len=%d)\n",
+ skb_tailroom(skb), hdr_len, skb->len);
+ return -1;
+ }
+
+ michael_mic_hdr(skb, tkey->tx_hdr);
+ pos = skb_put(skb, 8);
+ if (michael_mic(tkey->tx_tfm_michael, &tkey->key[16], tkey->tx_hdr,
+ skb->data + hdr_len, skb->len - 8 - hdr_len, pos))
+ return -1;
+
+ return 0;
+}
+
+static void lib80211_michael_mic_failure(struct net_device *dev,
+ struct ieee80211_hdr *hdr,
+ int keyidx)
+{
+ union iwreq_data wrqu;
+ struct iw_michaelmicfailure ev;
+
+ /* TODO: needed parameters: count, keyid, key type, TSC */
+ memset(&ev, 0, sizeof(ev));
+ ev.flags = keyidx & IW_MICFAILURE_KEY_ID;
+ if (hdr->addr1[0] & 0x01)
+ ev.flags |= IW_MICFAILURE_GROUP;
+ else
+ ev.flags |= IW_MICFAILURE_PAIRWISE;
+ ev.src_addr.sa_family = ARPHRD_ETHER;
+ memcpy(ev.src_addr.sa_data, hdr->addr2, ETH_ALEN);
+ memset(&wrqu, 0, sizeof(wrqu));
+ wrqu.data.length = sizeof(ev);
+ wireless_send_event(dev, IWEVMICHAELMICFAILURE, &wrqu, (char *)&ev);
+}
+
+static int lib80211_michael_mic_verify(struct sk_buff *skb, int keyidx,
+ int hdr_len, void *priv)
+{
+ struct lib80211_tkip_data *tkey = priv;
+ u8 mic[8];
+
+ if (!tkey->key_set)
+ return -1;
+
+ michael_mic_hdr(skb, tkey->rx_hdr);
+ if (michael_mic(tkey->rx_tfm_michael, &tkey->key[24], tkey->rx_hdr,
+ skb->data + hdr_len, skb->len - 8 - hdr_len, mic))
+ return -1;
+ if (memcmp(mic, skb->data + skb->len - 8, 8) != 0) {
+ struct ieee80211_hdr *hdr;
+ hdr = (struct ieee80211_hdr *)skb->data;
+ printk(KERN_DEBUG "%s: Michael MIC verification failed for "
+ "MSDU from %pM keyidx=%d\n",
+ skb->dev ? skb->dev->name : "N/A", hdr->addr2,
+ keyidx);
+ if (skb->dev)
+ lib80211_michael_mic_failure(skb->dev, hdr, keyidx);
+ tkey->dot11RSNAStatsTKIPLocalMICFailures++;
+ return -1;
+ }
+
+ /* Update TSC counters for RX now that the packet verification has
+ * completed. */
+ tkey->rx_iv32 = tkey->rx_iv32_new;
+ tkey->rx_iv16 = tkey->rx_iv16_new;
+
+ skb_trim(skb, skb->len - 8);
+
+ return 0;
+}
+
+static int lib80211_tkip_set_key(void *key, int len, u8 * seq, void *priv)
+{
+ struct lib80211_tkip_data *tkey = priv;
+ int keyidx;
+ struct crypto_hash *tfm = tkey->tx_tfm_michael;
+ struct crypto_blkcipher *tfm2 = tkey->tx_tfm_arc4;
+ struct crypto_hash *tfm3 = tkey->rx_tfm_michael;
+ struct crypto_blkcipher *tfm4 = tkey->rx_tfm_arc4;
+
+ keyidx = tkey->key_idx;
+ memset(tkey, 0, sizeof(*tkey));
+ tkey->key_idx = keyidx;
+ tkey->tx_tfm_michael = tfm;
+ tkey->tx_tfm_arc4 = tfm2;
+ tkey->rx_tfm_michael = tfm3;
+ tkey->rx_tfm_arc4 = tfm4;
+ if (len == TKIP_KEY_LEN) {
+ memcpy(tkey->key, key, TKIP_KEY_LEN);
+ tkey->key_set = 1;
+ tkey->tx_iv16 = 1; /* TSC is initialized to 1 */
+ if (seq) {
+ tkey->rx_iv32 = (seq[5] << 24) | (seq[4] << 16) |
+ (seq[3] << 8) | seq[2];
+ tkey->rx_iv16 = (seq[1] << 8) | seq[0];
+ }
+ } else if (len == 0)
+ tkey->key_set = 0;
+ else
+ return -1;
+
+ return 0;
+}
+
+static int lib80211_tkip_get_key(void *key, int len, u8 * seq, void *priv)
+{
+ struct lib80211_tkip_data *tkey = priv;
+
+ if (len < TKIP_KEY_LEN)
+ return -1;
+
+ if (!tkey->key_set)
+ return 0;
+ memcpy(key, tkey->key, TKIP_KEY_LEN);
+
+ if (seq) {
+ /* Return the sequence number of the last transmitted frame. */
+ u16 iv16 = tkey->tx_iv16;
+ u32 iv32 = tkey->tx_iv32;
+ if (iv16 == 0)
+ iv32--;
+ iv16--;
+ seq[0] = tkey->tx_iv16;
+ seq[1] = tkey->tx_iv16 >> 8;
+ seq[2] = tkey->tx_iv32;
+ seq[3] = tkey->tx_iv32 >> 8;
+ seq[4] = tkey->tx_iv32 >> 16;
+ seq[5] = tkey->tx_iv32 >> 24;
+ }
+
+ return TKIP_KEY_LEN;
+}
+
+static char *lib80211_tkip_print_stats(char *p, void *priv)
+{
+ struct lib80211_tkip_data *tkip = priv;
+ p += sprintf(p, "key[%d] alg=TKIP key_set=%d "
+ "tx_pn=%02x%02x%02x%02x%02x%02x "
+ "rx_pn=%02x%02x%02x%02x%02x%02x "
+ "replays=%d icv_errors=%d local_mic_failures=%d\n",
+ tkip->key_idx, tkip->key_set,
+ (tkip->tx_iv32 >> 24) & 0xff,
+ (tkip->tx_iv32 >> 16) & 0xff,
+ (tkip->tx_iv32 >> 8) & 0xff,
+ tkip->tx_iv32 & 0xff,
+ (tkip->tx_iv16 >> 8) & 0xff,
+ tkip->tx_iv16 & 0xff,
+ (tkip->rx_iv32 >> 24) & 0xff,
+ (tkip->rx_iv32 >> 16) & 0xff,
+ (tkip->rx_iv32 >> 8) & 0xff,
+ tkip->rx_iv32 & 0xff,
+ (tkip->rx_iv16 >> 8) & 0xff,
+ tkip->rx_iv16 & 0xff,
+ tkip->dot11RSNAStatsTKIPReplays,
+ tkip->dot11RSNAStatsTKIPICVErrors,
+ tkip->dot11RSNAStatsTKIPLocalMICFailures);
+ return p;
+}
+
+static struct lib80211_crypto_ops lib80211_crypt_tkip = {
+ .name = "TKIP",
+ .init = lib80211_tkip_init,
+ .deinit = lib80211_tkip_deinit,
+ .build_iv = lib80211_tkip_hdr,
+ .encrypt_mpdu = lib80211_tkip_encrypt,
+ .decrypt_mpdu = lib80211_tkip_decrypt,
+ .encrypt_msdu = lib80211_michael_mic_add,
+ .decrypt_msdu = lib80211_michael_mic_verify,
+ .set_key = lib80211_tkip_set_key,
+ .get_key = lib80211_tkip_get_key,
+ .print_stats = lib80211_tkip_print_stats,
+ .extra_mpdu_prefix_len = 4 + 4, /* IV + ExtIV */
+ .extra_mpdu_postfix_len = 4, /* ICV */
+ .extra_msdu_postfix_len = 8, /* MIC */
+ .get_flags = lib80211_tkip_get_flags,
+ .set_flags = lib80211_tkip_set_flags,
+ .owner = THIS_MODULE,
+};
+
+static int __init lib80211_crypto_tkip_init(void)
+{
+ return lib80211_register_crypto_ops(&lib80211_crypt_tkip);
+}
+
+static void __exit lib80211_crypto_tkip_exit(void)
+{
+ lib80211_unregister_crypto_ops(&lib80211_crypt_tkip);
+}
+
+module_init(lib80211_crypto_tkip_init);
+module_exit(lib80211_crypto_tkip_exit);
diff --git a/net/wireless/lib80211_crypt_wep.c b/net/wireless/lib80211_crypt_wep.c
new file mode 100644
index 000000000000..6d41e05ca33b
--- /dev/null
+++ b/net/wireless/lib80211_crypt_wep.c
@@ -0,0 +1,296 @@
+/*
+ * lib80211 crypt: host-based WEP encryption implementation for lib80211
+ *
+ * Copyright (c) 2002-2004, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2008, John W. Linville <linville@tuxdriver.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. See README and COPYING for
+ * more details.
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <linux/scatterlist.h>
+#include <linux/skbuff.h>
+#include <linux/mm.h>
+#include <asm/string.h>
+
+#include <net/lib80211.h>
+
+#include <linux/crypto.h>
+#include <linux/crc32.h>
+
+MODULE_AUTHOR("Jouni Malinen");
+MODULE_DESCRIPTION("lib80211 crypt: WEP");
+MODULE_LICENSE("GPL");
+
+struct lib80211_wep_data {
+ u32 iv;
+#define WEP_KEY_LEN 13
+ u8 key[WEP_KEY_LEN + 1];
+ u8 key_len;
+ u8 key_idx;
+ struct crypto_blkcipher *tx_tfm;
+ struct crypto_blkcipher *rx_tfm;
+};
+
+static void *lib80211_wep_init(int keyidx)
+{
+ struct lib80211_wep_data *priv;
+
+ priv = kzalloc(sizeof(*priv), GFP_ATOMIC);
+ if (priv == NULL)
+ goto fail;
+ priv->key_idx = keyidx;
+
+ priv->tx_tfm = crypto_alloc_blkcipher("ecb(arc4)", 0, CRYPTO_ALG_ASYNC);
+ if (IS_ERR(priv->tx_tfm)) {
+ printk(KERN_DEBUG "lib80211_crypt_wep: could not allocate "
+ "crypto API arc4\n");
+ priv->tx_tfm = NULL;
+ goto fail;
+ }
+
+ priv->rx_tfm = crypto_alloc_blkcipher("ecb(arc4)", 0, CRYPTO_ALG_ASYNC);
+ if (IS_ERR(priv->rx_tfm)) {
+ printk(KERN_DEBUG "lib80211_crypt_wep: could not allocate "
+ "crypto API arc4\n");
+ priv->rx_tfm = NULL;
+ goto fail;
+ }
+ /* start WEP IV from a random value */
+ get_random_bytes(&priv->iv, 4);
+
+ return priv;
+
+ fail:
+ if (priv) {
+ if (priv->tx_tfm)
+ crypto_free_blkcipher(priv->tx_tfm);
+ if (priv->rx_tfm)
+ crypto_free_blkcipher(priv->rx_tfm);
+ kfree(priv);
+ }
+ return NULL;
+}
+
+static void lib80211_wep_deinit(void *priv)
+{
+ struct lib80211_wep_data *_priv = priv;
+ if (_priv) {
+ if (_priv->tx_tfm)
+ crypto_free_blkcipher(_priv->tx_tfm);
+ if (_priv->rx_tfm)
+ crypto_free_blkcipher(_priv->rx_tfm);
+ }
+ kfree(priv);
+}
+
+/* Add WEP IV/key info to a frame that has at least 4 bytes of headroom */
+static int lib80211_wep_build_iv(struct sk_buff *skb, int hdr_len,
+ u8 *key, int keylen, void *priv)
+{
+ struct lib80211_wep_data *wep = priv;
+ u32 klen, len;
+ u8 *pos;
+
+ if (skb_headroom(skb) < 4 || skb->len < hdr_len)
+ return -1;
+
+ len = skb->len - hdr_len;
+ pos = skb_push(skb, 4);
+ memmove(pos, pos + 4, hdr_len);
+ pos += hdr_len;
+
+ klen = 3 + wep->key_len;
+
+ wep->iv++;
+
+ /* Fluhrer, Mantin, and Shamir have reported weaknesses in the key
+ * scheduling algorithm of RC4. At least IVs (KeyByte + 3, 0xff, N)
+ * can be used to speedup attacks, so avoid using them. */
+ if ((wep->iv & 0xff00) == 0xff00) {
+ u8 B = (wep->iv >> 16) & 0xff;
+ if (B >= 3 && B < klen)
+ wep->iv += 0x0100;
+ }
+
+ /* Prepend 24-bit IV to RC4 key and TX frame */
+ *pos++ = (wep->iv >> 16) & 0xff;
+ *pos++ = (wep->iv >> 8) & 0xff;
+ *pos++ = wep->iv & 0xff;
+ *pos++ = wep->key_idx << 6;
+
+ return 0;
+}
+
+/* Perform WEP encryption on given skb that has at least 4 bytes of headroom
+ * for IV and 4 bytes of tailroom for ICV. Both IV and ICV will be transmitted,
+ * so the payload length increases with 8 bytes.
+ *
+ * WEP frame payload: IV + TX key idx, RC4(data), ICV = RC4(CRC32(data))
+ */
+static int lib80211_wep_encrypt(struct sk_buff *skb, int hdr_len, void *priv)
+{
+ struct lib80211_wep_data *wep = priv;
+ struct blkcipher_desc desc = { .tfm = wep->tx_tfm };
+ u32 crc, klen, len;
+ u8 *pos, *icv;
+ struct scatterlist sg;
+ u8 key[WEP_KEY_LEN + 3];
+
+ /* other checks are in lib80211_wep_build_iv */
+ if (skb_tailroom(skb) < 4)
+ return -1;
+
+ /* add the IV to the frame */
+ if (lib80211_wep_build_iv(skb, hdr_len, NULL, 0, priv))
+ return -1;
+
+ /* Copy the IV into the first 3 bytes of the key */
+ skb_copy_from_linear_data_offset(skb, hdr_len, key, 3);
+
+ /* Copy rest of the WEP key (the secret part) */
+ memcpy(key + 3, wep->key, wep->key_len);
+
+ len = skb->len - hdr_len - 4;
+ pos = skb->data + hdr_len + 4;
+ klen = 3 + wep->key_len;
+
+ /* Append little-endian CRC32 over only the data and encrypt it to produce ICV */
+ crc = ~crc32_le(~0, pos, len);
+ icv = skb_put(skb, 4);
+ icv[0] = crc;
+ icv[1] = crc >> 8;
+ icv[2] = crc >> 16;
+ icv[3] = crc >> 24;
+
+ crypto_blkcipher_setkey(wep->tx_tfm, key, klen);
+ sg_init_one(&sg, pos, len + 4);
+ return crypto_blkcipher_encrypt(&desc, &sg, &sg, len + 4);
+}
+
+/* Perform WEP decryption on given buffer. Buffer includes whole WEP part of
+ * the frame: IV (4 bytes), encrypted payload (including SNAP header),
+ * ICV (4 bytes). len includes both IV and ICV.
+ *
+ * Returns 0 if frame was decrypted successfully and ICV was correct and -1 on
+ * failure. If frame is OK, IV and ICV will be removed.
+ */
+static int lib80211_wep_decrypt(struct sk_buff *skb, int hdr_len, void *priv)
+{
+ struct lib80211_wep_data *wep = priv;
+ struct blkcipher_desc desc = { .tfm = wep->rx_tfm };
+ u32 crc, klen, plen;
+ u8 key[WEP_KEY_LEN + 3];
+ u8 keyidx, *pos, icv[4];
+ struct scatterlist sg;
+
+ if (skb->len < hdr_len + 8)
+ return -1;
+
+ pos = skb->data + hdr_len;
+ key[0] = *pos++;
+ key[1] = *pos++;
+ key[2] = *pos++;
+ keyidx = *pos++ >> 6;
+ if (keyidx != wep->key_idx)
+ return -1;
+
+ klen = 3 + wep->key_len;
+
+ /* Copy rest of the WEP key (the secret part) */
+ memcpy(key + 3, wep->key, wep->key_len);
+
+ /* Apply RC4 to data and compute CRC32 over decrypted data */
+ plen = skb->len - hdr_len - 8;
+
+ crypto_blkcipher_setkey(wep->rx_tfm, key, klen);
+ sg_init_one(&sg, pos, plen + 4);
+ if (crypto_blkcipher_decrypt(&desc, &sg, &sg, plen + 4))
+ return -7;
+
+ crc = ~crc32_le(~0, pos, plen);
+ icv[0] = crc;
+ icv[1] = crc >> 8;
+ icv[2] = crc >> 16;
+ icv[3] = crc >> 24;
+ if (memcmp(icv, pos + plen, 4) != 0) {
+ /* ICV mismatch - drop frame */
+ return -2;
+ }
+
+ /* Remove IV and ICV */
+ memmove(skb->data + 4, skb->data, hdr_len);
+ skb_pull(skb, 4);
+ skb_trim(skb, skb->len - 4);
+
+ return 0;
+}
+
+static int lib80211_wep_set_key(void *key, int len, u8 * seq, void *priv)
+{
+ struct lib80211_wep_data *wep = priv;
+
+ if (len < 0 || len > WEP_KEY_LEN)
+ return -1;
+
+ memcpy(wep->key, key, len);
+ wep->key_len = len;
+
+ return 0;
+}
+
+static int lib80211_wep_get_key(void *key, int len, u8 * seq, void *priv)
+{
+ struct lib80211_wep_data *wep = priv;
+
+ if (len < wep->key_len)
+ return -1;
+
+ memcpy(key, wep->key, wep->key_len);
+
+ return wep->key_len;
+}
+
+static char *lib80211_wep_print_stats(char *p, void *priv)
+{
+ struct lib80211_wep_data *wep = priv;
+ p += sprintf(p, "key[%d] alg=WEP len=%d\n", wep->key_idx, wep->key_len);
+ return p;
+}
+
+static struct lib80211_crypto_ops lib80211_crypt_wep = {
+ .name = "WEP",
+ .init = lib80211_wep_init,
+ .deinit = lib80211_wep_deinit,
+ .build_iv = lib80211_wep_build_iv,
+ .encrypt_mpdu = lib80211_wep_encrypt,
+ .decrypt_mpdu = lib80211_wep_decrypt,
+ .encrypt_msdu = NULL,
+ .decrypt_msdu = NULL,
+ .set_key = lib80211_wep_set_key,
+ .get_key = lib80211_wep_get_key,
+ .print_stats = lib80211_wep_print_stats,
+ .extra_mpdu_prefix_len = 4, /* IV */
+ .extra_mpdu_postfix_len = 4, /* ICV */
+ .owner = THIS_MODULE,
+};
+
+static int __init lib80211_crypto_wep_init(void)
+{
+ return lib80211_register_crypto_ops(&lib80211_crypt_wep);
+}
+
+static void __exit lib80211_crypto_wep_exit(void)
+{
+ lib80211_unregister_crypto_ops(&lib80211_crypt_wep);
+}
+
+module_init(lib80211_crypto_wep_init);
+module_exit(lib80211_crypto_wep_exit);
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 572793c8c7ab..1e728fff474e 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -58,6 +58,9 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
[NL80211_ATTR_WIPHY] = { .type = NLA_U32 },
[NL80211_ATTR_WIPHY_NAME] = { .type = NLA_NUL_STRING,
.len = BUS_ID_SIZE-1 },
+ [NL80211_ATTR_WIPHY_TXQ_PARAMS] = { .type = NLA_NESTED },
+ [NL80211_ATTR_WIPHY_FREQ] = { .type = NLA_U32 },
+ [NL80211_ATTR_WIPHY_CHANNEL_TYPE] = { .type = NLA_U32 },
[NL80211_ATTR_IFTYPE] = { .type = NLA_U32 },
[NL80211_ATTR_IFINDEX] = { .type = NLA_U32 },
@@ -84,7 +87,7 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
.len = NL80211_MAX_SUPP_RATES },
[NL80211_ATTR_STA_PLINK_ACTION] = { .type = NLA_U8 },
[NL80211_ATTR_STA_VLAN] = { .type = NLA_U32 },
- [NL80211_ATTR_MNTR_FLAGS] = { .type = NLA_NESTED },
+ [NL80211_ATTR_MNTR_FLAGS] = { /* NLA_NESTED can't be empty */ },
[NL80211_ATTR_MESH_ID] = { .type = NLA_BINARY,
.len = IEEE80211_MAX_MESH_ID_LEN },
[NL80211_ATTR_MPATH_NEXT_HOP] = { .type = NLA_U32 },
@@ -95,6 +98,10 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
[NL80211_ATTR_BSS_CTS_PROT] = { .type = NLA_U8 },
[NL80211_ATTR_BSS_SHORT_PREAMBLE] = { .type = NLA_U8 },
[NL80211_ATTR_BSS_SHORT_SLOT_TIME] = { .type = NLA_U8 },
+ [NL80211_ATTR_BSS_BASIC_RATES] = { .type = NLA_BINARY,
+ .len = NL80211_MAX_SUPP_RATES },
+
+ [NL80211_ATTR_MESH_PARAMS] = { .type = NLA_NESTED },
[NL80211_ATTR_HT_CAPABILITY] = { .type = NLA_BINARY,
.len = NL80211_HT_CAPABILITY_LEN },
@@ -157,6 +164,19 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
if (!nl_band)
goto nla_put_failure;
+ /* add HT info */
+ if (dev->wiphy.bands[band]->ht_cap.ht_supported) {
+ NLA_PUT(msg, NL80211_BAND_ATTR_HT_MCS_SET,
+ sizeof(dev->wiphy.bands[band]->ht_cap.mcs),
+ &dev->wiphy.bands[band]->ht_cap.mcs);
+ NLA_PUT_U16(msg, NL80211_BAND_ATTR_HT_CAPA,
+ dev->wiphy.bands[band]->ht_cap.cap);
+ NLA_PUT_U8(msg, NL80211_BAND_ATTR_HT_AMPDU_FACTOR,
+ dev->wiphy.bands[band]->ht_cap.ampdu_factor);
+ NLA_PUT_U8(msg, NL80211_BAND_ATTR_HT_AMPDU_DENSITY,
+ dev->wiphy.bands[band]->ht_cap.ampdu_density);
+ }
+
/* add frequencies */
nl_freqs = nla_nest_start(msg, NL80211_BAND_ATTR_FREQS);
if (!nl_freqs)
@@ -180,6 +200,9 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
if (chan->flags & IEEE80211_CHAN_RADAR)
NLA_PUT_FLAG(msg, NL80211_FREQUENCY_ATTR_RADAR);
+ NLA_PUT_U32(msg, NL80211_FREQUENCY_ATTR_MAX_TX_POWER,
+ DBM_TO_MBM(chan->max_power));
+
nla_nest_end(msg, nl_freq);
}
@@ -269,20 +292,142 @@ static int nl80211_get_wiphy(struct sk_buff *skb, struct genl_info *info)
return -ENOBUFS;
}
+static const struct nla_policy txq_params_policy[NL80211_TXQ_ATTR_MAX + 1] = {
+ [NL80211_TXQ_ATTR_QUEUE] = { .type = NLA_U8 },
+ [NL80211_TXQ_ATTR_TXOP] = { .type = NLA_U16 },
+ [NL80211_TXQ_ATTR_CWMIN] = { .type = NLA_U16 },
+ [NL80211_TXQ_ATTR_CWMAX] = { .type = NLA_U16 },
+ [NL80211_TXQ_ATTR_AIFS] = { .type = NLA_U8 },
+};
+
+static int parse_txq_params(struct nlattr *tb[],
+ struct ieee80211_txq_params *txq_params)
+{
+ if (!tb[NL80211_TXQ_ATTR_QUEUE] || !tb[NL80211_TXQ_ATTR_TXOP] ||
+ !tb[NL80211_TXQ_ATTR_CWMIN] || !tb[NL80211_TXQ_ATTR_CWMAX] ||
+ !tb[NL80211_TXQ_ATTR_AIFS])
+ return -EINVAL;
+
+ txq_params->queue = nla_get_u8(tb[NL80211_TXQ_ATTR_QUEUE]);
+ txq_params->txop = nla_get_u16(tb[NL80211_TXQ_ATTR_TXOP]);
+ txq_params->cwmin = nla_get_u16(tb[NL80211_TXQ_ATTR_CWMIN]);
+ txq_params->cwmax = nla_get_u16(tb[NL80211_TXQ_ATTR_CWMAX]);
+ txq_params->aifs = nla_get_u8(tb[NL80211_TXQ_ATTR_AIFS]);
+
+ return 0;
+}
+
static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev;
- int result;
-
- if (!info->attrs[NL80211_ATTR_WIPHY_NAME])
- return -EINVAL;
+ int result = 0, rem_txq_params = 0;
+ struct nlattr *nl_txq_params;
rdev = cfg80211_get_dev_from_info(info);
if (IS_ERR(rdev))
return PTR_ERR(rdev);
- result = cfg80211_dev_rename(rdev, nla_data(info->attrs[NL80211_ATTR_WIPHY_NAME]));
+ if (info->attrs[NL80211_ATTR_WIPHY_NAME]) {
+ result = cfg80211_dev_rename(
+ rdev, nla_data(info->attrs[NL80211_ATTR_WIPHY_NAME]));
+ if (result)
+ goto bad_res;
+ }
+
+ if (info->attrs[NL80211_ATTR_WIPHY_TXQ_PARAMS]) {
+ struct ieee80211_txq_params txq_params;
+ struct nlattr *tb[NL80211_TXQ_ATTR_MAX + 1];
+
+ if (!rdev->ops->set_txq_params) {
+ result = -EOPNOTSUPP;
+ goto bad_res;
+ }
+
+ nla_for_each_nested(nl_txq_params,
+ info->attrs[NL80211_ATTR_WIPHY_TXQ_PARAMS],
+ rem_txq_params) {
+ nla_parse(tb, NL80211_TXQ_ATTR_MAX,
+ nla_data(nl_txq_params),
+ nla_len(nl_txq_params),
+ txq_params_policy);
+ result = parse_txq_params(tb, &txq_params);
+ if (result)
+ goto bad_res;
+
+ result = rdev->ops->set_txq_params(&rdev->wiphy,
+ &txq_params);
+ if (result)
+ goto bad_res;
+ }
+ }
+
+ if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) {
+ enum nl80211_channel_type channel_type = NL80211_CHAN_NO_HT;
+ struct ieee80211_channel *chan;
+ struct ieee80211_sta_ht_cap *ht_cap;
+ u32 freq, sec_freq;
+
+ if (!rdev->ops->set_channel) {
+ result = -EOPNOTSUPP;
+ goto bad_res;
+ }
+
+ result = -EINVAL;
+
+ if (info->attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]) {
+ channel_type = nla_get_u32(info->attrs[
+ NL80211_ATTR_WIPHY_CHANNEL_TYPE]);
+ if (channel_type != NL80211_CHAN_NO_HT &&
+ channel_type != NL80211_CHAN_HT20 &&
+ channel_type != NL80211_CHAN_HT40PLUS &&
+ channel_type != NL80211_CHAN_HT40MINUS)
+ goto bad_res;
+ }
+
+ freq = nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_FREQ]);
+ chan = ieee80211_get_channel(&rdev->wiphy, freq);
+
+ /* Primary channel not allowed */
+ if (!chan || chan->flags & IEEE80211_CHAN_DISABLED)
+ goto bad_res;
+
+ if (channel_type == NL80211_CHAN_HT40MINUS)
+ sec_freq = freq - 20;
+ else if (channel_type == NL80211_CHAN_HT40PLUS)
+ sec_freq = freq + 20;
+ else
+ sec_freq = 0;
+
+ ht_cap = &rdev->wiphy.bands[chan->band]->ht_cap;
+ /* no HT capabilities */
+ if (channel_type != NL80211_CHAN_NO_HT &&
+ !ht_cap->ht_supported)
+ goto bad_res;
+
+ if (sec_freq) {
+ struct ieee80211_channel *schan;
+
+ /* no 40 MHz capabilities */
+ if (!(ht_cap->cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) ||
+ (ht_cap->cap & IEEE80211_HT_CAP_40MHZ_INTOLERANT))
+ goto bad_res;
+
+ schan = ieee80211_get_channel(&rdev->wiphy, sec_freq);
+
+ /* Secondary channel not allowed */
+ if (!schan || schan->flags & IEEE80211_CHAN_DISABLED)
+ goto bad_res;
+ }
+
+ result = rdev->ops->set_channel(&rdev->wiphy, chan,
+ channel_type);
+ if (result)
+ goto bad_res;
+ }
+
+
+ bad_res:
cfg80211_put_dev(rdev);
return result;
}
@@ -945,12 +1090,46 @@ static int parse_station_flags(struct nlattr *nla, u32 *staflags)
return 0;
}
+static u16 nl80211_calculate_bitrate(struct rate_info *rate)
+{
+ int modulation, streams, bitrate;
+
+ if (!(rate->flags & RATE_INFO_FLAGS_MCS))
+ return rate->legacy;
+
+ /* the formula below does only work for MCS values smaller than 32 */
+ if (rate->mcs >= 32)
+ return 0;
+
+ modulation = rate->mcs & 7;
+ streams = (rate->mcs >> 3) + 1;
+
+ bitrate = (rate->flags & RATE_INFO_FLAGS_40_MHZ_WIDTH) ?
+ 13500000 : 6500000;
+
+ if (modulation < 4)
+ bitrate *= (modulation + 1);
+ else if (modulation == 4)
+ bitrate *= (modulation + 2);
+ else
+ bitrate *= (modulation + 3);
+
+ bitrate *= streams;
+
+ if (rate->flags & RATE_INFO_FLAGS_SHORT_GI)
+ bitrate = (bitrate / 9) * 10;
+
+ /* do NOT round down here */
+ return (bitrate + 50000) / 100000;
+}
+
static int nl80211_send_station(struct sk_buff *msg, u32 pid, u32 seq,
int flags, struct net_device *dev,
u8 *mac_addr, struct station_info *sinfo)
{
void *hdr;
- struct nlattr *sinfoattr;
+ struct nlattr *sinfoattr, *txrate;
+ u16 bitrate;
hdr = nl80211hdr_put(msg, pid, seq, flags, NL80211_CMD_NEW_STATION);
if (!hdr)
@@ -980,7 +1159,29 @@ static int nl80211_send_station(struct sk_buff *msg, u32 pid, u32 seq,
if (sinfo->filled & STATION_INFO_PLINK_STATE)
NLA_PUT_U8(msg, NL80211_STA_INFO_PLINK_STATE,
sinfo->plink_state);
+ if (sinfo->filled & STATION_INFO_SIGNAL)
+ NLA_PUT_U8(msg, NL80211_STA_INFO_SIGNAL,
+ sinfo->signal);
+ if (sinfo->filled & STATION_INFO_TX_BITRATE) {
+ txrate = nla_nest_start(msg, NL80211_STA_INFO_TX_BITRATE);
+ if (!txrate)
+ goto nla_put_failure;
+
+ /* nl80211_calculate_bitrate will return 0 for mcs >= 32 */
+ bitrate = nl80211_calculate_bitrate(&sinfo->txrate);
+ if (bitrate > 0)
+ NLA_PUT_U16(msg, NL80211_RATE_INFO_BITRATE, bitrate);
+
+ if (sinfo->txrate.flags & RATE_INFO_FLAGS_MCS)
+ NLA_PUT_U8(msg, NL80211_RATE_INFO_MCS,
+ sinfo->txrate.mcs);
+ if (sinfo->txrate.flags & RATE_INFO_FLAGS_40_MHZ_WIDTH)
+ NLA_PUT_FLAG(msg, NL80211_RATE_INFO_40_MHZ_WIDTH);
+ if (sinfo->txrate.flags & RATE_INFO_FLAGS_SHORT_GI)
+ NLA_PUT_FLAG(msg, NL80211_RATE_INFO_SHORT_GI);
+ nla_nest_end(msg, txrate);
+ }
nla_nest_end(msg, sinfoattr);
return genlmsg_end(msg, hdr);
@@ -1598,6 +1799,12 @@ static int nl80211_set_bss(struct sk_buff *skb, struct genl_info *info)
if (info->attrs[NL80211_ATTR_BSS_SHORT_SLOT_TIME])
params.use_short_slot_time =
nla_get_u8(info->attrs[NL80211_ATTR_BSS_SHORT_SLOT_TIME]);
+ if (info->attrs[NL80211_ATTR_BSS_BASIC_RATES]) {
+ params.basic_rates =
+ nla_data(info->attrs[NL80211_ATTR_BSS_BASIC_RATES]);
+ params.basic_rates_len =
+ nla_len(info->attrs[NL80211_ATTR_BSS_BASIC_RATES]);
+ }
err = get_drv_dev_by_info_ifindex(info->attrs, &drv, &dev);
if (err)
@@ -1680,11 +1887,188 @@ static int nl80211_req_set_reg(struct sk_buff *skb, struct genl_info *info)
return -EINVAL;
#endif
mutex_lock(&cfg80211_drv_mutex);
- r = __regulatory_hint(NULL, REGDOM_SET_BY_USER, data, NULL);
+ r = __regulatory_hint(NULL, REGDOM_SET_BY_USER, data, 0, ENVIRON_ANY);
mutex_unlock(&cfg80211_drv_mutex);
return r;
}
+static int nl80211_get_mesh_params(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct cfg80211_registered_device *drv;
+ struct mesh_config cur_params;
+ int err;
+ struct net_device *dev;
+ void *hdr;
+ struct nlattr *pinfoattr;
+ struct sk_buff *msg;
+
+ /* Look up our device */
+ err = get_drv_dev_by_info_ifindex(info->attrs, &drv, &dev);
+ if (err)
+ return err;
+
+ /* Get the mesh params */
+ rtnl_lock();
+ err = drv->ops->get_mesh_params(&drv->wiphy, dev, &cur_params);
+ rtnl_unlock();
+ if (err)
+ goto out;
+
+ /* Draw up a netlink message to send back */
+ msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!msg) {
+ err = -ENOBUFS;
+ goto out;
+ }
+ hdr = nl80211hdr_put(msg, info->snd_pid, info->snd_seq, 0,
+ NL80211_CMD_GET_MESH_PARAMS);
+ if (!hdr)
+ goto nla_put_failure;
+ pinfoattr = nla_nest_start(msg, NL80211_ATTR_MESH_PARAMS);
+ if (!pinfoattr)
+ goto nla_put_failure;
+ NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex);
+ NLA_PUT_U16(msg, NL80211_MESHCONF_RETRY_TIMEOUT,
+ cur_params.dot11MeshRetryTimeout);
+ NLA_PUT_U16(msg, NL80211_MESHCONF_CONFIRM_TIMEOUT,
+ cur_params.dot11MeshConfirmTimeout);
+ NLA_PUT_U16(msg, NL80211_MESHCONF_HOLDING_TIMEOUT,
+ cur_params.dot11MeshHoldingTimeout);
+ NLA_PUT_U16(msg, NL80211_MESHCONF_MAX_PEER_LINKS,
+ cur_params.dot11MeshMaxPeerLinks);
+ NLA_PUT_U8(msg, NL80211_MESHCONF_MAX_RETRIES,
+ cur_params.dot11MeshMaxRetries);
+ NLA_PUT_U8(msg, NL80211_MESHCONF_TTL,
+ cur_params.dot11MeshTTL);
+ NLA_PUT_U8(msg, NL80211_MESHCONF_AUTO_OPEN_PLINKS,
+ cur_params.auto_open_plinks);
+ NLA_PUT_U8(msg, NL80211_MESHCONF_HWMP_MAX_PREQ_RETRIES,
+ cur_params.dot11MeshHWMPmaxPREQretries);
+ NLA_PUT_U32(msg, NL80211_MESHCONF_PATH_REFRESH_TIME,
+ cur_params.path_refresh_time);
+ NLA_PUT_U16(msg, NL80211_MESHCONF_MIN_DISCOVERY_TIMEOUT,
+ cur_params.min_discovery_timeout);
+ NLA_PUT_U32(msg, NL80211_MESHCONF_HWMP_ACTIVE_PATH_TIMEOUT,
+ cur_params.dot11MeshHWMPactivePathTimeout);
+ NLA_PUT_U16(msg, NL80211_MESHCONF_HWMP_PREQ_MIN_INTERVAL,
+ cur_params.dot11MeshHWMPpreqMinInterval);
+ NLA_PUT_U16(msg, NL80211_MESHCONF_HWMP_NET_DIAM_TRVS_TIME,
+ cur_params.dot11MeshHWMPnetDiameterTraversalTime);
+ nla_nest_end(msg, pinfoattr);
+ genlmsg_end(msg, hdr);
+ err = genlmsg_unicast(msg, info->snd_pid);
+ goto out;
+
+nla_put_failure:
+ genlmsg_cancel(msg, hdr);
+ err = -EMSGSIZE;
+out:
+ /* Cleanup */
+ cfg80211_put_dev(drv);
+ dev_put(dev);
+ return err;
+}
+
+#define FILL_IN_MESH_PARAM_IF_SET(table, cfg, param, mask, attr_num, nla_fn) \
+do {\
+ if (table[attr_num]) {\
+ cfg.param = nla_fn(table[attr_num]); \
+ mask |= (1 << (attr_num - 1)); \
+ } \
+} while (0);\
+
+static struct nla_policy
+nl80211_meshconf_params_policy[NL80211_MESHCONF_ATTR_MAX+1] __read_mostly = {
+ [NL80211_MESHCONF_RETRY_TIMEOUT] = { .type = NLA_U16 },
+ [NL80211_MESHCONF_CONFIRM_TIMEOUT] = { .type = NLA_U16 },
+ [NL80211_MESHCONF_HOLDING_TIMEOUT] = { .type = NLA_U16 },
+ [NL80211_MESHCONF_MAX_PEER_LINKS] = { .type = NLA_U16 },
+ [NL80211_MESHCONF_MAX_RETRIES] = { .type = NLA_U8 },
+ [NL80211_MESHCONF_TTL] = { .type = NLA_U8 },
+ [NL80211_MESHCONF_AUTO_OPEN_PLINKS] = { .type = NLA_U8 },
+
+ [NL80211_MESHCONF_HWMP_MAX_PREQ_RETRIES] = { .type = NLA_U8 },
+ [NL80211_MESHCONF_PATH_REFRESH_TIME] = { .type = NLA_U32 },
+ [NL80211_MESHCONF_MIN_DISCOVERY_TIMEOUT] = { .type = NLA_U16 },
+ [NL80211_MESHCONF_HWMP_ACTIVE_PATH_TIMEOUT] = { .type = NLA_U32 },
+ [NL80211_MESHCONF_HWMP_PREQ_MIN_INTERVAL] = { .type = NLA_U16 },
+ [NL80211_MESHCONF_HWMP_NET_DIAM_TRVS_TIME] = { .type = NLA_U16 },
+};
+
+static int nl80211_set_mesh_params(struct sk_buff *skb, struct genl_info *info)
+{
+ int err;
+ u32 mask;
+ struct cfg80211_registered_device *drv;
+ struct net_device *dev;
+ struct mesh_config cfg;
+ struct nlattr *tb[NL80211_MESHCONF_ATTR_MAX + 1];
+ struct nlattr *parent_attr;
+
+ parent_attr = info->attrs[NL80211_ATTR_MESH_PARAMS];
+ if (!parent_attr)
+ return -EINVAL;
+ if (nla_parse_nested(tb, NL80211_MESHCONF_ATTR_MAX,
+ parent_attr, nl80211_meshconf_params_policy))
+ return -EINVAL;
+
+ err = get_drv_dev_by_info_ifindex(info->attrs, &drv, &dev);
+ if (err)
+ return err;
+
+ /* This makes sure that there aren't more than 32 mesh config
+ * parameters (otherwise our bitfield scheme would not work.) */
+ BUILD_BUG_ON(NL80211_MESHCONF_ATTR_MAX > 32);
+
+ /* Fill in the params struct */
+ mask = 0;
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshRetryTimeout,
+ mask, NL80211_MESHCONF_RETRY_TIMEOUT, nla_get_u16);
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshConfirmTimeout,
+ mask, NL80211_MESHCONF_CONFIRM_TIMEOUT, nla_get_u16);
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHoldingTimeout,
+ mask, NL80211_MESHCONF_HOLDING_TIMEOUT, nla_get_u16);
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshMaxPeerLinks,
+ mask, NL80211_MESHCONF_MAX_PEER_LINKS, nla_get_u16);
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshMaxRetries,
+ mask, NL80211_MESHCONF_MAX_RETRIES, nla_get_u8);
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshTTL,
+ mask, NL80211_MESHCONF_TTL, nla_get_u8);
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, auto_open_plinks,
+ mask, NL80211_MESHCONF_AUTO_OPEN_PLINKS, nla_get_u8);
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMPmaxPREQretries,
+ mask, NL80211_MESHCONF_HWMP_MAX_PREQ_RETRIES,
+ nla_get_u8);
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, path_refresh_time,
+ mask, NL80211_MESHCONF_PATH_REFRESH_TIME, nla_get_u32);
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, min_discovery_timeout,
+ mask, NL80211_MESHCONF_MIN_DISCOVERY_TIMEOUT,
+ nla_get_u16);
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMPactivePathTimeout,
+ mask, NL80211_MESHCONF_HWMP_ACTIVE_PATH_TIMEOUT,
+ nla_get_u32);
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMPpreqMinInterval,
+ mask, NL80211_MESHCONF_HWMP_PREQ_MIN_INTERVAL,
+ nla_get_u16);
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg,
+ dot11MeshHWMPnetDiameterTraversalTime,
+ mask, NL80211_MESHCONF_HWMP_NET_DIAM_TRVS_TIME,
+ nla_get_u16);
+
+ /* Apply changes */
+ rtnl_lock();
+ err = drv->ops->set_mesh_params(&drv->wiphy, dev, &cfg, mask);
+ rtnl_unlock();
+
+ /* cleanup */
+ cfg80211_put_dev(drv);
+ dev_put(dev);
+ return err;
+}
+
+#undef FILL_IN_MESH_PARAM_IF_SET
+
static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *tb[NL80211_REG_RULE_ATTR_MAX + 1];
@@ -1743,12 +2127,9 @@ static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)
mutex_lock(&cfg80211_drv_mutex);
r = set_regdom(rd);
mutex_unlock(&cfg80211_drv_mutex);
- if (r)
- goto bad_reg;
-
return r;
-bad_reg:
+ bad_reg:
kfree(rd);
return -EINVAL;
}
@@ -1902,6 +2283,18 @@ static struct genl_ops nl80211_ops[] = {
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
},
+ {
+ .cmd = NL80211_CMD_GET_MESH_PARAMS,
+ .doit = nl80211_get_mesh_params,
+ .policy = nl80211_policy,
+ /* can be retrieved by unprivileged users */
+ },
+ {
+ .cmd = NL80211_CMD_SET_MESH_PARAMS,
+ .doit = nl80211_set_mesh_params,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
};
/* multicast groups */
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index eb3b1a9f9b12..4f877535e666 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -42,17 +42,40 @@
#include "core.h"
#include "reg.h"
-/* wiphy is set if this request's initiator is REGDOM_SET_BY_DRIVER */
+/**
+ * struct regulatory_request - receipt of last regulatory request
+ *
+ * @wiphy: this is set if this request's initiator is
+ * %REGDOM_SET_BY_COUNTRY_IE or %REGDOM_SET_BY_DRIVER. This
+ * can be used by the wireless core to deal with conflicts
+ * and potentially inform users of which devices specifically
+ * cased the conflicts.
+ * @initiator: indicates who sent this request, could be any of
+ * of those set in reg_set_by, %REGDOM_SET_BY_*
+ * @alpha2: the ISO / IEC 3166 alpha2 country code of the requested
+ * regulatory domain. We have a few special codes:
+ * 00 - World regulatory domain
+ * 99 - built by driver but a specific alpha2 cannot be determined
+ * 98 - result of an intersection between two regulatory domains
+ * @intersect: indicates whether the wireless core should intersect
+ * the requested regulatory domain with the presently set regulatory
+ * domain.
+ * @country_ie_checksum: checksum of the last processed and accepted
+ * country IE
+ * @country_ie_env: lets us know if the AP is telling us we are outdoor,
+ * indoor, or if it doesn't matter
+ */
struct regulatory_request {
- struct list_head list;
struct wiphy *wiphy;
- int granted;
enum reg_set_by initiator;
char alpha2[2];
+ bool intersect;
+ u32 country_ie_checksum;
+ enum environment_cap country_ie_env;
};
-static LIST_HEAD(regulatory_requests);
-DEFINE_MUTEX(cfg80211_reg_mutex);
+/* Receipt of information from last regulatory request */
+static struct regulatory_request *last_request;
/* To trigger userspace events */
static struct platform_device *reg_pdev;
@@ -63,13 +86,16 @@ static u32 supported_bandwidths[] = {
MHZ_TO_KHZ(20),
};
-static struct list_head regulatory_requests;
-
/* Central wireless core regulatory domains, we only need two,
* the current one and a world regulatory domain in case we have no
* information to give us an alpha2 */
static const struct ieee80211_regdomain *cfg80211_regdomain;
+/* We use this as a place for the rd structure built from the
+ * last parsed country IE to rest until CRDA gets back to us with
+ * what it thinks should apply for the same country */
+static const struct ieee80211_regdomain *country_ie_regdomain;
+
/* We keep a static world regulatory domain in case of the absence of CRDA */
static const struct ieee80211_regdomain world_regdom = {
.n_reg_rules = 1,
@@ -204,7 +230,7 @@ static void reset_regdomains(void)
* core upon initialization */
static void update_world_regdomain(const struct ieee80211_regdomain *rd)
{
- BUG_ON(list_empty(&regulatory_requests));
+ BUG_ON(!last_request);
reset_regdomains();
@@ -249,6 +275,18 @@ static bool is_unknown_alpha2(const char *alpha2)
return false;
}
+static bool is_intersected_alpha2(const char *alpha2)
+{
+ if (!alpha2)
+ return false;
+ /* Special case where regulatory domain is the
+ * result of an intersection between two regulatory domain
+ * structures */
+ if (alpha2[0] == '9' && alpha2[1] == '8')
+ return true;
+ return false;
+}
+
static bool is_an_alpha2(const char *alpha2)
{
if (!alpha2)
@@ -277,6 +315,25 @@ static bool regdom_changed(const char *alpha2)
return true;
}
+/**
+ * country_ie_integrity_changes - tells us if the country IE has changed
+ * @checksum: checksum of country IE of fields we are interested in
+ *
+ * If the country IE has not changed you can ignore it safely. This is
+ * useful to determine if two devices are seeing two different country IEs
+ * even on the same alpha2. Note that this will return false if no IE has
+ * been set on the wireless core yet.
+ */
+static bool country_ie_integrity_changes(u32 checksum)
+{
+ /* If no IE has been set then the checksum doesn't change */
+ if (unlikely(!last_request->country_ie_checksum))
+ return false;
+ if (unlikely(last_request->country_ie_checksum != checksum))
+ return true;
+ return false;
+}
+
/* This lets us keep regulatory code which is updated on a regulatory
* basis in userspace. */
static int call_crda(const char *alpha2)
@@ -300,121 +357,13 @@ static int call_crda(const char *alpha2)
return kobject_uevent_env(&reg_pdev->dev.kobj, KOBJ_CHANGE, envp);
}
-/* This has the logic which determines when a new request
- * should be ignored. */
-static int ignore_request(struct wiphy *wiphy, enum reg_set_by set_by,
- char *alpha2, struct ieee80211_regdomain *rd)
-{
- struct regulatory_request *last_request = NULL;
-
- /* All initial requests are respected */
- if (list_empty(&regulatory_requests))
- return 0;
-
- last_request = list_first_entry(&regulatory_requests,
- struct regulatory_request, list);
-
- switch (set_by) {
- case REGDOM_SET_BY_INIT:
- return -EINVAL;
- case REGDOM_SET_BY_CORE:
- /* Always respect new wireless core hints, should only
- * come in for updating the world regulatory domain at init
- * anyway */
- return 0;
- case REGDOM_SET_BY_COUNTRY_IE:
- if (last_request->initiator == set_by) {
- if (last_request->wiphy != wiphy) {
- /* Two cards with two APs claiming different
- * different Country IE alpha2s!
- * You're special!! */
- if (!alpha2_equal(last_request->alpha2,
- cfg80211_regdomain->alpha2)) {
- /* XXX: Deal with conflict, consider
- * building a new one out of the
- * intersection */
- WARN_ON(1);
- return -EOPNOTSUPP;
- }
- return -EALREADY;
- }
- /* Two consecutive Country IE hints on the same wiphy */
- if (!alpha2_equal(cfg80211_regdomain->alpha2, alpha2))
- return 0;
- return -EALREADY;
- }
- if (WARN(!is_alpha2_set(alpha2) || !is_an_alpha2(alpha2),
- "Invalid Country IE regulatory hint passed "
- "to the wireless core\n"))
- return -EINVAL;
- /* We ignore Country IE hints for now, as we haven't yet
- * added the dot11MultiDomainCapabilityEnabled flag
- * for wiphys */
- return 1;
- case REGDOM_SET_BY_DRIVER:
- BUG_ON(!wiphy);
- if (last_request->initiator == set_by) {
- /* Two separate drivers hinting different things,
- * this is possible if you have two devices present
- * on a system with different EEPROM regulatory
- * readings. XXX: Do intersection, we support only
- * the first regulatory hint for now */
- if (last_request->wiphy != wiphy)
- return -EALREADY;
- if (rd)
- return -EALREADY;
- /* Driver should not be trying to hint different
- * regulatory domains! */
- BUG_ON(!alpha2_equal(alpha2,
- cfg80211_regdomain->alpha2));
- return -EALREADY;
- }
- if (last_request->initiator == REGDOM_SET_BY_CORE)
- return 0;
- /* XXX: Handle intersection, and add the
- * dot11MultiDomainCapabilityEnabled flag to wiphy. For now
- * we assume the driver has this set to false, following the
- * 802.11d dot11MultiDomainCapabilityEnabled documentation */
- if (last_request->initiator == REGDOM_SET_BY_COUNTRY_IE)
- return 0;
- return 0;
- case REGDOM_SET_BY_USER:
- if (last_request->initiator == set_by ||
- last_request->initiator == REGDOM_SET_BY_CORE)
- return 0;
- /* Drivers can use their wiphy's reg_notifier()
- * to override any information */
- if (last_request->initiator == REGDOM_SET_BY_DRIVER)
- return 0;
- /* XXX: Handle intersection */
- if (last_request->initiator == REGDOM_SET_BY_COUNTRY_IE)
- return -EOPNOTSUPP;
- return 0;
- default:
- return -EINVAL;
- }
-}
-
-static bool __reg_is_valid_request(const char *alpha2,
- struct regulatory_request **request)
-{
- struct regulatory_request *req;
- if (list_empty(&regulatory_requests))
- return false;
- list_for_each_entry(req, &regulatory_requests, list) {
- if (alpha2_equal(req->alpha2, alpha2)) {
- *request = req;
- return true;
- }
- }
- return false;
-}
-
/* Used by nl80211 before kmalloc'ing our regulatory domain */
bool reg_is_valid_request(const char *alpha2)
{
- struct regulatory_request *request = NULL;
- return __reg_is_valid_request(alpha2, &request);
+ if (!last_request)
+ return false;
+
+ return alpha2_equal(last_request->alpha2, alpha2);
}
/* Sanity check on a regulatory rule */
@@ -423,7 +372,7 @@ static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule)
const struct ieee80211_freq_range *freq_range = &rule->freq_range;
u32 freq_diff;
- if (freq_range->start_freq_khz == 0 || freq_range->end_freq_khz == 0)
+ if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0)
return false;
if (freq_range->start_freq_khz > freq_range->end_freq_khz)
@@ -431,7 +380,7 @@ static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule)
freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz;
- if (freq_range->max_bandwidth_khz > freq_diff)
+ if (freq_diff <= 0 || freq_range->max_bandwidth_khz > freq_diff)
return false;
return true;
@@ -445,6 +394,9 @@ static bool is_valid_rd(const struct ieee80211_regdomain *rd)
if (!rd->n_reg_rules)
return false;
+ if (WARN_ON(rd->n_reg_rules > NL80211_MAX_SUPP_REG_RULES))
+ return false;
+
for (i = 0; i < rd->n_reg_rules; i++) {
reg_rule = &rd->reg_rules[i];
if (!is_valid_reg_rule(reg_rule))
@@ -469,6 +421,311 @@ static u32 freq_max_bandwidth(const struct ieee80211_freq_range *freq_range,
return 0;
}
+/* Converts a country IE to a regulatory domain. A regulatory domain
+ * structure has a lot of information which the IE doesn't yet have,
+ * so for the other values we use upper max values as we will intersect
+ * with our userspace regulatory agent to get lower bounds. */
+static struct ieee80211_regdomain *country_ie_2_rd(
+ u8 *country_ie,
+ u8 country_ie_len,
+ u32 *checksum)
+{
+ struct ieee80211_regdomain *rd = NULL;
+ unsigned int i = 0;
+ char alpha2[2];
+ u32 flags = 0;
+ u32 num_rules = 0, size_of_regd = 0;
+ u8 *triplets_start = NULL;
+ u8 len_at_triplet = 0;
+ /* the last channel we have registered in a subband (triplet) */
+ int last_sub_max_channel = 0;
+
+ *checksum = 0xDEADBEEF;
+
+ /* Country IE requirements */
+ BUG_ON(country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN ||
+ country_ie_len & 0x01);
+
+ alpha2[0] = country_ie[0];
+ alpha2[1] = country_ie[1];
+
+ /*
+ * Third octet can be:
+ * 'I' - Indoor
+ * 'O' - Outdoor
+ *
+ * anything else we assume is no restrictions
+ */
+ if (country_ie[2] == 'I')
+ flags = NL80211_RRF_NO_OUTDOOR;
+ else if (country_ie[2] == 'O')
+ flags = NL80211_RRF_NO_INDOOR;
+
+ country_ie += 3;
+ country_ie_len -= 3;
+
+ triplets_start = country_ie;
+ len_at_triplet = country_ie_len;
+
+ *checksum ^= ((flags ^ alpha2[0] ^ alpha2[1]) << 8);
+
+ /* We need to build a reg rule for each triplet, but first we must
+ * calculate the number of reg rules we will need. We will need one
+ * for each channel subband */
+ while (country_ie_len >= 3) {
+ struct ieee80211_country_ie_triplet *triplet =
+ (struct ieee80211_country_ie_triplet *) country_ie;
+ int cur_sub_max_channel = 0, cur_channel = 0;
+
+ if (triplet->ext.reg_extension_id >=
+ IEEE80211_COUNTRY_EXTENSION_ID) {
+ country_ie += 3;
+ country_ie_len -= 3;
+ continue;
+ }
+
+ cur_channel = triplet->chans.first_channel;
+ cur_sub_max_channel = ieee80211_channel_to_frequency(
+ cur_channel + triplet->chans.num_channels);
+
+ /* Basic sanity check */
+ if (cur_sub_max_channel < cur_channel)
+ return NULL;
+
+ /* Do not allow overlapping channels. Also channels
+ * passed in each subband must be monotonically
+ * increasing */
+ if (last_sub_max_channel) {
+ if (cur_channel <= last_sub_max_channel)
+ return NULL;
+ if (cur_sub_max_channel <= last_sub_max_channel)
+ return NULL;
+ }
+
+ /* When dot11RegulatoryClassesRequired is supported
+ * we can throw ext triplets as part of this soup,
+ * for now we don't care when those change as we
+ * don't support them */
+ *checksum ^= ((cur_channel ^ cur_sub_max_channel) << 8) |
+ ((cur_sub_max_channel ^ cur_sub_max_channel) << 16) |
+ ((triplet->chans.max_power ^ cur_sub_max_channel) << 24);
+
+ last_sub_max_channel = cur_sub_max_channel;
+
+ country_ie += 3;
+ country_ie_len -= 3;
+ num_rules++;
+
+ /* Note: this is not a IEEE requirement but
+ * simply a memory requirement */
+ if (num_rules > NL80211_MAX_SUPP_REG_RULES)
+ return NULL;
+ }
+
+ country_ie = triplets_start;
+ country_ie_len = len_at_triplet;
+
+ size_of_regd = sizeof(struct ieee80211_regdomain) +
+ (num_rules * sizeof(struct ieee80211_reg_rule));
+
+ rd = kzalloc(size_of_regd, GFP_KERNEL);
+ if (!rd)
+ return NULL;
+
+ rd->n_reg_rules = num_rules;
+ rd->alpha2[0] = alpha2[0];
+ rd->alpha2[1] = alpha2[1];
+
+ /* This time around we fill in the rd */
+ while (country_ie_len >= 3) {
+ struct ieee80211_country_ie_triplet *triplet =
+ (struct ieee80211_country_ie_triplet *) country_ie;
+ struct ieee80211_reg_rule *reg_rule = NULL;
+ struct ieee80211_freq_range *freq_range = NULL;
+ struct ieee80211_power_rule *power_rule = NULL;
+
+ /* Must parse if dot11RegulatoryClassesRequired is true,
+ * we don't support this yet */
+ if (triplet->ext.reg_extension_id >=
+ IEEE80211_COUNTRY_EXTENSION_ID) {
+ country_ie += 3;
+ country_ie_len -= 3;
+ continue;
+ }
+
+ reg_rule = &rd->reg_rules[i];
+ freq_range = &reg_rule->freq_range;
+ power_rule = &reg_rule->power_rule;
+
+ reg_rule->flags = flags;
+
+ /* The +10 is since the regulatory domain expects
+ * the actual band edge, not the center of freq for
+ * its start and end freqs, assuming 20 MHz bandwidth on
+ * the channels passed */
+ freq_range->start_freq_khz =
+ MHZ_TO_KHZ(ieee80211_channel_to_frequency(
+ triplet->chans.first_channel) - 10);
+ freq_range->end_freq_khz =
+ MHZ_TO_KHZ(ieee80211_channel_to_frequency(
+ triplet->chans.first_channel +
+ triplet->chans.num_channels) + 10);
+
+ /* Large arbitrary values, we intersect later */
+ /* Increment this if we ever support >= 40 MHz channels
+ * in IEEE 802.11 */
+ freq_range->max_bandwidth_khz = MHZ_TO_KHZ(40);
+ power_rule->max_antenna_gain = DBI_TO_MBI(100);
+ power_rule->max_eirp = DBM_TO_MBM(100);
+
+ country_ie += 3;
+ country_ie_len -= 3;
+ i++;
+
+ BUG_ON(i > NL80211_MAX_SUPP_REG_RULES);
+ }
+
+ return rd;
+}
+
+
+/* Helper for regdom_intersect(), this does the real
+ * mathematical intersection fun */
+static int reg_rules_intersect(
+ const struct ieee80211_reg_rule *rule1,
+ const struct ieee80211_reg_rule *rule2,
+ struct ieee80211_reg_rule *intersected_rule)
+{
+ const struct ieee80211_freq_range *freq_range1, *freq_range2;
+ struct ieee80211_freq_range *freq_range;
+ const struct ieee80211_power_rule *power_rule1, *power_rule2;
+ struct ieee80211_power_rule *power_rule;
+ u32 freq_diff;
+
+ freq_range1 = &rule1->freq_range;
+ freq_range2 = &rule2->freq_range;
+ freq_range = &intersected_rule->freq_range;
+
+ power_rule1 = &rule1->power_rule;
+ power_rule2 = &rule2->power_rule;
+ power_rule = &intersected_rule->power_rule;
+
+ freq_range->start_freq_khz = max(freq_range1->start_freq_khz,
+ freq_range2->start_freq_khz);
+ freq_range->end_freq_khz = min(freq_range1->end_freq_khz,
+ freq_range2->end_freq_khz);
+ freq_range->max_bandwidth_khz = min(freq_range1->max_bandwidth_khz,
+ freq_range2->max_bandwidth_khz);
+
+ freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz;
+ if (freq_range->max_bandwidth_khz > freq_diff)
+ freq_range->max_bandwidth_khz = freq_diff;
+
+ power_rule->max_eirp = min(power_rule1->max_eirp,
+ power_rule2->max_eirp);
+ power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain,
+ power_rule2->max_antenna_gain);
+
+ intersected_rule->flags = (rule1->flags | rule2->flags);
+
+ if (!is_valid_reg_rule(intersected_rule))
+ return -EINVAL;
+
+ return 0;
+}
+
+/**
+ * regdom_intersect - do the intersection between two regulatory domains
+ * @rd1: first regulatory domain
+ * @rd2: second regulatory domain
+ *
+ * Use this function to get the intersection between two regulatory domains.
+ * Once completed we will mark the alpha2 for the rd as intersected, "98",
+ * as no one single alpha2 can represent this regulatory domain.
+ *
+ * Returns a pointer to the regulatory domain structure which will hold the
+ * resulting intersection of rules between rd1 and rd2. We will
+ * kzalloc() this structure for you.
+ */
+static struct ieee80211_regdomain *regdom_intersect(
+ const struct ieee80211_regdomain *rd1,
+ const struct ieee80211_regdomain *rd2)
+{
+ int r, size_of_regd;
+ unsigned int x, y;
+ unsigned int num_rules = 0, rule_idx = 0;
+ const struct ieee80211_reg_rule *rule1, *rule2;
+ struct ieee80211_reg_rule *intersected_rule;
+ struct ieee80211_regdomain *rd;
+ /* This is just a dummy holder to help us count */
+ struct ieee80211_reg_rule irule;
+
+ /* Uses the stack temporarily for counter arithmetic */
+ intersected_rule = &irule;
+
+ memset(intersected_rule, 0, sizeof(struct ieee80211_reg_rule));
+
+ if (!rd1 || !rd2)
+ return NULL;
+
+ /* First we get a count of the rules we'll need, then we actually
+ * build them. This is to so we can malloc() and free() a
+ * regdomain once. The reason we use reg_rules_intersect() here
+ * is it will return -EINVAL if the rule computed makes no sense.
+ * All rules that do check out OK are valid. */
+
+ for (x = 0; x < rd1->n_reg_rules; x++) {
+ rule1 = &rd1->reg_rules[x];
+ for (y = 0; y < rd2->n_reg_rules; y++) {
+ rule2 = &rd2->reg_rules[y];
+ if (!reg_rules_intersect(rule1, rule2,
+ intersected_rule))
+ num_rules++;
+ memset(intersected_rule, 0,
+ sizeof(struct ieee80211_reg_rule));
+ }
+ }
+
+ if (!num_rules)
+ return NULL;
+
+ size_of_regd = sizeof(struct ieee80211_regdomain) +
+ ((num_rules + 1) * sizeof(struct ieee80211_reg_rule));
+
+ rd = kzalloc(size_of_regd, GFP_KERNEL);
+ if (!rd)
+ return NULL;
+
+ for (x = 0; x < rd1->n_reg_rules; x++) {
+ rule1 = &rd1->reg_rules[x];
+ for (y = 0; y < rd2->n_reg_rules; y++) {
+ rule2 = &rd2->reg_rules[y];
+ /* This time around instead of using the stack lets
+ * write to the target rule directly saving ourselves
+ * a memcpy() */
+ intersected_rule = &rd->reg_rules[rule_idx];
+ r = reg_rules_intersect(rule1, rule2,
+ intersected_rule);
+ /* No need to memset here the intersected rule here as
+ * we're not using the stack anymore */
+ if (r)
+ continue;
+ rule_idx++;
+ }
+ }
+
+ if (rule_idx != num_rules) {
+ kfree(rd);
+ return NULL;
+ }
+
+ rd->n_reg_rules = num_rules;
+ rd->alpha2[0] = '9';
+ rd->alpha2[1] = '8';
+
+ return rd;
+}
+
/* XXX: add support for the rest of enum nl80211_reg_rule_flags, we may
* want to just have the channel structure use these */
static u32 map_regdom_flags(u32 rd_flags)
@@ -559,12 +816,23 @@ static void handle_band(struct ieee80211_supported_band *sband)
handle_channel(&sband->channels[i]);
}
+static bool ignore_reg_update(struct wiphy *wiphy, enum reg_set_by setby)
+{
+ if (!last_request)
+ return true;
+ if (setby == REGDOM_SET_BY_CORE &&
+ wiphy->fw_handles_regulatory)
+ return true;
+ return false;
+}
+
static void update_all_wiphy_regulatory(enum reg_set_by setby)
{
struct cfg80211_registered_device *drv;
list_for_each_entry(drv, &cfg80211_drv_list, list)
- wiphy_update_regulatory(&drv->wiphy, setby);
+ if (!ignore_reg_update(&drv->wiphy, setby))
+ wiphy_update_regulatory(&drv->wiphy, setby);
}
void wiphy_update_regulatory(struct wiphy *wiphy, enum reg_set_by setby)
@@ -578,78 +846,237 @@ void wiphy_update_regulatory(struct wiphy *wiphy, enum reg_set_by setby)
}
}
-/* Caller must hold &cfg80211_drv_mutex */
-int __regulatory_hint(struct wiphy *wiphy, enum reg_set_by set_by,
- const char *alpha2, struct ieee80211_regdomain *rd)
-{
- struct regulatory_request *request;
- char *rd_alpha2;
- int r = 0;
-
- r = ignore_request(wiphy, set_by, (char *) alpha2, rd);
- if (r)
- return r;
+/* Return value which can be used by ignore_request() to indicate
+ * it has been determined we should intersect two regulatory domains */
+#define REG_INTERSECT 1
- if (rd)
- rd_alpha2 = rd->alpha2;
- else
- rd_alpha2 = (char *) alpha2;
+/* This has the logic which determines when a new request
+ * should be ignored. */
+static int ignore_request(struct wiphy *wiphy, enum reg_set_by set_by,
+ const char *alpha2)
+{
+ /* All initial requests are respected */
+ if (!last_request)
+ return 0;
switch (set_by) {
+ case REGDOM_SET_BY_INIT:
+ return -EINVAL;
case REGDOM_SET_BY_CORE:
+ /*
+ * Always respect new wireless core hints, should only happen
+ * when updating the world regulatory domain at init.
+ */
+ return 0;
case REGDOM_SET_BY_COUNTRY_IE:
+ if (unlikely(!is_an_alpha2(alpha2)))
+ return -EINVAL;
+ if (last_request->initiator == REGDOM_SET_BY_COUNTRY_IE) {
+ if (last_request->wiphy != wiphy) {
+ /*
+ * Two cards with two APs claiming different
+ * different Country IE alpha2s. We could
+ * intersect them, but that seems unlikely
+ * to be correct. Reject second one for now.
+ */
+ if (!alpha2_equal(alpha2,
+ cfg80211_regdomain->alpha2))
+ return -EOPNOTSUPP;
+ return -EALREADY;
+ }
+ /* Two consecutive Country IE hints on the same wiphy.
+ * This should be picked up early by the driver/stack */
+ if (WARN_ON(!alpha2_equal(cfg80211_regdomain->alpha2,
+ alpha2)))
+ return 0;
+ return -EALREADY;
+ }
+ return REG_INTERSECT;
case REGDOM_SET_BY_DRIVER:
+ if (last_request->initiator == REGDOM_SET_BY_DRIVER)
+ return -EALREADY;
+ return 0;
case REGDOM_SET_BY_USER:
- request = kzalloc(sizeof(struct regulatory_request),
- GFP_KERNEL);
- if (!request)
- return -ENOMEM;
-
- request->alpha2[0] = rd_alpha2[0];
- request->alpha2[1] = rd_alpha2[1];
- request->initiator = set_by;
- request->wiphy = wiphy;
-
- list_add_tail(&request->list, &regulatory_requests);
- if (rd)
- break;
- r = call_crda(alpha2);
-#ifndef CONFIG_WIRELESS_OLD_REGULATORY
- if (r)
- printk(KERN_ERR "cfg80211: Failed calling CRDA\n");
-#endif
- break;
- default:
- r = -ENOTSUPP;
- break;
+ if (last_request->initiator == REGDOM_SET_BY_COUNTRY_IE)
+ return REG_INTERSECT;
+ /* If the user knows better the user should set the regdom
+ * to their country before the IE is picked up */
+ if (last_request->initiator == REGDOM_SET_BY_USER &&
+ last_request->intersect)
+ return -EOPNOTSUPP;
+ return 0;
}
- return r;
+ return -EINVAL;
}
-/* If rd is not NULL and if this call fails the caller must free it */
-int regulatory_hint(struct wiphy *wiphy, const char *alpha2,
- struct ieee80211_regdomain *rd)
+/* Caller must hold &cfg80211_drv_mutex */
+int __regulatory_hint(struct wiphy *wiphy, enum reg_set_by set_by,
+ const char *alpha2,
+ u32 country_ie_checksum,
+ enum environment_cap env)
{
- int r;
- BUG_ON(!rd && !alpha2);
+ struct regulatory_request *request;
+ bool intersect = false;
+ int r = 0;
- mutex_lock(&cfg80211_drv_mutex);
+ r = ignore_request(wiphy, set_by, alpha2);
- r = __regulatory_hint(wiphy, REGDOM_SET_BY_DRIVER, alpha2, rd);
- if (r || !rd)
- goto unlock_and_exit;
+ if (r == REG_INTERSECT)
+ intersect = true;
+ else if (r)
+ return r;
- /* If the driver passed a regulatory domain we skipped asking
- * userspace for one so we can now go ahead and set it */
- r = set_regdom(rd);
+ request = kzalloc(sizeof(struct regulatory_request),
+ GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+
+ request->alpha2[0] = alpha2[0];
+ request->alpha2[1] = alpha2[1];
+ request->initiator = set_by;
+ request->wiphy = wiphy;
+ request->intersect = intersect;
+ request->country_ie_checksum = country_ie_checksum;
+ request->country_ie_env = env;
+
+ kfree(last_request);
+ last_request = request;
+ /*
+ * Note: When CONFIG_WIRELESS_OLD_REGULATORY is enabled
+ * AND if CRDA is NOT present nothing will happen, if someone
+ * wants to bother with 11d with OLD_REG you can add a timer.
+ * If after x amount of time nothing happens you can call:
+ *
+ * return set_regdom(country_ie_regdomain);
+ *
+ * to intersect with the static rd
+ */
+ return call_crda(alpha2);
+}
+
+void regulatory_hint(struct wiphy *wiphy, const char *alpha2)
+{
+ BUG_ON(!alpha2);
-unlock_and_exit:
+ mutex_lock(&cfg80211_drv_mutex);
+ __regulatory_hint(wiphy, REGDOM_SET_BY_DRIVER, alpha2, 0, ENVIRON_ANY);
mutex_unlock(&cfg80211_drv_mutex);
- return r;
}
EXPORT_SYMBOL(regulatory_hint);
+static bool reg_same_country_ie_hint(struct wiphy *wiphy,
+ u32 country_ie_checksum)
+{
+ if (!last_request->wiphy)
+ return false;
+ if (likely(last_request->wiphy != wiphy))
+ return !country_ie_integrity_changes(country_ie_checksum);
+ /* We should not have let these through at this point, they
+ * should have been picked up earlier by the first alpha2 check
+ * on the device */
+ if (WARN_ON(!country_ie_integrity_changes(country_ie_checksum)))
+ return true;
+ return false;
+}
+
+void regulatory_hint_11d(struct wiphy *wiphy,
+ u8 *country_ie,
+ u8 country_ie_len)
+{
+ struct ieee80211_regdomain *rd = NULL;
+ char alpha2[2];
+ u32 checksum = 0;
+ enum environment_cap env = ENVIRON_ANY;
+
+ if (!last_request)
+ return;
+
+ mutex_lock(&cfg80211_drv_mutex);
+
+ /* IE len must be evenly divisible by 2 */
+ if (country_ie_len & 0x01)
+ goto out;
+
+ if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN)
+ goto out;
+
+ /* Pending country IE processing, this can happen after we
+ * call CRDA and wait for a response if a beacon was received before
+ * we were able to process the last regulatory_hint_11d() call */
+ if (country_ie_regdomain)
+ goto out;
+
+ alpha2[0] = country_ie[0];
+ alpha2[1] = country_ie[1];
+
+ if (country_ie[2] == 'I')
+ env = ENVIRON_INDOOR;
+ else if (country_ie[2] == 'O')
+ env = ENVIRON_OUTDOOR;
+
+ /* We will run this for *every* beacon processed for the BSSID, so
+ * we optimize an early check to exit out early if we don't have to
+ * do anything */
+ if (likely(last_request->wiphy)) {
+ struct cfg80211_registered_device *drv_last_ie;
+
+ drv_last_ie = wiphy_to_dev(last_request->wiphy);
+
+ /* Lets keep this simple -- we trust the first AP
+ * after we intersect with CRDA */
+ if (likely(last_request->wiphy == wiphy)) {
+ /* Ignore IEs coming in on this wiphy with
+ * the same alpha2 and environment cap */
+ if (likely(alpha2_equal(drv_last_ie->country_ie_alpha2,
+ alpha2) &&
+ env == drv_last_ie->env)) {
+ goto out;
+ }
+ /* the wiphy moved on to another BSSID or the AP
+ * was reconfigured. XXX: We need to deal with the
+ * case where the user suspends and goes to goes
+ * to another country, and then gets IEs from an
+ * AP with different settings */
+ goto out;
+ } else {
+ /* Ignore IEs coming in on two separate wiphys with
+ * the same alpha2 and environment cap */
+ if (likely(alpha2_equal(drv_last_ie->country_ie_alpha2,
+ alpha2) &&
+ env == drv_last_ie->env)) {
+ goto out;
+ }
+ /* We could potentially intersect though */
+ goto out;
+ }
+ }
+
+ rd = country_ie_2_rd(country_ie, country_ie_len, &checksum);
+ if (!rd)
+ goto out;
+
+ /* This will not happen right now but we leave it here for the
+ * the future when we want to add suspend/resume support and having
+ * the user move to another country after doing so, or having the user
+ * move to another AP. Right now we just trust the first AP. This is why
+ * this is marked as likley(). If we hit this before we add this support
+ * we want to be informed of it as it would indicate a mistake in the
+ * current design */
+ if (likely(WARN_ON(reg_same_country_ie_hint(wiphy, checksum))))
+ goto out;
+
+ /* We keep this around for when CRDA comes back with a response so
+ * we can intersect with that */
+ country_ie_regdomain = rd;
+
+ __regulatory_hint(wiphy, REGDOM_SET_BY_COUNTRY_IE,
+ country_ie_regdomain->alpha2, checksum, env);
+
+out:
+ mutex_unlock(&cfg80211_drv_mutex);
+}
+EXPORT_SYMBOL(regulatory_hint_11d);
static void print_rd_rules(const struct ieee80211_regdomain *rd)
{
@@ -689,7 +1116,25 @@ static void print_rd_rules(const struct ieee80211_regdomain *rd)
static void print_regdomain(const struct ieee80211_regdomain *rd)
{
- if (is_world_regdom(rd->alpha2))
+ if (is_intersected_alpha2(rd->alpha2)) {
+ struct wiphy *wiphy = NULL;
+ struct cfg80211_registered_device *drv;
+
+ if (last_request->initiator == REGDOM_SET_BY_COUNTRY_IE) {
+ if (last_request->wiphy) {
+ wiphy = last_request->wiphy;
+ drv = wiphy_to_dev(wiphy);
+ printk(KERN_INFO "cfg80211: Current regulatory "
+ "domain updated by AP to: %c%c\n",
+ drv->country_ie_alpha2[0],
+ drv->country_ie_alpha2[1]);
+ } else
+ printk(KERN_INFO "cfg80211: Current regulatory "
+ "domain intersected: \n");
+ } else
+ printk(KERN_INFO "cfg80211: Current regulatory "
+ "intersected: \n");
+ } else if (is_world_regdom(rd->alpha2))
printk(KERN_INFO "cfg80211: World regulatory "
"domain updated:\n");
else {
@@ -705,21 +1150,50 @@ static void print_regdomain(const struct ieee80211_regdomain *rd)
print_rd_rules(rd);
}
-void print_regdomain_info(const struct ieee80211_regdomain *rd)
+static void print_regdomain_info(const struct ieee80211_regdomain *rd)
{
printk(KERN_INFO "cfg80211: Regulatory domain: %c%c\n",
rd->alpha2[0], rd->alpha2[1]);
print_rd_rules(rd);
}
-static int __set_regdom(const struct ieee80211_regdomain *rd)
+#ifdef CONFIG_CFG80211_REG_DEBUG
+static void reg_country_ie_process_debug(
+ const struct ieee80211_regdomain *rd,
+ const struct ieee80211_regdomain *country_ie_regdomain,
+ const struct ieee80211_regdomain *intersected_rd)
{
- struct regulatory_request *request = NULL;
+ printk(KERN_DEBUG "cfg80211: Received country IE:\n");
+ print_regdomain_info(country_ie_regdomain);
+ printk(KERN_DEBUG "cfg80211: CRDA thinks this should applied:\n");
+ print_regdomain_info(rd);
+ if (intersected_rd) {
+ printk(KERN_DEBUG "cfg80211: We intersect both of these "
+ "and get:\n");
+ print_regdomain_info(rd);
+ return;
+ }
+ printk(KERN_DEBUG "cfg80211: Intersection between both failed\n");
+}
+#else
+static inline void reg_country_ie_process_debug(
+ const struct ieee80211_regdomain *rd,
+ const struct ieee80211_regdomain *country_ie_regdomain,
+ const struct ieee80211_regdomain *intersected_rd)
+{
+}
+#endif
+/* Takes ownership of rd only if it doesn't fail */
+static int __set_regdom(const struct ieee80211_regdomain *rd)
+{
+ const struct ieee80211_regdomain *intersected_rd = NULL;
+ struct cfg80211_registered_device *drv = NULL;
+ struct wiphy *wiphy = NULL;
/* Some basic sanity checks first */
if (is_world_regdom(rd->alpha2)) {
- if (WARN_ON(!__reg_is_valid_request(rd->alpha2, &request)))
+ if (WARN_ON(!reg_is_valid_request(rd->alpha2)))
return -EINVAL;
update_world_regdomain(rd);
return 0;
@@ -729,45 +1203,102 @@ static int __set_regdom(const struct ieee80211_regdomain *rd)
!is_unknown_alpha2(rd->alpha2))
return -EINVAL;
- if (list_empty(&regulatory_requests))
+ if (!last_request)
return -EINVAL;
- /* allow overriding the static definitions if CRDA is present */
- if (!is_old_static_regdom(cfg80211_regdomain) &&
- !regdom_changed(rd->alpha2))
- return -EINVAL;
+ /* Lets only bother proceeding on the same alpha2 if the current
+ * rd is non static (it means CRDA was present and was used last)
+ * and the pending request came in from a country IE */
+ if (last_request->initiator != REGDOM_SET_BY_COUNTRY_IE) {
+ /* If someone else asked us to change the rd lets only bother
+ * checking if the alpha2 changes if CRDA was already called */
+ if (!is_old_static_regdom(cfg80211_regdomain) &&
+ !regdom_changed(rd->alpha2))
+ return -EINVAL;
+ }
+
+ wiphy = last_request->wiphy;
/* Now lets set the regulatory domain, update all driver channels
* and finally inform them of what we have done, in case they want
* to review or adjust their own settings based on their own
* internal EEPROM data */
- if (WARN_ON(!__reg_is_valid_request(rd->alpha2, &request)))
+ if (WARN_ON(!reg_is_valid_request(rd->alpha2)))
return -EINVAL;
- reset_regdomains();
+ if (!is_valid_rd(rd)) {
+ printk(KERN_ERR "cfg80211: Invalid "
+ "regulatory domain detected:\n");
+ print_regdomain_info(rd);
+ return -EINVAL;
+ }
- /* Country IE parsing coming soon */
- switch (request->initiator) {
- case REGDOM_SET_BY_CORE:
- case REGDOM_SET_BY_DRIVER:
- case REGDOM_SET_BY_USER:
- if (!is_valid_rd(rd)) {
- printk(KERN_ERR "cfg80211: Invalid "
- "regulatory domain detected:\n");
- print_regdomain_info(rd);
+ if (!last_request->intersect) {
+ reset_regdomains();
+ cfg80211_regdomain = rd;
+ return 0;
+ }
+
+ /* Intersection requires a bit more work */
+
+ if (last_request->initiator != REGDOM_SET_BY_COUNTRY_IE) {
+
+ intersected_rd = regdom_intersect(rd, cfg80211_regdomain);
+ if (!intersected_rd)
return -EINVAL;
- }
- break;
- case REGDOM_SET_BY_COUNTRY_IE: /* Not yet */
- WARN_ON(1);
- default:
- return -EOPNOTSUPP;
+
+ /* We can trash what CRDA provided now */
+ kfree(rd);
+ rd = NULL;
+
+ reset_regdomains();
+ cfg80211_regdomain = intersected_rd;
+
+ return 0;
}
- /* Tada! */
- cfg80211_regdomain = rd;
- request->granted = 1;
+ /*
+ * Country IE requests are handled a bit differently, we intersect
+ * the country IE rd with what CRDA believes that country should have
+ */
+
+ BUG_ON(!country_ie_regdomain);
+
+ if (rd != country_ie_regdomain) {
+ /* Intersect what CRDA returned and our what we
+ * had built from the Country IE received */
+
+ intersected_rd = regdom_intersect(rd, country_ie_regdomain);
+
+ reg_country_ie_process_debug(rd, country_ie_regdomain,
+ intersected_rd);
+
+ kfree(country_ie_regdomain);
+ country_ie_regdomain = NULL;
+ } else {
+ /* This would happen when CRDA was not present and
+ * OLD_REGULATORY was enabled. We intersect our Country
+ * IE rd and what was set on cfg80211 originally */
+ intersected_rd = regdom_intersect(rd, cfg80211_regdomain);
+ }
+
+ if (!intersected_rd)
+ return -EINVAL;
+
+ drv = wiphy_to_dev(wiphy);
+
+ drv->country_ie_alpha2[0] = rd->alpha2[0];
+ drv->country_ie_alpha2[1] = rd->alpha2[1];
+ drv->env = last_request->country_ie_env;
+
+ BUG_ON(intersected_rd == rd);
+
+ kfree(rd);
+ rd = NULL;
+
+ reset_regdomains();
+ cfg80211_regdomain = intersected_rd;
return 0;
}
@@ -775,52 +1306,41 @@ static int __set_regdom(const struct ieee80211_regdomain *rd)
/* Use this call to set the current regulatory domain. Conflicts with
* multiple drivers can be ironed out later. Caller must've already
- * kmalloc'd the rd structure. If this calls fails you should kfree()
- * the passed rd. Caller must hold cfg80211_drv_mutex */
+ * kmalloc'd the rd structure. Caller must hold cfg80211_drv_mutex */
int set_regdom(const struct ieee80211_regdomain *rd)
{
- struct regulatory_request *this_request = NULL, *prev_request = NULL;
int r;
- if (!list_empty(&regulatory_requests))
- prev_request = list_first_entry(&regulatory_requests,
- struct regulatory_request, list);
-
/* Note that this doesn't update the wiphys, this is done below */
r = __set_regdom(rd);
- if (r)
+ if (r) {
+ kfree(rd);
return r;
-
- BUG_ON((!__reg_is_valid_request(rd->alpha2, &this_request)));
-
- /* The initial standard core update of the world regulatory domain, no
- * need to keep that request info around if it didn't fail. */
- if (is_world_regdom(rd->alpha2) &&
- this_request->initiator == REGDOM_SET_BY_CORE &&
- this_request->granted) {
- list_del(&this_request->list);
- kfree(this_request);
- this_request = NULL;
- }
-
- /* Remove old requests, we only leave behind the last one */
- if (prev_request) {
- list_del(&prev_request->list);
- kfree(prev_request);
- prev_request = NULL;
}
/* This would make this whole thing pointless */
- BUG_ON(rd != cfg80211_regdomain);
+ if (!last_request->intersect)
+ BUG_ON(rd != cfg80211_regdomain);
/* update all wiphys now with the new established regulatory domain */
- update_all_wiphy_regulatory(this_request->initiator);
+ update_all_wiphy_regulatory(last_request->initiator);
- print_regdomain(rd);
+ print_regdomain(cfg80211_regdomain);
return r;
}
+/* Caller must hold cfg80211_drv_mutex */
+void reg_device_remove(struct wiphy *wiphy)
+{
+ if (!last_request || !last_request->wiphy)
+ return;
+ if (last_request->wiphy != wiphy)
+ return;
+ last_request->wiphy = NULL;
+ last_request->country_ie_env = ENVIRON_ANY;
+}
+
int regulatory_init(void)
{
int err;
@@ -838,13 +1358,13 @@ int regulatory_init(void)
* you have CRDA you get it updated, otherwise you get
* stuck with the static values. We ignore "EU" code as
* that is not a valid ISO / IEC 3166 alpha2 */
- if (ieee80211_regdom[0] != 'E' && ieee80211_regdom[1] != 'U')
+ if (ieee80211_regdom[0] != 'E' || ieee80211_regdom[1] != 'U')
err = __regulatory_hint(NULL, REGDOM_SET_BY_CORE,
- ieee80211_regdom, NULL);
+ ieee80211_regdom, 0, ENVIRON_ANY);
#else
cfg80211_regdomain = cfg80211_world_regdom;
- err = __regulatory_hint(NULL, REGDOM_SET_BY_CORE, "00", NULL);
+ err = __regulatory_hint(NULL, REGDOM_SET_BY_CORE, "00", 0, ENVIRON_ANY);
if (err)
printk(KERN_ERR "cfg80211: calling CRDA failed - "
"unable to update world regulatory domain, "
@@ -856,16 +1376,15 @@ int regulatory_init(void)
void regulatory_exit(void)
{
- struct regulatory_request *req, *req_tmp;
-
mutex_lock(&cfg80211_drv_mutex);
reset_regdomains();
- list_for_each_entry_safe(req, req_tmp, &regulatory_requests, list) {
- list_del(&req->list);
- kfree(req);
- }
+ kfree(country_ie_regdomain);
+ country_ie_regdomain = NULL;
+
+ kfree(last_request);
+
platform_device_unregister(reg_pdev);
mutex_unlock(&cfg80211_drv_mutex);
diff --git a/net/wireless/reg.h b/net/wireless/reg.h
index a33362872f3c..a76ea3ff7cd6 100644
--- a/net/wireless/reg.h
+++ b/net/wireless/reg.h
@@ -1,13 +1,44 @@
#ifndef __NET_WIRELESS_REG_H
#define __NET_WIRELESS_REG_H
-extern struct mutex cfg80211_reg_mutex;
bool is_world_regdom(const char *alpha2);
bool reg_is_valid_request(const char *alpha2);
+void reg_device_remove(struct wiphy *wiphy);
+
int regulatory_init(void);
void regulatory_exit(void);
int set_regdom(const struct ieee80211_regdomain *rd);
+enum environment_cap {
+ ENVIRON_ANY,
+ ENVIRON_INDOOR,
+ ENVIRON_OUTDOOR,
+};
+
+
+/**
+ * __regulatory_hint - hint to the wireless core a regulatory domain
+ * @wiphy: if the hint comes from country information from an AP, this
+ * is required to be set to the wiphy that received the information
+ * @alpha2: the ISO/IEC 3166 alpha2 being claimed the regulatory domain
+ * should be in.
+ * @country_ie_checksum: checksum of processed country IE, set this to 0
+ * if the hint did not come from a country IE
+ * @country_ie_env: the environment the IE told us we are in, %ENVIRON_*
+ *
+ * The Wireless subsystem can use this function to hint to the wireless core
+ * what it believes should be the current regulatory domain by giving it an
+ * ISO/IEC 3166 alpha2 country code it knows its regulatory domain should be
+ * in.
+ *
+ * Returns zero if all went fine, %-EALREADY if a regulatory domain had
+ * already been set or other standard error codes.
+ *
+ */
+extern int __regulatory_hint(struct wiphy *wiphy, enum reg_set_by set_by,
+ const char *alpha2, u32 country_ie_checksum,
+ enum environment_cap country_ie_env);
+
#endif /* __NET_WIRELESS_REG_H */
diff --git a/net/wireless/sysfs.c b/net/wireless/sysfs.c
index 29f820e18251..79a382877641 100644
--- a/net/wireless/sysfs.c
+++ b/net/wireless/sysfs.c
@@ -23,25 +23,20 @@ static inline struct cfg80211_registered_device *dev_to_rdev(
return container_of(dev, struct cfg80211_registered_device, wiphy.dev);
}
-static ssize_t _show_index(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- return sprintf(buf, "%d\n", dev_to_rdev(dev)->idx);
+#define SHOW_FMT(name, fmt, member) \
+static ssize_t name ## _show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ return sprintf(buf, fmt "\n", dev_to_rdev(dev)->member); \
}
-static ssize_t _show_permaddr(struct device *dev,
- struct device_attribute *attr,
- char *buf)
-{
- unsigned char *addr = dev_to_rdev(dev)->wiphy.perm_addr;
-
- return sprintf(buf, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n",
- addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
-}
+SHOW_FMT(index, "%d", idx);
+SHOW_FMT(macaddress, "%pM", wiphy.perm_addr);
static struct device_attribute ieee80211_dev_attrs[] = {
- __ATTR(index, S_IRUGO, _show_index, NULL),
- __ATTR(macaddress, S_IRUGO, _show_permaddr, NULL),
+ __ATTR_RO(index),
+ __ATTR_RO(macaddress),
{}
};
diff --git a/net/wireless/util.c b/net/wireless/util.c
index f54424693a38..e76cc28b0345 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -7,6 +7,25 @@
#include <asm/bitops.h>
#include "core.h"
+struct ieee80211_rate *
+ieee80211_get_response_rate(struct ieee80211_supported_band *sband,
+ u64 basic_rates, int bitrate)
+{
+ struct ieee80211_rate *result = &sband->bitrates[0];
+ int i;
+
+ for (i = 0; i < sband->n_bitrates; i++) {
+ if (!(basic_rates & BIT(i)))
+ continue;
+ if (sband->bitrates[i].bitrate > bitrate)
+ continue;
+ result = &sband->bitrates[i];
+ }
+
+ return result;
+}
+EXPORT_SYMBOL(ieee80211_get_response_rate);
+
int ieee80211_channel_to_frequency(int chan)
{
if (chan < 14)
diff --git a/net/wireless/wext-compat.c b/net/wireless/wext-compat.c
new file mode 100644
index 000000000000..58e489fd4aed
--- /dev/null
+++ b/net/wireless/wext-compat.c
@@ -0,0 +1,139 @@
+/*
+ * cfg80211 - wext compat code
+ *
+ * This is temporary code until all wireless functionality is migrated
+ * into cfg80211, when that happens all the exports here go away and
+ * we directly assign the wireless handlers of wireless interfaces.
+ *
+ * Copyright 2008 Johannes Berg <johannes@sipsolutions.net>
+ */
+
+#include <linux/wireless.h>
+#include <linux/nl80211.h>
+#include <net/iw_handler.h>
+#include <net/wireless.h>
+#include <net/cfg80211.h>
+#include "core.h"
+
+int cfg80211_wext_giwname(struct net_device *dev,
+ struct iw_request_info *info,
+ char *name, char *extra)
+{
+ struct wireless_dev *wdev = dev->ieee80211_ptr;
+ struct ieee80211_supported_band *sband;
+ bool is_ht = false, is_a = false, is_b = false, is_g = false;
+
+ if (!wdev)
+ return -EOPNOTSUPP;
+
+ sband = wdev->wiphy->bands[IEEE80211_BAND_5GHZ];
+ if (sband) {
+ is_a = true;
+ is_ht |= sband->ht_cap.ht_supported;
+ }
+
+ sband = wdev->wiphy->bands[IEEE80211_BAND_2GHZ];
+ if (sband) {
+ int i;
+ /* Check for mandatory rates */
+ for (i = 0; i < sband->n_bitrates; i++) {
+ if (sband->bitrates[i].bitrate == 10)
+ is_b = true;
+ if (sband->bitrates[i].bitrate == 60)
+ is_g = true;
+ }
+ is_ht |= sband->ht_cap.ht_supported;
+ }
+
+ strcpy(name, "IEEE 802.11");
+ if (is_a)
+ strcat(name, "a");
+ if (is_b)
+ strcat(name, "b");
+ if (is_g)
+ strcat(name, "g");
+ if (is_ht)
+ strcat(name, "n");
+
+ return 0;
+}
+EXPORT_SYMBOL(cfg80211_wext_giwname);
+
+int cfg80211_wext_siwmode(struct net_device *dev, struct iw_request_info *info,
+ u32 *mode, char *extra)
+{
+ struct wireless_dev *wdev = dev->ieee80211_ptr;
+ struct cfg80211_registered_device *rdev;
+ struct vif_params vifparams;
+ enum nl80211_iftype type;
+
+ if (!wdev)
+ return -EOPNOTSUPP;
+
+ rdev = wiphy_to_dev(wdev->wiphy);
+
+ if (!rdev->ops->change_virtual_intf)
+ return -EOPNOTSUPP;
+
+ /* don't support changing VLANs, you just re-create them */
+ if (wdev->iftype == NL80211_IFTYPE_AP_VLAN)
+ return -EOPNOTSUPP;
+
+ switch (*mode) {
+ case IW_MODE_INFRA:
+ type = NL80211_IFTYPE_STATION;
+ break;
+ case IW_MODE_ADHOC:
+ type = NL80211_IFTYPE_ADHOC;
+ break;
+ case IW_MODE_REPEAT:
+ type = NL80211_IFTYPE_WDS;
+ break;
+ case IW_MODE_MONITOR:
+ type = NL80211_IFTYPE_MONITOR;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ memset(&vifparams, 0, sizeof(vifparams));
+
+ return rdev->ops->change_virtual_intf(wdev->wiphy, dev->ifindex, type,
+ NULL, &vifparams);
+}
+EXPORT_SYMBOL(cfg80211_wext_siwmode);
+
+int cfg80211_wext_giwmode(struct net_device *dev, struct iw_request_info *info,
+ u32 *mode, char *extra)
+{
+ struct wireless_dev *wdev = dev->ieee80211_ptr;
+
+ if (!wdev)
+ return -EOPNOTSUPP;
+
+ switch (wdev->iftype) {
+ case NL80211_IFTYPE_AP:
+ *mode = IW_MODE_MASTER;
+ break;
+ case NL80211_IFTYPE_STATION:
+ *mode = IW_MODE_INFRA;
+ break;
+ case NL80211_IFTYPE_ADHOC:
+ *mode = IW_MODE_ADHOC;
+ break;
+ case NL80211_IFTYPE_MONITOR:
+ *mode = IW_MODE_MONITOR;
+ break;
+ case NL80211_IFTYPE_WDS:
+ *mode = IW_MODE_REPEAT;
+ break;
+ case NL80211_IFTYPE_AP_VLAN:
+ *mode = IW_MODE_SECOND; /* FIXME */
+ break;
+ default:
+ *mode = IW_MODE_AUTO;
+ break;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(cfg80211_wext_giwmode);
diff --git a/net/wireless/wext.c b/net/wireless/wext.c
index d98ffb75119a..e49a2d1ef1e4 100644
--- a/net/wireless/wext.c
+++ b/net/wireless/wext.c
@@ -64,7 +64,7 @@
* o Remove spy_offset from struct iw_handler_def
* o Start deprecating dev->get_wireless_stats, output a warning
* o If IW_QUAL_DBM is set, show dBm values in /proc/net/wireless
- * o Don't loose INVALID/DBM flags when clearing UPDATED flags (iwstats)
+ * o Don't lose INVALID/DBM flags when clearing UPDATED flags (iwstats)
*
* v8 - 17.02.06 - Jean II
* o RtNetlink requests support (SET/GET)
OpenPOWER on IntegriCloud