mwl8k: Check outgoing rate for a station to decide if ampdu can be created
[deliverable/linux.git] / drivers / net / wireless / mwl8k.c
index dcd4508b1fd47e0d8cc326cef4b30d8394d77dc0..5473f4ca0ca013a2989ec3c895b6148438d95af2 100644 (file)
@@ -63,6 +63,7 @@ MODULE_PARM_DESC(ap_mode_default,
 #define MWL8K_HIU_A2H_INTERRUPT_CLEAR_SEL      0x00000c38
 #define MWL8K_HIU_A2H_INTERRUPT_STATUS_MASK    0x00000c3c
 #define  MWL8K_A2H_INT_DUMMY                    (1 << 20)
+#define  MWL8K_A2H_INT_BA_WATCHDOG              (1 << 14)
 #define  MWL8K_A2H_INT_CHNL_SWITCHED            (1 << 11)
 #define  MWL8K_A2H_INT_QUEUE_EMPTY              (1 << 10)
 #define  MWL8K_A2H_INT_RADAR_DETECT             (1 << 7)
@@ -82,7 +83,8 @@ MODULE_PARM_DESC(ap_mode_default,
                                 MWL8K_A2H_INT_MAC_EVENT | \
                                 MWL8K_A2H_INT_OPC_DONE | \
                                 MWL8K_A2H_INT_RX_READY | \
-                                MWL8K_A2H_INT_TX_DONE)
+                                MWL8K_A2H_INT_TX_DONE | \
+                                MWL8K_A2H_INT_BA_WATCHDOG)
 
 #define MWL8K_RX_QUEUES                1
 #define MWL8K_TX_WMM_QUEUES    4
@@ -181,6 +183,7 @@ struct mwl8k_priv {
        u8 num_ampdu_queues;
        spinlock_t stream_lock;
        struct mwl8k_ampdu_stream ampdu[MWL8K_MAX_AMPDU_QUEUES];
+       struct work_struct watchdog_ba_handle;
 
        /* firmware access */
        struct mutex fw_mutex;
@@ -288,6 +291,7 @@ struct mwl8k_vif {
 struct mwl8k_sta {
        /* Index into station database. Returned by UPDATE_STADB.  */
        u8 peer_id;
+       u8 is_ampdu_allowed;
 };
 #define MWL8K_STA(_sta) ((struct mwl8k_sta *)&((_sta)->drv_priv))
 
@@ -375,6 +379,7 @@ static const struct ieee80211_rate mwl8k_rates_50[] = {
 #define MWL8K_CMD_ENABLE_SNIFFER       0x0150
 #define MWL8K_CMD_SET_MAC_ADDR         0x0202          /* per-vif */
 #define MWL8K_CMD_SET_RATEADAPT_MODE   0x0203
+#define MWL8K_CMD_GET_WATCHDOG_BITMAP  0x0205
 #define MWL8K_CMD_BSS_START            0x1100          /* per-vif */
 #define MWL8K_CMD_SET_NEW_STN          0x1111          /* per-vif */
 #define MWL8K_CMD_UPDATE_ENCRYPTION    0x1122          /* per-vif */
@@ -420,6 +425,7 @@ static const char *mwl8k_cmd_name(__le16 cmd, char *buf, int bufsize)
                MWL8K_CMDNAME(UPDATE_ENCRYPTION);
                MWL8K_CMDNAME(UPDATE_STADB);
                MWL8K_CMDNAME(BASTREAM);
+               MWL8K_CMDNAME(GET_WATCHDOG_BITMAP);
        default:
                snprintf(buf, bufsize, "0x%x", cmd);
        }
@@ -1512,6 +1518,27 @@ static int mwl8k_tx_wait_empty(struct ieee80211_hw *hw)
                     MWL8K_TXD_STATUS_OK_RETRY |                \
                     MWL8K_TXD_STATUS_OK_MORE_RETRY))
 
+/* The firmware will fill in the rate information
+ * for each packet that gets queued in the hardware
+ * in this structure
+ */
+
+struct rateinfo {
+       __le16  format:1;
+       __le16  short_gi:1;
+       __le16  band_width:1;
+       __le16  rate_id_mcs:6;
+       __le16  adv_coding:2;
+       __le16  antenna:2;
+       __le16  act_sub_chan:2;
+       __le16  preamble_type:1;
+       __le16  power_id:4;
+       __le16  antenna2:1;
+       __le16  reserved:1;
+       __le16  tx_bf_frame:1;
+       __le16  green_field:1;
+} __packed;
+
 static int
 mwl8k_txq_reclaim(struct ieee80211_hw *hw, int index, int limit, int force)
 {
@@ -1528,6 +1555,11 @@ mwl8k_txq_reclaim(struct ieee80211_hw *hw, int index, int limit, int force)
                struct sk_buff *skb;
                struct ieee80211_tx_info *info;
                u32 status;
+               struct ieee80211_sta *sta;
+               struct mwl8k_sta *sta_info = NULL;
+               u16 rate_info;
+               struct rateinfo *rate;
+               struct ieee80211_hdr *wh;
 
                tx = txq->head;
                tx_desc = txq->txd + tx;
@@ -1556,11 +1588,34 @@ mwl8k_txq_reclaim(struct ieee80211_hw *hw, int index, int limit, int force)
 
                mwl8k_remove_dma_header(skb, tx_desc->qos_control);
 
+               wh = (struct ieee80211_hdr *) skb->data;
+
                /* Mark descriptor as unused */
                tx_desc->pkt_phys_addr = 0;
                tx_desc->pkt_len = 0;
 
                info = IEEE80211_SKB_CB(skb);
+               if (ieee80211_is_data(wh->frame_control)) {
+                       sta = info->control.sta;
+                       if (sta) {
+                               sta_info = MWL8K_STA(sta);
+                               BUG_ON(sta_info == NULL);
+                               rate_info = le16_to_cpu(tx_desc->rate_info);
+                               rate = (struct rateinfo *)&rate_info;
+                               /* If rate is < 6.5 Mpbs for an ht station
+                                * do not form an ampdu. If the station is a
+                                * legacy station (format = 0), do not form an
+                                * ampdu
+                                */
+                               if (rate->rate_id_mcs < 1 ||
+                                   rate->format == 0) {
+                                       sta_info->is_ampdu_allowed = false;
+                               } else {
+                                       sta_info->is_ampdu_allowed = true;
+                               }
+                       }
+               }
+
                ieee80211_tx_info_clear_status(info);
 
                /* Rate control is happening in the firmware.
@@ -1577,7 +1632,8 @@ mwl8k_txq_reclaim(struct ieee80211_hw *hw, int index, int limit, int force)
                processed++;
        }
 
-       if (processed && priv->radio_on && !mutex_is_locked(&priv->fw_mutex))
+       if (index < MWL8K_TX_WMM_QUEUES && processed && priv->radio_on &&
+           !mutex_is_locked(&priv->fw_mutex))
                ieee80211_wake_queue(hw, index);
 
        return processed;
@@ -1677,6 +1733,7 @@ mwl8k_txq_xmit(struct ieee80211_hw *hw, int index, struct sk_buff *skb)
        struct mwl8k_priv *priv = hw->priv;
        struct ieee80211_tx_info *tx_info;
        struct mwl8k_vif *mwl8k_vif;
+       struct ieee80211_sta *sta;
        struct ieee80211_hdr *wh;
        struct mwl8k_tx_queue *txq;
        struct mwl8k_tx_desc *tx;
@@ -1684,6 +1741,10 @@ mwl8k_txq_xmit(struct ieee80211_hw *hw, int index, struct sk_buff *skb)
        u32 txstatus;
        u8 txdatarate;
        u16 qos;
+       int txpriority;
+       u8 tid = 0;
+       struct mwl8k_ampdu_stream *stream = NULL;
+       bool start_ba_session = false;
 
        wh = (struct ieee80211_hdr *)skb->data;
        if (ieee80211_is_data_qos(wh->frame_control))
@@ -1699,6 +1760,7 @@ mwl8k_txq_xmit(struct ieee80211_hw *hw, int index, struct sk_buff *skb)
        wh = &((struct mwl8k_dma_data *)skb->data)->wh;
 
        tx_info = IEEE80211_SKB_CB(skb);
+       sta = tx_info->control.sta;
        mwl8k_vif = MWL8K_VIF(tx_info->control.vif);
 
        if (tx_info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) {
@@ -1726,12 +1788,72 @@ mwl8k_txq_xmit(struct ieee80211_hw *hw, int index, struct sk_buff *skb)
                        qos |= MWL8K_QOS_ACK_POLICY_NORMAL;
        }
 
+       txpriority = index;
+
+       if (ieee80211_is_data_qos(wh->frame_control) &&
+           skb->protocol != cpu_to_be16(ETH_P_PAE) &&
+           sta->ht_cap.ht_supported && priv->ap_fw) {
+               tid = qos & 0xf;
+               spin_lock(&priv->stream_lock);
+               stream = mwl8k_lookup_stream(hw, sta->addr, tid);
+               if (stream != NULL) {
+                       if (stream->state == AMPDU_STREAM_ACTIVE) {
+                               txpriority = stream->txq_idx;
+                               index = stream->txq_idx;
+                       } else if (stream->state == AMPDU_STREAM_NEW) {
+                               /* We get here if the driver sends us packets
+                                * after we've initiated a stream, but before
+                                * our ampdu_action routine has been called
+                                * with IEEE80211_AMPDU_TX_START to get the SSN
+                                * for the ADDBA request.  So this packet can
+                                * go out with no risk of sequence number
+                                * mismatch.  No special handling is required.
+                                */
+                       } else {
+                               /* Drop packets that would go out after the
+                                * ADDBA request was sent but before the ADDBA
+                                * response is received.  If we don't do this,
+                                * the recipient would probably receive it
+                                * after the ADDBA request with SSN 0.  This
+                                * will cause the recipient's BA receive window
+                                * to shift, which would cause the subsequent
+                                * packets in the BA stream to be discarded.
+                                * mac80211 queues our packets for us in this
+                                * case, so this is really just a safety check.
+                                */
+                               wiphy_warn(hw->wiphy,
+                                          "Cannot send packet while ADDBA "
+                                          "dialog is underway.\n");
+                               spin_unlock(&priv->stream_lock);
+                               dev_kfree_skb(skb);
+                               return;
+                       }
+               } else {
+                       /* Defer calling mwl8k_start_stream so that the current
+                        * skb can go out before the ADDBA request.  This
+                        * prevents sequence number mismatch at the recepient
+                        * as described above.
+                        */
+                       if (MWL8K_STA(sta)->is_ampdu_allowed) {
+                               stream = mwl8k_add_stream(hw, sta, tid);
+                               if (stream != NULL)
+                                       start_ba_session = true;
+                       }
+               }
+               spin_unlock(&priv->stream_lock);
+       }
+
        dma = pci_map_single(priv->pdev, skb->data,
                                skb->len, PCI_DMA_TODEVICE);
 
        if (pci_dma_mapping_error(priv->pdev, dma)) {
                wiphy_debug(hw->wiphy,
                            "failed to dma map skb, dropping TX frame.\n");
+               if (start_ba_session) {
+                       spin_lock(&priv->stream_lock);
+                       mwl8k_remove_stream(hw, stream);
+                       spin_unlock(&priv->stream_lock);
+               }
                dev_kfree_skb(skb);
                return;
        }
@@ -1740,12 +1862,22 @@ mwl8k_txq_xmit(struct ieee80211_hw *hw, int index, struct sk_buff *skb)
 
        txq = priv->txq + index;
 
+       if (index >= MWL8K_TX_WMM_QUEUES && txq->len >= MWL8K_TX_DESCS) {
+               /* This is the case in which the tx packet is destined for an
+                * AMPDU queue and that AMPDU queue is full.  Because we don't
+                * start and stop the AMPDU queues, we must drop these packets.
+                */
+               dev_kfree_skb(skb);
+               spin_unlock_bh(&priv->tx_lock);
+               return;
+       }
+
        BUG_ON(txq->skb[txq->tail] != NULL);
        txq->skb[txq->tail] = skb;
 
        tx = txq->txd + txq->tail;
        tx->data_rate = txdatarate;
-       tx->tx_priority = index;
+       tx->tx_priority = txpriority;
        tx->qos_control = cpu_to_le16(qos);
        tx->pkt_phys_addr = cpu_to_le32(dma);
        tx->pkt_len = cpu_to_le16(skb->len);
@@ -1764,12 +1896,20 @@ mwl8k_txq_xmit(struct ieee80211_hw *hw, int index, struct sk_buff *skb)
        if (txq->tail == MWL8K_TX_DESCS)
                txq->tail = 0;
 
-       if (txq->head == txq->tail)
+       if (txq->head == txq->tail && index < MWL8K_TX_WMM_QUEUES)
                ieee80211_stop_queue(hw, index);
 
        mwl8k_tx_start(priv);
 
        spin_unlock_bh(&priv->tx_lock);
+
+       /* Initiate the ampdu session here */
+       if (start_ba_session) {
+               spin_lock(&priv->stream_lock);
+               if (mwl8k_start_stream(hw, stream))
+                       mwl8k_remove_stream(hw, stream);
+               spin_unlock(&priv->stream_lock);
+       }
 }
 
 
@@ -3236,6 +3376,65 @@ static int mwl8k_cmd_set_rateadapt_mode(struct ieee80211_hw *hw, __u16 mode)
        return rc;
 }
 
+/*
+ * CMD_GET_WATCHDOG_BITMAP.
+ */
+struct mwl8k_cmd_get_watchdog_bitmap {
+       struct mwl8k_cmd_pkt header;
+       u8      bitmap;
+} __packed;
+
+static int mwl8k_cmd_get_watchdog_bitmap(struct ieee80211_hw *hw, u8 *bitmap)
+{
+       struct mwl8k_cmd_get_watchdog_bitmap *cmd;
+       int rc;
+
+       cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+       if (cmd == NULL)
+               return -ENOMEM;
+
+       cmd->header.code = cpu_to_le16(MWL8K_CMD_GET_WATCHDOG_BITMAP);
+       cmd->header.length = cpu_to_le16(sizeof(*cmd));
+
+       rc = mwl8k_post_cmd(hw, &cmd->header);
+       if (!rc)
+               *bitmap = cmd->bitmap;
+
+       kfree(cmd);
+
+       return rc;
+}
+
+#define INVALID_BA     0xAA
+static void mwl8k_watchdog_ba_events(struct work_struct *work)
+{
+       int rc;
+       u8 bitmap = 0, stream_index;
+       struct mwl8k_ampdu_stream *streams;
+       struct mwl8k_priv *priv =
+               container_of(work, struct mwl8k_priv, watchdog_ba_handle);
+
+       rc = mwl8k_cmd_get_watchdog_bitmap(priv->hw, &bitmap);
+       if (rc)
+               return;
+
+       if (bitmap == INVALID_BA)
+               return;
+
+       /* the bitmap is the hw queue number.  Map it to the ampdu queue. */
+       stream_index = bitmap - MWL8K_TX_WMM_QUEUES;
+
+       BUG_ON(stream_index >= priv->num_ampdu_queues);
+
+       streams = &priv->ampdu[stream_index];
+
+       if (streams->state == AMPDU_STREAM_ACTIVE)
+               ieee80211_stop_tx_ba_session(streams->sta, streams->tid);
+
+       return;
+}
+
+
 /*
  * CMD_BSS_START.
  */
@@ -3931,6 +4130,11 @@ static irqreturn_t mwl8k_interrupt(int irq, void *dev_id)
                tasklet_schedule(&priv->poll_rx_task);
        }
 
+       if (status & MWL8K_A2H_INT_BA_WATCHDOG) {
+               status &= ~MWL8K_A2H_INT_BA_WATCHDOG;
+               ieee80211_queue_work(hw, &priv->watchdog_ba_handle);
+       }
+
        if (status)
                iowrite32(~status, priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS);
 
@@ -4083,6 +4287,7 @@ static void mwl8k_stop(struct ieee80211_hw *hw)
 
        /* Stop finalize join worker */
        cancel_work_sync(&priv->finalize_join_worker);
+       cancel_work_sync(&priv->watchdog_ba_handle);
        if (priv->beacon_skb != NULL)
                dev_kfree_skb(priv->beacon_skb);
 
@@ -4566,6 +4771,8 @@ static int mwl8k_sta_add(struct ieee80211_hw *hw,
                ret = mwl8k_cmd_update_stadb_add(hw, vif, sta);
                if (ret >= 0) {
                        MWL8K_STA(sta)->peer_id = ret;
+                       if (sta->ht_cap.ht_supported)
+                               MWL8K_STA(sta)->is_ampdu_allowed = true;
                        ret = 0;
                }
 
@@ -4632,21 +4839,118 @@ static int mwl8k_get_survey(struct ieee80211_hw *hw, int idx,
        return 0;
 }
 
+#define MAX_AMPDU_ATTEMPTS 5
+
 static int
 mwl8k_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
                   enum ieee80211_ampdu_mlme_action action,
                   struct ieee80211_sta *sta, u16 tid, u16 *ssn,
                   u8 buf_size)
 {
+
+       int i, rc = 0;
+       struct mwl8k_priv *priv = hw->priv;
+       struct mwl8k_ampdu_stream *stream;
+       u8 *addr = sta->addr;
+
+       if (!(hw->flags & IEEE80211_HW_AMPDU_AGGREGATION))
+               return -ENOTSUPP;
+
+       spin_lock(&priv->stream_lock);
+       stream = mwl8k_lookup_stream(hw, addr, tid);
+
        switch (action) {
        case IEEE80211_AMPDU_RX_START:
        case IEEE80211_AMPDU_RX_STOP:
-               if (!(hw->flags & IEEE80211_HW_AMPDU_AGGREGATION))
-                       return -ENOTSUPP;
-               return 0;
+               break;
+       case IEEE80211_AMPDU_TX_START:
+               /* By the time we get here the hw queues may contain outgoing
+                * packets for this RA/TID that are not part of this BA
+                * session.  The hw will assign sequence numbers to these
+                * packets as they go out.  So if we query the hw for its next
+                * sequence number and use that for the SSN here, it may end up
+                * being wrong, which will lead to sequence number mismatch at
+                * the recipient.  To avoid this, we reset the sequence number
+                * to O for the first MPDU in this BA stream.
+                */
+               *ssn = 0;
+               if (stream == NULL) {
+                       /* This means that somebody outside this driver called
+                        * ieee80211_start_tx_ba_session.  This is unexpected
+                        * because we do our own rate control.  Just warn and
+                        * move on.
+                        */
+                       wiphy_warn(hw->wiphy, "Unexpected call to %s.  "
+                                  "Proceeding anyway.\n", __func__);
+                       stream = mwl8k_add_stream(hw, sta, tid);
+               }
+               if (stream == NULL) {
+                       wiphy_debug(hw->wiphy, "no free AMPDU streams\n");
+                       rc = -EBUSY;
+                       break;
+               }
+               stream->state = AMPDU_STREAM_IN_PROGRESS;
+
+               /* Release the lock before we do the time consuming stuff */
+               spin_unlock(&priv->stream_lock);
+               for (i = 0; i < MAX_AMPDU_ATTEMPTS; i++) {
+                       rc = mwl8k_check_ba(hw, stream);
+
+                       if (!rc)
+                               break;
+                       /*
+                        * HW queues take time to be flushed, give them
+                        * sufficient time
+                        */
+
+                       msleep(1000);
+               }
+               spin_lock(&priv->stream_lock);
+               if (rc) {
+                       wiphy_err(hw->wiphy, "Stream for tid %d busy after %d"
+                               " attempts\n", tid, MAX_AMPDU_ATTEMPTS);
+                       mwl8k_remove_stream(hw, stream);
+                       rc = -EBUSY;
+                       break;
+               }
+               ieee80211_start_tx_ba_cb_irqsafe(vif, addr, tid);
+               break;
+       case IEEE80211_AMPDU_TX_STOP:
+               if (stream == NULL)
+                       break;
+               if (stream->state == AMPDU_STREAM_ACTIVE) {
+                       spin_unlock(&priv->stream_lock);
+                       mwl8k_destroy_ba(hw, stream);
+                       spin_lock(&priv->stream_lock);
+               }
+               mwl8k_remove_stream(hw, stream);
+               ieee80211_stop_tx_ba_cb_irqsafe(vif, addr, tid);
+               break;
+       case IEEE80211_AMPDU_TX_OPERATIONAL:
+               BUG_ON(stream == NULL);
+               BUG_ON(stream->state != AMPDU_STREAM_IN_PROGRESS);
+               spin_unlock(&priv->stream_lock);
+               rc = mwl8k_create_ba(hw, stream, buf_size);
+               spin_lock(&priv->stream_lock);
+               if (!rc)
+                       stream->state = AMPDU_STREAM_ACTIVE;
+               else {
+                       spin_unlock(&priv->stream_lock);
+                       mwl8k_destroy_ba(hw, stream);
+                       spin_lock(&priv->stream_lock);
+                       wiphy_debug(hw->wiphy,
+                               "Failed adding stream for sta %pM tid %d\n",
+                               addr, tid);
+                       mwl8k_remove_stream(hw, stream);
+               }
+               break;
+
        default:
-               return -ENOTSUPP;
+               rc = -ENOTSUPP;
        }
+
+       spin_unlock(&priv->stream_lock);
+       return rc;
 }
 
 static const struct ieee80211_ops mwl8k_ops = {
@@ -4920,7 +5224,8 @@ static int mwl8k_probe_hw(struct ieee80211_hw *hw)
 
        iowrite32(0, priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS);
        iowrite32(0, priv->regs + MWL8K_HIU_A2H_INTERRUPT_MASK);
-       iowrite32(MWL8K_A2H_INT_TX_DONE | MWL8K_A2H_INT_RX_READY,
+       iowrite32(MWL8K_A2H_INT_TX_DONE|MWL8K_A2H_INT_RX_READY|
+                 MWL8K_A2H_INT_BA_WATCHDOG,
                  priv->regs + MWL8K_HIU_A2H_INTERRUPT_CLEAR_SEL);
        iowrite32(0xffffffff, priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS_MASK);
 
@@ -5078,6 +5383,8 @@ static int mwl8k_firmware_load_success(struct mwl8k_priv *priv)
 
        /* Finalize join worker */
        INIT_WORK(&priv->finalize_join_worker, mwl8k_finalize_join_worker);
+       /* Handle watchdog ba events */
+       INIT_WORK(&priv->watchdog_ba_handle, mwl8k_watchdog_ba_events);
 
        /* TX reclaim and RX tasklets.  */
        tasklet_init(&priv->poll_tx_task, mwl8k_tx_poll, (unsigned long)hw);
This page took 0.031001 seconds and 5 git commands to generate.