Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/linville/wirel...
[pandora-kernel.git] / drivers / net / wireless / ath / ath9k / htc_drv_main.c
index 04cb243..db8c0c0 100644 (file)
@@ -110,6 +110,9 @@ static void ath9k_htc_vif_iter(void *data, u8 *mac, struct ieee80211_vif *vif)
        struct ath9k_htc_priv *priv = data;
        struct ieee80211_bss_conf *bss_conf = &vif->bss_conf;
 
+       if ((vif->type == NL80211_IFTYPE_AP) && bss_conf->enable_beacon)
+               priv->reconfig_beacon = true;
+
        if (bss_conf->assoc) {
                priv->rearm_ani = true;
                priv->reconfig_beacon = true;
@@ -124,7 +127,7 @@ static void ath9k_htc_vif_reconfig(struct ath9k_htc_priv *priv)
        ieee80211_iterate_active_interfaces_atomic(priv->hw,
                                                   ath9k_htc_vif_iter, priv);
        if (priv->rearm_ani)
-               ath_start_ani(priv);
+               ath9k_htc_start_ani(priv);
 
        if (priv->reconfig_beacon) {
                ath9k_htc_ps_wakeup(priv);
@@ -166,6 +169,18 @@ static void ath9k_htc_set_bssid_mask(struct ath9k_htc_priv *priv,
        ath_hw_setbssidmask(common);
 }
 
+static void ath9k_htc_set_opmode(struct ath9k_htc_priv *priv)
+{
+       if (priv->num_ibss_vif)
+               priv->ah->opmode = NL80211_IFTYPE_ADHOC;
+       else if (priv->num_ap_vif)
+               priv->ah->opmode = NL80211_IFTYPE_AP;
+       else
+               priv->ah->opmode = NL80211_IFTYPE_STATION;
+
+       ath9k_hw_setopmode(priv->ah);
+}
+
 void ath9k_htc_reset(struct ath9k_htc_priv *priv)
 {
        struct ath_hw *ah = priv->ah;
@@ -180,7 +195,7 @@ void ath9k_htc_reset(struct ath9k_htc_priv *priv)
        mutex_lock(&priv->mutex);
        ath9k_htc_ps_wakeup(priv);
 
-       cancel_delayed_work_sync(&priv->ath9k_ani_work);
+       ath9k_htc_stop_ani(priv);
        ieee80211_stop_queues(priv->hw);
        htc_stop(priv->htc);
        WMI_CMD(WMI_DISABLE_INTR_CMDID);
@@ -276,6 +291,11 @@ static int ath9k_htc_set_channel(struct ath9k_htc_priv *priv,
                goto err;
 
        htc_start(priv->htc);
+
+       if (!(priv->op_flags & OP_SCANNING) &&
+           !(hw->conf.flags & IEEE80211_CONF_OFFCHANNEL))
+               ath9k_htc_vif_reconfig(priv);
+
 err:
        ath9k_htc_ps_restore(priv);
        return ret;
@@ -905,7 +925,7 @@ void ath9k_htc_debug_remove_root(void)
 /* ANI */
 /*******/
 
-void ath_start_ani(struct ath9k_htc_priv *priv)
+void ath9k_htc_start_ani(struct ath9k_htc_priv *priv)
 {
        struct ath_common *common = ath9k_hw_common(priv->ah);
        unsigned long timestamp = jiffies_to_msecs(jiffies);
@@ -914,15 +934,22 @@ void ath_start_ani(struct ath9k_htc_priv *priv)
        common->ani.shortcal_timer = timestamp;
        common->ani.checkani_timer = timestamp;
 
-       ieee80211_queue_delayed_work(common->hw, &priv->ath9k_ani_work,
+       priv->op_flags |= OP_ANI_RUNNING;
+
+       ieee80211_queue_delayed_work(common->hw, &priv->ani_work,
                                     msecs_to_jiffies(ATH_ANI_POLLINTERVAL));
 }
 
-void ath9k_ani_work(struct work_struct *work)
+void ath9k_htc_stop_ani(struct ath9k_htc_priv *priv)
+{
+       cancel_delayed_work_sync(&priv->ani_work);
+       priv->op_flags &= ~OP_ANI_RUNNING;
+}
+
+void ath9k_htc_ani_work(struct work_struct *work)
 {
        struct ath9k_htc_priv *priv =
-               container_of(work, struct ath9k_htc_priv,
-                            ath9k_ani_work.work);
+               container_of(work, struct ath9k_htc_priv, ani_work.work);
        struct ath_hw *ah = priv->ah;
        struct ath_common *common = ath9k_hw_common(ah);
        bool longcal = false;
@@ -931,7 +958,8 @@ void ath9k_ani_work(struct work_struct *work)
        unsigned int timestamp = jiffies_to_msecs(jiffies);
        u32 cal_interval, short_cal_interval;
 
-       short_cal_interval = ATH_STA_SHORT_CALINTERVAL;
+       short_cal_interval = (ah->opmode == NL80211_IFTYPE_AP) ?
+               ATH_AP_SHORT_CALINTERVAL : ATH_STA_SHORT_CALINTERVAL;
 
        /* Only calibrate if awake */
        if (ah->power_mode != ATH9K_PM_AWAKE)
@@ -1000,7 +1028,7 @@ set_timer:
        if (!common->ani.caldone)
                cal_interval = min(cal_interval, (u32)short_cal_interval);
 
-       ieee80211_queue_delayed_work(common->hw, &priv->ath9k_ani_work,
+       ieee80211_queue_delayed_work(common->hw, &priv->ani_work,
                                     msecs_to_jiffies(cal_interval));
 }
 
@@ -1008,7 +1036,7 @@ set_timer:
 /* mac80211 Callbacks */
 /**********************/
 
-static int ath9k_htc_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
+static void ath9k_htc_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
 {
        struct ieee80211_hdr *hdr;
        struct ath9k_htc_priv *priv = hw->priv;
@@ -1021,7 +1049,7 @@ static int ath9k_htc_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
        padsize = padpos & 3;
        if (padsize && skb->len > padpos) {
                if (skb_headroom(skb) < padsize)
-                       return -1;
+                       goto fail_tx;
                skb_push(skb, padsize);
                memmove(skb->data, skb->data + padsize, padpos);
        }
@@ -1042,11 +1070,10 @@ static int ath9k_htc_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
                goto fail_tx;
        }
 
-       return 0;
+       return;
 
 fail_tx:
        dev_kfree_skb_any(skb);
-       return 0;
 }
 
 static int ath9k_htc_start(struct ieee80211_hw *hw)
@@ -1154,7 +1181,7 @@ static void ath9k_htc_stop(struct ieee80211_hw *hw)
        cancel_work_sync(&priv->fatal_work);
        cancel_work_sync(&priv->ps_work);
        cancel_delayed_work_sync(&priv->ath9k_led_blink_work);
-       cancel_delayed_work_sync(&priv->ath9k_ani_work);
+       ath9k_htc_stop_ani(priv);
        ath9k_led_stop_brightness(priv);
 
        mutex_lock(&priv->mutex);
@@ -1193,9 +1220,23 @@ static int ath9k_htc_add_interface(struct ieee80211_hw *hw,
        mutex_lock(&priv->mutex);
 
        if (priv->nvifs >= ATH9K_HTC_MAX_VIF) {
-               ret = -ENOBUFS;
                mutex_unlock(&priv->mutex);
-               return ret;
+               return -ENOBUFS;
+       }
+
+       if (priv->num_ibss_vif ||
+           (priv->nvifs && vif->type == NL80211_IFTYPE_ADHOC)) {
+               ath_err(common, "IBSS coexistence with other modes is not allowed\n");
+               mutex_unlock(&priv->mutex);
+               return -ENOBUFS;
+       }
+
+       if (((vif->type == NL80211_IFTYPE_AP) ||
+            (vif->type == NL80211_IFTYPE_ADHOC)) &&
+           ((priv->num_ap_vif + priv->num_ibss_vif) >= ATH9K_HTC_MAX_BCN_VIF)) {
+               ath_err(common, "Max. number of beaconing interfaces reached\n");
+               mutex_unlock(&priv->mutex);
+               return -ENOBUFS;
        }
 
        ath9k_htc_ps_wakeup(priv);
@@ -1209,6 +1250,9 @@ static int ath9k_htc_add_interface(struct ieee80211_hw *hw,
        case NL80211_IFTYPE_ADHOC:
                hvif.opmode = cpu_to_be32(HTC_M_IBSS);
                break;
+       case NL80211_IFTYPE_AP:
+               hvif.opmode = cpu_to_be32(HTC_M_HOSTAP);
+               break;
        default:
                ath_err(common,
                        "Interface type %d not yet supported\n", vif->type);
@@ -1235,11 +1279,17 @@ static int ath9k_htc_add_interface(struct ieee80211_hw *hw,
 
        ath9k_htc_set_bssid_mask(priv, vif);
 
-       priv->ah->opmode = vif->type;
        priv->vif_slot |= (1 << avp->index);
        priv->nvifs++;
        priv->vif = vif;
 
+       INC_VIF(priv, vif->type);
+       ath9k_htc_set_opmode(priv);
+
+       if ((priv->ah->opmode == NL80211_IFTYPE_AP) &&
+           !(priv->op_flags & OP_ANI_RUNNING))
+               ath9k_htc_start_ani(priv);
+
        ath_dbg(common, ATH_DBG_CONFIG,
                "Attach a VIF of type: %d at idx: %d\n", vif->type, avp->index);
 
@@ -1273,6 +1323,20 @@ static void ath9k_htc_remove_interface(struct ieee80211_hw *hw,
        ath9k_htc_remove_station(priv, vif, NULL);
        priv->vif = NULL;
 
+       DEC_VIF(priv, vif->type);
+       ath9k_htc_set_opmode(priv);
+
+       /*
+        * Stop ANI only if there are no associated station interfaces.
+        */
+       if ((vif->type == NL80211_IFTYPE_AP) && (priv->num_ap_vif == 0)) {
+               priv->rearm_ani = false;
+               ieee80211_iterate_active_interfaces_atomic(priv->hw,
+                                                  ath9k_htc_vif_iter, priv);
+               if (!priv->rearm_ani)
+                       ath9k_htc_stop_ani(priv);
+       }
+
        ath_dbg(common, ATH_DBG_CONFIG, "Detach Interface at idx: %d\n", avp->index);
 
        ath9k_htc_ps_restore(priv);
@@ -1537,43 +1601,80 @@ static void ath9k_htc_bss_info_changed(struct ieee80211_hw *hw,
        struct ath9k_htc_priv *priv = hw->priv;
        struct ath_hw *ah = priv->ah;
        struct ath_common *common = ath9k_hw_common(ah);
+       bool set_assoc;
 
        mutex_lock(&priv->mutex);
        ath9k_htc_ps_wakeup(priv);
 
+       /*
+        * Set the HW AID/BSSID only for the first station interface
+        * or in IBSS mode.
+        */
+       set_assoc = !!((priv->ah->opmode == NL80211_IFTYPE_ADHOC) ||
+                      ((priv->ah->opmode == NL80211_IFTYPE_STATION) &&
+                       (priv->num_sta_vif == 1)));
+
+
        if (changed & BSS_CHANGED_ASSOC) {
-               common->curaid = bss_conf->assoc ?
-                                bss_conf->aid : 0;
-               ath_dbg(common, ATH_DBG_CONFIG, "BSS Changed ASSOC %d\n",
-                       bss_conf->assoc);
+               if (set_assoc) {
+                       ath_dbg(common, ATH_DBG_CONFIG, "BSS Changed ASSOC %d\n",
+                               bss_conf->assoc);
 
-               if (bss_conf->assoc)
-                       ath_start_ani(priv);
-               else
-                       cancel_delayed_work_sync(&priv->ath9k_ani_work);
+                       common->curaid = bss_conf->assoc ?
+                               bss_conf->aid : 0;
+
+                       if (bss_conf->assoc)
+                               ath9k_htc_start_ani(priv);
+                       else
+                               ath9k_htc_stop_ani(priv);
+               }
        }
 
        if (changed & BSS_CHANGED_BSSID) {
-               /* Set BSSID */
-               memcpy(common->curbssid, bss_conf->bssid, ETH_ALEN);
-               ath9k_hw_write_associd(ah);
+               if (set_assoc) {
+                       memcpy(common->curbssid, bss_conf->bssid, ETH_ALEN);
+                       ath9k_hw_write_associd(ah);
 
-               ath_dbg(common, ATH_DBG_CONFIG,
-                       "BSSID: %pM aid: 0x%x\n",
-                       common->curbssid, common->curaid);
+                       ath_dbg(common, ATH_DBG_CONFIG,
+                               "BSSID: %pM aid: 0x%x\n",
+                               common->curbssid, common->curaid);
+               }
        }
 
-       if ((changed & BSS_CHANGED_BEACON_INT) ||
-           (changed & BSS_CHANGED_BEACON) ||
-           ((changed & BSS_CHANGED_BEACON_ENABLED) &&
-           bss_conf->enable_beacon)) {
+       if ((changed & BSS_CHANGED_BEACON_ENABLED) && bss_conf->enable_beacon) {
+               ath_dbg(common, ATH_DBG_CONFIG,
+                       "Beacon enabled for BSS: %pM\n", bss_conf->bssid);
                priv->op_flags |= OP_ENABLE_BEACON;
                ath9k_htc_beacon_config(priv, vif);
        }
 
-       if ((changed & BSS_CHANGED_BEACON_ENABLED) &&
-           !bss_conf->enable_beacon) {
-               priv->op_flags &= ~OP_ENABLE_BEACON;
+       if ((changed & BSS_CHANGED_BEACON_ENABLED) && !bss_conf->enable_beacon) {
+               /*
+                * Disable SWBA interrupt only if there are no
+                * AP/IBSS interfaces.
+                */
+               if ((priv->num_ap_vif <= 1) || priv->num_ibss_vif) {
+                       ath_dbg(common, ATH_DBG_CONFIG,
+                               "Beacon disabled for BSS: %pM\n",
+                               bss_conf->bssid);
+                       priv->op_flags &= ~OP_ENABLE_BEACON;
+                       ath9k_htc_beacon_config(priv, vif);
+               }
+       }
+
+       if (changed & BSS_CHANGED_BEACON_INT) {
+               /*
+                * Reset the HW TSF for the first AP interface.
+                */
+               if ((priv->ah->opmode == NL80211_IFTYPE_AP) &&
+                   (priv->nvifs == 1) &&
+                   (priv->num_ap_vif == 1) &&
+                   (vif->type == NL80211_IFTYPE_AP)) {
+                       priv->op_flags |= OP_TSF_RESET;
+               }
+               ath_dbg(common, ATH_DBG_CONFIG,
+                       "Beacon interval changed for BSS: %pM\n",
+                       bss_conf->bssid);
                ath9k_htc_beacon_config(priv, vif);
        }
 
@@ -1679,7 +1780,7 @@ static void ath9k_htc_sw_scan_start(struct ieee80211_hw *hw)
        priv->op_flags |= OP_SCANNING;
        spin_unlock_bh(&priv->beacon_lock);
        cancel_work_sync(&priv->ps_work);
-       cancel_delayed_work_sync(&priv->ath9k_ani_work);
+       ath9k_htc_stop_ani(priv);
        mutex_unlock(&priv->mutex);
 }