diff options
Diffstat (limited to 'drivers/net/wireless/ti/wl1251/main.c')
-rw-r--r-- | drivers/net/wireless/ti/wl1251/main.c | 153 |
1 files changed, 139 insertions, 14 deletions
diff --git a/drivers/net/wireless/ti/wl1251/main.c b/drivers/net/wireless/ti/wl1251/main.c index 3291ffa9527..757e25784a8 100644 --- a/drivers/net/wireless/ti/wl1251/main.c +++ b/drivers/net/wireless/ti/wl1251/main.c @@ -28,6 +28,7 @@ #include <linux/etherdevice.h> #include <linux/vmalloc.h> #include <linux/slab.h> +#include <linux/netdevice.h> #include "wl1251.h" #include "wl12xx_80211.h" @@ -479,10 +480,13 @@ static void wl1251_op_stop(struct ieee80211_hw *hw) wl->next_tx_complete = 0; wl->elp = false; wl->station_mode = STATION_ACTIVE_MODE; + wl->psm_entry_retry = 0; wl->tx_queue_stopped = false; wl->power_level = WL1251_DEFAULT_POWER_LEVEL; wl->rssi_thold = 0; wl->channel = WL1251_DEFAULT_CHANNEL; + wl->monitor_present = false; + wl->joined = false; wl1251_debugfs_reset(wl); @@ -521,7 +525,7 @@ static int wl1251_op_add_interface(struct ieee80211_hw *hw, goto out; } - if (memcmp(wl->mac_addr, vif->addr, ETH_ALEN)) { + if (!ether_addr_equal_unaligned(wl->mac_addr, vif->addr)) { memcpy(wl->mac_addr, vif->addr, ETH_ALEN); SET_IEEE80211_PERM_ADDR(wl->hw, wl->mac_addr); ret = wl1251_acx_station_id(wl); @@ -542,6 +546,7 @@ static void wl1251_op_remove_interface(struct ieee80211_hw *hw, mutex_lock(&wl->mutex); wl1251_debug(DEBUG_MAC80211, "mac80211 remove interface"); wl->vif = NULL; + memset(wl->bssid, 0, ETH_ALEN); mutex_unlock(&wl->mutex); } @@ -566,6 +571,11 @@ static int wl1251_build_qos_null_data(struct wl1251 *wl) sizeof(template)); } +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; @@ -575,8 +585,10 @@ static int wl1251_op_config(struct ieee80211_hw *hw, u32 changed) channel = ieee80211_frequency_to_channel( conf->chandef.chan->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); @@ -586,16 +598,44 @@ static int wl1251_op_config(struct ieee80211_hw *hw, u32 changed) 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. + */ + if (wl->vif == NULL) { + 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; @@ -611,8 +651,7 @@ static int wl1251_op_config(struct ieee80211_hw *hw, u32 changed) 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) { + } else if (!wl1251_can_do_pm(conf, wl) && wl->psm_requested) { wl1251_debug(DEBUG_PSM, "psm disabled"); wl->psm_requested = false; @@ -648,6 +687,16 @@ static int wl1251_op_config(struct ieee80211_hw *hw, u32 changed) wl->power_level = conf->power_level; } + /* + * Tell stack that connection is lost because hw encryption isn't + * supported in monitor mode. + * This requires temporary enabling of the hw connection monitor flag + */ + if ((changed & IEEE80211_CONF_CHANGE_MONITOR) && wl->vif) { + wl->hw->flags |= IEEE80211_HW_CONNECTION_MONITOR; + ieee80211_connection_loss(wl->vif); + } + out_sleep: wl1251_ps_elp_sleep(wl); @@ -657,6 +706,44 @@ out: 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 | \ @@ -667,8 +754,9 @@ out: 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; @@ -677,9 +765,11 @@ static void wl1251_op_configure_filter(struct ieee80211_hw *hw, *total &= WL1251_SUPPORTED_FILTERS; changed &= WL1251_SUPPORTED_FILTERS; - if (changed == 0) + if (changed == 0) { /* no filters which we support changed */ + kfree(fp); return; + } mutex_lock(&wl->mutex); @@ -716,6 +806,15 @@ static void wl1251_op_configure_filter(struct ieee80211_hw *hw, 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); @@ -723,6 +822,7 @@ static void wl1251_op_configure_filter(struct ieee80211_hw *hw, out: mutex_unlock(&wl->mutex); + kfree(fp); } /* HW encryption */ @@ -802,12 +902,12 @@ static int wl1251_op_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, 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: @@ -818,6 +918,10 @@ static int wl1251_op_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, 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"); @@ -930,6 +1034,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_idle; } @@ -977,6 +1082,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"); @@ -1023,6 +1129,9 @@ static void wl1251_op_bss_info_changed(struct ieee80211_hw *hw, } if (changed & BSS_CHANGED_ASSOC) { + /* Disable temporary enabled hw connection monitor flag */ + wl->hw->flags &= ~IEEE80211_HW_CONNECTION_MONITOR; + if (bss_conf->assoc) { wl->beacon_int = bss_conf->beacon_int; @@ -1075,6 +1184,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) @@ -1245,6 +1365,7 @@ static const struct ieee80211_ops wl1251_ops = { .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, @@ -1347,7 +1468,6 @@ int wl1251_init_ieee80211(struct wl1251 *wl) /* unit us */ /* FIXME: find a proper value */ - wl->hw->channel_change_time = 10000; wl->hw->flags = IEEE80211_HW_SIGNAL_DBM | IEEE80211_HW_SUPPORTS_PS | @@ -1401,7 +1521,10 @@ struct ieee80211_hw *wl1251_alloc_hw(void) 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; @@ -1413,6 +1536,7 @@ struct ieee80211_hw *wl1251_alloc_hw(void) wl->elp = false; wl->station_mode = STATION_ACTIVE_MODE; wl->psm_requested = false; + wl->psm_entry_retry = 0; wl->tx_queue_stopped = false; wl->power_level = WL1251_DEFAULT_POWER_LEVEL; wl->rssi_thold = 0; @@ -1478,3 +1602,4 @@ MODULE_DESCRIPTION("TI wl1251 Wireles LAN Driver Core"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Kalle Valo <kvalo@adurom.com>"); MODULE_FIRMWARE(WL1251_FW_NAME); +MODULE_FIRMWARE(WL1251_NVS_NAME); |