diff options
Diffstat (limited to 'mwl_mac80211.c')
-rw-r--r-- | mwl_mac80211.c | 850 |
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); +} |