wl1251: Prepare for idle mode support
[pandora-wifi.git] / drivers / net / wireless / wl12xx / wl1251_main.c
index c835cd5..8ca7901 100644 (file)
 #include "wl1251_debugfs.h"
 #include "wl1251_boot.h"
 
+/* HACK */
+#ifndef FIF_PROBE_REQ
+#define FIF_PROBE_REQ 0
+#endif
+
 void wl1251_enable_interrupts(struct wl1251 *wl)
 {
        wl->if_ops->enable_irq(wl);
@@ -323,6 +328,65 @@ out:
        mutex_unlock(&wl->mutex);
 }
 
+/* Atomic irq handler */
+void wl1251_irq(struct wl1251 *wl)
+{
+       unsigned long flags;
+
+       wl1251_debug(DEBUG_IRQ, "IRQ");
+
+       /* complete the ELP completion */
+       spin_lock_irqsave(&wl->wl_lock, flags);
+       if (wl->elp_compl) {
+               complete(wl->elp_compl);
+               wl->elp_compl = NULL;
+       }
+       spin_unlock_irqrestore(&wl->wl_lock, flags);
+
+       ieee80211_queue_work(wl->hw, &wl->irq_work);
+}
+EXPORT_SYMBOL_GPL(wl1251_irq);
+
+static void wl1251_convert_filters(struct wl1251 *wl,
+                                 unsigned int mac80211_filters)
+{
+       wl->rx_config = WL1251_DEFAULT_RX_CONFIG;
+       wl->rx_filter = WL1251_DEFAULT_RX_FILTER;
+
+       if (mac80211_filters & FIF_PROMISC_IN_BSS) {
+               wl->rx_config |= CFG_BSSID_FILTER_EN;
+               wl->rx_config &= ~CFG_UNI_FILTER_EN;
+       }
+       if (mac80211_filters & FIF_ALLMULTI)
+               /*
+                * CFG_MC_FILTER_EN in rx_config needs to be 0 to receive
+                * all multicast frames
+                */
+               wl->rx_config &= ~CFG_MC_FILTER_EN;
+       if (mac80211_filters & FIF_FCSFAIL)
+               wl->rx_filter |= CFG_RX_FCS_ERROR;
+       if (mac80211_filters & FIF_BCN_PRBRESP_PROMISC) {
+               wl->rx_config &= ~CFG_BSSID_FILTER_EN;
+               wl->rx_config &= ~CFG_SSID_FILTER_EN;
+       }
+       if (mac80211_filters & FIF_CONTROL)
+               wl->rx_filter |= CFG_RX_CTL_EN;
+       if (mac80211_filters & FIF_OTHER_BSS)
+               wl->rx_config &= ~CFG_BSSID_FILTER_EN;
+       if (mac80211_filters & FIF_PROBE_REQ)
+               wl->rx_filter |= CFG_RX_PREQ_EN;
+
+       /*
+        * If we have no associated BSSID, the filter must be disabled
+        * or else association will break
+        */
+       if (is_zero_ether_addr(wl->bssid))
+               wl->rx_config &= ~CFG_BSSID_FILTER_EN;
+
+       wl1251_debug(DEBUG_FILTERS, "rx_config %04x, rx_filter %04x",
+               wl->rx_config, wl->rx_filter);
+}
+
 static int wl1251_join(struct wl1251 *wl, u8 bss_type, u8 channel,
                       u16 beacon_interval, u8 dtim_period)
 {
@@ -335,6 +399,8 @@ static int wl1251_join(struct wl1251 *wl, u8 bss_type, u8 channel,
        if (ret < 0)
                goto out;
 
+       /* Join uses filters, update them */
+       wl1251_convert_filters(wl, wl->mac80211_filters);
 
        ret = wl1251_cmd_join(wl, bss_type, channel, beacon_interval,
                              dtim_period);
@@ -349,36 +415,10 @@ out:
        return ret;
 }
 
-static void wl1251_filter_work(struct work_struct *work)
-{
-       struct wl1251 *wl =
-               container_of(work, struct wl1251, filter_work);
-       int ret;
-
-       mutex_lock(&wl->mutex);
-
-       if (wl->state == WL1251_STATE_OFF)
-               goto out;
-
-       ret = wl1251_ps_elp_wakeup(wl);
-       if (ret < 0)
-               goto out;
-
-       ret = wl1251_join(wl, wl->bss_type, wl->channel, wl->beacon_int,
-                         wl->dtim_period);
-       if (ret < 0)
-               goto out_sleep;
-
-out_sleep:
-       wl1251_ps_elp_sleep(wl);
-
-out:
-       mutex_unlock(&wl->mutex);
-}
-
 static int wl1251_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
 {
        struct wl1251 *wl = hw->priv;
+       unsigned long flags;
 
        skb_queue_tail(&wl->tx_queue, skb);
 
@@ -393,16 +433,13 @@ static int wl1251_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
         * The workqueue is slow to process the tx_queue and we need stop
         * the queue here, otherwise the queue will get too long.
         */
-       if (skb_queue_len(&wl->tx_queue) >= WL1251_TX_QUEUE_MAX_LENGTH) {
+       if (skb_queue_len(&wl->tx_queue) >= WL1251_TX_QUEUE_HIGH_WATERMARK) {
                wl1251_debug(DEBUG_TX, "op_tx: tx_queue full, stop queues");
-               ieee80211_stop_queues(wl->hw);
 
-               /*
-                * FIXME: this is racy, the variable is not properly
-                * protected. Maybe fix this by removing the stupid
-                * variable altogether and checking the real queue state?
-                */
+               spin_lock_irqsave(&wl->wl_lock, flags);
+               ieee80211_stop_queues(wl->hw);
                wl->tx_queue_stopped = true;
+               spin_unlock_irqrestore(&wl->wl_lock, flags);
        }
 
        return NETDEV_TX_OK;
@@ -485,7 +522,6 @@ static void wl1251_op_stop(struct ieee80211_hw *hw)
 
        cancel_work_sync(&wl->irq_work);
        cancel_work_sync(&wl->tx_work);
-       cancel_work_sync(&wl->filter_work);
 
        mutex_lock(&wl->mutex);
 
@@ -504,7 +540,7 @@ static void wl1251_op_stop(struct ieee80211_hw *hw)
        wl->rx_last_id = 0;
        wl->next_tx_complete = 0;
        wl->elp = false;
-       wl->psm = 0;
+       wl->station_mode = STATION_ACTIVE_MODE;
        wl->tx_queue_stopped = false;
        wl->power_level = WL1251_DEFAULT_POWER_LEVEL;
        wl->channel = WL1251_DEFAULT_CHANNEL;
@@ -610,8 +646,19 @@ static int wl1251_op_config(struct ieee80211_hw *hw, u32 changed)
        if (channel != wl->channel) {
                wl->channel = channel;
 
-               ret = wl1251_join(wl, wl->bss_type, wl->channel,
-                                 wl->beacon_int, wl->dtim_period);
+               /*
+                * Use ENABLE_RX command for channel switching when no
+                * interface is present (monitor mode only).
+                * This leaves the tx path disabled in firmware, whereas
+                * the usual JOIN command seems to transmit some frames
+                * at firmware level.
+                */
+               if (wl->vif == NULL) {
+                       ret = wl1251_cmd_data_path_rx(wl, wl->channel, 1);
+               } else {
+                       ret = wl1251_join(wl, wl->bss_type, wl->channel,
+                                         wl->beacon_int, wl->dtim_period);
+               }
                if (ret < 0)
                        goto out_sleep;
        }
@@ -638,7 +685,7 @@ static int wl1251_op_config(struct ieee80211_hw *hw, u32 changed)
 
                wl->psm_requested = false;
 
-               if (wl->psm) {
+               if (wl->station_mode != STATION_ACTIVE_MODE) {
                        ret = wl1251_ps_set_mode(wl, STATION_ACTIVE_MODE);
                        if (ret < 0)
                                goto out_sleep;
@@ -667,15 +714,18 @@ out:
                                  FIF_FCSFAIL | \
                                  FIF_BCN_PRBRESP_PROMISC | \
                                  FIF_CONTROL | \
-                                 FIF_OTHER_BSS)
+                                 FIF_OTHER_BSS | \
+                                 FIF_PROBE_REQ)
 
 static void wl1251_op_configure_filter(struct ieee80211_hw *hw,
                                       unsigned int changed,
                                       unsigned int *total,u64 multicast)
 {
        struct wl1251 *wl = hw->priv;
+       int ret;
 
        wl1251_debug(DEBUG_MAC80211, "mac80211 configure filter");
+       wl1251_debug(DEBUG_FILTERS, "configure filter %x %x", *total, changed);
 
        *total &= WL1251_SUPPORTED_FILTERS;
        changed &= WL1251_SUPPORTED_FILTERS;
@@ -684,38 +734,25 @@ static void wl1251_op_configure_filter(struct ieee80211_hw *hw,
                /* no filters which we support changed */
                return;
 
-       /* FIXME: wl->rx_config and wl->rx_filter are not protected */
+       mutex_lock(&wl->mutex);
 
-       wl->rx_config = WL1251_DEFAULT_RX_CONFIG;
-       wl->rx_filter = WL1251_DEFAULT_RX_FILTER;
+       wl->mac80211_filters = *total;
+       wl1251_convert_filters(wl, *total);
 
-       if (*total & FIF_PROMISC_IN_BSS) {
-               wl->rx_config |= CFG_BSSID_FILTER_EN;
-               wl->rx_config |= CFG_RX_ALL_GOOD;
-       }
-       if (*total & FIF_ALLMULTI)
-               /*
-                * CFG_MC_FILTER_EN in rx_config needs to be 0 to receive
-                * all multicast frames
-                */
-               wl->rx_config &= ~CFG_MC_FILTER_EN;
-       if (*total & FIF_FCSFAIL)
-               wl->rx_filter |= CFG_RX_FCS_ERROR;
-       if (*total & FIF_BCN_PRBRESP_PROMISC) {
-               wl->rx_config &= ~CFG_BSSID_FILTER_EN;
-               wl->rx_config &= ~CFG_SSID_FILTER_EN;
-       }
-       if (*total & FIF_CONTROL)
-               wl->rx_filter |= CFG_RX_CTL_EN;
-       if (*total & FIF_OTHER_BSS)
-               wl->rx_filter &= ~CFG_BSSID_FILTER_EN;
+       if (wl->state == WL1251_STATE_OFF)
+               goto out;
 
-       /*
-        * FIXME: workqueues need to be properly cancelled on stop(), for
-        * now let's just disable changing the filter settings. They will
-        * be updated any on config().
-        */
-       /* schedule_work(&wl->filter_work); */
+       ret = wl1251_ps_elp_wakeup(wl);
+       if (ret < 0)
+               goto out;
+
+       /* apply configured filters */
+       wl1251_acx_rx_config(wl, wl->rx_config, wl->rx_filter);
+
+       wl1251_ps_elp_sleep(wl);
+
+out:
+       mutex_unlock(&wl->mutex);
 }
 
 /* HW encryption */
@@ -909,6 +946,7 @@ static int wl1251_op_hw_scan(struct ieee80211_hw *hw,
        ret = wl1251_cmd_scan(wl, ssid, ssid_len, req->channels,
                              req->n_channels, WL1251_SCAN_NUM_PROBES);
        if (ret < 0) {
+               wl1251_debug(DEBUG_SCAN, "scan failed %d", ret);
                wl->scanning = false;
                goto out_sleep;
        }
@@ -1042,6 +1080,9 @@ static void wl1251_op_bss_info_changed(struct ieee80211_hw *hw,
 
        if (changed & BSS_CHANGED_BEACON) {
                beacon = ieee80211_beacon_get(hw, vif);
+               if (!beacon)
+                       goto out_sleep;
+
                ret = wl1251_cmd_template_set(wl, CMD_BEACON, beacon->data,
                                              beacon->len);
 
@@ -1299,7 +1340,8 @@ int wl1251_init_ieee80211(struct wl1251 *wl)
                IEEE80211_HW_BEACON_FILTER |
                IEEE80211_HW_SUPPORTS_UAPSD;
 
-       wl->hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION);
+       wl->hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
+                                        BIT(NL80211_IFTYPE_ADHOC);
        wl->hw->wiphy->max_scan_ssids = 1;
        wl->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &wl1251_band_2ghz;
 
@@ -1344,7 +1386,6 @@ struct ieee80211_hw *wl1251_alloc_hw(void)
 
        skb_queue_head_init(&wl->tx_queue);
 
-       INIT_WORK(&wl->filter_work, wl1251_filter_work);
        INIT_DELAYED_WORK(&wl->elp_work, wl1251_elp_work);
        wl->channel = WL1251_DEFAULT_CHANNEL;
        wl->scanning = false;
@@ -1357,7 +1398,7 @@ struct ieee80211_hw *wl1251_alloc_hw(void)
        wl->rx_config = WL1251_DEFAULT_RX_CONFIG;
        wl->rx_filter = WL1251_DEFAULT_RX_FILTER;
        wl->elp = false;
-       wl->psm = 0;
+       wl->station_mode = STATION_ACTIVE_MODE;
        wl->psm_requested = false;
        wl->tx_queue_stopped = false;
        wl->power_level = WL1251_DEFAULT_POWER_LEVEL;