p54: disable PS by default
[pandora-kernel.git] / drivers / net / wireless / p54 / main.c
index f9b4f6a..77203e3 100644 (file)
@@ -65,51 +65,64 @@ static int p54_set_tim(struct ieee80211_hw *dev, struct ieee80211_sta *sta,
        return p54_update_beacon_tim(priv, sta->aid, set);
 }
 
-static int p54_beacon_format_ie_tim(struct sk_buff *skb)
+u8 *p54_find_ie(struct sk_buff *skb, u8 ie)
 {
-       /*
-        * the good excuse for this mess is ... the firmware.
-        * The dummy TIM MUST be at the end of the beacon frame,
-        * because it'll be overwritten!
-        */
-
        struct ieee80211_mgmt *mgmt = (void *)skb->data;
        u8 *pos, *end;
 
        if (skb->len <= sizeof(mgmt))
-               return -EINVAL;
+               return NULL;
 
        pos = (u8 *)mgmt->u.beacon.variable;
        end = skb->data + skb->len;
        while (pos < end) {
                if (pos + 2 + pos[1] > end)
-                       return -EINVAL;
+                       return NULL;
 
-               if (pos[0] == WLAN_EID_TIM) {
-                       u8 dtim_len = pos[1];
-                       u8 dtim_period = pos[3];
-                       u8 *next = pos + 2 + dtim_len;
+               if (pos[0] == ie)
+                       return pos;
 
-                       if (dtim_len < 3)
-                               return -EINVAL;
+               pos += 2 + pos[1];
+       }
+       return NULL;
+}
 
-                       memmove(pos, next, end - next);
+static int p54_beacon_format_ie_tim(struct sk_buff *skb)
+{
+       /*
+        * the good excuse for this mess is ... the firmware.
+        * The dummy TIM MUST be at the end of the beacon frame,
+        * because it'll be overwritten!
+        */
+       u8 *tim;
+       u8 dtim_len;
+       u8 dtim_period;
+       u8 *next;
 
-                       if (dtim_len > 3)
-                               skb_trim(skb, skb->len - (dtim_len - 3));
+       tim = p54_find_ie(skb, WLAN_EID_TIM);
+       if (!tim)
+               return 0;
 
-                       pos = end - (dtim_len + 2);
+       dtim_len = tim[1];
+       dtim_period = tim[3];
+       next = tim + 2 + dtim_len;
+
+       if (dtim_len < 3)
+               return -EINVAL;
+
+       memmove(tim, next, skb_tail_pointer(skb) - next);
+       tim = skb_tail_pointer(skb) - (dtim_len + 2);
+
+       /* add the dummy at the end */
+       tim[0] = WLAN_EID_TIM;
+       tim[1] = 3;
+       tim[2] = 0;
+       tim[3] = dtim_period;
+       tim[4] = 0;
+
+       if (dtim_len > 3)
+               skb_trim(skb, skb->len - (dtim_len - 3));
 
-                       /* add the dummy at the end */
-                       pos[0] = WLAN_EID_TIM;
-                       pos[1] = 3;
-                       pos[2] = 0;
-                       pos[3] = dtim_period;
-                       pos[4] = 0;
-                       return 0;
-               }
-               pos += 2 + pos[1];
-       }
        return 0;
 }
 
@@ -117,7 +130,6 @@ static int p54_beacon_update(struct p54_common *priv,
                        struct ieee80211_vif *vif)
 {
        struct sk_buff *beacon;
-       __le32 old_beacon_req_id;
        int ret;
 
        beacon = ieee80211_beacon_get(priv->hw, vif);
@@ -127,15 +139,16 @@ static int p54_beacon_update(struct p54_common *priv,
        if (ret)
                return ret;
 
-       old_beacon_req_id = priv->beacon_req_id;
-       priv->beacon_req_id = GET_REQ_ID(beacon);
-
-       ret = p54_tx_80211(priv->hw, beacon);
-       if (ret) {
-               priv->beacon_req_id = old_beacon_req_id;
-               return -ENOSPC;
-       }
-
+       /*
+        * During operation, the firmware takes care of beaconing.
+        * The driver only needs to upload a new beacon template, once
+        * the template was changed by the stack or userspace.
+        *
+        * LMAC API 3.2.2 also specifies that the driver does not need
+        * to cancel the old beacon template by hand, instead the firmware
+        * will release the previous one through the feedback mechanism.
+        */
+       WARN_ON(p54_tx_80211(priv->hw, beacon));
        priv->tsf_high32 = 0;
        priv->tsf_low32 = 0;
 
@@ -167,7 +180,7 @@ static int p54_start(struct ieee80211_hw *dev)
                goto out;
        }
 
-       queue_delayed_work(dev->workqueue, &priv->work, 0);
+       ieee80211_queue_delayed_work(dev, &priv->work, 0);
 
        priv->softled_state = 0;
        err = p54_set_leds(priv);
@@ -240,9 +253,14 @@ static void p54_remove_interface(struct ieee80211_hw *dev,
 
        mutex_lock(&priv->conf_mutex);
        priv->vif = NULL;
-       if (priv->beacon_req_id) {
+
+       /*
+        * LMAC API 3.2.2 states that any active beacon template must be
+        * canceled by the driver before attempting a mode transition.
+        */
+       if (le32_to_cpu(priv->beacon_req_id) != 0) {
                p54_tx_cancel(priv, priv->beacon_req_id);
-               priv->beacon_req_id = cpu_to_le32(0);
+               wait_for_completion_interruptible_timeout(&priv->beacon_comp, HZ);
        }
        priv->mode = NL80211_IFTYPE_MONITOR;
        memset(priv->mac_addr, 0, ETH_ALEN);
@@ -270,6 +288,11 @@ static int p54_config(struct ieee80211_hw *dev, u32 changed)
                if (ret)
                        goto out;
        }
+       if (changed & IEEE80211_CONF_CHANGE_IDLE) {
+               ret = p54_setup_mac(priv);
+               if (ret)
+                       goto out;
+       }
 
 out:
        mutex_unlock(&priv->conf_mutex);
@@ -299,7 +322,7 @@ static int p54_conf_tx(struct ieee80211_hw *dev, u16 queue,
        int ret;
 
        mutex_lock(&priv->conf_mutex);
-       if ((params) && !(queue > 4)) {
+       if (queue < dev->queues) {
                P54_SET_QUEUE(priv->qos_params[queue], params->aifs,
                        params->cw_min, params->cw_max, params->txop);
                ret = p54_set_edcf(priv);
@@ -384,6 +407,9 @@ static void p54_bss_info_changed(struct ieee80211_hw *dev,
                        priv->wakeup_timer = info->beacon_int *
                                             info->dtim_period * 5;
                        p54_setup_mac(priv);
+               } else {
+                       priv->wakeup_timer = 500;
+                       priv->aid = 0;
                }
        }
 
@@ -517,6 +543,9 @@ struct ieee80211_hw *p54_init_common(size_t priv_data_len)
        skb_queue_head_init(&priv->tx_pending);
        dev->flags = IEEE80211_HW_RX_INCLUDES_FCS |
                     IEEE80211_HW_SIGNAL_DBM |
+                    IEEE80211_HW_SUPPORTS_PS |
+                    IEEE80211_HW_PS_NULLFUNC_STACK |
+                    IEEE80211_HW_BEACON_FILTER |
                     IEEE80211_HW_NOISE_DBM;
 
        dev->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
@@ -525,6 +554,7 @@ struct ieee80211_hw *p54_init_common(size_t priv_data_len)
                                      BIT(NL80211_IFTYPE_MESH_POINT);
 
        dev->channel_change_time = 1000;        /* TODO: find actual value */
+       priv->beacon_req_id = cpu_to_le32(0);
        priv->tx_stats[P54_QUEUE_BEACON].limit = 1;
        priv->tx_stats[P54_QUEUE_FWSCAN].limit = 1;
        priv->tx_stats[P54_QUEUE_MGMT].limit = 3;
@@ -545,9 +575,16 @@ struct ieee80211_hw *p54_init_common(size_t priv_data_len)
        dev->extra_tx_headroom = sizeof(struct p54_hdr) + 4 +
                                 sizeof(struct p54_tx_data);
 
+       /*
+        * For now, disable PS by default because it affects
+        * link stability significantly.
+        */
+       dev->wiphy->ps_default = false;
+
        mutex_init(&priv->conf_mutex);
        mutex_init(&priv->eeprom_mutex);
        init_completion(&priv->eeprom_comp);
+       init_completion(&priv->beacon_comp);
        INIT_DELAYED_WORK(&priv->work, p54_work);
 
        return dev;
@@ -579,6 +616,10 @@ EXPORT_SYMBOL_GPL(p54_register_common);
 void p54_free_common(struct ieee80211_hw *dev)
 {
        struct p54_common *priv = dev->priv;
+       unsigned int i;
+
+       for (i = 0; i < IEEE80211_NUM_BANDS; i++)
+               kfree(priv->band_table[i]);
 
        kfree(priv->iq_autocal);
        kfree(priv->output_limit);