summaryrefslogtreecommitdiff
path: root/mwl_mac80211.c
diff options
context:
space:
mode:
Diffstat (limited to 'mwl_mac80211.c')
-rw-r--r--mwl_mac80211.c850
1 files changed, 850 insertions, 0 deletions
diff --git a/mwl_mac80211.c b/mwl_mac80211.c
new file mode 100644
index 0000000..5f14bdc
--- /dev/null
+++ b/mwl_mac80211.c
@@ -0,0 +1,850 @@
+/*
+* Copyright (c) 2006-2014 Marvell International Ltd.
+*
+* Permission to use, copy, modify, and/or distribute this software for any
+* purpose with or without fee is hereby granted, provided that the above
+* copyright notice and this permission notice appear in all copies.
+*
+* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/*
+*
+* Description: This file implements mac80211 related functions.
+*
+*/
+
+#include "mwl_sysadpt.h"
+#include "mwl_dev.h"
+#include "mwl_debug.h"
+#include "mwl_fwcmd.h"
+#include "mwl_tx.h"
+#include "mwl_mac80211.h"
+
+/* CONSTANTS AND MACROS
+*/
+
+#define MWL_DRV_NAME KBUILD_MODNAME
+
+#define MAX_AMPDU_ATTEMPTS 5
+
+/* PRIVATE FUNCTION DECLARATION
+*/
+
+static void mwl_mac80211_tx(struct ieee80211_hw *hw,
+ struct ieee80211_tx_control *control,
+ struct sk_buff *skb);
+static int mwl_mac80211_start(struct ieee80211_hw *hw);
+static void mwl_mac80211_stop(struct ieee80211_hw *hw);
+static int mwl_mac80211_add_interface(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif);
+static void mwl_mac80211_remove_interface(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif);
+static int mwl_mac80211_config(struct ieee80211_hw *hw,
+ u32 changed);
+static void mwl_mac80211_bss_info_changed(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *info,
+ u32 changed);
+static void mwl_mac80211_configure_filter(struct ieee80211_hw *hw,
+ unsigned int changed_flags,
+ unsigned int *total_flags,
+ u64 multicast);
+static int mwl_mac80211_set_key(struct ieee80211_hw *hw,
+ enum set_key_cmd cmd_param,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta,
+ struct ieee80211_key_conf *key);
+static int mwl_mac80211_set_rts_threshold(struct ieee80211_hw *hw,
+ u32 value);
+static int mwl_mac80211_sta_add(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta);
+static int mwl_mac80211_sta_remove(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta);
+static int mwl_mac80211_conf_tx(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ u16 queue,
+ const struct ieee80211_tx_queue_params *params);
+static int mwl_mac80211_get_stats(struct ieee80211_hw *hw,
+ struct ieee80211_low_level_stats *stats);
+static int mwl_mac80211_get_survey(struct ieee80211_hw *hw,
+ int idx,
+ struct survey_info *survey);
+static int mwl_mac80211_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);
+
+static void mwl_mac80211_remove_vif(struct mwl_priv *priv,
+ struct mwl_vif *vif);
+
+/* PRIVATE VARIABLES
+*/
+
+static struct ieee80211_ops mwl_mac80211_ops = {
+ .tx = mwl_mac80211_tx,
+ .start = mwl_mac80211_start,
+ .stop = mwl_mac80211_stop,
+ .add_interface = mwl_mac80211_add_interface,
+ .remove_interface = mwl_mac80211_remove_interface,
+ .config = mwl_mac80211_config,
+ .bss_info_changed = mwl_mac80211_bss_info_changed,
+ .configure_filter = mwl_mac80211_configure_filter,
+ .set_key = mwl_mac80211_set_key,
+ .set_rts_threshold = mwl_mac80211_set_rts_threshold,
+ .sta_add = mwl_mac80211_sta_add,
+ .sta_remove = mwl_mac80211_sta_remove,
+ .conf_tx = mwl_mac80211_conf_tx,
+ .get_stats = mwl_mac80211_get_stats,
+ .get_survey = mwl_mac80211_get_survey,
+ .ampdu_action = mwl_mac80211_ampdu_action,
+};
+
+static irqreturn_t (*mwl_mac80211_isr)(int irq, void *dev_id) = NULL;
+
+static const struct ieee80211_rate mwl_rates_24[] = {
+ { .bitrate = 10, .hw_value = 2, },
+ { .bitrate = 20, .hw_value = 4, },
+ { .bitrate = 55, .hw_value = 11, },
+ { .bitrate = 110, .hw_value = 22, },
+ { .bitrate = 220, .hw_value = 44, },
+ { .bitrate = 60, .hw_value = 12, },
+ { .bitrate = 90, .hw_value = 18, },
+ { .bitrate = 120, .hw_value = 24, },
+ { .bitrate = 180, .hw_value = 36, },
+ { .bitrate = 240, .hw_value = 48, },
+ { .bitrate = 360, .hw_value = 72, },
+ { .bitrate = 480, .hw_value = 96, },
+ { .bitrate = 540, .hw_value = 108, },
+};
+
+static const struct ieee80211_rate mwl_rates_50[] = {
+ { .bitrate = 60, .hw_value = 12, },
+ { .bitrate = 90, .hw_value = 18, },
+ { .bitrate = 120, .hw_value = 24, },
+ { .bitrate = 180, .hw_value = 36, },
+ { .bitrate = 240, .hw_value = 48, },
+ { .bitrate = 360, .hw_value = 72, },
+ { .bitrate = 480, .hw_value = 96, },
+ { .bitrate = 540, .hw_value = 108, },
+};
+
+/* PUBLIC FUNCTION DEFINITION
+*/
+
+struct ieee80211_ops *mwl_mac80211_get_ops(void)
+{
+ return &mwl_mac80211_ops;
+}
+
+void mwl_mac80211_set_isr(irqreturn_t (*isr)(int irq, void *dev_id))
+{
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ mwl_mac80211_isr = isr;
+
+ WLDBG_EXIT(DBG_LEVEL_5);
+}
+
+/* PRIVATE FUNCTION DEFINITION
+*/
+
+static void mwl_mac80211_tx(struct ieee80211_hw *hw,
+ struct ieee80211_tx_control *control,
+ struct sk_buff *skb)
+{
+ struct mwl_priv *priv;
+ int index;
+
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ BUG_ON(!hw);
+ priv = hw->priv;
+ BUG_ON(!priv);
+
+ BUG_ON(!skb);
+
+ if (!priv->radio_on) {
+
+ WLDBG_EXIT_INFO(DBG_LEVEL_5,
+ "dropped TX frame since radio disabled");
+ dev_kfree_skb_any(skb);
+ return;
+ }
+
+ index = skb_get_queue_mapping(skb);
+
+ mwl_tx_xmit(hw, index, control->sta, skb);
+
+ WLDBG_EXIT(DBG_LEVEL_5);
+}
+
+static int mwl_mac80211_start(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv;
+ int rc;
+
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ BUG_ON(!hw);
+ priv = hw->priv;
+ BUG_ON(!priv);
+
+ rc = request_irq(priv->pdev->irq, mwl_mac80211_isr,
+ IRQF_SHARED, MWL_DRV_NAME, hw);
+ if (rc) {
+ priv->irq = -1;
+ WLDBG_ERROR(DBG_LEVEL_5, "fail to register IRQ handler");
+ return rc;
+ }
+ priv->irq = priv->pdev->irq;
+
+ /* Enable TX reclaim and RX tasklets.
+ */
+ tasklet_enable(&priv->tx_task);
+ tasklet_enable(&priv->rx_task);
+
+ /* Enable interrupts
+ */
+ mwl_fwcmd_int_enable(hw);
+
+ rc = mwl_fwcmd_radio_enable(hw);
+
+ if (!rc)
+ rc = mwl_fwcmd_set_rate_adapt_mode(hw, 0);
+
+ if (!rc)
+ rc = mwl_fwcmd_set_wmm_mode(hw, true);
+
+ if (!rc)
+ rc = mwl_fwcmd_set_dwds_stamode(hw, true);
+
+ if (!rc)
+ rc = mwl_fwcmd_set_fw_flush_timer(hw, 0);
+
+ if (rc) {
+
+ mwl_fwcmd_int_disable(hw);
+ free_irq(priv->pdev->irq, hw);
+ priv->irq = -1;
+ tasklet_disable(&priv->tx_task);
+ tasklet_disable(&priv->rx_task);
+
+ } else {
+
+ ieee80211_wake_queues(hw);
+
+ }
+
+ WLDBG_EXIT(DBG_LEVEL_5);
+
+ return rc;
+}
+
+static void mwl_mac80211_stop(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv;
+
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ BUG_ON(!hw);
+ priv = hw->priv;
+ BUG_ON(!priv);
+
+ mwl_fwcmd_radio_disable(hw);
+
+ ieee80211_stop_queues(hw);
+
+ /* Disable interrupts
+ */
+ mwl_fwcmd_int_disable(hw);
+
+ if (priv->irq != -1) {
+ free_irq(priv->pdev->irq, hw);
+ priv->irq = -1;
+ }
+
+ cancel_work_sync(&priv->watchdog_ba_handle);
+
+ /* Disable TX reclaim and RX tasklets.
+ */
+ tasklet_disable(&priv->tx_task);
+ tasklet_disable(&priv->rx_task);
+
+ /* Return all skbs to mac80211
+ */
+ mwl_tx_done((unsigned long)hw);
+
+ WLDBG_EXIT(DBG_LEVEL_5);
+}
+
+static int mwl_mac80211_add_interface(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ u32 macids_supported;
+ int macid;
+
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ macids_supported = priv->ap_macids_supported;
+
+ macid = ffs(macids_supported & ~priv->macids_used);
+
+ if (!macid--) {
+
+ WLDBG_EXIT_INFO(DBG_LEVEL_5, "no macid can be allocated");
+ return -EBUSY;
+ }
+
+ /* Setup driver private area.
+ */
+ mwl_vif = MWL_VIF(vif);
+ memset(mwl_vif, 0, sizeof(*mwl_vif));
+ mwl_vif->vif = vif;
+ mwl_vif->macid = macid;
+ mwl_vif->seqno = 0;
+ memcpy(mwl_vif->bssid, vif->addr, ETH_ALEN);
+ mwl_vif->is_hw_crypto_enabled = false;
+ mwl_vif->beacon_info.valid = false;
+ mwl_vif->iv16 = 1;
+ mwl_vif->iv32 = 0;
+
+ mwl_fwcmd_set_new_stn_add_self(hw, vif);
+
+ priv->macids_used |= 1 << mwl_vif->macid;
+ list_add_tail(&mwl_vif->list, &priv->vif_list);
+
+ WLDBG_EXIT(DBG_LEVEL_5);
+
+ return 0;
+}
+
+static void mwl_mac80211_remove_interface(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif = MWL_VIF(vif);
+
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ mwl_fwcmd_set_new_stn_del(hw, vif, vif->addr);
+
+ mwl_mac80211_remove_vif(priv, mwl_vif);
+
+ WLDBG_EXIT(DBG_LEVEL_5);
+}
+
+static int mwl_mac80211_config(struct ieee80211_hw *hw,
+ u32 changed)
+{
+ struct ieee80211_conf *conf = &hw->conf;
+ int rc;
+
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ WLDBG_INFO(DBG_LEVEL_5, "change: 0x%x", changed);
+
+ if (conf->flags & IEEE80211_CONF_IDLE)
+ rc = mwl_fwcmd_radio_disable(hw);
+ else
+ rc = mwl_fwcmd_radio_enable(hw);
+
+ if (rc)
+ goto out;
+
+ if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
+
+ if (conf->chandef.chan->band == IEEE80211_BAND_2GHZ)
+ mwl_fwcmd_set_apmode(hw, AP_MODE_2_4GHZ_11AC_MIXED);
+ else if (conf->chandef.chan->band == IEEE80211_BAND_5GHZ)
+ mwl_fwcmd_set_apmode(hw, AP_MODE_11AC);
+
+ rc = mwl_fwcmd_set_rf_channel(hw, conf);
+
+ if (rc)
+ goto out;
+
+ rc = mwl_fwcmd_max_tx_power(hw, conf, 0);
+
+ if (rc)
+ goto out;
+
+ rc = mwl_fwcmd_tx_power(hw, conf, 0);
+
+ if (rc)
+ goto out;
+
+ rc = mwl_fwcmd_set_cdd(hw);
+ }
+
+out:
+
+ WLDBG_EXIT_INFO(DBG_LEVEL_5, "return code: %d", rc);
+
+ return rc;
+}
+
+static void mwl_mac80211_bss_info_changed(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *info,
+ u32 changed)
+{
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ WLDBG_INFO(DBG_LEVEL_5, "change: 0x%x", changed);
+
+ if (changed & BSS_CHANGED_ERP_PREAMBLE) {
+
+ mwl_fwcmd_set_radio_preamble(hw,
+ vif->bss_conf.use_short_preamble);
+ }
+
+ if (changed & BSS_CHANGED_BASIC_RATES) {
+
+ int idx;
+ int rate;
+
+ /*
+ * Use lowest supported basic rate for multicasts
+ * and management frames (such as probe responses --
+ * beacons will always go out at 1 Mb/s).
+ */
+ idx = ffs(vif->bss_conf.basic_rates);
+ if (idx)
+ idx--;
+
+ if (hw->conf.chandef.chan->band == IEEE80211_BAND_2GHZ)
+ rate = mwl_rates_24[idx].hw_value;
+ else
+ rate = mwl_rates_50[idx].hw_value;
+
+ mwl_fwcmd_use_fixed_rate(hw, rate, rate);
+ }
+
+ if (changed & (BSS_CHANGED_BEACON_INT | BSS_CHANGED_BEACON)) {
+
+ struct sk_buff *skb;
+
+ skb = ieee80211_beacon_get(hw, vif);
+
+ if (skb != NULL) {
+
+ mwl_fwcmd_set_beacon(hw, vif, skb->data, skb->len);
+ dev_kfree_skb_any(skb);
+ }
+
+ if ((info->ssid[0] != '\0') && (info->ssid_len != 0))
+ mwl_fwcmd_broadcast_ssid_enable(hw, vif, true);
+ else
+ mwl_fwcmd_broadcast_ssid_enable(hw, vif, false);
+ }
+
+ if (changed & BSS_CHANGED_BEACON_ENABLED) {
+
+ mwl_fwcmd_bss_start(hw, vif, info->enable_beacon);
+ }
+
+ WLDBG_EXIT(DBG_LEVEL_5);
+}
+
+static void mwl_mac80211_configure_filter(struct ieee80211_hw *hw,
+ unsigned int changed_flags,
+ unsigned int *total_flags,
+ u64 multicast)
+{
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ /*
+ * AP firmware doesn't allow fine-grained control over
+ * the receive filter.
+ */
+ *total_flags &= FIF_ALLMULTI | FIF_BCN_PRBRESP_PROMISC;
+
+ WLDBG_EXIT(DBG_LEVEL_5);
+}
+
+static int mwl_mac80211_set_key(struct ieee80211_hw *hw,
+ enum set_key_cmd cmd_param,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta,
+ struct ieee80211_key_conf *key)
+{
+ int rc = 0;
+ u8 encr_type;
+ u8 *addr;
+ struct mwl_vif *mwl_vif = MWL_VIF(vif);
+ struct mwl_sta *sta_info = MWL_STA(sta);
+
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ BUG_ON(!mwl_vif);
+ BUG_ON(!sta_info);
+
+ if (vif->type == NL80211_IFTYPE_STATION) {
+
+ WLDBG_EXIT_INFO(DBG_LEVEL_5, "station mode is not supported");
+ return -EOPNOTSUPP;
+ }
+
+ if (sta == NULL)
+ addr = vif->addr;
+ else
+ addr = sta->addr;
+
+ if (cmd_param == SET_KEY) {
+
+ rc = mwl_fwcmd_encryption_set_key(hw, vif, addr, key);
+
+ if (rc)
+ goto out;
+
+ if ((key->cipher == WLAN_CIPHER_SUITE_WEP40)
+ || (key->cipher == WLAN_CIPHER_SUITE_WEP104)) {
+
+ encr_type = ENCR_TYPE_WEP;
+
+ } else if (key->cipher == WLAN_CIPHER_SUITE_CCMP) {
+
+ encr_type = ENCR_TYPE_AES;
+
+ if ((key->flags & IEEE80211_KEY_FLAG_PAIRWISE) == 0)
+ mwl_vif->keyidx = key->keyidx;
+
+ } else if (key->cipher == WLAN_CIPHER_SUITE_TKIP) {
+
+ encr_type = ENCR_TYPE_TKIP;
+
+ } else {
+
+ encr_type = ENCR_TYPE_DISABLE;
+
+ }
+
+ rc = mwl_fwcmd_update_encryption_enable(hw, vif, addr,
+ encr_type);
+
+ if (rc)
+ goto out;
+
+ mwl_vif->is_hw_crypto_enabled = true;
+
+ } else {
+
+ rc = mwl_fwcmd_encryption_remove_key(hw, vif, addr, key);
+
+ if (rc)
+ goto out;
+
+ }
+
+out:
+
+ WLDBG_EXIT_INFO(DBG_LEVEL_5, "return code: %d", rc);
+
+ return rc;
+}
+
+static int mwl_mac80211_set_rts_threshold(struct ieee80211_hw *hw,
+ u32 value)
+{
+ int rc;
+
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ rc = mwl_fwcmd_set_rts_threshold(hw, value);
+
+ WLDBG_EXIT_INFO(DBG_LEVEL_5, "return code: %d", rc);
+
+ return rc;
+}
+
+static int mwl_mac80211_sta_add(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ struct mwl_vif *mwl_vif = MWL_VIF(vif);
+ struct mwl_sta *sta_info = MWL_STA(sta);
+ struct ieee80211_key_conf *key;
+ int rc;
+ int i;
+
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ BUG_ON(!mwl_vif);
+ BUG_ON(!sta_info);
+
+ memset(sta_info, 0, sizeof(*sta_info));
+ sta_info->iv16 = 1;
+ sta_info->iv32 = 0;
+ if (sta->ht_cap.ht_supported)
+ sta_info->is_ampdu_allowed = true;
+
+ rc = mwl_fwcmd_set_new_stn_add(hw, vif, sta);
+
+ for (i = 0; i < NUM_WEP_KEYS; i++) {
+
+ key = IEEE80211_KEY_CONF(mwl_vif->wep_key_conf[i].key);
+
+ if (mwl_vif->wep_key_conf[i].enabled)
+ mwl_mac80211_set_key(hw, SET_KEY, vif, sta, key);
+ }
+
+ WLDBG_EXIT_INFO(DBG_LEVEL_5, "return code: %d", rc);
+
+ return rc;
+}
+
+static int mwl_mac80211_sta_remove(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ int rc;
+
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ rc = mwl_fwcmd_set_new_stn_del(hw, vif, sta->addr);
+
+ WLDBG_EXIT_INFO(DBG_LEVEL_5, "return code: %d", rc);
+
+ return rc;
+}
+
+static int mwl_mac80211_conf_tx(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ u16 queue,
+ const struct ieee80211_tx_queue_params *params)
+{
+ struct mwl_priv *priv = hw->priv;
+ int rc = 0;
+
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ BUG_ON(queue > SYSADPT_TX_WMM_QUEUES - 1);
+
+ memcpy(&priv->wmm_params[queue], params, sizeof(*params));
+
+ if (!priv->wmm_enabled) {
+
+ rc = mwl_fwcmd_set_wmm_mode(hw, true);
+ priv->wmm_enabled = true;
+ }
+
+ if (!rc) {
+
+ int q = SYSADPT_TX_WMM_QUEUES - 1 - queue;
+
+ rc = mwl_fwcmd_set_edca_params(hw, q,
+ params->cw_min, params->cw_max,
+ params->aifs, params->txop);
+ }
+
+ WLDBG_EXIT_INFO(DBG_LEVEL_5, "return code: %d", rc);
+
+ return rc;
+}
+
+static int mwl_mac80211_get_stats(struct ieee80211_hw *hw,
+ struct ieee80211_low_level_stats *stats)
+{
+ int rc;
+
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ rc = mwl_fwcmd_get_stat(hw, stats);
+
+ WLDBG_EXIT_INFO(DBG_LEVEL_5, "return code: %d", rc);
+
+ return rc;
+}
+
+static int mwl_mac80211_get_survey(struct ieee80211_hw *hw,
+ int idx,
+ struct survey_info *survey)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct ieee80211_conf *conf = &hw->conf;
+
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ if (idx != 0)
+ return -ENOENT;
+
+ survey->channel = conf->chandef.chan;
+ survey->filled = SURVEY_INFO_NOISE_DBM;
+ survey->noise = priv->noise;
+
+ WLDBG_EXIT(DBG_LEVEL_5);
+
+ return 0;
+}
+
+static int mwl_mac80211_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 mwl_priv *priv = hw->priv;
+ struct mwl_ampdu_stream *stream;
+ u8 *addr = sta->addr, idx;
+ struct mwl_sta *sta_info = MWL_STA(sta);
+
+ WLDBG_ENTER(DBG_LEVEL_5);
+
+ if (!(hw->flags & IEEE80211_HW_AMPDU_AGGREGATION)) {
+
+ WLDBG_EXIT_INFO(DBG_LEVEL_5, "no HW AMPDU");
+ return -ENOTSUPP;
+ }
+
+ SPIN_LOCK(&priv->locks.stream_lock);
+
+ stream = mwl_fwcmd_lookup_stream(hw, addr, tid);
+
+ switch (action) {
+
+ case IEEE80211_AMPDU_RX_START:
+ case IEEE80211_AMPDU_RX_STOP:
+ 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.
+ */
+ stream = mwl_fwcmd_add_stream(hw, sta, tid);
+ }
+
+ if (stream == NULL) {
+
+ WLDBG_EXIT_INFO(DBG_LEVEL_5, "no stream found");
+ rc = -EBUSY;
+ break;
+ }
+
+ stream->state = AMPDU_STREAM_IN_PROGRESS;
+
+ /* Release the lock before we do the time consuming stuff
+ */
+ SPIN_UNLOCK(&priv->locks.stream_lock);
+
+ for (i = 0; i < MAX_AMPDU_ATTEMPTS; i++) {
+
+ /* Check if link is still valid
+ */
+ if (!sta_info->is_ampdu_allowed) {
+
+ SPIN_LOCK(&priv->locks.stream_lock);
+ mwl_fwcmd_remove_stream(hw, stream);
+ SPIN_UNLOCK(&priv->locks.stream_lock);
+ WLDBG_EXIT_INFO(DBG_LEVEL_5, "link is no valid now");
+ return -EBUSY;
+ }
+
+ rc = mwl_fwcmd_check_ba(hw, stream, vif);
+
+ if (!rc)
+ break;
+
+ WL_MSEC_SLEEP(1000);
+ }
+
+ SPIN_LOCK(&priv->locks.stream_lock);
+
+ if (rc) {
+
+ mwl_fwcmd_remove_stream(hw, stream);
+ WLDBG_EXIT_INFO(DBG_LEVEL_5, "error code: %d", rc);
+ rc = -EBUSY;
+ break;
+ }
+
+ ieee80211_start_tx_ba_cb_irqsafe(vif, addr, tid);
+ break;
+
+ case IEEE80211_AMPDU_TX_STOP_CONT:
+ case IEEE80211_AMPDU_TX_STOP_FLUSH:
+ case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
+
+ if (stream) {
+
+ if (stream->state == AMPDU_STREAM_ACTIVE) {
+
+ idx = stream->idx;
+ SPIN_UNLOCK(&priv->locks.stream_lock);
+ mwl_fwcmd_destroy_ba(hw, idx);
+ SPIN_LOCK(&priv->locks.stream_lock);
+ }
+
+ mwl_fwcmd_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->locks.stream_lock);
+ rc = mwl_fwcmd_create_ba(hw, stream, buf_size, vif);
+ SPIN_LOCK(&priv->locks.stream_lock);
+
+ if (!rc)
+ stream->state = AMPDU_STREAM_ACTIVE;
+ else {
+
+ idx = stream->idx;
+ SPIN_UNLOCK(&priv->locks.stream_lock);
+ mwl_fwcmd_destroy_ba(hw, idx);
+ SPIN_LOCK(&priv->locks.stream_lock);
+ mwl_fwcmd_remove_stream(hw, stream);
+ }
+ break;
+
+ default:
+ rc = -ENOTSUPP;
+
+ }
+
+ SPIN_UNLOCK(&priv->locks.stream_lock);
+
+ WLDBG_EXIT(DBG_LEVEL_5);
+
+ return rc;
+}
+
+static void mwl_mac80211_remove_vif(struct mwl_priv *priv, struct mwl_vif *vif)
+{
+ if (!priv->macids_used)
+ return;
+
+ priv->macids_used &= ~(1 << vif->macid);
+ list_del(&vif->list);
+}