wl1251: implement hardware ARP filtering
[pandora-kernel.git] / drivers / net / wireless / wl1251 / main.c
index ba3268e..7fcbb79 100644 (file)
@@ -212,12 +212,11 @@ out:
        return ret;
 }
 
-#define WL1251_IRQ_LOOP_COUNT 10
-static void wl1251_irq_work(struct work_struct *work)
+#define WL1251_IRQ_LOOP_COUNT 100
+irqreturn_t wl1251_irq(int irq, void *cookie)
 {
        u32 intr, ctr = WL1251_IRQ_LOOP_COUNT;
-       struct wl1251 *wl =
-               container_of(work, struct wl1251, irq_work);
+       struct wl1251 *wl = cookie;
        int ret;
 
        mutex_lock(&wl->mutex);
@@ -308,6 +307,22 @@ static void wl1251_irq_work(struct work_struct *work)
                        wl1251_debug(DEBUG_IRQ,
                                     "WL1251_ACX_INTR_INIT_COMPLETE");
 
+               while (skb_queue_len(&wl->tx_queue) > 0
+                      && wl1251_tx_path_status(wl) == 0) {
+
+                       struct sk_buff *skb = skb_dequeue(&wl->tx_queue);
+                       if (skb == NULL)
+                               goto out_sleep;
+
+                       ret = wl1251_tx_frame(wl, skb);
+                       if (ret == -EBUSY) {
+                               skb_queue_head(&wl->tx_queue, skb);
+                               break;
+                       } else if (ret < 0) {
+                               dev_kfree_skb(skb);
+                       }
+               }
+
                if (--ctr == 0)
                        break;
 
@@ -320,6 +335,16 @@ out_sleep:
 
 out:
        mutex_unlock(&wl->mutex);
+       return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_GPL(wl1251_irq);
+
+static void wl1251_irq_work(struct work_struct *work)
+{
+       struct wl1251 *wl =
+               container_of(work, struct wl1251, irq_work);
+
+       wl1251_irq(0, wl);
 }
 
 static int wl1251_join(struct wl1251 *wl, u8 bss_type, u8 channel,
@@ -334,6 +359,12 @@ static int wl1251_join(struct wl1251 *wl, u8 bss_type, u8 channel,
        if (ret < 0)
                goto out;
 
+       /*
+        * Join command applies filters, and if we are not associated,
+        * BSSID filter must be disabled for association to work.
+        */
+       if (is_zero_ether_addr(wl->bssid))
+               wl->rx_config &= ~CFG_BSSID_FILTER_EN;
 
        ret = wl1251_cmd_join(wl, bss_type, channel, beacon_interval,
                              dtim_period);
@@ -348,33 +379,6 @@ 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 void wl1251_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
 {
        struct wl1251 *wl = hw->priv;
@@ -478,7 +482,8 @@ 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);
+       cancel_delayed_work_sync(&wl->ps_work);
+       cancel_delayed_work_sync(&wl->elp_work);
 
        mutex_lock(&wl->mutex);
 
@@ -582,6 +587,92 @@ static int wl1251_build_qos_null_data(struct wl1251 *wl)
                                       sizeof(template));
 }
 
+static void wl1251_ps_work(struct work_struct *work)
+{
+       struct delayed_work *dwork;
+       struct wl1251 *wl;
+       unsigned long diff, wait;
+       bool need_ps;
+       bool have_ps;
+       int ret;
+       int i;
+
+       dwork = container_of(work, struct delayed_work, work);
+       wl = container_of(dwork, struct wl1251, ps_work);
+
+       mutex_lock(&wl->mutex);
+
+       /* don't change PS modes while still transitioning, to avoid possbile
+        * fw bugs (it normally takes ~130ms to enable and ~10ms to disable) */
+       if (wl->ps_transitioning) {
+               diff = jiffies - wl->ps_change_jiffies;
+               if (diff > msecs_to_jiffies(500)) {
+                       wl1251_error("PS change taking too long: %lu", diff);
+                       wl->ps_transitioning = false;
+               } else {
+                       //wl1251_error("PS still transitioning");
+                       ieee80211_queue_delayed_work(wl->hw, &wl->ps_work,
+                               msecs_to_jiffies(50));
+                       goto out;
+               }
+       }
+
+       need_ps = wl->psm_requested && !wl->bss_lost;
+       have_ps = wl->station_mode == STATION_POWER_SAVE_MODE;
+
+       if (need_ps == have_ps) {
+               //wl1251_info("ps: already in mode %d", have_ps);
+               goto out;
+       }
+
+       /* don't enter PS if there was recent activity */
+       if (need_ps) {
+               wait = 0;
+
+               diff = jiffies - wl->last_io_jiffies;
+               if (diff < msecs_to_jiffies(150)) {
+                       //wl1251_info("ps: postponed psm, j %ld", diff);
+                       wait = msecs_to_jiffies(150) - diff + 1;
+               }
+
+               for (i = 0; i < ARRAY_SIZE(wl->tx_frames); i++) {
+                       if (wl->tx_frames[i] != NULL) {
+                               //wl1251_error("  frm %d busy", i);
+                               if (wait < msecs_to_jiffies(50))
+                                       wait = msecs_to_jiffies(50);
+                               break;
+                       }
+               }
+
+               if (wait > 0) {
+                       ieee80211_queue_delayed_work(wl->hw, &wl->ps_work, wait);
+                       goto out;
+               }
+       }
+
+       ret = wl1251_ps_elp_wakeup(wl);
+       if (ret < 0)
+               goto out;
+
+       if (need_ps) {
+               wl1251_acx_wr_tbtt_and_dtim(wl, wl->beacon_int,
+                                           wl->dtim_period);
+       }
+       ret = wl1251_ps_set_mode(wl,
+               need_ps ? STATION_POWER_SAVE_MODE : STATION_ACTIVE_MODE);
+       if (ret < 0)
+               goto out_sleep;
+
+       //wl1251_info("psm %d, j %ld, d %ld", need_ps,
+       //      jiffies - wl->last_io_jiffies);
+
+out_sleep:
+       wl1251_ps_elp_sleep(wl);
+
+out:
+       mutex_unlock(&wl->mutex);
+}
+
 static int wl1251_op_config(struct ieee80211_hw *hw, u32 changed)
 {
        struct wl1251 *wl = hw->priv;
@@ -617,26 +708,14 @@ static int wl1251_op_config(struct ieee80211_hw *hw, u32 changed)
 
                wl->dtim_period = conf->ps_dtim_period;
 
-               ret = wl1251_acx_wr_tbtt_and_dtim(wl, wl->beacon_int,
-                                                 wl->dtim_period);
-
-               /*
-                * mac80211 enables PSM only if we're already associated.
-                */
-               ret = wl1251_ps_set_mode(wl, STATION_POWER_SAVE_MODE);
-               if (ret < 0)
-                       goto out_sleep;
+               ieee80211_queue_delayed_work(wl->hw, &wl->ps_work, 0);
        } else if (!(conf->flags & IEEE80211_CONF_PS) &&
                   wl->psm_requested) {
                wl1251_debug(DEBUG_PSM, "psm disabled");
 
                wl->psm_requested = false;
 
-               if (wl->station_mode != STATION_ACTIVE_MODE) {
-                       ret = wl1251_ps_set_mode(wl, STATION_ACTIVE_MODE);
-                       if (ret < 0)
-                               goto out_sleep;
-               }
+               ieee80211_queue_delayed_work(wl->hw, &wl->ps_work, 0);
        }
 
        if (changed & IEEE80211_CONF_CHANGE_IDLE) {
@@ -648,6 +727,11 @@ static int wl1251_op_config(struct ieee80211_hw *hw, u32 changed)
                        ret = wl1251_ps_set_mode(wl, STATION_ACTIVE_MODE);
                        if (ret < 0)
                                goto out_sleep;
+
+                       ret = wl1251_event_wait(wl, PS_REPORT_EVENT_ID, 100);
+                       if (ret < 0)
+                               wl1251_error("error waiting for wakeup");
+
                        ret = wl1251_join(wl, wl->bss_type, wl->channel,
                                          wl->beacon_int, wl->dtim_period);
                        if (ret < 0)
@@ -677,13 +761,15 @@ 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");
 
@@ -694,7 +780,7 @@ 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;
@@ -717,15 +803,25 @@ static void wl1251_op_configure_filter(struct ieee80211_hw *hw,
        }
        if (*total & FIF_CONTROL)
                wl->rx_filter |= CFG_RX_CTL_EN;
-       if (*total & FIF_OTHER_BSS)
-               wl->rx_filter &= ~CFG_BSSID_FILTER_EN;
+       if (*total & FIF_OTHER_BSS || is_zero_ether_addr(wl->bssid))
+               wl->rx_config &= ~CFG_BSSID_FILTER_EN;
+       if (*total & FIF_PROBE_REQ)
+               wl->rx_filter |= CFG_RX_PREQ_EN;
 
-       /*
-        * 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); */
+       if (wl->state == WL1251_STATE_OFF)
+               goto out;
+
+       ret = wl1251_ps_elp_wakeup(wl);
+       if (ret < 0)
+               goto out;
+
+       /* send filters to firmware */
+       wl1251_acx_rx_config(wl, wl->rx_config, wl->rx_filter);
+
+       wl1251_ps_elp_sleep(wl);
+
+out:
+       mutex_unlock(&wl->mutex);
 }
 
 /* HW encryption */
@@ -964,6 +1060,7 @@ static void wl1251_op_bss_info_changed(struct ieee80211_hw *hw,
 {
        struct wl1251 *wl = hw->priv;
        struct sk_buff *beacon, *skb;
+       bool enable;
        int ret;
 
        wl1251_debug(DEBUG_MAC80211, "mac80211 bss info changed");
@@ -1062,6 +1159,17 @@ static void wl1251_op_bss_info_changed(struct ieee80211_hw *hw,
                }
        }
 
+       if (changed & BSS_CHANGED_ARP_FILTER) {
+               __be32 addr = bss_conf->arp_addr_list[0];
+               WARN_ON(wl->bss_type != BSS_TYPE_STA_BSS);
+
+               enable = bss_conf->arp_addr_cnt == 1 && bss_conf->assoc;
+               wl1251_acx_arp_ip_filter(wl, enable, addr);
+
+               if (ret < 0)
+                       goto out_sleep;
+       }
+
        if (changed & BSS_CHANGED_BEACON) {
                beacon = ieee80211_beacon_get(hw, vif);
                if (!beacon)
@@ -1302,6 +1410,34 @@ static int wl1251_read_eeprom_mac(struct wl1251 *wl)
        return 0;
 }
 
+/* temporary (?) hack for EEPROM dumping
+ * (it seems this can only be done before fw is running) */
+static int wl1251_dump_eeprom(struct wl1251 *wl)
+{
+       int ret;
+
+       wl1251_set_partition(wl, 0, 0, REGISTERS_BASE, REGISTERS_DOWN_SIZE);
+
+       wl->eeprom_dump = kzalloc(1024, GFP_KERNEL);
+       if (wl->eeprom_dump == NULL) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       ret = wl1251_read_eeprom(wl, 0, wl->eeprom_dump, 1024);
+       if (ret != 0) {
+               wl1251_error("eeprom dump failed: %d", ret);
+               kfree(wl->eeprom_dump);
+               wl->eeprom_dump = NULL;
+               goto out;
+       }
+
+       wl1251_info("eeprom dumped.");
+
+out:
+       return ret;
+}
+
 static int wl1251_register_hw(struct wl1251 *wl)
 {
        int ret;
@@ -1351,6 +1487,8 @@ int wl1251_init_ieee80211(struct wl1251 *wl)
 
        if (wl->use_eeprom)
                wl1251_read_eeprom_mac(wl);
+       if (wl->dump_eeprom)
+               wl1251_dump_eeprom(wl);
 
        ret = wl1251_register_hw(wl);
        if (ret)
@@ -1388,7 +1526,7 @@ 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->ps_work, wl1251_ps_work);
        INIT_DELAYED_WORK(&wl->elp_work, wl1251_elp_work);
        wl->channel = WL1251_DEFAULT_CHANNEL;
        wl->scanning = false;
@@ -1460,6 +1598,9 @@ int wl1251_free_hw(struct wl1251 *wl)
 
        ieee80211_free_hw(wl->hw);
 
+       if (wl->eeprom_dump != NULL)
+               kfree(wl->eeprom_dump);
+
        return 0;
 }
 EXPORT_SYMBOL_GPL(wl1251_free_hw);