#include <linux/etherdevice.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
+#include <linux/netdevice.h>
#include "wl1251.h"
#include "wl12xx_80211.h"
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);
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;
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,
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);
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;
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);
wl->power_level = WL1251_DEFAULT_POWER_LEVEL;
wl->rssi_thold = 0;
wl->channel = WL1251_DEFAULT_CHANNEL;
+ wl->monitor_present = false;
+ wl->joined = false;
+ wl->long_doze_mode_set = false;
wl1251_debugfs_reset(wl);
mutex_lock(&wl->mutex);
wl1251_debug(DEBUG_MAC80211, "mac80211 remove interface");
wl->vif = NULL;
+ memset(wl->bssid, 0, ETH_ALEN);
mutex_unlock(&wl->mutex);
}
+static int wl1251_build_null_data(struct wl1251 *wl)
+{
+ struct sk_buff *skb = NULL;
+ int size;
+ void *ptr;
+ int ret = -ENOMEM;
+
+
+ if (wl->bss_type == BSS_TYPE_IBSS) {
+ size = sizeof(struct wl12xx_null_data_template);
+ ptr = NULL;
+ } else {
+ skb = ieee80211_nullfunc_get(wl->hw, wl->vif);
+ if (!skb)
+ goto out;
+ size = skb->len;
+ ptr = skb->data;
+ }
+
+ ret = wl1251_cmd_template_set(wl, CMD_NULL_DATA, ptr, size);
+
+out:
+ dev_kfree_skb(skb);
+ if (ret)
+ wl1251_warning("cmd buld null data failed %d", ret);
+
+ return ret;
+
+}
+
static int wl1251_build_qos_null_data(struct wl1251 *wl)
{
struct ieee80211_qos_hdr template;
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 bool wl1251_can_do_pm(struct ieee80211_conf *conf, struct wl1251 *wl)
+{
+ return (conf->flags & IEEE80211_CONF_PS) && !wl->monitor_present;
+}
+
static int wl1251_op_config(struct ieee80211_hw *hw, u32 changed)
{
struct wl1251 *wl = hw->priv;
channel = ieee80211_frequency_to_channel(conf->channel->center_freq);
- wl1251_debug(DEBUG_MAC80211, "mac80211 config ch %d psm %s power %d",
+ wl1251_debug(DEBUG_MAC80211,
+ "mac80211 config ch %d monitor %s psm %s power %d",
channel,
+ conf->flags & IEEE80211_CONF_MONITOR ? "on" : "off",
conf->flags & IEEE80211_CONF_PS ? "on" : "off",
conf->power_level);
if (ret < 0)
goto out;
+ if (changed & IEEE80211_CONF_CHANGE_MONITOR) {
+ u32 mode;
+
+ if (conf->flags & IEEE80211_CONF_MONITOR) {
+ wl->monitor_present = true;
+ mode = DF_SNIFF_MODE_ENABLE | DF_ENCRYPTION_DISABLE;
+ } else {
+ wl->monitor_present = false;
+ mode = 0;
+ }
+
+ ret = wl1251_acx_feature_cfg(wl, mode);
+ if (ret < 0)
+ goto out_sleep;
+ }
+
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.
+ *
+ * Note that bss_type must be BSS_TYPE_STA_BSS, also at least
+ * one join has to be performed before CMD_ENABLE_RX can
+ * properly switch channels (join will be done by CONF_IDLE).
+ */
+ if (wl->vif == NULL) {
+ wl->bss_type = BSS_TYPE_STA_BSS;
+ wl->joined = false;
+ 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;
}
- if (conf->flags & IEEE80211_CONF_PS && !wl->psm_requested) {
+ if (wl1251_can_do_pm(conf, wl) && !wl->psm_requested) {
wl1251_debug(DEBUG_PSM, "psm enabled");
wl->psm_requested = true;
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;
- } else if (!(conf->flags & IEEE80211_CONF_PS) &&
- wl->psm_requested) {
+ ieee80211_queue_delayed_work(wl->hw, &wl->ps_work, 0);
+ } else if (!wl1251_can_do_pm(conf, wl) && 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) {
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)
return ret;
}
+struct wl1251_filter_params {
+ bool enabled;
+ int mc_list_length;
+ u8 mc_list[ACX_MC_ADDRESS_GROUP_MAX][ETH_ALEN];
+};
+
+static u64 wl1251_op_prepare_multicast(struct ieee80211_hw *hw,
+ struct netdev_hw_addr_list *mc_list)
+{
+ struct wl1251_filter_params *fp;
+ struct netdev_hw_addr *ha;
+ struct wl1251 *wl = hw->priv;
+
+ if (unlikely(wl->state == WL1251_STATE_OFF))
+ return 0;
+
+ fp = kzalloc(sizeof(*fp), GFP_ATOMIC);
+ if (!fp) {
+ wl1251_error("Out of memory setting filters.");
+ return 0;
+ }
+
+ /* update multicast filtering parameters */
+ fp->mc_list_length = 0;
+ if (netdev_hw_addr_list_count(mc_list) > ACX_MC_ADDRESS_GROUP_MAX) {
+ fp->enabled = false;
+ } else {
+ fp->enabled = true;
+ netdev_hw_addr_list_for_each(ha, mc_list) {
+ memcpy(fp->mc_list[fp->mc_list_length],
+ ha->addr, ETH_ALEN);
+ fp->mc_list_length++;
+ }
+ }
+
+ return (u64)(unsigned long)fp;
+}
+
#define WL1251_SUPPORTED_FILTERS (FIF_PROMISC_IN_BSS | \
FIF_ALLMULTI | \
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)
+ unsigned int *total, u64 multicast)
{
+ struct wl1251_filter_params *fp = (void *)(unsigned long)multicast;
struct wl1251 *wl = hw->priv;
+ int ret;
wl1251_debug(DEBUG_MAC80211, "mac80211 configure filter");
*total &= WL1251_SUPPORTED_FILTERS;
changed &= WL1251_SUPPORTED_FILTERS;
- if (changed == 0)
+ if (changed == 0) {
/* no filters which we support changed */
+ kfree(fp);
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;
}
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;
+
+ if (*total & FIF_ALLMULTI || *total & FIF_PROMISC_IN_BSS)
+ ret = wl1251_acx_group_address_tbl(wl, false, NULL, 0);
+ else if (fp)
+ ret = wl1251_acx_group_address_tbl(wl, fp->enabled,
+ fp->mc_list,
+ fp->mc_list_length);
+ 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);
+ kfree(fp);
}
/* HW encryption */
mutex_lock(&wl->mutex);
- ret = wl1251_ps_elp_wakeup(wl);
- if (ret < 0)
- goto out_unlock;
-
switch (cmd) {
case SET_KEY:
+ if (wl->monitor_present) {
+ ret = -EOPNOTSUPP;
+ goto out_unlock;
+ }
wl_cmd->key_action = KEY_ADD_OR_REPLACE;
break;
case DISABLE_KEY:
break;
}
+ ret = wl1251_ps_elp_wakeup(wl);
+ if (ret < 0)
+ goto out_unlock;
+
ret = wl1251_set_key_type(wl, wl_cmd, cmd, key, addr);
if (ret < 0) {
wl1251_error("Set KEY type failed");
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;
}
{
struct wl1251 *wl = hw->priv;
struct sk_buff *beacon, *skb;
+ bool do_join = false;
+ bool enable;
int ret;
wl1251_debug(DEBUG_MAC80211, "mac80211 bss info changed");
wl->rssi_thold = bss_conf->cqm_rssi_thold;
}
- if (changed & BSS_CHANGED_BSSID) {
+ if ((changed & BSS_CHANGED_BSSID) &&
+ memcmp(wl->bssid, bss_conf->bssid, ETH_ALEN)) {
memcpy(wl->bssid, bss_conf->bssid, ETH_ALEN);
- skb = ieee80211_nullfunc_get(wl->hw, wl->vif);
- if (!skb)
- goto out_sleep;
-
- ret = wl1251_cmd_template_set(wl, CMD_NULL_DATA,
- skb->data, skb->len);
- dev_kfree_skb(skb);
- if (ret < 0)
- goto out_sleep;
-
- ret = wl1251_build_qos_null_data(wl);
- if (ret < 0)
- goto out;
+ if (!is_zero_ether_addr(wl->bssid)) {
+ ret = wl1251_build_null_data(wl);
+ if (ret < 0)
+ goto out_sleep;
- if (wl->bss_type != BSS_TYPE_IBSS) {
- ret = wl1251_join(wl, wl->bss_type, wl->channel,
- wl->beacon_int, wl->dtim_period);
+ ret = wl1251_build_qos_null_data(wl);
if (ret < 0)
goto out_sleep;
+
+ do_join = true;
}
}
}
}
+ 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)
if (ret < 0)
goto out_sleep;
- ret = wl1251_join(wl, wl->bss_type, wl->beacon_int,
- wl->channel, wl->dtim_period);
+ do_join = true;
+ }
+ if (do_join) {
+ ret = wl1251_join(wl, wl->bss_type, wl->channel,
+ wl->beacon_int, wl->dtim_period);
if (ret < 0)
goto out_sleep;
}
.add_interface = wl1251_op_add_interface,
.remove_interface = wl1251_op_remove_interface,
.config = wl1251_op_config,
+ .prepare_multicast = wl1251_op_prepare_multicast,
.configure_filter = wl1251_op_configure_filter,
.tx = wl1251_op_tx,
.set_key = wl1251_op_set_key,
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;
if (wl->use_eeprom)
wl1251_read_eeprom_mac(wl);
+ if (wl->dump_eeprom)
+ wl1251_dump_eeprom(wl);
ret = wl1251_register_hw(wl);
if (ret)
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->monitor_present = false;
+ wl->joined = false;
wl->scanning = false;
+ wl->bss_type = MAX_BSS_TYPE;
wl->default_key = 0;
wl->listen_int = 1;
wl->rx_counter = 0;
ieee80211_free_hw(wl->hw);
+ if (wl->eeprom_dump != NULL)
+ kfree(wl->eeprom_dump);
+
return 0;
}
EXPORT_SYMBOL_GPL(wl1251_free_hw);