[MAC80211]: rework key handling

This moves all the key handling code out from ieee80211_ioctl.c
into key.c and also does the following changes including documentation
updates in mac80211.h:

 1) Turn off hardware acceleration for keys when the interface
    is down. This is necessary because otherwise monitor
    interfaces could be decrypting frames for other interfaces
    that are down at the moment. Also, it should go some way
    towards better suspend/resume support, in any case the
    routines used here could be used for that as well.
    Additionally, this makes the driver interface nicer, keys
    for a specific local MAC address are only ever present
    while an interface with that MAC address is enabled.

 2) Change driver set_key() callback interface to allow only
    return values of -ENOSPC, -EOPNOTSUPP and 0, warn on all
    other return values. This allows debugging the stack when
    a driver notices it's handed a key while it is down.

 3) Invert the flag meaning to KEY_FLAG_UPLOADED_TO_HARDWARE.

 4) Remove REMOVE_ALL_KEYS command as it isn't used nor do we
    want to use it, we'll use DISABLE_KEY for each key. It is
    hard to use REMOVE_ALL_KEYS because we can handle multiple
    virtual interfaces with different key configuration, so we'd
    have to keep track of a lot of state for this and that isn't
    worth it.

 5) Warn when disabling a key fails, it musn't.

 6) Remove IEEE80211_HW_NO_TKIP_WMM_HWACCEL in favour of per-key
    IEEE80211_KEY_FLAG_WMM_STA to let driver sort it out itself.

 7) Tell driver that a (non-WEP) key is used only for transmission
    by using an all-zeroes station MAC address when configuring.

 8) Change the set_key() callback to have access to the local MAC
    address the key is being added for.

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Acked-by: Michael Wu <flamingice@sourmilk.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
diff --git a/net/mac80211/debugfs_key.c b/net/mac80211/debugfs_key.c
index 246938c..36e7812 100644
--- a/net/mac80211/debugfs_key.c
+++ b/net/mac80211/debugfs_key.c
@@ -25,6 +25,7 @@
 	return simple_read_from_buffer(userbuf, count, ppos, buf, res);	\
 }
 #define KEY_READ_D(name) KEY_READ(name, name, 20, "%d\n")
+#define KEY_READ_X(name) KEY_READ(name, name, 20, "0x%x\n")
 
 #define KEY_OPS(name)							\
 static const struct file_operations key_ ##name## _ops = {		\
@@ -39,7 +40,6 @@
 #define KEY_CONF_READ(name, buflen, format_string)			\
 	KEY_READ(conf_##name, conf.name, buflen, format_string)
 #define KEY_CONF_READ_D(name) KEY_CONF_READ(name, 20, "%d\n")
-#define KEY_CONF_READ_X(name) KEY_CONF_READ(name, 20, "0x%x\n")
 
 #define KEY_CONF_OPS(name)						\
 static const struct file_operations key_ ##name## _ops = {		\
@@ -54,7 +54,7 @@
 KEY_CONF_FILE(keylen, D);
 KEY_CONF_FILE(keyidx, D);
 KEY_CONF_FILE(hw_key_idx, D);
-KEY_CONF_FILE(flags, X);
+KEY_FILE(flags, X);
 KEY_FILE(tx_rx_count, D);
 
 static ssize_t key_algorithm_read(struct file *file,
diff --git a/net/mac80211/ieee80211.c b/net/mac80211/ieee80211.c
index 5d5034f..73e314e 100644
--- a/net/mac80211/ieee80211.c
+++ b/net/mac80211/ieee80211.c
@@ -442,6 +442,7 @@
 	} else {
 		ieee80211_if_config(dev);
 		ieee80211_reset_erp_info(dev);
+		ieee80211_enable_keys(sdata);
 	}
 
 	if (sdata->type == IEEE80211_IF_TYPE_STA &&
@@ -510,6 +511,9 @@
 		local->monitors--;
 		if (!local->monitors)
 			local->hw.conf.flags &= ~IEEE80211_CONF_RADIOTAP;
+	} else {
+		/* disable all keys for as long as this netdev is down */
+		ieee80211_disable_keys(sdata);
 	}
 
 	local->open_count--;
@@ -908,7 +912,7 @@
 	}
 
 	if (skb->len >= mic_len &&
-	    (key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT))
+	    !(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE))
 		skb_trim(skb, skb->len - mic_len);
 	if (skb->len >= iv_len && skb->len > hdrlen) {
 		memmove(skb->data + iv_len, skb->data, hdrlen);
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 7b5cc14..0149f90 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -291,6 +291,9 @@
 
 	struct wireless_dev wdev;
 
+	/* keys */
+	struct list_head key_list;
+
 	struct net_device *dev;
 	struct ieee80211_local *local;
 
@@ -810,11 +813,6 @@
 int ieee80211_subif_start_xmit(struct sk_buff *skb, struct net_device *dev);
 int ieee80211_mgmt_start_xmit(struct sk_buff *skb, struct net_device *dev);
 
-/* key handling */
-struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata,
-					  int idx, size_t key_len, gfp_t flags);
-void ieee80211_key_free(struct ieee80211_key *key);
-
 /* utility functions/constants */
 extern void *mac80211_wiphy_privid; /* for wiphy privid */
 extern const unsigned char rfc1042_header[6];
diff --git a/net/mac80211/ieee80211_iface.c b/net/mac80211/ieee80211_iface.c
index 8bb85f1..f9c74bb 100644
--- a/net/mac80211/ieee80211_iface.c
+++ b/net/mac80211/ieee80211_iface.c
@@ -25,6 +25,8 @@
 	sdata->eapol = 1;
 	for (i = 0; i < IEEE80211_FRAGMENT_MAX; i++)
 		skb_queue_head_init(&sdata->fragments[i].skb_list);
+
+	INIT_LIST_HEAD(&sdata->key_list);
 }
 
 static void ieee80211_if_sdata_deinit(struct ieee80211_sub_if_data *sdata)
@@ -210,25 +212,12 @@
 	struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
 	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 	struct sta_info *sta;
-	int i;
 
 	ASSERT_RTNL();
+
+	ieee80211_free_keys(sdata);
+
 	ieee80211_if_sdata_deinit(sdata);
-	for (i = 0; i < NUM_DEFAULT_KEYS; i++) {
-		if (!sdata->keys[i])
-			continue;
-#if 0
-		/* The interface is down at the moment, so there is not
-		 * really much point in disabling the keys at this point. */
-		memset(addr, 0xff, ETH_ALEN);
-		if (local->ops->set_key)
-			local->ops->set_key(local_to_hw(local), DISABLE_KEY, addr,
-					    local->keys[i],
-					    local->default_wep_only);
-#endif
-		ieee80211_key_free(sdata->keys[i]);
-		sdata->keys[i] = NULL;
-	}
 
 	switch (sdata->type) {
 	case IEEE80211_IF_TYPE_AP: {
diff --git a/net/mac80211/ieee80211_ioctl.c b/net/mac80211/ieee80211_ioctl.c
index dc05bc6..8296e7d 100644
--- a/net/mac80211/ieee80211_ioctl.c
+++ b/net/mac80211/ieee80211_ioctl.c
@@ -25,28 +25,6 @@
 #include "ieee80211_rate.h"
 #include "wpa.h"
 #include "aes_ccm.h"
-#include "debugfs_key.h"
-
-static void ieee80211_set_hw_encryption(struct net_device *dev,
-					struct sta_info *sta, u8 addr[ETH_ALEN],
-					struct ieee80211_key *key)
-{
-	struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
-
-	/* default to sw encryption; this will be cleared by low-level
-	 * driver if the hw supports requested encryption */
-	if (key)
-		key->conf.flags |= IEEE80211_KEY_FORCE_SW_ENCRYPT;
-
-	if (key && local->ops->set_key) {
-		if (local->ops->set_key(local_to_hw(local), SET_KEY, addr,
-					&key->conf)) {
-			key->conf.flags |= IEEE80211_KEY_FORCE_SW_ENCRYPT;
-			key->conf.hw_key_idx = HW_KEY_IDX_INVALID;
-		}
-	}
-}
-
 
 static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr,
 				    int idx, int alg, int set_tx_key,
@@ -55,8 +33,7 @@
 	struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
 	int ret = 0;
 	struct sta_info *sta;
-	struct ieee80211_key *key, *old_key;
-	int try_hwaccel = 1;
+	struct ieee80211_key *key;
 	struct ieee80211_sub_if_data *sdata;
 
 	sdata = IEEE80211_DEV_TO_SUB_IF(dev);
@@ -69,16 +46,6 @@
 			return -EINVAL;
 		}
 		key = sdata->keys[idx];
-
-		/* TODO: consider adding hwaccel support for these; at least
-		 * Atheros key cache should be able to handle this since AP is
-		 * only transmitting frames with default keys. */
-		/* FIX: hw key cache can be used when only one virtual
-		 * STA is associated with each AP. If more than one STA
-		 * is associated to the same AP, software encryption
-		 * must be used. This should be done automatically
-		 * based on configured station devices. For the time
-		 * being, this can be only set at compile time. */
 	} else {
 		set_tx_key = 0;
 		if (idx != 0) {
@@ -101,139 +68,28 @@
 		key = sta->key;
 	}
 
-	/* FIX:
-	 * Cannot configure default hwaccel keys with WEP algorithm, if
-	 * any of the virtual interfaces is using static WEP
-	 * configuration because hwaccel would otherwise try to decrypt
-	 * these frames.
-	 *
-	 * For now, just disable WEP hwaccel for broadcast when there is
-	 * possibility of conflict with default keys. This can maybe later be
-	 * optimized by using non-default keys (at least with Atheros ar521x).
-	 */
-	if (!sta && alg == ALG_WEP &&
-	    sdata->type != IEEE80211_IF_TYPE_IBSS &&
-	    sdata->type != IEEE80211_IF_TYPE_AP) {
-		try_hwaccel = 0;
-	}
-
-	if (local->hw.flags & IEEE80211_HW_DEVICE_HIDES_WEP) {
-		/* Software encryption cannot be used with devices that hide
-		 * encryption from the host system, so always try to use
-		 * hardware acceleration with such devices. */
-		try_hwaccel = 1;
-	}
-
-	if ((local->hw.flags & IEEE80211_HW_NO_TKIP_WMM_HWACCEL) &&
-	    alg == ALG_TKIP) {
-		if (sta && (sta->flags & WLAN_STA_WME)) {
-		/* Hardware does not support hwaccel with TKIP when using WMM.
-		 */
-			try_hwaccel = 0;
-		}
-		else if (sdata->type == IEEE80211_IF_TYPE_STA) {
-			sta = sta_info_get(local, sdata->u.sta.bssid);
-			if (sta) {
-				if (sta->flags & WLAN_STA_WME) {
-					try_hwaccel = 0;
-				}
-				sta_info_put(sta);
-				sta = NULL;
-			}
-		}
-	}
-
 	if (alg == ALG_NONE) {
-		if (try_hwaccel && key &&
-		    key->conf.hw_key_idx != HW_KEY_IDX_INVALID &&
-		    local->ops->set_key &&
-		    local->ops->set_key(local_to_hw(local), DISABLE_KEY,
-					sta_addr, &key->conf)) {
-			printk(KERN_DEBUG "%s: set_encrypt - low-level disable"
-			       " failed\n", dev->name);
-			ret = -EINVAL;
-		}
-
-		if (set_tx_key || sdata->default_key == key) {
-			ieee80211_debugfs_key_remove_default(sdata);
-			sdata->default_key = NULL;
-		}
-		ieee80211_debugfs_key_remove(key);
-		if (sta)
-			sta->key = NULL;
-		else
-			sdata->keys[idx] = NULL;
 		ieee80211_key_free(key);
 		key = NULL;
 	} else {
-		old_key = key;
-		key = ieee80211_key_alloc(sta ? NULL : sdata, idx, key_len,
-					  GFP_KERNEL);
+		/*
+		 * Need to free it before allocating a new one with
+		 * with the same index or the ordering to the driver's
+		 * set_key() callback becomes confused.
+		 */
+		ieee80211_key_free(key);
+		key = ieee80211_key_alloc(sdata, sta, alg, idx, key_len, _key);
 		if (!key) {
 			ret = -ENOMEM;
 			goto err_out;
 		}
-
-		/* default to sw encryption; low-level driver sets these if the
-		 * requested encryption is supported */
-		key->conf.hw_key_idx = HW_KEY_IDX_INVALID;
-		key->conf.flags |= IEEE80211_KEY_FORCE_SW_ENCRYPT;
-
-		key->conf.alg = alg;
-		key->conf.keyidx = idx;
-		key->conf.keylen = key_len;
-		memcpy(key->conf.key, _key, key_len);
-
-		if (alg == ALG_CCMP) {
-			/* Initialize AES key state here as an optimization
-			 * so that it does not need to be initialized for every
-			 * packet. */
-			key->u.ccmp.tfm = ieee80211_aes_key_setup_encrypt(
-				key->conf.key);
-			if (!key->u.ccmp.tfm) {
-				ret = -ENOMEM;
-				goto err_free;
-			}
-		}
-
-		if (set_tx_key || sdata->default_key == old_key) {
-			ieee80211_debugfs_key_remove_default(sdata);
-			sdata->default_key = NULL;
-		}
-		ieee80211_debugfs_key_remove(old_key);
-		if (sta)
-			sta->key = key;
-		else
-			sdata->keys[idx] = key;
-		ieee80211_key_free(old_key);
-		ieee80211_debugfs_key_add(local, key);
-		if (sta)
-			ieee80211_debugfs_key_sta_link(key, sta);
-
-		if (try_hwaccel &&
-		    (alg == ALG_WEP || alg == ALG_TKIP || alg == ALG_CCMP))
-			ieee80211_set_hw_encryption(dev, sta, sta_addr, key);
 	}
 
-	if (set_tx_key || (!sta && !sdata->default_key && key)) {
-		sdata->default_key = key;
-		if (key)
-			ieee80211_debugfs_key_add_default(sdata);
+	if (set_tx_key || (!sta && !sdata->default_key && key))
+		ieee80211_set_default_key(sdata, idx);
 
-		if (local->ops->set_key_idx &&
-		    local->ops->set_key_idx(local_to_hw(local), idx))
-			printk(KERN_DEBUG "%s: failed to set TX key idx for "
-			       "low-level driver\n", dev->name);
-	}
-
-	if (sta)
-		sta_info_put(sta);
-
-	return 0;
-
-err_free:
-	ieee80211_key_free(key);
-err_out:
+	ret = 0;
+ err_out:
 	if (sta)
 		sta_info_put(sta);
 	return ret;
@@ -1181,12 +1037,7 @@
 		alg = ALG_NONE;
 	else if (erq->length == 0) {
 		/* No key data - just set the default TX key index */
-		if (sdata->default_key != sdata->keys[idx]) {
-			ieee80211_debugfs_key_remove_default(sdata);
-			sdata->default_key = sdata->keys[idx];
-			if (sdata->default_key)
-				ieee80211_debugfs_key_add_default(sdata);
-		}
+		ieee80211_set_default_key(sdata, idx);
 		return 0;
 	}
 
@@ -1232,7 +1083,7 @@
 	}
 
 	memcpy(key, sdata->keys[idx]->conf.key,
-	       min((int)erq->length, sdata->keys[idx]->conf.keylen));
+	       min_t(int, erq->length, sdata->keys[idx]->conf.keylen));
 	erq->length = sdata->keys[idx]->conf.keylen;
 	erq->flags |= IW_ENCODE_ENABLED;
 
diff --git a/net/mac80211/ieee80211_key.h b/net/mac80211/ieee80211_key.h
index 58e1925..a4e5fbb 100644
--- a/net/mac80211/ieee80211_key.h
+++ b/net/mac80211/ieee80211_key.h
@@ -41,7 +41,21 @@
 
 #define NUM_RX_DATA_QUEUES 17
 
+struct ieee80211_local;
+struct ieee80211_sub_if_data;
+struct sta_info;
+
+#define KEY_FLAG_UPLOADED_TO_HARDWARE	(1<<0)
+
 struct ieee80211_key {
+	struct ieee80211_local *local;
+	struct ieee80211_sub_if_data *sdata;
+	struct sta_info *sta;
+
+	struct list_head list;
+
+	unsigned int flags;
+
 	union {
 		struct {
 			/* last used TSC */
@@ -97,4 +111,16 @@
 	struct ieee80211_key_conf conf;
 };
 
+struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata,
+					  struct sta_info *sta,
+					  ieee80211_key_alg alg,
+					  int idx,
+					  size_t key_len,
+					  const u8 *key_data);
+void ieee80211_key_free(struct ieee80211_key *key);
+void ieee80211_set_default_key(struct ieee80211_sub_if_data *sdata, int idx);
+void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata);
+void ieee80211_enable_keys(struct ieee80211_sub_if_data *sdata);
+void ieee80211_disable_keys(struct ieee80211_sub_if_data *sdata);
+
 #endif /* IEEE80211_KEY_H */
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index 843d157..178f00c 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -2,25 +2,198 @@
  * Copyright 2002-2005, Instant802 Networks, Inc.
  * Copyright 2005-2006, Devicescape Software, Inc.
  * Copyright 2006-2007	Jiri Benc <jbenc@suse.cz>
+ * Copyright 2007	Johannes Berg <johannes@sipsolutions.net>
  *
  * 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.
  */
 
+#include <linux/if_ether.h>
+#include <linux/etherdevice.h>
+#include <linux/list.h>
 #include <net/mac80211.h>
 #include "ieee80211_i.h"
 #include "debugfs_key.h"
 #include "aes_ccm.h"
 
+
+/*
+ * Key handling basics
+ *
+ * Key handling in mac80211 is done based on per-interface (sub_if_data)
+ * keys and per-station keys. Since each station belongs to an interface,
+ * each station key also belongs to that interface.
+ *
+ * Hardware acceleration is done on a best-effort basis, for each key
+ * that is eligible the hardware is asked to enable that key but if
+ * it cannot do that they key is simply kept for software encryption.
+ * There is currently no way of knowing this except by looking into
+ * debugfs.
+ *
+ * All operations here are called under RTNL so no extra locking is
+ * required.
+ */
+
+static const u8 bcast_addr[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+static const u8 zero_addr[ETH_ALEN];
+
+static const u8 *get_mac_for_key(struct ieee80211_key *key)
+{
+	const u8 *addr = bcast_addr;
+
+	/*
+	 * If we're an AP we won't ever receive frames with a non-WEP
+	 * group key so we tell the driver that by using the zero MAC
+	 * address to indicate a transmit-only key.
+	 */
+	if (key->conf.alg != ALG_WEP &&
+	    (key->sdata->type == IEEE80211_IF_TYPE_AP ||
+	     key->sdata->type == IEEE80211_IF_TYPE_VLAN))
+		addr = zero_addr;
+
+	if (key->sta)
+		addr = key->sta->addr;
+
+	return addr;
+}
+
+static void ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
+{
+	const u8 *addr;
+	int ret;
+
+	if (!key->local->ops->set_key)
+		return;
+
+	addr = get_mac_for_key(key);
+
+	ret = key->local->ops->set_key(local_to_hw(key->local), SET_KEY,
+				       key->sdata->dev->dev_addr, addr,
+				       &key->conf);
+
+	WARN_ON(!ret && (key->conf.hw_key_idx == HW_KEY_IDX_INVALID));
+
+	if (!ret)
+		key->flags |= KEY_FLAG_UPLOADED_TO_HARDWARE;
+
+	if (ret && ret != -ENOSPC && ret != -EOPNOTSUPP)
+		printk(KERN_ERR "mac80211-%s: failed to set key "
+		       "(%d, " MAC_FMT ") to hardware (%d)\n",
+		       wiphy_name(key->local->hw.wiphy),
+		       key->conf.keyidx, MAC_ARG(addr), ret);
+}
+
+static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
+{
+	const u8 *addr;
+	int ret;
+
+	if (!key->local->ops->set_key)
+		return;
+
+	if (!(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE))
+		return;
+
+	addr = get_mac_for_key(key);
+
+	ret = key->local->ops->set_key(local_to_hw(key->local), DISABLE_KEY,
+				       key->sdata->dev->dev_addr, addr,
+				       &key->conf);
+
+	if (ret)
+		printk(KERN_ERR "mac80211-%s: failed to remove key "
+		       "(%d, " MAC_FMT ") from hardware (%d)\n",
+		       wiphy_name(key->local->hw.wiphy),
+		       key->conf.keyidx, MAC_ARG(addr), ret);
+
+	key->flags &= ~KEY_FLAG_UPLOADED_TO_HARDWARE;
+	key->conf.hw_key_idx = HW_KEY_IDX_INVALID;
+}
+
 struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata,
-					  int idx, size_t key_len, gfp_t flags)
+					  struct sta_info *sta,
+					  ieee80211_key_alg alg,
+					  int idx,
+					  size_t key_len,
+					  const u8 *key_data)
 {
 	struct ieee80211_key *key;
 
-	key = kzalloc(sizeof(struct ieee80211_key) + key_len, flags);
+	BUG_ON(alg == ALG_NONE);
+
+	key = kzalloc(sizeof(struct ieee80211_key) + key_len, GFP_KERNEL);
 	if (!key)
 		return NULL;
+
+	/*
+	 * Default to software encryption; we'll later upload the
+	 * key to the hardware if possible.
+	 */
+	key->conf.hw_key_idx = HW_KEY_IDX_INVALID;
+	key->conf.flags = 0;
+	key->flags = 0;
+
+	key->conf.alg = alg;
+	key->conf.keyidx = idx;
+	key->conf.keylen = key_len;
+	memcpy(key->conf.key, key_data, key_len);
+
+	key->local = sdata->local;
+	key->sdata = sdata;
+	key->sta = sta;
+
+	if (alg == ALG_CCMP) {
+		/*
+		 * Initialize AES key state here as an optimization so that
+		 * it does not need to be initialized for every packet.
+		 */
+		key->u.ccmp.tfm = ieee80211_aes_key_setup_encrypt(key_data);
+		if (!key->u.ccmp.tfm) {
+			ieee80211_key_free(key);
+			return NULL;
+		}
+	}
+
+	ieee80211_debugfs_key_add(key->local, key);
+
+	if (sta) {
+		ieee80211_debugfs_key_sta_link(key, sta);
+		sta->key = key;
+		/*
+		 * some hardware cannot handle TKIP with QoS, so
+		 * we indicate whether QoS could be in use.
+		 */
+		if (sta->flags & WLAN_STA_WME)
+			key->conf.flags |= IEEE80211_KEY_FLAG_WMM_STA;
+	} else {
+		if (sdata->type == IEEE80211_IF_TYPE_STA) {
+			struct sta_info *ap;
+
+			/* same here, the AP could be using QoS */
+			ap = sta_info_get(key->local, key->sdata->u.sta.bssid);
+			if (ap) {
+				if (ap->flags & WLAN_STA_WME)
+					key->conf.flags |=
+						IEEE80211_KEY_FLAG_WMM_STA;
+				sta_info_put(ap);
+			}
+		}
+
+		if (idx >= 0 && idx < NUM_DEFAULT_KEYS) {
+			if (!sdata->keys[idx])
+				sdata->keys[idx] = key;
+			else
+				WARN_ON(1);
+		} else
+			WARN_ON(1);
+	}
+
+	list_add(&key->list, &sdata->key_list);
+
+	if (netif_running(key->sdata->dev))
+		ieee80211_key_enable_hw_accel(key);
+
 	return key;
 }
 
@@ -29,8 +202,74 @@
 	if (!key)
 		return;
 
+	ieee80211_key_disable_hw_accel(key);
+
+	if (key->sta) {
+		key->sta->key = NULL;
+	} else {
+		if (key->sdata->default_key == key)
+			ieee80211_set_default_key(key->sdata, -1);
+		if (key->conf.keyidx >= 0 &&
+		    key->conf.keyidx < NUM_DEFAULT_KEYS)
+			key->sdata->keys[key->conf.keyidx] = NULL;
+		else
+			WARN_ON(1);
+	}
+
 	if (key->conf.alg == ALG_CCMP)
 		ieee80211_aes_key_free(key->u.ccmp.tfm);
 	ieee80211_debugfs_key_remove(key);
+
+	list_del(&key->list);
+
 	kfree(key);
 }
+
+void ieee80211_set_default_key(struct ieee80211_sub_if_data *sdata, int idx)
+{
+	struct ieee80211_key *key = NULL;
+
+	if (idx >= 0 && idx < NUM_DEFAULT_KEYS)
+		key = sdata->keys[idx];
+
+	if (sdata->default_key != key) {
+		ieee80211_debugfs_key_remove_default(sdata);
+
+		sdata->default_key = key;
+
+		if (sdata->default_key)
+			ieee80211_debugfs_key_add_default(sdata);
+
+		if (sdata->local->ops->set_key_idx)
+			sdata->local->ops->set_key_idx(
+				local_to_hw(sdata->local), idx);
+	}
+}
+
+void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_key *key, *tmp;
+
+	list_for_each_entry_safe(key, tmp, &sdata->key_list, list)
+		ieee80211_key_free(key);
+}
+
+void ieee80211_enable_keys(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_key *key;
+
+	WARN_ON(!netif_running(sdata->dev));
+	if (!netif_running(sdata->dev))
+		return;
+
+	list_for_each_entry(key, &sdata->key_list, list)
+		ieee80211_key_enable_hw_accel(key);
+}
+
+void ieee80211_disable_keys(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_key *key;
+
+	list_for_each_entry(key, &sdata->key_list, list)
+		ieee80211_key_disable_hw_accel(key);
+}
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 976b646..ba94f58 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -528,7 +528,7 @@
 
 	/* Check for weak IVs, if hwaccel did not remove IV from the frame */
 	if ((rx->local->hw.flags & IEEE80211_HW_WEP_INCLUDE_IV) ||
-	    (rx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT))
+	    !(rx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE))
 		if (ieee80211_wep_is_weak_iv(rx->skb, rx->key))
 			rx->sta->wep_weak_iv_count++;
 
@@ -553,7 +553,7 @@
 	}
 
 	if (!(rx->u.rx.status->flag & RX_FLAG_DECRYPTED) ||
-	    (rx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)) {
+	    !(rx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)) {
 		if (ieee80211_wep_decrypt(rx->local, rx->skb, rx->key)) {
 			if (net_ratelimit())
 				printk(KERN_DEBUG "%s: RX WEP frame, decrypt "
@@ -897,8 +897,7 @@
 	 * uploaded to the hardware.
 	 */
 	if ((rx->local->hw.flags & IEEE80211_HW_DEVICE_HIDES_WEP) &&
-	    (!rx->key ||
-	     !(rx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)))
+	    (!rx->key || (rx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)))
 		return TXRX_CONTINUE;
 
 	/* Drop unencrypted frames if key is set. */
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index fba2d79..c17172a 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -19,7 +19,6 @@
 #include "ieee80211_i.h"
 #include "ieee80211_rate.h"
 #include "sta_info.h"
-#include "debugfs_key.h"
 #include "debugfs_sta.h"
 
 /* Caller must hold local->sta_lock */
@@ -118,8 +117,6 @@
 	}
 	rate_control_free_sta(sta->rate_ctrl, sta->rate_ctrl_priv);
 	rate_control_put(sta->rate_ctrl);
-	if (sta->key)
-		ieee80211_debugfs_key_sta_del(sta->key, sta);
 	kfree(sta);
 }
 
@@ -230,11 +227,8 @@
 	       local->mdev->name, MAC_ARG(sta->addr));
 #endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
 
-	if (sta->key) {
-		ieee80211_debugfs_key_remove(sta->key);
-		ieee80211_key_free(sta->key);
-		sta->key = NULL;
-	}
+	ieee80211_key_free(sta->key);
+	sta->key = NULL;
 
 	rate_control_remove_sta_debugfs(sta);
 	ieee80211_sta_debugfs_remove(sta);
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index d70140c..b65ff65 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -536,7 +536,7 @@
 
 static int wep_encrypt_skb(struct ieee80211_txrx_data *tx, struct sk_buff *skb)
 {
-	if (tx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT) {
+	if (!(tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)) {
 		if (ieee80211_wep_encrypt(tx->local, skb, tx->key))
 			return -1;
 	} else {
@@ -832,7 +832,7 @@
 	 */
 
 	control->retry_limit = 1; /* no retry */
-	control->key_idx = -1; /* no encryption key */
+	control->key_idx = HW_KEY_IDX_INVALID;
 	control->flags &= ~(IEEE80211_TXCTL_USE_RTS_CTS |
 			    IEEE80211_TXCTL_USE_CTS_PROTECT);
 	control->flags |= IEEE80211_TXCTL_DO_NOT_ENCRYPT |
diff --git a/net/mac80211/wpa.c b/net/mac80211/wpa.c
index 4a2a9aa..b6cd66e0 100644
--- a/net/mac80211/wpa.c
+++ b/net/mac80211/wpa.c
@@ -89,7 +89,7 @@
 	if (ieee80211_get_hdr_info(skb, &sa, &da, &qos_tid, &data, &data_len))
 		return TXRX_DROP;
 
-	if (!(tx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT) &&
+	if ((tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) &&
 	    !(tx->flags & IEEE80211_TXRXD_FRAGMENTED) &&
 	    !(tx->local->hw.flags & IEEE80211_HW_TKIP_INCLUDE_MMIC) &&
 	    !wpa_test) {
@@ -146,7 +146,7 @@
 		return TXRX_CONTINUE;
 
 	if ((rx->u.rx.status->flag & RX_FLAG_DECRYPTED) &&
-	    !(rx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)) {
+	    (rx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)) {
 		if (rx->local->hw.flags & IEEE80211_HW_WEP_INCLUDE_IV) {
 			if (skb->len < MICHAEL_MIC_LEN)
 				return TXRX_DROP;
@@ -205,10 +205,10 @@
 	hdrlen = ieee80211_get_hdrlen(fc);
 	len = skb->len - hdrlen;
 
-	if (tx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)
-		tailneed = TKIP_ICV_LEN;
-	else
+	if (tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)
 		tailneed = 0;
+	else
+		tailneed = TKIP_ICV_LEN;
 
 	if ((skb_headroom(skb) < TKIP_IV_LEN ||
 	     skb_tailroom(skb) < tailneed)) {
@@ -227,7 +227,7 @@
 	if (key->u.tkip.iv16 == 0)
 		key->u.tkip.iv32++;
 
-	if (!(tx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)) {
+	if (tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) {
 		u32 flags = tx->local->hw.flags;
 		hdr = (struct ieee80211_hdr *)skb->data;
 
@@ -286,7 +286,7 @@
 	tx->u.tx.control->iv_len = TKIP_IV_LEN;
 	ieee80211_tx_set_iswep(tx);
 
-	if (!(tx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT) &&
+	if ((tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) &&
 	    !(tx->local->hw.flags & IEEE80211_HW_WEP_INCLUDE_IV) &&
 	    !wpa_test) {
 		/* hwaccel - with no need for preallocated room for IV/ICV */
@@ -331,7 +331,7 @@
 		return TXRX_DROP;
 
 	if ((rx->u.rx.status->flag & RX_FLAG_DECRYPTED) &&
-	    !(key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)) {
+	    (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)) {
 		if (!(rx->local->hw.flags & IEEE80211_HW_WEP_INCLUDE_IV)) {
 			/* Hardware takes care of all processing, including
 			 * replay protection, so no need to continue here. */
@@ -475,10 +475,10 @@
 	hdrlen = ieee80211_get_hdrlen(fc);
 	len = skb->len - hdrlen;
 
-	if (key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)
-		tailneed = CCMP_MIC_LEN;
-	else
+	if (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)
 		tailneed = 0;
+	else
+		tailneed = CCMP_MIC_LEN;
 
 	if ((skb_headroom(skb) < CCMP_HDR_LEN ||
 	     skb_tailroom(skb) < tailneed)) {
@@ -504,7 +504,7 @@
 
 	ccmp_pn2hdr(pos, pn, key->conf.keyidx);
 
-	if (!(key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)) {
+	if (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) {
 		/* hwaccel - with preallocated room for CCMP header */
 		tx->u.tx.control->key_idx = key->conf.hw_key_idx;
 		return 0;
@@ -537,7 +537,7 @@
 	tx->u.tx.control->iv_len = CCMP_HDR_LEN;
 	ieee80211_tx_set_iswep(tx);
 
-	if (!(tx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT) &&
+	if ((tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) &&
 	    !(tx->local->hw.flags & IEEE80211_HW_WEP_INCLUDE_IV)) {
 		/* hwaccel - with no need for preallocated room for CCMP "
 		 * header or MIC fields */
@@ -586,7 +586,7 @@
 		return TXRX_DROP;
 
 	if ((rx->u.rx.status->flag & RX_FLAG_DECRYPTED) &&
-	    !(key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT) &&
+	    (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) &&
 	    !(rx->local->hw.flags & IEEE80211_HW_WEP_INCLUDE_IV))
 		return TXRX_CONTINUE;
 
@@ -607,7 +607,7 @@
 	}
 
 	if ((rx->u.rx.status->flag & RX_FLAG_DECRYPTED) &&
-	    !(key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)) {
+	    (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)) {
 		/* hwaccel has already decrypted frame and verified MIC */
 	} else {
 		u8 *scratch, *b_0, *aad;