Merge tag 'master-2014-11-04' of git://git.kernel.org/pub/scm/linux/kernel/git/linvil...
[pandora-kernel.git] / drivers / net / wireless / ath / ath9k / main.c
index 30c66df..a91ee92 100644 (file)
@@ -54,7 +54,8 @@ u8 ath9k_parse_mpdudensity(u8 mpdudensity)
        }
 }
 
-static bool ath9k_has_pending_frames(struct ath_softc *sc, struct ath_txq *txq)
+static bool ath9k_has_pending_frames(struct ath_softc *sc, struct ath_txq *txq,
+                                    bool sw_pending)
 {
        bool pending = false;
 
@@ -65,6 +66,9 @@ static bool ath9k_has_pending_frames(struct ath_softc *sc, struct ath_txq *txq)
                goto out;
        }
 
+       if (!sw_pending)
+               goto out;
+
        if (txq->mac80211_qnum >= 0) {
                struct list_head *list;
 
@@ -270,7 +274,7 @@ static bool ath_complete_reset(struct ath_softc *sc, bool start)
        return true;
 }
 
-int ath_reset_internal(struct ath_softc *sc, struct ath9k_channel *hchan)
+static int ath_reset_internal(struct ath_softc *sc, struct ath9k_channel *hchan)
 {
        struct ath_hw *ah = sc->sc_ah;
        struct ath_common *common = ath9k_hw_common(ah);
@@ -281,6 +285,7 @@ int ath_reset_internal(struct ath_softc *sc, struct ath9k_channel *hchan)
        __ath_cancel_work(sc);
 
        tasklet_disable(&sc->intr_tq);
+       tasklet_disable(&sc->bcon_tasklet);
        spin_lock_bh(&sc->sc_pcu_lock);
 
        if (!sc->cur_chan->offchannel) {
@@ -326,6 +331,7 @@ int ath_reset_internal(struct ath_softc *sc, struct ath9k_channel *hchan)
 
 out:
        spin_unlock_bh(&sc->sc_pcu_lock);
+       tasklet_enable(&sc->bcon_tasklet);
        tasklet_enable(&sc->intr_tq);
 
        return r;
@@ -539,11 +545,10 @@ irqreturn_t ath_isr(int irq, void *dev)
                sched = true;
 
        /*
-        * If a FATAL or RXORN interrupt is received, we have to reset the
-        * chip immediately.
+        * If a FATAL interrupt is received, we have to reset the chip
+        * immediately.
         */
-       if ((status & ATH9K_INT_FATAL) || ((status & ATH9K_INT_RXORN) &&
-           !(ah->caps.hw_caps & ATH9K_HW_CAP_EDMA)))
+       if (status & ATH9K_INT_FATAL)
                goto chip_reset;
 
        if ((ah->config.hw_hang_checks & HW_BB_WATCHDOG) &&
@@ -598,17 +603,29 @@ chip_reset:
 #undef SCHED_INTR
 }
 
-int ath_reset(struct ath_softc *sc)
+/*
+ * This function is called when a HW reset cannot be deferred
+ * and has to be immediate.
+ */
+int ath_reset(struct ath_softc *sc, struct ath9k_channel *hchan)
 {
+       struct ath_common *common = ath9k_hw_common(sc->sc_ah);
        int r;
 
+       set_bit(ATH_OP_HW_RESET, &common->op_flags);
+
        ath9k_ps_wakeup(sc);
-       r = ath_reset_internal(sc, NULL);
+       r = ath_reset_internal(sc, hchan);
        ath9k_ps_restore(sc);
 
        return r;
 }
 
+/*
+ * When a HW reset can be deferred, it is added to the
+ * hw_reset_work workqueue, but we set ATH_OP_HW_RESET before
+ * queueing.
+ */
 void ath9k_queue_reset(struct ath_softc *sc, enum ath_reset_type type)
 {
        struct ath_common *common = ath9k_hw_common(sc->sc_ah);
@@ -623,7 +640,9 @@ void ath_reset_work(struct work_struct *work)
 {
        struct ath_softc *sc = container_of(work, struct ath_softc, hw_reset_work);
 
-       ath_reset(sc);
+       ath9k_ps_wakeup(sc);
+       ath_reset_internal(sc, NULL);
+       ath9k_ps_restore(sc);
 }
 
 /**********************/
@@ -1037,7 +1056,7 @@ static void ath9k_set_offchannel_state(struct ath_softc *sc)
 
        eth_zero_addr(common->curbssid);
        eth_broadcast_addr(common->bssidmask);
-       ether_addr_copy(common->macaddr, vif->addr);
+       memcpy(common->macaddr, vif->addr, ETH_ALEN);
        common->curaid = 0;
        ah->opmode = vif->type;
        ah->imask &= ~ATH9K_INT_SWBA;
@@ -1078,7 +1097,7 @@ void ath9k_calculate_summary_state(struct ath_softc *sc,
        ath9k_calculate_iter_data(sc, ctx, &iter_data);
 
        if (iter_data.has_hw_macaddr)
-               ether_addr_copy(common->macaddr, iter_data.hw_macaddr);
+               memcpy(common->macaddr, iter_data.hw_macaddr, ETH_ALEN);
 
        memcpy(common->bssidmask, iter_data.mask, ETH_ALEN);
        ath_hw_setbssidmask(common);
@@ -1550,6 +1569,40 @@ static int ath9k_sta_remove(struct ieee80211_hw *hw,
        return 0;
 }
 
+static int ath9k_sta_state(struct ieee80211_hw *hw,
+                          struct ieee80211_vif *vif,
+                          struct ieee80211_sta *sta,
+                          enum ieee80211_sta_state old_state,
+                          enum ieee80211_sta_state new_state)
+{
+       struct ath_softc *sc = hw->priv;
+       struct ath_common *common = ath9k_hw_common(sc->sc_ah);
+       int ret = 0;
+
+       if (old_state == IEEE80211_STA_AUTH &&
+           new_state == IEEE80211_STA_ASSOC) {
+               ret = ath9k_sta_add(hw, vif, sta);
+               ath_dbg(common, CONFIG,
+                       "Add station: %pM\n", sta->addr);
+       } else if (old_state == IEEE80211_STA_ASSOC &&
+                  new_state == IEEE80211_STA_AUTH) {
+               ret = ath9k_sta_remove(hw, vif, sta);
+               ath_dbg(common, CONFIG,
+                       "Remove station: %pM\n", sta->addr);
+       }
+
+       if (ath9k_is_chanctx_enabled()) {
+               if (vif->type == NL80211_IFTYPE_STATION) {
+                       if (old_state == IEEE80211_STA_ASSOC &&
+                           new_state == IEEE80211_STA_AUTHORIZED)
+                               ath_chanctx_event(sc, vif,
+                                                 ATH_CHANCTX_EVENT_AUTHORIZED);
+               }
+       }
+
+       return ret;
+}
+
 static void ath9k_sta_set_tx_filter(struct ath_hw *ah,
                                    struct ath_node *an,
                                    bool set)
@@ -1734,17 +1787,11 @@ static void ath9k_bss_info_changed(struct ieee80211_hw *hw,
                ath_dbg(common, CONFIG, "BSSID %pM Changed ASSOC %d\n",
                        bss_conf->bssid, bss_conf->assoc);
 
-               ether_addr_copy(avp->bssid, bss_conf->bssid);
+               memcpy(avp->bssid, bss_conf->bssid, ETH_ALEN);
                avp->aid = bss_conf->aid;
                avp->assoc = bss_conf->assoc;
 
                ath9k_calculate_summary_state(sc, avp->chanctx);
-
-               if (ath9k_is_chanctx_enabled()) {
-                       if (bss_conf->assoc)
-                               ath_chanctx_event(sc, vif,
-                                                 ATH_CHANCTX_EVENT_ASSOC);
-               }
        }
 
        if (changed & BSS_CHANGED_IBSS) {
@@ -1840,6 +1887,7 @@ static int ath9k_ampdu_action(struct ieee80211_hw *hw,
                              u16 tid, u16 *ssn, u8 buf_size)
 {
        struct ath_softc *sc = hw->priv;
+       struct ath_common *common = ath9k_hw_common(sc->sc_ah);
        bool flush = false;
        int ret = 0;
 
@@ -1851,6 +1899,12 @@ static int ath9k_ampdu_action(struct ieee80211_hw *hw,
        case IEEE80211_AMPDU_RX_STOP:
                break;
        case IEEE80211_AMPDU_TX_START:
+               if (ath9k_is_chanctx_enabled()) {
+                       if (test_bit(ATH_OP_SCANNING, &common->op_flags)) {
+                               ret = -EBUSY;
+                               break;
+                       }
+               }
                ath9k_ps_wakeup(sc);
                ret = ath_tx_aggr_start(sc, sta, tid, ssn);
                if (!ret)
@@ -1964,7 +2018,8 @@ static void ath9k_set_coverage_class(struct ieee80211_hw *hw,
        mutex_unlock(&sc->mutex);
 }
 
-static bool ath9k_has_tx_pending(struct ath_softc *sc)
+static bool ath9k_has_tx_pending(struct ath_softc *sc,
+                                bool sw_pending)
 {
        int i, npend = 0;
 
@@ -1972,7 +2027,8 @@ static bool ath9k_has_tx_pending(struct ath_softc *sc)
                if (!ATH_TXQ_SETUP(sc, i))
                        continue;
 
-               npend = ath9k_has_pending_frames(sc, &sc->tx.txq[i]);
+               npend = ath9k_has_pending_frames(sc, &sc->tx.txq[i],
+                                                sw_pending);
                if (npend)
                        break;
        }
@@ -1984,18 +2040,38 @@ static void ath9k_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
                        u32 queues, bool drop)
 {
        struct ath_softc *sc = hw->priv;
+       struct ath_common *common = ath9k_hw_common(sc->sc_ah);
 
+       if (ath9k_is_chanctx_enabled()) {
+               if (!test_bit(ATH_OP_MULTI_CHANNEL, &common->op_flags))
+                       goto flush;
+
+               /*
+                * If MCC is active, extend the flush timeout
+                * and wait for the HW/SW queues to become
+                * empty. This needs to be done outside the
+                * sc->mutex lock to allow the channel scheduler
+                * to switch channel contexts.
+                *
+                * The vif queues have been stopped in mac80211,
+                * so there won't be any incoming frames.
+                */
+               __ath9k_flush(hw, queues, drop, true, true);
+               return;
+       }
+flush:
        mutex_lock(&sc->mutex);
-       __ath9k_flush(hw, queues, drop);
+       __ath9k_flush(hw, queues, drop, true, false);
        mutex_unlock(&sc->mutex);
 }
 
-void __ath9k_flush(struct ieee80211_hw *hw, u32 queues, bool drop)
+void __ath9k_flush(struct ieee80211_hw *hw, u32 queues, bool drop,
+                  bool sw_pending, bool timeout_override)
 {
        struct ath_softc *sc = hw->priv;
        struct ath_hw *ah = sc->sc_ah;
        struct ath_common *common = ath9k_hw_common(ah);
-       int timeout = HZ / 5; /* 200 ms */
+       int timeout;
        bool drain_txq;
 
        cancel_delayed_work_sync(&sc->tx_complete_work);
@@ -2010,7 +2086,17 @@ void __ath9k_flush(struct ieee80211_hw *hw, u32 queues, bool drop)
                return;
        }
 
-       if (wait_event_timeout(sc->tx_wait, !ath9k_has_tx_pending(sc),
+       spin_lock_bh(&sc->chan_lock);
+       if (timeout_override)
+               timeout = HZ / 5;
+       else
+               timeout = sc->cur_chan->flush_timeout;
+       spin_unlock_bh(&sc->chan_lock);
+
+       ath_dbg(common, CHAN_CTX,
+               "Flush timeout: %d\n", jiffies_to_msecs(timeout));
+
+       if (wait_event_timeout(sc->tx_wait, !ath9k_has_tx_pending(sc, sw_pending),
                               timeout) > 0)
                drop = false;
 
@@ -2021,7 +2107,7 @@ void __ath9k_flush(struct ieee80211_hw *hw, u32 queues, bool drop)
                spin_unlock_bh(&sc->sc_pcu_lock);
 
                if (!drain_txq)
-                       ath_reset(sc);
+                       ath_reset(sc, NULL);
 
                ath9k_ps_restore(sc);
        }
@@ -2033,7 +2119,7 @@ static bool ath9k_tx_frames_pending(struct ieee80211_hw *hw)
 {
        struct ath_softc *sc = hw->priv;
 
-       return ath9k_has_tx_pending(sc);
+       return ath9k_has_tx_pending(sc, true);
 }
 
 static int ath9k_tx_last_beacon(struct ieee80211_hw *hw)
@@ -2310,7 +2396,6 @@ static int ath9k_add_chanctx(struct ieee80211_hw *hw,
                        conf->def.chan->center_freq);
 
                ath_chanctx_set_channel(sc, ctx, &conf->def);
-               ath_chanctx_event(sc, NULL, ATH_CHANCTX_EVENT_ASSIGN);
 
                mutex_unlock(&sc->mutex);
                return 0;
@@ -2419,7 +2504,11 @@ static void ath9k_mgd_prepare_tx(struct ieee80211_hw *hw,
        struct ath_softc *sc = hw->priv;
        struct ath_common *common = ath9k_hw_common(sc->sc_ah);
        struct ath_vif *avp = (struct ath_vif *) vif->drv_priv;
+       struct ath_beacon_config *cur_conf;
+       struct ath_chanctx *go_ctx;
+       unsigned long timeout;
        bool changed = false;
+       u32 beacon_int;
 
        if (!test_bit(ATH_OP_MULTI_CHANNEL, &common->op_flags))
                return;
@@ -2430,19 +2519,59 @@ static void ath9k_mgd_prepare_tx(struct ieee80211_hw *hw,
        mutex_lock(&sc->mutex);
 
        spin_lock_bh(&sc->chan_lock);
-       if (sc->next_chan || (sc->cur_chan != avp->chanctx)) {
-               sc->next_chan = avp->chanctx;
+       if (sc->next_chan || (sc->cur_chan != avp->chanctx))
                changed = true;
+       spin_unlock_bh(&sc->chan_lock);
+
+       if (!changed)
+               goto out;
+
+       if (test_bit(ATH_OP_SCANNING, &common->op_flags)) {
+               ath_dbg(common, CHAN_CTX,
+                       "%s: Aborting HW scan\n", __func__);
+
+               mutex_unlock(&sc->mutex);
+
+               del_timer_sync(&sc->offchannel.timer);
+               ath_scan_complete(sc, true);
+               flush_work(&sc->chanctx_work);
+
+               mutex_lock(&sc->mutex);
+       }
+
+       go_ctx = ath_is_go_chanctx_present(sc);
+
+       if (go_ctx) {
+               /*
+                * Wait till the GO interface gets a chance
+                * to send out an NoA.
+                */
+               spin_lock_bh(&sc->chan_lock);
+               sc->sched.mgd_prepare_tx = true;
+               cur_conf = &go_ctx->beacon;
+               beacon_int = TU_TO_USEC(cur_conf->beacon_interval);
+               spin_unlock_bh(&sc->chan_lock);
+
+               timeout = usecs_to_jiffies(beacon_int);
+               init_completion(&sc->go_beacon);
+
+               if (wait_for_completion_timeout(&sc->go_beacon,
+                                               timeout) == 0)
+                       ath_dbg(common, CHAN_CTX,
+                               "Failed to send new NoA\n");
        }
+
        ath_dbg(common, CHAN_CTX,
-               "%s: Set chanctx state to FORCE_ACTIVE, changed: %d\n",
-               __func__, changed);
+               "%s: Set chanctx state to FORCE_ACTIVE for vif: %pM\n",
+               __func__, vif->addr);
+
+       spin_lock_bh(&sc->chan_lock);
+       sc->next_chan = avp->chanctx;
        sc->sched.state = ATH_CHANCTX_STATE_FORCE_ACTIVE;
        spin_unlock_bh(&sc->chan_lock);
 
-       if (changed)
-               ath_chanctx_set_next(sc, true);
-
+       ath_chanctx_set_next(sc, true);
+out:
        mutex_unlock(&sc->mutex);
 }
 
@@ -2474,8 +2603,7 @@ struct ieee80211_ops ath9k_ops = {
        .remove_interface   = ath9k_remove_interface,
        .config             = ath9k_config,
        .configure_filter   = ath9k_configure_filter,
-       .sta_add            = ath9k_sta_add,
-       .sta_remove         = ath9k_sta_remove,
+       .sta_state          = ath9k_sta_state,
        .sta_notify         = ath9k_sta_notify,
        .conf_tx            = ath9k_conf_tx,
        .bss_info_changed   = ath9k_bss_info_changed,