sh: convert /proc/cpu/aligmnent, /proc/cpu/kernel_alignment to seq_file
[safe/jmp/linux-2.6] / net / mac80211 / util.c
index 9cd07e1..aeb65b3 100644 (file)
 #include <linux/if_arp.h>
 #include <linux/wireless.h>
 #include <linux/bitmap.h>
+#include <linux/crc32.h>
 #include <net/net_namespace.h>
 #include <net/cfg80211.h>
 #include <net/rtnetlink.h>
 
 #include "ieee80211_i.h"
+#include "driver-ops.h"
 #include "rate.h"
 #include "mesh.h"
 #include "wme.h"
+#include "led.h"
+#include "wep.h"
 
 /* privid for wiphys to determine whether they belong to us or not */
 void *mac80211_wiphy_privid = &mac80211_wiphy_privid;
 
-/* See IEEE 802.1H for LLC/SNAP encapsulation/decapsulation */
-/* Ethernet-II snap header (RFC1042 for most EtherTypes) */
-const unsigned char rfc1042_header[] =
-       { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00 };
-
-/* Bridge-Tunnel header (for EtherTypes ETH_P_AARP and ETH_P_IPX) */
-const unsigned char bridge_tunnel_header[] =
-       { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0xf8 };
+struct ieee80211_hw *wiphy_to_ieee80211_hw(struct wiphy *wiphy)
+{
+       struct ieee80211_local *local;
+       BUG_ON(!wiphy);
 
+       local = wiphy_priv(wiphy);
+       return &local->hw;
+}
+EXPORT_SYMBOL(wiphy_to_ieee80211_hw);
 
 u8 *ieee80211_get_bssid(struct ieee80211_hdr *hdr, size_t len,
-                       enum ieee80211_if_types type)
+                       enum nl80211_iftype type)
 {
-       u16 fc;
+       __le16 fc = hdr->frame_control;
 
         /* drop ACK/CTS frames and incorrect hdr len (ctrl) */
        if (len < 16)
                return NULL;
 
-       fc = le16_to_cpu(hdr->frame_control);
-
-       switch (fc & IEEE80211_FCTL_FTYPE) {
-       case IEEE80211_FTYPE_DATA:
+       if (ieee80211_is_data(fc)) {
                if (len < 24) /* drop incorrect hdr len (data) */
                        return NULL;
-               switch (fc & (IEEE80211_FCTL_TODS | IEEE80211_FCTL_FROMDS)) {
-               case IEEE80211_FCTL_TODS:
-                       return hdr->addr1;
-               case (IEEE80211_FCTL_TODS | IEEE80211_FCTL_FROMDS):
+
+               if (ieee80211_has_a4(fc))
                        return NULL;
-               case IEEE80211_FCTL_FROMDS:
+               if (ieee80211_has_tods(fc))
+                       return hdr->addr1;
+               if (ieee80211_has_fromds(fc))
                        return hdr->addr2;
-               case 0:
-                       return hdr->addr3;
-               }
-               break;
-       case IEEE80211_FTYPE_MGMT:
+
+               return hdr->addr3;
+       }
+
+       if (ieee80211_is_mgmt(fc)) {
                if (len < 24) /* drop incorrect hdr len (mgmt) */
                        return NULL;
                return hdr->addr3;
-       case IEEE80211_FTYPE_CTL:
-               if ((fc & IEEE80211_FCTL_STYPE) == IEEE80211_STYPE_PSPOLL)
+       }
+
+       if (ieee80211_is_ctl(fc)) {
+               if(ieee80211_is_pspoll(fc))
                        return hdr->addr1;
-               else if ((fc & IEEE80211_FCTL_STYPE) ==
-                                               IEEE80211_STYPE_BACK_REQ) {
+
+               if (ieee80211_is_back_req(fc)) {
                        switch (type) {
-                       case IEEE80211_IF_TYPE_STA:
+                       case NL80211_IFTYPE_STATION:
                                return hdr->addr2;
-                       case IEEE80211_IF_TYPE_AP:
-                       case IEEE80211_IF_TYPE_VLAN:
+                       case NL80211_IFTYPE_AP:
+                       case NL80211_IFTYPE_AP_VLAN:
                                return hdr->addr1;
                        default:
-                               return NULL;
+                               break; /* fall through to the return */
                        }
                }
-               else
-                       return NULL;
        }
 
        return NULL;
 }
 
-int ieee80211_get_hdrlen(u16 fc)
-{
-       int hdrlen = 24;
-
-       switch (fc & IEEE80211_FCTL_FTYPE) {
-       case IEEE80211_FTYPE_DATA:
-               if ((fc & IEEE80211_FCTL_FROMDS) && (fc & IEEE80211_FCTL_TODS))
-                       hdrlen = 30; /* Addr4 */
-               /*
-                * The QoS Control field is two bytes and its presence is
-                * indicated by the IEEE80211_STYPE_QOS_DATA bit. Add 2 to
-                * hdrlen if that bit is set.
-                * This works by masking out the bit and shifting it to
-                * bit position 1 so the result has the value 0 or 2.
-                */
-               hdrlen += (fc & IEEE80211_STYPE_QOS_DATA)
-                               >> (ilog2(IEEE80211_STYPE_QOS_DATA)-1);
-               break;
-       case IEEE80211_FTYPE_CTL:
-               /*
-                * ACK and CTS are 10 bytes, all others 16. To see how
-                * to get this condition consider
-                *   subtype mask:   0b0000000011110000 (0x00F0)
-                *   ACK subtype:    0b0000000011010000 (0x00D0)
-                *   CTS subtype:    0b0000000011000000 (0x00C0)
-                *   bits that matter:         ^^^      (0x00E0)
-                *   value of those: 0b0000000011000000 (0x00C0)
-                */
-               if ((fc & 0xE0) == 0xC0)
-                       hdrlen = 10;
-               else
-                       hdrlen = 16;
-               break;
-       }
-
-       return hdrlen;
-}
-EXPORT_SYMBOL(ieee80211_get_hdrlen);
-
-int ieee80211_get_hdrlen_from_skb(const struct sk_buff *skb)
-{
-       const struct ieee80211_hdr *hdr = (const struct ieee80211_hdr *) skb->data;
-       int hdrlen;
-
-       if (unlikely(skb->len < 10))
-               return 0;
-       hdrlen = ieee80211_get_hdrlen(le16_to_cpu(hdr->frame_control));
-       if (unlikely(hdrlen > skb->len))
-               return 0;
-       return hdrlen;
-}
-EXPORT_SYMBOL(ieee80211_get_hdrlen_from_skb);
-
-int ieee80211_get_mesh_hdrlen(struct ieee80211s_hdr *meshhdr)
-{
-       int ae = meshhdr->flags & IEEE80211S_FLAGS_AE;
-       /* 7.1.3.5a.2 */
-       switch (ae) {
-       case 0:
-               return 6;
-       case 1:
-               return 12;
-       case 2:
-               return 18;
-       case 3:
-               return 24;
-       default:
-               return 6;
-       }
-}
-
 void ieee80211_tx_set_protected(struct ieee80211_tx_data *tx)
 {
-       struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) tx->skb->data;
+       struct sk_buff *skb = tx->skb;
+       struct ieee80211_hdr *hdr;
 
-       hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PROTECTED);
-       if (tx->extra_frag) {
-               struct ieee80211_hdr *fhdr;
-               int i;
-               for (i = 0; i < tx->num_extra_frag; i++) {
-                       fhdr = (struct ieee80211_hdr *)
-                               tx->extra_frag[i]->data;
-                       fhdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PROTECTED);
-               }
-       }
+       do {
+               hdr = (struct ieee80211_hdr *) skb->data;
+               hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PROTECTED);
+       } while ((skb = skb->next));
 }
 
 int ieee80211_frame_duration(struct ieee80211_local *local, size_t len,
@@ -241,16 +166,21 @@ __le16 ieee80211_generic_frame_duration(struct ieee80211_hw *hw,
                                        struct ieee80211_rate *rate)
 {
        struct ieee80211_local *local = hw_to_local(hw);
-       struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+       struct ieee80211_sub_if_data *sdata;
        u16 dur;
        int erp;
+       bool short_preamble = false;
 
        erp = 0;
-       if (sdata->flags & IEEE80211_SDATA_OPERATING_GMODE)
-               erp = rate->flags & IEEE80211_RATE_ERP_G;
+       if (vif) {
+               sdata = vif_to_sdata(vif);
+               short_preamble = sdata->vif.bss_conf.use_short_preamble;
+               if (sdata->flags & IEEE80211_SDATA_OPERATING_GMODE)
+                       erp = rate->flags & IEEE80211_RATE_ERP_G;
+       }
 
        dur = ieee80211_frame_duration(local, frame_len, rate->bitrate, erp,
-                                      sdata->bss_conf.use_short_preamble);
+                                      short_preamble);
 
        return cpu_to_le16(dur);
 }
@@ -258,22 +188,29 @@ EXPORT_SYMBOL(ieee80211_generic_frame_duration);
 
 __le16 ieee80211_rts_duration(struct ieee80211_hw *hw,
                              struct ieee80211_vif *vif, size_t frame_len,
-                             const struct ieee80211_tx_control *frame_txctl)
+                             const struct ieee80211_tx_info *frame_txctl)
 {
        struct ieee80211_local *local = hw_to_local(hw);
        struct ieee80211_rate *rate;
-       struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+       struct ieee80211_sub_if_data *sdata;
        bool short_preamble;
        int erp;
        u16 dur;
+       struct ieee80211_supported_band *sband;
 
-       short_preamble = sdata->bss_conf.use_short_preamble;
+       sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
 
-       rate = frame_txctl->rts_cts_rate;
+       short_preamble = false;
+
+       rate = &sband->bitrates[frame_txctl->control.rts_cts_rate_idx];
 
        erp = 0;
-       if (sdata->flags & IEEE80211_SDATA_OPERATING_GMODE)
-               erp = rate->flags & IEEE80211_RATE_ERP_G;
+       if (vif) {
+               sdata = vif_to_sdata(vif);
+               short_preamble = sdata->vif.bss_conf.use_short_preamble;
+               if (sdata->flags & IEEE80211_SDATA_OPERATING_GMODE)
+                       erp = rate->flags & IEEE80211_RATE_ERP_G;
+       }
 
        /* CTS duration */
        dur = ieee80211_frame_duration(local, 10, rate->bitrate,
@@ -292,26 +229,33 @@ EXPORT_SYMBOL(ieee80211_rts_duration);
 __le16 ieee80211_ctstoself_duration(struct ieee80211_hw *hw,
                                    struct ieee80211_vif *vif,
                                    size_t frame_len,
-                                   const struct ieee80211_tx_control *frame_txctl)
+                                   const struct ieee80211_tx_info *frame_txctl)
 {
        struct ieee80211_local *local = hw_to_local(hw);
        struct ieee80211_rate *rate;
-       struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+       struct ieee80211_sub_if_data *sdata;
        bool short_preamble;
        int erp;
        u16 dur;
+       struct ieee80211_supported_band *sband;
+
+       sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
 
-       short_preamble = sdata->bss_conf.use_short_preamble;
+       short_preamble = false;
 
-       rate = frame_txctl->rts_cts_rate;
+       rate = &sband->bitrates[frame_txctl->control.rts_cts_rate_idx];
        erp = 0;
-       if (sdata->flags & IEEE80211_SDATA_OPERATING_GMODE)
-               erp = rate->flags & IEEE80211_RATE_ERP_G;
+       if (vif) {
+               sdata = vif_to_sdata(vif);
+               short_preamble = sdata->vif.bss_conf.use_short_preamble;
+               if (sdata->flags & IEEE80211_SDATA_OPERATING_GMODE)
+                       erp = rate->flags & IEEE80211_RATE_ERP_G;
+       }
 
        /* Data frame duration */
        dur = ieee80211_frame_duration(local, frame_len, rate->bitrate,
                                       erp, short_preamble);
-       if (!(frame_txctl->flags & IEEE80211_TXCTL_NO_ACK)) {
+       if (!(frame_txctl->flags & IEEE80211_TX_CTL_NO_ACK)) {
                /* ACK duration */
                dur += ieee80211_frame_duration(local, 10, rate->bitrate,
                                                erp, short_preamble);
@@ -321,62 +265,181 @@ __le16 ieee80211_ctstoself_duration(struct ieee80211_hw *hw,
 }
 EXPORT_SYMBOL(ieee80211_ctstoself_duration);
 
-void ieee80211_wake_queue(struct ieee80211_hw *hw, int queue)
+static void __ieee80211_wake_queue(struct ieee80211_hw *hw, int queue,
+                                  enum queue_stop_reason reason)
 {
        struct ieee80211_local *local = hw_to_local(hw);
 
-       if (test_and_clear_bit(IEEE80211_LINK_STATE_XOFF,
-                              &local->state[queue])) {
-               if (test_bit(IEEE80211_LINK_STATE_PENDING,
-                            &local->state[queue]))
-                       tasklet_schedule(&local->tx_pending_tasklet);
-               else
-                       if (!ieee80211_qdisc_installed(local->mdev)) {
-                               if (queue == 0)
-                                       netif_wake_queue(local->mdev);
-                       } else
-                               __netif_schedule(local->mdev);
-       }
+       if (WARN_ON(queue >= hw->queues))
+               return;
+
+       __clear_bit(reason, &local->queue_stop_reasons[queue]);
+
+       if (local->queue_stop_reasons[queue] != 0)
+               /* someone still has this queue stopped */
+               return;
+
+       if (!skb_queue_empty(&local->pending[queue]))
+               tasklet_schedule(&local->tx_pending_tasklet);
+}
+
+void ieee80211_wake_queue_by_reason(struct ieee80211_hw *hw, int queue,
+                                   enum queue_stop_reason reason)
+{
+       struct ieee80211_local *local = hw_to_local(hw);
+       unsigned long flags;
+
+       spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
+       __ieee80211_wake_queue(hw, queue, reason);
+       spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
+}
+
+void ieee80211_wake_queue(struct ieee80211_hw *hw, int queue)
+{
+       ieee80211_wake_queue_by_reason(hw, queue,
+                                      IEEE80211_QUEUE_STOP_REASON_DRIVER);
 }
 EXPORT_SYMBOL(ieee80211_wake_queue);
 
-void ieee80211_stop_queue(struct ieee80211_hw *hw, int queue)
+static void __ieee80211_stop_queue(struct ieee80211_hw *hw, int queue,
+                                  enum queue_stop_reason reason)
 {
        struct ieee80211_local *local = hw_to_local(hw);
 
-       if (!ieee80211_qdisc_installed(local->mdev) && queue == 0)
-               netif_stop_queue(local->mdev);
-       set_bit(IEEE80211_LINK_STATE_XOFF, &local->state[queue]);
+       if (WARN_ON(queue >= hw->queues))
+               return;
+
+       __set_bit(reason, &local->queue_stop_reasons[queue]);
+}
+
+void ieee80211_stop_queue_by_reason(struct ieee80211_hw *hw, int queue,
+                                   enum queue_stop_reason reason)
+{
+       struct ieee80211_local *local = hw_to_local(hw);
+       unsigned long flags;
+
+       spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
+       __ieee80211_stop_queue(hw, queue, reason);
+       spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
+}
+
+void ieee80211_stop_queue(struct ieee80211_hw *hw, int queue)
+{
+       ieee80211_stop_queue_by_reason(hw, queue,
+                                      IEEE80211_QUEUE_STOP_REASON_DRIVER);
 }
 EXPORT_SYMBOL(ieee80211_stop_queue);
 
-void ieee80211_start_queues(struct ieee80211_hw *hw)
+void ieee80211_add_pending_skb(struct ieee80211_local *local,
+                              struct sk_buff *skb)
+{
+       struct ieee80211_hw *hw = &local->hw;
+       unsigned long flags;
+       int queue = skb_get_queue_mapping(skb);
+       struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+
+       if (WARN_ON(!info->control.vif)) {
+               kfree_skb(skb);
+               return;
+       }
+
+       spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
+       __ieee80211_stop_queue(hw, queue, IEEE80211_QUEUE_STOP_REASON_SKB_ADD);
+       __skb_queue_tail(&local->pending[queue], skb);
+       __ieee80211_wake_queue(hw, queue, IEEE80211_QUEUE_STOP_REASON_SKB_ADD);
+       spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
+}
+
+int ieee80211_add_pending_skbs(struct ieee80211_local *local,
+                              struct sk_buff_head *skbs)
+{
+       struct ieee80211_hw *hw = &local->hw;
+       struct sk_buff *skb;
+       unsigned long flags;
+       int queue, ret = 0, i;
+
+       spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
+       for (i = 0; i < hw->queues; i++)
+               __ieee80211_stop_queue(hw, i,
+                       IEEE80211_QUEUE_STOP_REASON_SKB_ADD);
+
+       while ((skb = skb_dequeue(skbs))) {
+               struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+
+               if (WARN_ON(!info->control.vif)) {
+                       kfree_skb(skb);
+                       continue;
+               }
+
+               ret++;
+               queue = skb_get_queue_mapping(skb);
+               __skb_queue_tail(&local->pending[queue], skb);
+       }
+
+       for (i = 0; i < hw->queues; i++)
+               __ieee80211_wake_queue(hw, i,
+                       IEEE80211_QUEUE_STOP_REASON_SKB_ADD);
+       spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
+
+       return ret;
+}
+
+void ieee80211_stop_queues_by_reason(struct ieee80211_hw *hw,
+                                   enum queue_stop_reason reason)
 {
        struct ieee80211_local *local = hw_to_local(hw);
+       unsigned long flags;
        int i;
 
-       for (i = 0; i < hw->queues + hw->ampdu_queues; i++)
-               clear_bit(IEEE80211_LINK_STATE_XOFF, &local->state[i]);
-       if (!ieee80211_qdisc_installed(local->mdev))
-               netif_start_queue(local->mdev);
+       spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
+
+       for (i = 0; i < hw->queues; i++)
+               __ieee80211_stop_queue(hw, i, reason);
+
+       spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
 }
-EXPORT_SYMBOL(ieee80211_start_queues);
 
 void ieee80211_stop_queues(struct ieee80211_hw *hw)
 {
-       int i;
-
-       for (i = 0; i < hw->queues + hw->ampdu_queues; i++)
-               ieee80211_stop_queue(hw, i);
+       ieee80211_stop_queues_by_reason(hw,
+                                       IEEE80211_QUEUE_STOP_REASON_DRIVER);
 }
 EXPORT_SYMBOL(ieee80211_stop_queues);
 
-void ieee80211_wake_queues(struct ieee80211_hw *hw)
+int ieee80211_queue_stopped(struct ieee80211_hw *hw, int queue)
 {
+       struct ieee80211_local *local = hw_to_local(hw);
+       unsigned long flags;
+       int ret;
+
+       if (WARN_ON(queue >= hw->queues))
+               return true;
+
+       spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
+       ret = !!local->queue_stop_reasons[queue];
+       spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
+       return ret;
+}
+EXPORT_SYMBOL(ieee80211_queue_stopped);
+
+void ieee80211_wake_queues_by_reason(struct ieee80211_hw *hw,
+                                    enum queue_stop_reason reason)
+{
+       struct ieee80211_local *local = hw_to_local(hw);
+       unsigned long flags;
        int i;
 
-       for (i = 0; i < hw->queues + hw->ampdu_queues; i++)
-               ieee80211_wake_queue(hw, i);
+       spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
+
+       for (i = 0; i < hw->queues; i++)
+               __ieee80211_wake_queue(hw, i, reason);
+
+       spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
+}
+
+void ieee80211_wake_queues(struct ieee80211_hw *hw)
+{
+       ieee80211_wake_queues_by_reason(hw, IEEE80211_QUEUE_STOP_REASON_DRIVER);
 }
 EXPORT_SYMBOL(ieee80211_wake_queues);
 
@@ -389,23 +452,56 @@ void ieee80211_iterate_active_interfaces(
        struct ieee80211_local *local = hw_to_local(hw);
        struct ieee80211_sub_if_data *sdata;
 
+       mutex_lock(&local->iflist_mtx);
+
+       list_for_each_entry(sdata, &local->interfaces, list) {
+               switch (sdata->vif.type) {
+               case __NL80211_IFTYPE_AFTER_LAST:
+               case NL80211_IFTYPE_UNSPECIFIED:
+               case NL80211_IFTYPE_MONITOR:
+               case NL80211_IFTYPE_AP_VLAN:
+                       continue;
+               case NL80211_IFTYPE_AP:
+               case NL80211_IFTYPE_STATION:
+               case NL80211_IFTYPE_ADHOC:
+               case NL80211_IFTYPE_WDS:
+               case NL80211_IFTYPE_MESH_POINT:
+                       break;
+               }
+               if (netif_running(sdata->dev))
+                       iterator(data, sdata->dev->dev_addr,
+                                &sdata->vif);
+       }
+
+       mutex_unlock(&local->iflist_mtx);
+}
+EXPORT_SYMBOL_GPL(ieee80211_iterate_active_interfaces);
+
+void ieee80211_iterate_active_interfaces_atomic(
+       struct ieee80211_hw *hw,
+       void (*iterator)(void *data, u8 *mac,
+                        struct ieee80211_vif *vif),
+       void *data)
+{
+       struct ieee80211_local *local = hw_to_local(hw);
+       struct ieee80211_sub_if_data *sdata;
+
        rcu_read_lock();
 
        list_for_each_entry_rcu(sdata, &local->interfaces, list) {
                switch (sdata->vif.type) {
-               case IEEE80211_IF_TYPE_INVALID:
-               case IEEE80211_IF_TYPE_MNTR:
-               case IEEE80211_IF_TYPE_VLAN:
+               case __NL80211_IFTYPE_AFTER_LAST:
+               case NL80211_IFTYPE_UNSPECIFIED:
+               case NL80211_IFTYPE_MONITOR:
+               case NL80211_IFTYPE_AP_VLAN:
                        continue;
-               case IEEE80211_IF_TYPE_AP:
-               case IEEE80211_IF_TYPE_STA:
-               case IEEE80211_IF_TYPE_IBSS:
-               case IEEE80211_IF_TYPE_WDS:
-               case IEEE80211_IF_TYPE_MESH_POINT:
+               case NL80211_IFTYPE_AP:
+               case NL80211_IFTYPE_STATION:
+               case NL80211_IFTYPE_ADHOC:
+               case NL80211_IFTYPE_WDS:
+               case NL80211_IFTYPE_MESH_POINT:
                        break;
                }
-               if (sdata->dev == local->mdev)
-                       continue;
                if (netif_running(sdata->dev))
                        iterator(data, sdata->dev->dev_addr,
                                 &sdata->vif);
@@ -413,4 +509,657 @@ void ieee80211_iterate_active_interfaces(
 
        rcu_read_unlock();
 }
-EXPORT_SYMBOL_GPL(ieee80211_iterate_active_interfaces);
+EXPORT_SYMBOL_GPL(ieee80211_iterate_active_interfaces_atomic);
+
+/*
+ * Nothing should have been stuffed into the workqueue during
+ * the suspend->resume cycle. If this WARN is seen then there
+ * is a bug with either the driver suspend or something in
+ * mac80211 stuffing into the workqueue which we haven't yet
+ * cleared during mac80211's suspend cycle.
+ */
+static bool ieee80211_can_queue_work(struct ieee80211_local *local)
+{
+        if (WARN(local->suspended, "queueing ieee80211 work while "
+                "going to suspend\n"))
+                return false;
+
+       return true;
+}
+
+void ieee80211_queue_work(struct ieee80211_hw *hw, struct work_struct *work)
+{
+       struct ieee80211_local *local = hw_to_local(hw);
+
+       if (!ieee80211_can_queue_work(local))
+               return;
+
+       queue_work(local->workqueue, work);
+}
+EXPORT_SYMBOL(ieee80211_queue_work);
+
+void ieee80211_queue_delayed_work(struct ieee80211_hw *hw,
+                                 struct delayed_work *dwork,
+                                 unsigned long delay)
+{
+       struct ieee80211_local *local = hw_to_local(hw);
+
+       if (!ieee80211_can_queue_work(local))
+               return;
+
+       queue_delayed_work(local->workqueue, dwork, delay);
+}
+EXPORT_SYMBOL(ieee80211_queue_delayed_work);
+
+void ieee802_11_parse_elems(u8 *start, size_t len,
+                           struct ieee802_11_elems *elems)
+{
+       ieee802_11_parse_elems_crc(start, len, elems, 0, 0);
+}
+
+u32 ieee802_11_parse_elems_crc(u8 *start, size_t len,
+                              struct ieee802_11_elems *elems,
+                              u64 filter, u32 crc)
+{
+       size_t left = len;
+       u8 *pos = start;
+       bool calc_crc = filter != 0;
+
+       memset(elems, 0, sizeof(*elems));
+       elems->ie_start = start;
+       elems->total_len = len;
+
+       while (left >= 2) {
+               u8 id, elen;
+
+               id = *pos++;
+               elen = *pos++;
+               left -= 2;
+
+               if (elen > left)
+                       break;
+
+               if (calc_crc && id < 64 && (filter & BIT(id)))
+                       crc = crc32_be(crc, pos - 2, elen + 2);
+
+               switch (id) {
+               case WLAN_EID_SSID:
+                       elems->ssid = pos;
+                       elems->ssid_len = elen;
+                       break;
+               case WLAN_EID_SUPP_RATES:
+                       elems->supp_rates = pos;
+                       elems->supp_rates_len = elen;
+                       break;
+               case WLAN_EID_FH_PARAMS:
+                       elems->fh_params = pos;
+                       elems->fh_params_len = elen;
+                       break;
+               case WLAN_EID_DS_PARAMS:
+                       elems->ds_params = pos;
+                       elems->ds_params_len = elen;
+                       break;
+               case WLAN_EID_CF_PARAMS:
+                       elems->cf_params = pos;
+                       elems->cf_params_len = elen;
+                       break;
+               case WLAN_EID_TIM:
+                       if (elen >= sizeof(struct ieee80211_tim_ie)) {
+                               elems->tim = (void *)pos;
+                               elems->tim_len = elen;
+                       }
+                       break;
+               case WLAN_EID_IBSS_PARAMS:
+                       elems->ibss_params = pos;
+                       elems->ibss_params_len = elen;
+                       break;
+               case WLAN_EID_CHALLENGE:
+                       elems->challenge = pos;
+                       elems->challenge_len = elen;
+                       break;
+               case WLAN_EID_VENDOR_SPECIFIC:
+                       if (elen >= 4 && pos[0] == 0x00 && pos[1] == 0x50 &&
+                           pos[2] == 0xf2) {
+                               /* Microsoft OUI (00:50:F2) */
+
+                               if (calc_crc)
+                                       crc = crc32_be(crc, pos - 2, elen + 2);
+
+                               if (pos[3] == 1) {
+                                       /* OUI Type 1 - WPA IE */
+                                       elems->wpa = pos;
+                                       elems->wpa_len = elen;
+                               } else if (elen >= 5 && pos[3] == 2) {
+                                       /* OUI Type 2 - WMM IE */
+                                       if (pos[4] == 0) {
+                                               elems->wmm_info = pos;
+                                               elems->wmm_info_len = elen;
+                                       } else if (pos[4] == 1) {
+                                               elems->wmm_param = pos;
+                                               elems->wmm_param_len = elen;
+                                       }
+                               }
+                       }
+                       break;
+               case WLAN_EID_RSN:
+                       elems->rsn = pos;
+                       elems->rsn_len = elen;
+                       break;
+               case WLAN_EID_ERP_INFO:
+                       elems->erp_info = pos;
+                       elems->erp_info_len = elen;
+                       break;
+               case WLAN_EID_EXT_SUPP_RATES:
+                       elems->ext_supp_rates = pos;
+                       elems->ext_supp_rates_len = elen;
+                       break;
+               case WLAN_EID_HT_CAPABILITY:
+                       if (elen >= sizeof(struct ieee80211_ht_cap))
+                               elems->ht_cap_elem = (void *)pos;
+                       break;
+               case WLAN_EID_HT_INFORMATION:
+                       if (elen >= sizeof(struct ieee80211_ht_info))
+                               elems->ht_info_elem = (void *)pos;
+                       break;
+               case WLAN_EID_MESH_ID:
+                       elems->mesh_id = pos;
+                       elems->mesh_id_len = elen;
+                       break;
+               case WLAN_EID_MESH_CONFIG:
+                       elems->mesh_config = pos;
+                       elems->mesh_config_len = elen;
+                       break;
+               case WLAN_EID_PEER_LINK:
+                       elems->peer_link = pos;
+                       elems->peer_link_len = elen;
+                       break;
+               case WLAN_EID_PREQ:
+                       elems->preq = pos;
+                       elems->preq_len = elen;
+                       break;
+               case WLAN_EID_PREP:
+                       elems->prep = pos;
+                       elems->prep_len = elen;
+                       break;
+               case WLAN_EID_PERR:
+                       elems->perr = pos;
+                       elems->perr_len = elen;
+                       break;
+               case WLAN_EID_CHANNEL_SWITCH:
+                       elems->ch_switch_elem = pos;
+                       elems->ch_switch_elem_len = elen;
+                       break;
+               case WLAN_EID_QUIET:
+                       if (!elems->quiet_elem) {
+                               elems->quiet_elem = pos;
+                               elems->quiet_elem_len = elen;
+                       }
+                       elems->num_of_quiet_elem++;
+                       break;
+               case WLAN_EID_COUNTRY:
+                       elems->country_elem = pos;
+                       elems->country_elem_len = elen;
+                       break;
+               case WLAN_EID_PWR_CONSTRAINT:
+                       elems->pwr_constr_elem = pos;
+                       elems->pwr_constr_elem_len = elen;
+                       break;
+               case WLAN_EID_TIMEOUT_INTERVAL:
+                       elems->timeout_int = pos;
+                       elems->timeout_int_len = elen;
+                       break;
+               default:
+                       break;
+               }
+
+               left -= elen;
+               pos += elen;
+       }
+
+       return crc;
+}
+
+void ieee80211_set_wmm_default(struct ieee80211_sub_if_data *sdata)
+{
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_tx_queue_params qparam;
+       int queue;
+       bool use_11b;
+       int aCWmin, aCWmax;
+
+       if (!local->ops->conf_tx)
+               return;
+
+       memset(&qparam, 0, sizeof(qparam));
+
+       use_11b = (local->hw.conf.channel->band == IEEE80211_BAND_2GHZ) &&
+                !(sdata->flags & IEEE80211_SDATA_OPERATING_GMODE);
+
+       for (queue = 0; queue < local_to_hw(local)->queues; queue++) {
+               /* Set defaults according to 802.11-2007 Table 7-37 */
+               aCWmax = 1023;
+               if (use_11b)
+                       aCWmin = 31;
+               else
+                       aCWmin = 15;
+
+               switch (queue) {
+               case 3: /* AC_BK */
+                       qparam.cw_max = aCWmax;
+                       qparam.cw_min = aCWmin;
+                       qparam.txop = 0;
+                       qparam.aifs = 7;
+                       break;
+               default: /* never happens but let's not leave undefined */
+               case 2: /* AC_BE */
+                       qparam.cw_max = aCWmax;
+                       qparam.cw_min = aCWmin;
+                       qparam.txop = 0;
+                       qparam.aifs = 3;
+                       break;
+               case 1: /* AC_VI */
+                       qparam.cw_max = aCWmin;
+                       qparam.cw_min = (aCWmin + 1) / 2 - 1;
+                       if (use_11b)
+                               qparam.txop = 6016/32;
+                       else
+                               qparam.txop = 3008/32;
+                       qparam.aifs = 2;
+                       break;
+               case 0: /* AC_VO */
+                       qparam.cw_max = (aCWmin + 1) / 2 - 1;
+                       qparam.cw_min = (aCWmin + 1) / 4 - 1;
+                       if (use_11b)
+                               qparam.txop = 3264/32;
+                       else
+                               qparam.txop = 1504/32;
+                       qparam.aifs = 2;
+                       break;
+               }
+
+               drv_conf_tx(local, queue, &qparam);
+       }
+}
+
+void ieee80211_sta_def_wmm_params(struct ieee80211_sub_if_data *sdata,
+                                 const size_t supp_rates_len,
+                                 const u8 *supp_rates)
+{
+       struct ieee80211_local *local = sdata->local;
+       int i, have_higher_than_11mbit = 0;
+
+       /* cf. IEEE 802.11 9.2.12 */
+       for (i = 0; i < supp_rates_len; i++)
+               if ((supp_rates[i] & 0x7f) * 5 > 110)
+                       have_higher_than_11mbit = 1;
+
+       if (local->hw.conf.channel->band == IEEE80211_BAND_2GHZ &&
+           have_higher_than_11mbit)
+               sdata->flags |= IEEE80211_SDATA_OPERATING_GMODE;
+       else
+               sdata->flags &= ~IEEE80211_SDATA_OPERATING_GMODE;
+
+       ieee80211_set_wmm_default(sdata);
+}
+
+u32 ieee80211_mandatory_rates(struct ieee80211_local *local,
+                             enum ieee80211_band band)
+{
+       struct ieee80211_supported_band *sband;
+       struct ieee80211_rate *bitrates;
+       u32 mandatory_rates;
+       enum ieee80211_rate_flags mandatory_flag;
+       int i;
+
+       sband = local->hw.wiphy->bands[band];
+       if (!sband) {
+               WARN_ON(1);
+               sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
+       }
+
+       if (band == IEEE80211_BAND_2GHZ)
+               mandatory_flag = IEEE80211_RATE_MANDATORY_B;
+       else
+               mandatory_flag = IEEE80211_RATE_MANDATORY_A;
+
+       bitrates = sband->bitrates;
+       mandatory_rates = 0;
+       for (i = 0; i < sband->n_bitrates; i++)
+               if (bitrates[i].flags & mandatory_flag)
+                       mandatory_rates |= BIT(i);
+       return mandatory_rates;
+}
+
+void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata,
+                        u16 transaction, u16 auth_alg,
+                        u8 *extra, size_t extra_len, const u8 *bssid,
+                        const u8 *key, u8 key_len, u8 key_idx)
+{
+       struct ieee80211_local *local = sdata->local;
+       struct sk_buff *skb;
+       struct ieee80211_mgmt *mgmt;
+       int err;
+
+       skb = dev_alloc_skb(local->hw.extra_tx_headroom +
+                           sizeof(*mgmt) + 6 + extra_len);
+       if (!skb) {
+               printk(KERN_DEBUG "%s: failed to allocate buffer for auth "
+                      "frame\n", sdata->dev->name);
+               return;
+       }
+       skb_reserve(skb, local->hw.extra_tx_headroom);
+
+       mgmt = (struct ieee80211_mgmt *) skb_put(skb, 24 + 6);
+       memset(mgmt, 0, 24 + 6);
+       mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+                                         IEEE80211_STYPE_AUTH);
+       memcpy(mgmt->da, bssid, ETH_ALEN);
+       memcpy(mgmt->sa, sdata->dev->dev_addr, ETH_ALEN);
+       memcpy(mgmt->bssid, bssid, ETH_ALEN);
+       mgmt->u.auth.auth_alg = cpu_to_le16(auth_alg);
+       mgmt->u.auth.auth_transaction = cpu_to_le16(transaction);
+       mgmt->u.auth.status_code = cpu_to_le16(0);
+       if (extra)
+               memcpy(skb_put(skb, extra_len), extra, extra_len);
+
+       if (auth_alg == WLAN_AUTH_SHARED_KEY && transaction == 3) {
+               mgmt->frame_control |= cpu_to_le16(IEEE80211_FCTL_PROTECTED);
+               err = ieee80211_wep_encrypt(local, skb, key, key_len, key_idx);
+               WARN_ON(err);
+       }
+
+       ieee80211_tx_skb(sdata, skb, 0);
+}
+
+int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
+                            const u8 *ie, size_t ie_len)
+{
+       struct ieee80211_supported_band *sband;
+       u8 *pos, *supp_rates_len, *esupp_rates_len = NULL;
+       int i;
+
+       sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
+
+       pos = buffer;
+
+       *pos++ = WLAN_EID_SUPP_RATES;
+       supp_rates_len = pos;
+       *pos++ = 0;
+
+       for (i = 0; i < sband->n_bitrates; i++) {
+               struct ieee80211_rate *rate = &sband->bitrates[i];
+
+               if (esupp_rates_len) {
+                       *esupp_rates_len += 1;
+               } else if (*supp_rates_len == 8) {
+                       *pos++ = WLAN_EID_EXT_SUPP_RATES;
+                       esupp_rates_len = pos;
+                       *pos++ = 1;
+               } else
+                       *supp_rates_len += 1;
+
+               *pos++ = rate->bitrate / 5;
+       }
+
+       if (sband->ht_cap.ht_supported) {
+               __le16 tmp = cpu_to_le16(sband->ht_cap.cap);
+
+               *pos++ = WLAN_EID_HT_CAPABILITY;
+               *pos++ = sizeof(struct ieee80211_ht_cap);
+               memset(pos, 0, sizeof(struct ieee80211_ht_cap));
+               memcpy(pos, &tmp, sizeof(u16));
+               pos += sizeof(u16);
+               /* TODO: needs a define here for << 2 */
+               *pos++ = sband->ht_cap.ampdu_factor |
+                        (sband->ht_cap.ampdu_density << 2);
+               memcpy(pos, &sband->ht_cap.mcs, sizeof(sband->ht_cap.mcs));
+               pos += sizeof(sband->ht_cap.mcs);
+               pos += 2 + 4 + 1; /* ext info, BF cap, antsel */
+       }
+
+       /*
+        * If adding more here, adjust code in main.c
+        * that calculates local->scan_ies_len.
+        */
+
+       if (ie) {
+               memcpy(pos, ie, ie_len);
+               pos += ie_len;
+       }
+
+       return pos - buffer;
+}
+
+void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst,
+                             const u8 *ssid, size_t ssid_len,
+                             const u8 *ie, size_t ie_len)
+{
+       struct ieee80211_local *local = sdata->local;
+       struct sk_buff *skb;
+       struct ieee80211_mgmt *mgmt;
+       u8 *pos;
+
+       skb = dev_alloc_skb(local->hw.extra_tx_headroom + sizeof(*mgmt) + 200 +
+                           ie_len);
+       if (!skb) {
+               printk(KERN_DEBUG "%s: failed to allocate buffer for probe "
+                      "request\n", sdata->dev->name);
+               return;
+       }
+       skb_reserve(skb, local->hw.extra_tx_headroom);
+
+       mgmt = (struct ieee80211_mgmt *) skb_put(skb, 24);
+       memset(mgmt, 0, 24);
+       mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+                                         IEEE80211_STYPE_PROBE_REQ);
+       memcpy(mgmt->sa, sdata->dev->dev_addr, ETH_ALEN);
+       if (dst) {
+               memcpy(mgmt->da, dst, ETH_ALEN);
+               memcpy(mgmt->bssid, dst, ETH_ALEN);
+       } else {
+               memset(mgmt->da, 0xff, ETH_ALEN);
+               memset(mgmt->bssid, 0xff, ETH_ALEN);
+       }
+       pos = skb_put(skb, 2 + ssid_len);
+       *pos++ = WLAN_EID_SSID;
+       *pos++ = ssid_len;
+       memcpy(pos, ssid, ssid_len);
+       pos += ssid_len;
+
+       skb_put(skb, ieee80211_build_preq_ies(local, pos, ie, ie_len));
+
+       ieee80211_tx_skb(sdata, skb, 0);
+}
+
+u32 ieee80211_sta_get_rates(struct ieee80211_local *local,
+                           struct ieee802_11_elems *elems,
+                           enum ieee80211_band band)
+{
+       struct ieee80211_supported_band *sband;
+       struct ieee80211_rate *bitrates;
+       size_t num_rates;
+       u32 supp_rates;
+       int i, j;
+       sband = local->hw.wiphy->bands[band];
+
+       if (!sband) {
+               WARN_ON(1);
+               sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
+       }
+
+       bitrates = sband->bitrates;
+       num_rates = sband->n_bitrates;
+       supp_rates = 0;
+       for (i = 0; i < elems->supp_rates_len +
+                    elems->ext_supp_rates_len; i++) {
+               u8 rate = 0;
+               int own_rate;
+               if (i < elems->supp_rates_len)
+                       rate = elems->supp_rates[i];
+               else if (elems->ext_supp_rates)
+                       rate = elems->ext_supp_rates
+                               [i - elems->supp_rates_len];
+               own_rate = 5 * (rate & 0x7f);
+               for (j = 0; j < num_rates; j++)
+                       if (bitrates[j].bitrate == own_rate)
+                               supp_rates |= BIT(j);
+       }
+       return supp_rates;
+}
+
+void ieee80211_stop_device(struct ieee80211_local *local)
+{
+       ieee80211_led_radio(local, false);
+
+       cancel_work_sync(&local->reconfig_filter);
+       drv_stop(local);
+
+       flush_workqueue(local->workqueue);
+}
+
+int ieee80211_reconfig(struct ieee80211_local *local)
+{
+       struct ieee80211_hw *hw = &local->hw;
+       struct ieee80211_sub_if_data *sdata;
+       struct ieee80211_if_init_conf conf;
+       struct sta_info *sta;
+       unsigned long flags;
+       int res;
+       bool from_suspend = local->suspended;
+
+       /*
+        * We're going to start the hardware, at that point
+        * we are no longer suspended and can RX frames.
+        */
+       local->suspended = false;
+
+       /* restart hardware */
+       if (local->open_count) {
+               res = drv_start(local);
+
+               ieee80211_led_radio(local, true);
+       }
+
+       /* add interfaces */
+       list_for_each_entry(sdata, &local->interfaces, list) {
+               if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
+                   sdata->vif.type != NL80211_IFTYPE_MONITOR &&
+                   netif_running(sdata->dev)) {
+                       conf.vif = &sdata->vif;
+                       conf.type = sdata->vif.type;
+                       conf.mac_addr = sdata->dev->dev_addr;
+                       res = drv_add_interface(local, &conf);
+               }
+       }
+
+       /* add STAs back */
+       if (local->ops->sta_notify) {
+               spin_lock_irqsave(&local->sta_lock, flags);
+               list_for_each_entry(sta, &local->sta_list, list) {
+                       sdata = sta->sdata;
+                       if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+                               sdata = container_of(sdata->bss,
+                                            struct ieee80211_sub_if_data,
+                                            u.ap);
+
+                       drv_sta_notify(local, &sdata->vif, STA_NOTIFY_ADD,
+                                      &sta->sta);
+               }
+               spin_unlock_irqrestore(&local->sta_lock, flags);
+       }
+
+       /* Clear Suspend state so that ADDBA requests can be processed */
+
+       rcu_read_lock();
+
+       if (hw->flags & IEEE80211_HW_AMPDU_AGGREGATION) {
+               list_for_each_entry_rcu(sta, &local->sta_list, list) {
+                       clear_sta_flags(sta, WLAN_STA_SUSPEND);
+               }
+       }
+
+       rcu_read_unlock();
+
+       /* setup RTS threshold */
+       drv_set_rts_threshold(local, hw->wiphy->rts_threshold);
+
+       /* reconfigure hardware */
+       ieee80211_hw_config(local, ~0);
+
+       ieee80211_configure_filter(local);
+
+       /* Finally also reconfigure all the BSS information */
+       list_for_each_entry(sdata, &local->interfaces, list) {
+               u32 changed = ~0;
+               if (!netif_running(sdata->dev))
+                       continue;
+               switch (sdata->vif.type) {
+               case NL80211_IFTYPE_STATION:
+                       /* disable beacon change bits */
+                       changed &= ~(BSS_CHANGED_BEACON |
+                                    BSS_CHANGED_BEACON_ENABLED);
+                       /* fall through */
+               case NL80211_IFTYPE_ADHOC:
+               case NL80211_IFTYPE_AP:
+               case NL80211_IFTYPE_MESH_POINT:
+                       ieee80211_bss_info_change_notify(sdata, changed);
+                       break;
+               case NL80211_IFTYPE_WDS:
+                       break;
+               case NL80211_IFTYPE_AP_VLAN:
+               case NL80211_IFTYPE_MONITOR:
+                       /* ignore virtual */
+                       break;
+               case NL80211_IFTYPE_UNSPECIFIED:
+               case __NL80211_IFTYPE_AFTER_LAST:
+                       WARN_ON(1);
+                       break;
+               }
+       }
+
+       /* add back keys */
+       list_for_each_entry(sdata, &local->interfaces, list)
+               if (netif_running(sdata->dev))
+                       ieee80211_enable_keys(sdata);
+
+       ieee80211_wake_queues_by_reason(hw,
+                       IEEE80211_QUEUE_STOP_REASON_SUSPEND);
+
+       /*
+        * If this is for hw restart things are still running.
+        * We may want to change that later, however.
+        */
+       if (!from_suspend)
+               return 0;
+
+#ifdef CONFIG_PM
+       local->suspended = false;
+
+       list_for_each_entry(sdata, &local->interfaces, list) {
+               switch(sdata->vif.type) {
+               case NL80211_IFTYPE_STATION:
+                       ieee80211_sta_restart(sdata);
+                       break;
+               case NL80211_IFTYPE_ADHOC:
+                       ieee80211_ibss_restart(sdata);
+                       break;
+               case NL80211_IFTYPE_MESH_POINT:
+                       ieee80211_mesh_restart(sdata);
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       add_timer(&local->sta_cleanup);
+
+       spin_lock_irqsave(&local->sta_lock, flags);
+       list_for_each_entry(sta, &local->sta_list, list)
+               mesh_plink_restart(sta);
+       spin_unlock_irqrestore(&local->sta_lock, flags);
+#else
+       WARN_ON(1);
+#endif
+       return 0;
+}
+