ALG_CCMP,
 } ieee80211_key_alg;
 
+/*
+ * This flag indiciates that the station this key is being
+ * configured for may use QoS. If your hardware cannot handle
+ * that situation it should reject that key.
+ */
+#define IEEE80211_KEY_FLAG_WMM_STA     (1<<0)
+
 struct ieee80211_key_conf {
-       /* shall be changed by the driver to anything but HW_KEY_IDX_INVALID */
+       /*
+        * To be set by the driver to the key index it would like to
+        * get in the ieee80211_tx_control.key_idx which defaults
+        * to HW_KEY_IDX_INVALID so that shouldn't be used.
+        */
        int hw_key_idx;
 
+       /* key algorithm, ALG_NONE should never be seen by the driver */
        ieee80211_key_alg alg;
 
-       int keylen;
+       /* key flags, see above */
+       u8 flags;
+
+       /* key index: 0-3 */
+       s8 keyidx;
 
-#define IEEE80211_KEY_FORCE_SW_ENCRYPT (1<<0) /* to be cleared by low-level
-                                                driver */
-       u32 flags; /* key configuration flags defined above */
+       /* length of key material */
+       u8 keylen;
 
-       s8 keyidx;                      /* WEP key index */
+       /* the key material */
        u8 key[0];
 };
 
 #define IEEE80211_SEQ_COUNTER_TX       1
 
 typedef enum {
-       SET_KEY, DISABLE_KEY, REMOVE_ALL_KEYS,
+       SET_KEY, DISABLE_KEY,
 } set_key_cmd;
 
 /* This is driver-visible part of the per-hw state the stack keeps. */
 
 /* hole at 6 */
 
-       /* Force software encryption for TKIP packets if WMM is enabled. */
-#define IEEE80211_HW_NO_TKIP_WMM_HWACCEL (1<<7)
+/* hole at 7 */
 
        /*
         * Some devices handle Michael MIC internally and do not include MIC in
         *
         * This is called to enable hardware acceleration of encryption and
         * decryption. The address will be the broadcast address for default
-        * keys and the other station's hardware address for individual keys.
+        * keys, the other station's hardware address for individual keys or
+        * the zero address for keys that will be used only for transmission.
+        *
+        * The local_address parameter will always be set to our own address,
+        * this is only relevant if you support multiple local addresses.
+        *
         * When transmitting, the TX control data will use the hw_key_idx
         * selected by the low-level driver.
+        *
+        * Return 0 if the key is now in use, -EOPNOTSUPP or -ENOSPC if it
+        * couldn't be added; if you return 0 then hw_key_idx must be
+        * assigned to something other than HW_KEY_IDX_INVALID. When the cmd
+        * is DISABLE_KEY then it must succeed.
+        *
+        * This callback can sleep, and is only called between add_interface
+        * and remove_interface calls, i.e. while the interface with the
+        * given local_address is enabled.
+        *
+        * The ieee80211_key_conf structure pointed to by the key parameter
+        * is guaranteed to be valid until another call to set_key removes
+        * it, but it can only be used as a cookie to differentiate keys.
         */
        int (*set_key)(struct ieee80211_hw *hw, set_key_cmd cmd,
-                      u8 *address, struct ieee80211_key_conf *key);
+                      const u8 *local_address, const u8 *address,
+                      struct ieee80211_key_conf *key);
 
        /*
         * Set TX key index for default/broadcast keys. This is needed in cases
         * is not set), in other cases, this function pointer can be set to
         * NULL since the IEEE 802.11 module takes care of selecting the key
         * index for each TX frame.
+        *
+        * TODO: If you use this callback in your driver tell us if you need
+        *       any other information from it to make it easier, like the
+        *       key_conf instead.
         */
        int (*set_key_idx)(struct ieee80211_hw *hw, int idx);
 
 
        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 = {             \
 #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 = {             \
 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,
 
        } else {
                ieee80211_if_config(dev);
                ieee80211_reset_erp_info(dev);
+               ieee80211_enable_keys(sdata);
        }
 
        if (sdata->type == IEEE80211_IF_TYPE_STA &&
                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--;
        }
 
        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);
 
 
        struct wireless_dev wdev;
 
+       /* keys */
+       struct list_head key_list;
+
        struct net_device *dev;
        struct ieee80211_local *local;
 
 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];
 
        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)
        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: {
 
 #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,
        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);
                        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) {
                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 (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);
+       if (set_tx_key || (!sta && !sdata->default_key && key))
+               ieee80211_set_default_key(sdata, idx);
 
-       return 0;
-
-err_free:
-       ieee80211_key_free(key);
-err_out:
+       ret = 0;
+ err_out:
        if (sta)
                sta_info_put(sta);
        return ret;
                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;
        }
 
        }
 
        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;
 
 
 
 #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 */
        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 */
 
  * 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;
 }
 
        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);
+}
 
 
        /* 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++;
 
        }
 
        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 "
         * 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. */
 
 #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 */
        }
        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);
 }
 
               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);
 
 
 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 {
         */
 
        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 |
 
        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) {
                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;
        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)) {
        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;
 
        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 */
                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. */
        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)) {
 
        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;
        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 */
                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;
 
        }
 
        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;