summaryrefslogtreecommitdiff
path: root/mac80211.c
diff options
context:
space:
mode:
Diffstat (limited to 'mac80211.c')
-rw-r--r--mac80211.c737
1 files changed, 737 insertions, 0 deletions
diff --git a/mac80211.c b/mac80211.c
new file mode 100644
index 0000000..4000ea5
--- /dev/null
+++ b/mac80211.c
@@ -0,0 +1,737 @@
+/*
+ * Copyright (C) 2006-2015, Marvell International Ltd.
+ *
+ * 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.
+ */
+
+/* Description: This file implements mac80211 related functions.
+ */
+
+#include <linux/etherdevice.h>
+
+#include "sysadpt.h"
+#include "dev.h"
+#include "fwcmd.h"
+#include "tx.h"
+#include "mac80211.h"
+#include "isr.h"
+
+#define MWL_DRV_NAME KBUILD_MODNAME
+
+#define MAX_AMPDU_ATTEMPTS 5
+
+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, },
+};
+
+static void mwl_mac80211_tx(struct ieee80211_hw *hw,
+ struct ieee80211_tx_control *control,
+ struct sk_buff *skb)
+{
+ struct mwl_priv *priv;
+
+ priv = hw->priv;
+
+ if (!priv->radio_on) {
+ wiphy_warn(hw->wiphy,
+ "dropped TX frame since radio disabled");
+ dev_kfree_skb_any(skb);
+ return;
+ }
+
+ mwl_tx_xmit(hw, control, skb);
+}
+
+static int mwl_mac80211_start(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv;
+ int rc;
+
+ priv = hw->priv;
+
+ rc = request_irq(priv->pdev->irq, mwl_isr,
+ IRQF_SHARED, MWL_DRV_NAME, hw);
+ if (rc) {
+ priv->irq = -1;
+ wiphy_err(hw->wiphy, "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)
+ goto fwcmd_fail;
+ rc = mwl_fwcmd_set_rate_adapt_mode(hw, 0);
+ if (rc)
+ goto fwcmd_fail;
+ rc = mwl_fwcmd_set_wmm_mode(hw, true);
+ if (rc)
+ goto fwcmd_fail;
+ rc = mwl_fwcmd_set_dwds_stamode(hw, true);
+ if (rc)
+ goto fwcmd_fail;
+ rc = mwl_fwcmd_set_fw_flush_timer(hw, 0);
+ if (rc)
+ goto fwcmd_fail;
+
+ ieee80211_wake_queues(hw);
+ return 0;
+
+fwcmd_fail:
+ mwl_fwcmd_int_disable(hw);
+ free_irq(priv->pdev->irq, hw);
+ priv->irq = -1;
+ tasklet_disable(&priv->tx_task);
+ tasklet_disable(&priv->rx_task);
+
+ return rc;
+}
+
+static void mwl_mac80211_stop(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv;
+
+ priv = hw->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);
+}
+
+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;
+ unsigned long flags;
+
+ switch (vif->type) {
+ case NL80211_IFTYPE_AP:
+ macids_supported = priv->ap_macids_supported;
+ break;
+ case NL80211_IFTYPE_STATION:
+ macids_supported = priv->sta_macids_supported;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ macid = ffs(macids_supported & ~priv->macids_used);
+
+ if (!macid--) {
+ wiphy_warn(hw->wiphy, "no macid can be allocated");
+ return -EBUSY;
+ }
+
+ /* Setup driver private area. */
+ mwl_vif = mwl_dev_get_vif(vif);
+ memset(mwl_vif, 0, sizeof(*mwl_vif));
+ mwl_vif->vif = vif;
+ mwl_vif->macid = macid;
+ mwl_vif->seqno = 0;
+ mwl_vif->is_hw_crypto_enabled = false;
+ mwl_vif->is_sta = false;
+ mwl_vif->beacon_info.valid = false;
+ mwl_vif->iv16 = 1;
+ mwl_vif->iv32 = 0;
+ mwl_vif->keyidx = 0;
+
+ if (vif->type == NL80211_IFTYPE_STATION) {
+ ether_addr_copy(mwl_vif->sta_mac, vif->addr);
+ mwl_vif->is_sta = true;
+ mwl_fwcmd_bss_start(hw, vif, true);
+ mwl_fwcmd_set_infra_mode(hw, vif);
+ mwl_fwcmd_set_mac_addr_client(hw, vif, vif->addr);
+ }
+
+ if (vif->type == NL80211_IFTYPE_AP) {
+ ether_addr_copy(mwl_vif->bssid, vif->addr);
+ mwl_fwcmd_set_new_stn_add_self(hw, vif);
+ }
+
+ priv->macids_used |= 1 << mwl_vif->macid;
+ spin_lock_irqsave(&priv->vif_lock, flags);
+ list_add_tail(&mwl_vif->list, &priv->vif_list);
+ spin_unlock_irqrestore(&priv->vif_lock, flags);
+
+ return 0;
+}
+
+static void mwl_mac80211_remove_vif(struct mwl_priv *priv, struct mwl_vif *vif)
+{
+ unsigned long flags;
+ int num;
+ struct sk_buff *skb, *tmp;
+ struct ieee80211_tx_info *tx_info;
+ struct mwl_tx_ctrl *tx_ctrl;
+
+ if (!priv->macids_used)
+ return;
+
+ for (num = 1; num < SYSADPT_NUM_OF_DESC_DATA; num++) {
+ spin_lock_irqsave(&priv->txq[num].lock, flags);
+ skb_queue_walk_safe(&priv->txq[num], skb, tmp) {
+ tx_info = IEEE80211_SKB_CB(skb);
+ tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status;
+ if (tx_ctrl->vif == vif) {
+ __skb_unlink(skb, &priv->txq[num]);
+ dev_kfree_skb_any(skb);
+ }
+ }
+ spin_unlock_irqrestore(&priv->txq[num].lock, flags);
+ }
+
+ priv->macids_used &= ~(1 << vif->macid);
+ spin_lock_irqsave(&priv->vif_lock, flags);
+ list_del(&vif->list);
+ spin_unlock_irqrestore(&priv->vif_lock, flags);
+}
+
+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 = mwl_dev_get_vif(vif);
+
+ if (vif->type == NL80211_IFTYPE_STATION)
+ mwl_fwcmd_remove_mac_addr(hw, vif, vif->addr);
+
+ if (vif->type == NL80211_IFTYPE_AP)
+ mwl_fwcmd_set_new_stn_del(hw, vif, vif->addr);
+
+ mwl_mac80211_remove_vif(priv, mwl_vif);
+}
+
+static int mwl_mac80211_config(struct ieee80211_hw *hw,
+ u32 changed)
+{
+ struct ieee80211_conf *conf = &hw->conf;
+ int rc;
+
+ wiphy_debug(hw->wiphy, "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) {
+ int rate = 0;
+
+ if (conf->chandef.chan->band == IEEE80211_BAND_2GHZ) {
+ mwl_fwcmd_set_apmode(hw, AP_MODE_2_4GHZ_11AC_MIXED);
+ rate = mwl_rates_24[0].hw_value;
+ } else if (conf->chandef.chan->band == IEEE80211_BAND_5GHZ) {
+ mwl_fwcmd_set_apmode(hw, AP_MODE_11AC);
+ rate = mwl_rates_50[0].hw_value;
+ }
+
+ rc = mwl_fwcmd_set_rf_channel(hw, conf);
+ if (rc)
+ goto out;
+ rc = mwl_fwcmd_use_fixed_rate(hw, rate, rate);
+ 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:
+
+ return rc;
+}
+
+static void mwl_mac80211_bss_info_changed_sta(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *info,
+ u32 changed)
+{
+ if (changed & BSS_CHANGED_ERP_PREAMBLE)
+ mwl_fwcmd_set_radio_preamble(hw,
+ vif->bss_conf.use_short_preamble);
+
+ if ((changed & BSS_CHANGED_ASSOC) && vif->bss_conf.assoc)
+ mwl_fwcmd_set_aid(hw, vif, (u8 *)vif->bss_conf.bssid,
+ vif->bss_conf.aid);
+}
+
+static void mwl_mac80211_bss_info_changed_ap(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *info,
+ u32 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) {
+ mwl_fwcmd_set_beacon(hw, vif, skb->data, skb->len);
+ dev_kfree_skb_any(skb);
+ }
+
+ if ((info->ssid[0] != '\0') &&
+ (info->ssid_len != 0) &&
+ (!info->hidden_ssid))
+ 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);
+}
+
+static void mwl_mac80211_bss_info_changed(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *info,
+ u32 changed)
+{
+ wiphy_debug(hw->wiphy, "interface: %d, change: 0x%x",
+ vif->type, changed);
+
+ if (vif->type == NL80211_IFTYPE_STATION)
+ mwl_mac80211_bss_info_changed_sta(hw, vif, info, changed);
+
+ if (vif->type == NL80211_IFTYPE_AP)
+ mwl_mac80211_bss_info_changed_ap(hw, vif, info, changed);
+}
+
+static void mwl_mac80211_configure_filter(struct ieee80211_hw *hw,
+ unsigned int changed_flags,
+ unsigned int *total_flags,
+ u64 multicast)
+{
+ /* AP firmware doesn't allow fine-grained control over
+ * the receive filter.
+ */
+ *total_flags &= FIF_ALLMULTI | FIF_BCN_PRBRESP_PROMISC;
+}
+
+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)
+{
+ struct mwl_vif *mwl_vif;
+ int rc = 0;
+ u8 encr_type;
+ u8 *addr;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ if (!sta) {
+ addr = vif->addr;
+ } else {
+ addr = sta->addr;
+ if (mwl_vif->is_sta)
+ ether_addr_copy(mwl_vif->bssid, 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) {
+ if (!mwl_vif->is_sta)
+ 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:
+
+ return rc;
+}
+
+static int mwl_mac80211_set_rts_threshold(struct ieee80211_hw *hw,
+ u32 value)
+{
+ return mwl_fwcmd_set_rts_threshold(hw, value);
+}
+
+static int mwl_mac80211_sta_add(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct mwl_sta *sta_info;
+ unsigned long flags;
+ struct ieee80211_key_conf *key;
+ int rc;
+ int i;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+ sta_info = mwl_dev_get_sta(sta);
+
+ memset(sta_info, 0, sizeof(*sta_info));
+ sta_info->sta = sta;
+ if (sta->ht_cap.ht_supported) {
+ sta_info->is_ampdu_allowed = true;
+ sta_info->is_amsdu_allowed = true;
+ if (sta->ht_cap.cap & IEEE80211_HT_CAP_MAX_AMSDU)
+ sta_info->amsdu_ctrl.cap = MWL_AMSDU_SIZE_8K;
+ else
+ sta_info->amsdu_ctrl.cap = MWL_AMSDU_SIZE_4K;
+ }
+ sta_info->iv16 = 1;
+ sta_info->iv32 = 0;
+ spin_lock_irqsave(&priv->sta_lock, flags);
+ list_add_tail(&sta_info->list, &priv->sta_list);
+ spin_unlock_irqrestore(&priv->sta_lock, flags);
+
+ if (mwl_vif->is_sta)
+ mwl_fwcmd_set_new_stn_del(hw, vif, sta->addr);
+
+ rc = mwl_fwcmd_set_new_stn_add(hw, vif, sta);
+
+ for (i = 0; i < NUM_WEP_KEYS; i++) {
+ key = (struct 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);
+ }
+
+ return rc;
+}
+
+static int mwl_mac80211_sta_remove(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_sta *sta_info;
+ unsigned long flags;
+ int num;
+ struct sk_buff *skb, *tmp;
+ struct ieee80211_tx_info *tx_info;
+ struct mwl_tx_ctrl *tx_ctrl;
+ int rc;
+
+ sta_info = mwl_dev_get_sta(sta);
+
+ for (num = 1; num < SYSADPT_NUM_OF_DESC_DATA; num++) {
+ spin_lock_irqsave(&priv->txq[num].lock, flags);
+ skb_queue_walk_safe(&priv->txq[num], skb, tmp) {
+ tx_info = IEEE80211_SKB_CB(skb);
+ tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status;
+ if (tx_ctrl->sta == sta) {
+ __skb_unlink(skb, &priv->txq[num]);
+ dev_kfree_skb_any(skb);
+ }
+ }
+ spin_unlock_irqrestore(&priv->txq[num].lock, flags);
+ }
+
+ rc = mwl_fwcmd_set_new_stn_del(hw, vif, sta->addr);
+
+ spin_lock_irqsave(&priv->sta_lock, flags);
+ list_del(&sta_info->list);
+ spin_unlock_irqrestore(&priv->sta_lock, flags);
+
+ 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;
+
+ 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);
+ }
+
+ return rc;
+}
+
+static int mwl_mac80211_get_stats(struct ieee80211_hw *hw,
+ struct ieee80211_low_level_stats *stats)
+{
+ return mwl_fwcmd_get_stat(hw, stats);
+}
+
+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;
+
+ if (idx != 0)
+ return -ENOENT;
+
+ survey->channel = conf->chandef.chan;
+ survey->filled = SURVEY_INFO_NOISE_DBM;
+ survey->noise = priv->noise;
+
+ 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;
+
+ sta_info = mwl_dev_get_sta(sta);
+
+ spin_lock(&priv->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) {
+ /* 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) {
+ wiphy_warn(hw->wiphy, "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->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->stream_lock);
+ mwl_fwcmd_remove_stream(hw, stream);
+ spin_unlock(&priv->stream_lock);
+ wiphy_warn(hw->wiphy,
+ "link is no valid now");
+ return -EBUSY;
+ }
+
+ rc = mwl_fwcmd_check_ba(hw, stream, vif);
+
+ if (!rc)
+ break;
+
+ mdelay(1000);
+ }
+
+ spin_lock(&priv->stream_lock);
+
+ if (rc) {
+ mwl_fwcmd_remove_stream(hw, stream);
+ wiphy_err(hw->wiphy, "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->stream_lock);
+ mwl_fwcmd_destroy_ba(hw, idx);
+ spin_lock(&priv->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->state != AMPDU_STREAM_IN_PROGRESS);
+ spin_unlock(&priv->stream_lock);
+ rc = mwl_fwcmd_create_ba(hw, stream, buf_size, vif);
+ spin_lock(&priv->stream_lock);
+
+ if (!rc) {
+ stream->state = AMPDU_STREAM_ACTIVE;
+ } else {
+ idx = stream->idx;
+ spin_unlock(&priv->stream_lock);
+ mwl_fwcmd_destroy_ba(hw, idx);
+ spin_lock(&priv->stream_lock);
+ mwl_fwcmd_remove_stream(hw, stream);
+ }
+ break;
+ default:
+ rc = -ENOTSUPP;
+ }
+
+ spin_unlock(&priv->stream_lock);
+
+ return rc;
+}
+
+const 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,
+};