diff options
author | Stanislaw Gruszka <sgruszka@redhat.com> | 2011-11-15 12:18:51 (GMT) |
---|---|---|
committer | Stanislaw Gruszka <sgruszka@redhat.com> | 2011-11-15 12:18:51 (GMT) |
commit | 4ed47911a2e0f9a425d22253433452ffa59d533e (patch) | |
tree | f8b3c1a2a1f5c6300dd736047c5495ccd9115894 /drivers/net/wireless/iwlegacy/common.c | |
parent | d2dfb33ec9a8da257bb0e6ed2872af2fdf85260c (diff) | |
download | linux-fsl-qoriq-4ed47911a2e0f9a425d22253433452ffa59d533e.tar.xz |
iwlegacy: rename iwl-core.c to common.c
Signed-off-by: Stanislaw Gruszka <sgruszka@redhat.com>
Diffstat (limited to 'drivers/net/wireless/iwlegacy/common.c')
-rw-r--r-- | drivers/net/wireless/iwlegacy/common.c | 2608 |
1 files changed, 2608 insertions, 0 deletions
diff --git a/drivers/net/wireless/iwlegacy/common.c b/drivers/net/wireless/iwlegacy/common.c new file mode 100644 index 0000000..856a321 --- /dev/null +++ b/drivers/net/wireless/iwlegacy/common.c @@ -0,0 +1,2608 @@ +/****************************************************************************** + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * Contact Information: + * Intel Linux Wireless <ilw@linux.intel.com> + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + *****************************************************************************/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/etherdevice.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <net/mac80211.h> + +#include "iwl-eeprom.h" +#include "iwl-dev.h" +#include "iwl-debug.h" +#include "iwl-core.h" +#include "iwl-io.h" +#include "iwl-power.h" +#include "iwl-sta.h" +#include "iwl-helpers.h" + + +MODULE_DESCRIPTION("iwl-legacy: common functions for 3945 and 4965"); +MODULE_VERSION(IWLWIFI_VERSION); +MODULE_AUTHOR(DRV_COPYRIGHT " " DRV_AUTHOR); +MODULE_LICENSE("GPL"); + +/* + * set bt_coex_active to true, uCode will do kill/defer + * every time the priority line is asserted (BT is sending signals on the + * priority line in the PCIx). + * set bt_coex_active to false, uCode will ignore the BT activity and + * perform the normal operation + * + * User might experience transmit issue on some platform due to WiFi/BT + * co-exist problem. The possible behaviors are: + * Able to scan and finding all the available AP + * Not able to associate with any AP + * On those platforms, WiFi communication can be restored by set + * "bt_coex_active" module parameter to "false" + * + * default: bt_coex_active = true (BT_COEX_ENABLE) + */ +static bool bt_coex_active = true; +module_param(bt_coex_active, bool, S_IRUGO); +MODULE_PARM_DESC(bt_coex_active, "enable wifi/bluetooth co-exist"); + +u32 il_debug_level; +EXPORT_SYMBOL(il_debug_level); + +const u8 il_bcast_addr[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; +EXPORT_SYMBOL(il_bcast_addr); + + +/* This function both allocates and initializes hw and il. */ +struct ieee80211_hw *il_alloc_all(struct il_cfg *cfg) +{ + struct il_priv *il; + /* mac80211 allocates memory for this device instance, including + * space for this driver's ilate structure */ + struct ieee80211_hw *hw; + + hw = ieee80211_alloc_hw(sizeof(struct il_priv), + cfg->ops->ieee80211_ops); + if (hw == NULL) { + pr_err("%s: Can not allocate network device\n", + cfg->name); + goto out; + } + + il = hw->priv; + il->hw = hw; + +out: + return hw; +} +EXPORT_SYMBOL(il_alloc_all); + +#define MAX_BIT_RATE_40_MHZ 150 /* Mbps */ +#define MAX_BIT_RATE_20_MHZ 72 /* Mbps */ +static void il_init_ht_hw_capab(const struct il_priv *il, + struct ieee80211_sta_ht_cap *ht_info, + enum ieee80211_band band) +{ + u16 max_bit_rate = 0; + u8 rx_chains_num = il->hw_params.rx_chains_num; + u8 tx_chains_num = il->hw_params.tx_chains_num; + + ht_info->cap = 0; + memset(&ht_info->mcs, 0, sizeof(ht_info->mcs)); + + ht_info->ht_supported = true; + + ht_info->cap |= IEEE80211_HT_CAP_SGI_20; + max_bit_rate = MAX_BIT_RATE_20_MHZ; + if (il->hw_params.ht40_channel & BIT(band)) { + ht_info->cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + ht_info->cap |= IEEE80211_HT_CAP_SGI_40; + ht_info->mcs.rx_mask[4] = 0x01; + max_bit_rate = MAX_BIT_RATE_40_MHZ; + } + + if (il->cfg->mod_params->amsdu_size_8K) + ht_info->cap |= IEEE80211_HT_CAP_MAX_AMSDU; + + ht_info->ampdu_factor = CFG_HT_RX_AMPDU_FACTOR_DEF; + ht_info->ampdu_density = CFG_HT_MPDU_DENSITY_DEF; + + ht_info->mcs.rx_mask[0] = 0xFF; + if (rx_chains_num >= 2) + ht_info->mcs.rx_mask[1] = 0xFF; + if (rx_chains_num >= 3) + ht_info->mcs.rx_mask[2] = 0xFF; + + /* Highest supported Rx data rate */ + max_bit_rate *= rx_chains_num; + WARN_ON(max_bit_rate & ~IEEE80211_HT_MCS_RX_HIGHEST_MASK); + ht_info->mcs.rx_highest = cpu_to_le16(max_bit_rate); + + /* Tx MCS capabilities */ + ht_info->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; + if (tx_chains_num != rx_chains_num) { + ht_info->mcs.tx_params |= IEEE80211_HT_MCS_TX_RX_DIFF; + ht_info->mcs.tx_params |= ((tx_chains_num - 1) << + IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT); + } +} + +/** + * il_init_geos - Initialize mac80211's geo/channel info based from eeprom + */ +int il_init_geos(struct il_priv *il) +{ + struct il_channel_info *ch; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *channels; + struct ieee80211_channel *geo_ch; + struct ieee80211_rate *rates; + int i = 0; + s8 max_tx_power = 0; + + if (il->bands[IEEE80211_BAND_2GHZ].n_bitrates || + il->bands[IEEE80211_BAND_5GHZ].n_bitrates) { + D_INFO("Geography modes already initialized.\n"); + set_bit(S_GEO_CONFIGURED, &il->status); + return 0; + } + + channels = kzalloc(sizeof(struct ieee80211_channel) * + il->channel_count, GFP_KERNEL); + if (!channels) + return -ENOMEM; + + rates = kzalloc((sizeof(struct ieee80211_rate) * RATE_COUNT_LEGACY), + GFP_KERNEL); + if (!rates) { + kfree(channels); + return -ENOMEM; + } + + /* 5.2GHz channels start after the 2.4GHz channels */ + sband = &il->bands[IEEE80211_BAND_5GHZ]; + sband->channels = &channels[ARRAY_SIZE(il_eeprom_band_1)]; + /* just OFDM */ + sband->bitrates = &rates[IL_FIRST_OFDM_RATE]; + sband->n_bitrates = RATE_COUNT_LEGACY - IL_FIRST_OFDM_RATE; + + if (il->cfg->sku & IL_SKU_N) + il_init_ht_hw_capab(il, &sband->ht_cap, + IEEE80211_BAND_5GHZ); + + sband = &il->bands[IEEE80211_BAND_2GHZ]; + sband->channels = channels; + /* OFDM & CCK */ + sband->bitrates = rates; + sband->n_bitrates = RATE_COUNT_LEGACY; + + if (il->cfg->sku & IL_SKU_N) + il_init_ht_hw_capab(il, &sband->ht_cap, + IEEE80211_BAND_2GHZ); + + il->ieee_channels = channels; + il->ieee_rates = rates; + + for (i = 0; i < il->channel_count; i++) { + ch = &il->channel_info[i]; + + if (!il_is_channel_valid(ch)) + continue; + + sband = &il->bands[ch->band]; + + geo_ch = &sband->channels[sband->n_channels++]; + + geo_ch->center_freq = + ieee80211_channel_to_frequency(ch->channel, ch->band); + geo_ch->max_power = ch->max_power_avg; + geo_ch->max_antenna_gain = 0xff; + geo_ch->hw_value = ch->channel; + + if (il_is_channel_valid(ch)) { + if (!(ch->flags & EEPROM_CHANNEL_IBSS)) + geo_ch->flags |= IEEE80211_CHAN_NO_IBSS; + + if (!(ch->flags & EEPROM_CHANNEL_ACTIVE)) + geo_ch->flags |= IEEE80211_CHAN_PASSIVE_SCAN; + + if (ch->flags & EEPROM_CHANNEL_RADAR) + geo_ch->flags |= IEEE80211_CHAN_RADAR; + + geo_ch->flags |= ch->ht40_extension_channel; + + if (ch->max_power_avg > max_tx_power) + max_tx_power = ch->max_power_avg; + } else { + geo_ch->flags |= IEEE80211_CHAN_DISABLED; + } + + D_INFO("Channel %d Freq=%d[%sGHz] %s flag=0x%X\n", + ch->channel, geo_ch->center_freq, + il_is_channel_a_band(ch) ? "5.2" : "2.4", + geo_ch->flags & IEEE80211_CHAN_DISABLED ? + "restricted" : "valid", + geo_ch->flags); + } + + il->tx_power_device_lmt = max_tx_power; + il->tx_power_user_lmt = max_tx_power; + il->tx_power_next = max_tx_power; + + if (il->bands[IEEE80211_BAND_5GHZ].n_channels == 0 && + (il->cfg->sku & IL_SKU_A)) { + IL_INFO("Incorrectly detected BG card as ABG. " + "Please send your PCI ID 0x%04X:0x%04X to maintainer.\n", + il->pci_dev->device, + il->pci_dev->subsystem_device); + il->cfg->sku &= ~IL_SKU_A; + } + + IL_INFO("Tunable channels: %d 802.11bg, %d 802.11a channels\n", + il->bands[IEEE80211_BAND_2GHZ].n_channels, + il->bands[IEEE80211_BAND_5GHZ].n_channels); + + set_bit(S_GEO_CONFIGURED, &il->status); + + return 0; +} +EXPORT_SYMBOL(il_init_geos); + +/* + * il_free_geos - undo allocations in il_init_geos + */ +void il_free_geos(struct il_priv *il) +{ + kfree(il->ieee_channels); + kfree(il->ieee_rates); + clear_bit(S_GEO_CONFIGURED, &il->status); +} +EXPORT_SYMBOL(il_free_geos); + +static bool il_is_channel_extension(struct il_priv *il, + enum ieee80211_band band, + u16 channel, u8 extension_chan_offset) +{ + const struct il_channel_info *ch_info; + + ch_info = il_get_channel_info(il, band, channel); + if (!il_is_channel_valid(ch_info)) + return false; + + if (extension_chan_offset == IEEE80211_HT_PARAM_CHA_SEC_ABOVE) + return !(ch_info->ht40_extension_channel & + IEEE80211_CHAN_NO_HT40PLUS); + else if (extension_chan_offset == IEEE80211_HT_PARAM_CHA_SEC_BELOW) + return !(ch_info->ht40_extension_channel & + IEEE80211_CHAN_NO_HT40MINUS); + + return false; +} + +bool il_is_ht40_tx_allowed(struct il_priv *il, + struct il_rxon_context *ctx, + struct ieee80211_sta_ht_cap *ht_cap) +{ + if (!ctx->ht.enabled || !ctx->ht.is_40mhz) + return false; + + /* + * We do not check for IEEE80211_HT_CAP_SUP_WIDTH_20_40 + * the bit will not set if it is pure 40MHz case + */ + if (ht_cap && !ht_cap->ht_supported) + return false; + +#ifdef CONFIG_IWLEGACY_DEBUGFS + if (il->disable_ht40) + return false; +#endif + + return il_is_channel_extension(il, il->band, + le16_to_cpu(ctx->staging.channel), + ctx->ht.extension_chan_offset); +} +EXPORT_SYMBOL(il_is_ht40_tx_allowed); + +static u16 il_adjust_beacon_interval(u16 beacon_val, u16 max_beacon_val) +{ + u16 new_val; + u16 beacon_factor; + + /* + * If mac80211 hasn't given us a beacon interval, program + * the default into the device. + */ + if (!beacon_val) + return DEFAULT_BEACON_INTERVAL; + + /* + * If the beacon interval we obtained from the peer + * is too large, we'll have to wake up more often + * (and in IBSS case, we'll beacon too much) + * + * For example, if max_beacon_val is 4096, and the + * requested beacon interval is 7000, we'll have to + * use 3500 to be able to wake up on the beacons. + * + * This could badly influence beacon detection stats. + */ + + beacon_factor = (beacon_val + max_beacon_val) / max_beacon_val; + new_val = beacon_val / beacon_factor; + + if (!new_val) + new_val = max_beacon_val; + + return new_val; +} + +int +il_send_rxon_timing(struct il_priv *il, struct il_rxon_context *ctx) +{ + u64 tsf; + s32 interval_tm, rem; + struct ieee80211_conf *conf = NULL; + u16 beacon_int; + struct ieee80211_vif *vif = ctx->vif; + + conf = il_ieee80211_get_hw_conf(il->hw); + + lockdep_assert_held(&il->mutex); + + memset(&ctx->timing, 0, sizeof(struct il_rxon_time_cmd)); + + ctx->timing.timestamp = cpu_to_le64(il->timestamp); + ctx->timing.listen_interval = cpu_to_le16(conf->listen_interval); + + beacon_int = vif ? vif->bss_conf.beacon_int : 0; + + /* + * TODO: For IBSS we need to get atim_win from mac80211, + * for now just always use 0 + */ + ctx->timing.atim_win = 0; + + beacon_int = il_adjust_beacon_interval(beacon_int, + il->hw_params.max_beacon_itrvl * TIME_UNIT); + ctx->timing.beacon_interval = cpu_to_le16(beacon_int); + + tsf = il->timestamp; /* tsf is modifed by do_div: copy it */ + interval_tm = beacon_int * TIME_UNIT; + rem = do_div(tsf, interval_tm); + ctx->timing.beacon_init_val = cpu_to_le32(interval_tm - rem); + + ctx->timing.dtim_period = vif ? (vif->bss_conf.dtim_period ?: 1) : 1; + + D_ASSOC( + "beacon interval %d beacon timer %d beacon tim %d\n", + le16_to_cpu(ctx->timing.beacon_interval), + le32_to_cpu(ctx->timing.beacon_init_val), + le16_to_cpu(ctx->timing.atim_win)); + + return il_send_cmd_pdu(il, ctx->rxon_timing_cmd, + sizeof(ctx->timing), &ctx->timing); +} +EXPORT_SYMBOL(il_send_rxon_timing); + +void +il_set_rxon_hwcrypto(struct il_priv *il, + struct il_rxon_context *ctx, + int hw_decrypt) +{ + struct il_rxon_cmd *rxon = &ctx->staging; + + if (hw_decrypt) + rxon->filter_flags &= ~RXON_FILTER_DIS_DECRYPT_MSK; + else + rxon->filter_flags |= RXON_FILTER_DIS_DECRYPT_MSK; + +} +EXPORT_SYMBOL(il_set_rxon_hwcrypto); + +/* validate RXON structure is valid */ +int +il_check_rxon_cmd(struct il_priv *il, struct il_rxon_context *ctx) +{ + struct il_rxon_cmd *rxon = &ctx->staging; + bool error = false; + + if (rxon->flags & RXON_FLG_BAND_24G_MSK) { + if (rxon->flags & RXON_FLG_TGJ_NARROW_BAND_MSK) { + IL_WARN("check 2.4G: wrong narrow\n"); + error = true; + } + if (rxon->flags & RXON_FLG_RADAR_DETECT_MSK) { + IL_WARN("check 2.4G: wrong radar\n"); + error = true; + } + } else { + if (!(rxon->flags & RXON_FLG_SHORT_SLOT_MSK)) { + IL_WARN("check 5.2G: not short slot!\n"); + error = true; + } + if (rxon->flags & RXON_FLG_CCK_MSK) { + IL_WARN("check 5.2G: CCK!\n"); + error = true; + } + } + if ((rxon->node_addr[0] | rxon->bssid_addr[0]) & 0x1) { + IL_WARN("mac/bssid mcast!\n"); + error = true; + } + + /* make sure basic rates 6Mbps and 1Mbps are supported */ + if ((rxon->ofdm_basic_rates & RATE_6M_MASK) == 0 && + (rxon->cck_basic_rates & RATE_1M_MASK) == 0) { + IL_WARN("neither 1 nor 6 are basic\n"); + error = true; + } + + if (le16_to_cpu(rxon->assoc_id) > 2007) { + IL_WARN("aid > 2007\n"); + error = true; + } + + if ((rxon->flags & (RXON_FLG_CCK_MSK | RXON_FLG_SHORT_SLOT_MSK)) + == (RXON_FLG_CCK_MSK | RXON_FLG_SHORT_SLOT_MSK)) { + IL_WARN("CCK and short slot\n"); + error = true; + } + + if ((rxon->flags & (RXON_FLG_CCK_MSK | RXON_FLG_AUTO_DETECT_MSK)) + == (RXON_FLG_CCK_MSK | RXON_FLG_AUTO_DETECT_MSK)) { + IL_WARN("CCK and auto detect"); + error = true; + } + + if ((rxon->flags & (RXON_FLG_AUTO_DETECT_MSK | + RXON_FLG_TGG_PROTECT_MSK)) == + RXON_FLG_TGG_PROTECT_MSK) { + IL_WARN("TGg but no auto-detect\n"); + error = true; + } + + if (error) + IL_WARN("Tuning to channel %d\n", + le16_to_cpu(rxon->channel)); + + if (error) { + IL_ERR("Invalid RXON\n"); + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL(il_check_rxon_cmd); + +/** + * il_full_rxon_required - check if full RXON (vs RXON_ASSOC) cmd is needed + * @il: staging_rxon is compared to active_rxon + * + * If the RXON structure is changing enough to require a new tune, + * or is clearing the RXON_FILTER_ASSOC_MSK, then return 1 to indicate that + * a new tune (full RXON command, rather than RXON_ASSOC cmd) is required. + */ +int il_full_rxon_required(struct il_priv *il, + struct il_rxon_context *ctx) +{ + const struct il_rxon_cmd *staging = &ctx->staging; + const struct il_rxon_cmd *active = &ctx->active; + +#define CHK(cond) \ + if ((cond)) { \ + D_INFO("need full RXON - " #cond "\n"); \ + return 1; \ + } + +#define CHK_NEQ(c1, c2) \ + if ((c1) != (c2)) { \ + D_INFO("need full RXON - " \ + #c1 " != " #c2 " - %d != %d\n", \ + (c1), (c2)); \ + return 1; \ + } + + /* These items are only settable from the full RXON command */ + CHK(!il_is_associated_ctx(ctx)); + CHK(compare_ether_addr(staging->bssid_addr, active->bssid_addr)); + CHK(compare_ether_addr(staging->node_addr, active->node_addr)); + CHK(compare_ether_addr(staging->wlap_bssid_addr, + active->wlap_bssid_addr)); + CHK_NEQ(staging->dev_type, active->dev_type); + CHK_NEQ(staging->channel, active->channel); + CHK_NEQ(staging->air_propagation, active->air_propagation); + CHK_NEQ(staging->ofdm_ht_single_stream_basic_rates, + active->ofdm_ht_single_stream_basic_rates); + CHK_NEQ(staging->ofdm_ht_dual_stream_basic_rates, + active->ofdm_ht_dual_stream_basic_rates); + CHK_NEQ(staging->assoc_id, active->assoc_id); + + /* flags, filter_flags, ofdm_basic_rates, and cck_basic_rates can + * be updated with the RXON_ASSOC command -- however only some + * flag transitions are allowed using RXON_ASSOC */ + + /* Check if we are not switching bands */ + CHK_NEQ(staging->flags & RXON_FLG_BAND_24G_MSK, + active->flags & RXON_FLG_BAND_24G_MSK); + + /* Check if we are switching association toggle */ + CHK_NEQ(staging->filter_flags & RXON_FILTER_ASSOC_MSK, + active->filter_flags & RXON_FILTER_ASSOC_MSK); + +#undef CHK +#undef CHK_NEQ + + return 0; +} +EXPORT_SYMBOL(il_full_rxon_required); + +u8 il_get_lowest_plcp(struct il_priv *il, + struct il_rxon_context *ctx) +{ + /* + * Assign the lowest rate -- should really get this from + * the beacon skb from mac80211. + */ + if (ctx->staging.flags & RXON_FLG_BAND_24G_MSK) + return RATE_1M_PLCP; + else + return RATE_6M_PLCP; +} +EXPORT_SYMBOL(il_get_lowest_plcp); + +static void _il_set_rxon_ht(struct il_priv *il, + struct il_ht_config *ht_conf, + struct il_rxon_context *ctx) +{ + struct il_rxon_cmd *rxon = &ctx->staging; + + if (!ctx->ht.enabled) { + rxon->flags &= ~(RXON_FLG_CHANNEL_MODE_MSK | + RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK | + RXON_FLG_HT40_PROT_MSK | + RXON_FLG_HT_PROT_MSK); + return; + } + + rxon->flags |= cpu_to_le32(ctx->ht.protection << + RXON_FLG_HT_OPERATING_MODE_POS); + + /* Set up channel bandwidth: + * 20 MHz only, 20/40 mixed or pure 40 if ht40 ok */ + /* clear the HT channel mode before set the mode */ + rxon->flags &= ~(RXON_FLG_CHANNEL_MODE_MSK | + RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK); + if (il_is_ht40_tx_allowed(il, ctx, NULL)) { + /* pure ht40 */ + if (ctx->ht.protection == + IEEE80211_HT_OP_MODE_PROTECTION_20MHZ) { + rxon->flags |= RXON_FLG_CHANNEL_MODE_PURE_40; + /* Note: control channel is opposite of extension channel */ + switch (ctx->ht.extension_chan_offset) { + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: + rxon->flags &= + ~RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK; + break; + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: + rxon->flags |= + RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK; + break; + } + } else { + /* Note: control channel is opposite of extension channel */ + switch (ctx->ht.extension_chan_offset) { + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: + rxon->flags &= + ~(RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK); + rxon->flags |= RXON_FLG_CHANNEL_MODE_MIXED; + break; + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: + rxon->flags |= + RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK; + rxon->flags |= RXON_FLG_CHANNEL_MODE_MIXED; + break; + case IEEE80211_HT_PARAM_CHA_SEC_NONE: + default: + /* channel location only valid if in Mixed mode */ + IL_ERR( + "invalid extension channel offset\n"); + break; + } + } + } else { + rxon->flags |= RXON_FLG_CHANNEL_MODE_LEGACY; + } + + if (il->cfg->ops->hcmd->set_rxon_chain) + il->cfg->ops->hcmd->set_rxon_chain(il, ctx); + + D_ASSOC("rxon flags 0x%X operation mode :0x%X " + "extension channel offset 0x%x\n", + le32_to_cpu(rxon->flags), ctx->ht.protection, + ctx->ht.extension_chan_offset); +} + +void il_set_rxon_ht(struct il_priv *il, struct il_ht_config *ht_conf) +{ + _il_set_rxon_ht(il, ht_conf, &il->ctx); +} +EXPORT_SYMBOL(il_set_rxon_ht); + +/* Return valid, unused, channel for a passive scan to reset the RF */ +u8 il_get_single_channel_number(struct il_priv *il, + enum ieee80211_band band) +{ + const struct il_channel_info *ch_info; + int i; + u8 channel = 0; + u8 min, max; + + if (band == IEEE80211_BAND_5GHZ) { + min = 14; + max = il->channel_count; + } else { + min = 0; + max = 14; + } + + for (i = min; i < max; i++) { + channel = il->channel_info[i].channel; + if (channel == le16_to_cpu(il->ctx.staging.channel)) + continue; + + ch_info = il_get_channel_info(il, band, channel); + if (il_is_channel_valid(ch_info)) + break; + } + + return channel; +} +EXPORT_SYMBOL(il_get_single_channel_number); + +/** + * il_set_rxon_channel - Set the band and channel values in staging RXON + * @ch: requested channel as a pointer to struct ieee80211_channel + + * NOTE: Does not commit to the hardware; it sets appropriate bit fields + * in the staging RXON flag structure based on the ch->band + */ +int +il_set_rxon_channel(struct il_priv *il, struct ieee80211_channel *ch, + struct il_rxon_context *ctx) +{ + enum ieee80211_band band = ch->band; + u16 channel = ch->hw_value; + + if (le16_to_cpu(ctx->staging.channel) == channel && il->band == band) + return 0; + + ctx->staging.channel = cpu_to_le16(channel); + if (band == IEEE80211_BAND_5GHZ) + ctx->staging.flags &= ~RXON_FLG_BAND_24G_MSK; + else + ctx->staging.flags |= RXON_FLG_BAND_24G_MSK; + + il->band = band; + + D_INFO("Staging channel set to %d [%d]\n", channel, band); + + return 0; +} +EXPORT_SYMBOL(il_set_rxon_channel); + +void il_set_flags_for_band(struct il_priv *il, + struct il_rxon_context *ctx, + enum ieee80211_band band, + struct ieee80211_vif *vif) +{ + if (band == IEEE80211_BAND_5GHZ) { + ctx->staging.flags &= + ~(RXON_FLG_BAND_24G_MSK | RXON_FLG_AUTO_DETECT_MSK + | RXON_FLG_CCK_MSK); + ctx->staging.flags |= RXON_FLG_SHORT_SLOT_MSK; + } else { + /* Copied from il_post_associate() */ + if (vif && vif->bss_conf.use_short_slot) + ctx->staging.flags |= RXON_FLG_SHORT_SLOT_MSK; + else + ctx->staging.flags &= ~RXON_FLG_SHORT_SLOT_MSK; + + ctx->staging.flags |= RXON_FLG_BAND_24G_MSK; + ctx->staging.flags |= RXON_FLG_AUTO_DETECT_MSK; + ctx->staging.flags &= ~RXON_FLG_CCK_MSK; + } +} +EXPORT_SYMBOL(il_set_flags_for_band); + +/* + * initialize rxon structure with default values from eeprom + */ +void il_connection_init_rx_config(struct il_priv *il, + struct il_rxon_context *ctx) +{ + const struct il_channel_info *ch_info; + + memset(&ctx->staging, 0, sizeof(ctx->staging)); + + if (!ctx->vif) { + ctx->staging.dev_type = ctx->unused_devtype; + } else + switch (ctx->vif->type) { + + case NL80211_IFTYPE_STATION: + ctx->staging.dev_type = ctx->station_devtype; + ctx->staging.filter_flags = RXON_FILTER_ACCEPT_GRP_MSK; + break; + + case NL80211_IFTYPE_ADHOC: + ctx->staging.dev_type = ctx->ibss_devtype; + ctx->staging.flags = RXON_FLG_SHORT_PREAMBLE_MSK; + ctx->staging.filter_flags = RXON_FILTER_BCON_AWARE_MSK | + RXON_FILTER_ACCEPT_GRP_MSK; + break; + + default: + IL_ERR("Unsupported interface type %d\n", + ctx->vif->type); + break; + } + +#if 0 + /* TODO: Figure out when short_preamble would be set and cache from + * that */ + if (!hw_to_local(il->hw)->short_preamble) + ctx->staging.flags &= ~RXON_FLG_SHORT_PREAMBLE_MSK; + else + ctx->staging.flags |= RXON_FLG_SHORT_PREAMBLE_MSK; +#endif + + ch_info = il_get_channel_info(il, il->band, + le16_to_cpu(ctx->active.channel)); + + if (!ch_info) + ch_info = &il->channel_info[0]; + + ctx->staging.channel = cpu_to_le16(ch_info->channel); + il->band = ch_info->band; + + il_set_flags_for_band(il, ctx, il->band, ctx->vif); + + ctx->staging.ofdm_basic_rates = + (IL_OFDM_RATES_MASK >> IL_FIRST_OFDM_RATE) & 0xFF; + ctx->staging.cck_basic_rates = + (IL_CCK_RATES_MASK >> IL_FIRST_CCK_RATE) & 0xF; + + /* clear both MIX and PURE40 mode flag */ + ctx->staging.flags &= ~(RXON_FLG_CHANNEL_MODE_MIXED | + RXON_FLG_CHANNEL_MODE_PURE_40); + if (ctx->vif) + memcpy(ctx->staging.node_addr, ctx->vif->addr, ETH_ALEN); + + ctx->staging.ofdm_ht_single_stream_basic_rates = 0xff; + ctx->staging.ofdm_ht_dual_stream_basic_rates = 0xff; +} +EXPORT_SYMBOL(il_connection_init_rx_config); + +void il_set_rate(struct il_priv *il) +{ + const struct ieee80211_supported_band *hw = NULL; + struct ieee80211_rate *rate; + int i; + + hw = il_get_hw_mode(il, il->band); + if (!hw) { + IL_ERR("Failed to set rate: unable to get hw mode\n"); + return; + } + + il->active_rate = 0; + + for (i = 0; i < hw->n_bitrates; i++) { + rate = &(hw->bitrates[i]); + if (rate->hw_value < RATE_COUNT_LEGACY) + il->active_rate |= (1 << rate->hw_value); + } + + D_RATE("Set active_rate = %0x\n", il->active_rate); + + il->ctx.staging.cck_basic_rates = + (IL_CCK_BASIC_RATES_MASK >> IL_FIRST_CCK_RATE) & 0xF; + + il->ctx.staging.ofdm_basic_rates = + (IL_OFDM_BASIC_RATES_MASK >> IL_FIRST_OFDM_RATE) & 0xFF; +} +EXPORT_SYMBOL(il_set_rate); + +void il_chswitch_done(struct il_priv *il, bool is_success) +{ + struct il_rxon_context *ctx = &il->ctx; + + if (test_bit(S_EXIT_PENDING, &il->status)) + return; + + if (test_and_clear_bit(S_CHANNEL_SWITCH_PENDING, &il->status)) + ieee80211_chswitch_done(ctx->vif, is_success); +} +EXPORT_SYMBOL(il_chswitch_done); + +void il_hdl_csa(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il_csa_notification *csa = &(pkt->u.csa_notif); + + struct il_rxon_context *ctx = &il->ctx; + struct il_rxon_cmd *rxon = (void *)&ctx->active; + + if (!test_bit(S_CHANNEL_SWITCH_PENDING, &il->status)) + return; + + if (!le32_to_cpu(csa->status) && csa->channel == il->switch_channel) { + rxon->channel = csa->channel; + ctx->staging.channel = csa->channel; + D_11H("CSA notif: channel %d\n", + le16_to_cpu(csa->channel)); + il_chswitch_done(il, true); + } else { + IL_ERR("CSA notif (fail) : channel %d\n", + le16_to_cpu(csa->channel)); + il_chswitch_done(il, false); + } +} +EXPORT_SYMBOL(il_hdl_csa); + +#ifdef CONFIG_IWLEGACY_DEBUG +void il_print_rx_config_cmd(struct il_priv *il, + struct il_rxon_context *ctx) +{ + struct il_rxon_cmd *rxon = &ctx->staging; + + D_RADIO("RX CONFIG:\n"); + il_print_hex_dump(il, IL_DL_RADIO, (u8 *) rxon, sizeof(*rxon)); + D_RADIO("u16 channel: 0x%x\n", + le16_to_cpu(rxon->channel)); + D_RADIO("u32 flags: 0x%08X\n", le32_to_cpu(rxon->flags)); + D_RADIO("u32 filter_flags: 0x%08x\n", + le32_to_cpu(rxon->filter_flags)); + D_RADIO("u8 dev_type: 0x%x\n", rxon->dev_type); + D_RADIO("u8 ofdm_basic_rates: 0x%02x\n", + rxon->ofdm_basic_rates); + D_RADIO("u8 cck_basic_rates: 0x%02x\n", + rxon->cck_basic_rates); + D_RADIO("u8[6] node_addr: %pM\n", rxon->node_addr); + D_RADIO("u8[6] bssid_addr: %pM\n", rxon->bssid_addr); + D_RADIO("u16 assoc_id: 0x%x\n", + le16_to_cpu(rxon->assoc_id)); +} +EXPORT_SYMBOL(il_print_rx_config_cmd); +#endif +/** + * il_irq_handle_error - called for HW or SW error interrupt from card + */ +void il_irq_handle_error(struct il_priv *il) +{ + /* Set the FW error flag -- cleared on il_down */ + set_bit(S_FW_ERROR, &il->status); + + /* Cancel currently queued command. */ + clear_bit(S_HCMD_ACTIVE, &il->status); + + IL_ERR("Loaded firmware version: %s\n", + il->hw->wiphy->fw_version); + + il->cfg->ops->lib->dump_nic_error_log(il); + if (il->cfg->ops->lib->dump_fh) + il->cfg->ops->lib->dump_fh(il, NULL, false); +#ifdef CONFIG_IWLEGACY_DEBUG + if (il_get_debug_level(il) & IL_DL_FW_ERRORS) + il_print_rx_config_cmd(il, + &il->ctx); +#endif + + wake_up(&il->wait_command_queue); + + /* Keep the restart process from trying to send host + * commands by clearing the INIT status bit */ + clear_bit(S_READY, &il->status); + + if (!test_bit(S_EXIT_PENDING, &il->status)) { + IL_DBG(IL_DL_FW_ERRORS, + "Restarting adapter due to uCode error.\n"); + + if (il->cfg->mod_params->restart_fw) + queue_work(il->workqueue, &il->restart); + } +} +EXPORT_SYMBOL(il_irq_handle_error); + +static int il_apm_stop_master(struct il_priv *il) +{ + int ret = 0; + + /* stop device's busmaster DMA activity */ + il_set_bit(il, CSR_RESET, CSR_RESET_REG_FLAG_STOP_MASTER); + + ret = _il_poll_bit(il, CSR_RESET, CSR_RESET_REG_FLAG_MASTER_DISABLED, + CSR_RESET_REG_FLAG_MASTER_DISABLED, 100); + if (ret) + IL_WARN("Master Disable Timed Out, 100 usec\n"); + + D_INFO("stop master\n"); + + return ret; +} + +void il_apm_stop(struct il_priv *il) +{ + D_INFO("Stop card, put in low power state\n"); + + /* Stop device's DMA activity */ + il_apm_stop_master(il); + + /* Reset the entire device */ + il_set_bit(il, CSR_RESET, CSR_RESET_REG_FLAG_SW_RESET); + + udelay(10); + + /* + * Clear "initialization complete" bit to move adapter from + * D0A* (powered-up Active) --> D0U* (Uninitialized) state. + */ + il_clear_bit(il, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_INIT_DONE); +} +EXPORT_SYMBOL(il_apm_stop); + + +/* + * Start up NIC's basic functionality after it has been reset + * (e.g. after platform boot, or shutdown via il_apm_stop()) + * NOTE: This does not load uCode nor start the embedded processor + */ +int il_apm_init(struct il_priv *il) +{ + int ret = 0; + u16 lctl; + + D_INFO("Init card's basic functions\n"); + + /* + * Use "set_bit" below rather than "write", to preserve any hardware + * bits already set by default after reset. + */ + + /* Disable L0S exit timer (platform NMI Work/Around) */ + il_set_bit(il, CSR_GIO_CHICKEN_BITS, + CSR_GIO_CHICKEN_BITS_REG_BIT_DIS_L0S_EXIT_TIMER); + + /* + * Disable L0s without affecting L1; + * don't wait for ICH L0s (ICH bug W/A) + */ + il_set_bit(il, CSR_GIO_CHICKEN_BITS, + CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX); + + /* Set FH wait threshold to maximum (HW error during stress W/A) */ + il_set_bit(il, CSR_DBG_HPET_MEM_REG, + CSR_DBG_HPET_MEM_REG_VAL); + + /* + * Enable HAP INTA (interrupt from management bus) to + * wake device's PCI Express link L1a -> L0s + * NOTE: This is no-op for 3945 (non-existent bit) + */ + il_set_bit(il, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_HAP_WAKE_L1A); + + /* + * HW bug W/A for instability in PCIe bus L0->L0S->L1 transition. + * Check if BIOS (or OS) enabled L1-ASPM on this device. + * If so (likely), disable L0S, so device moves directly L0->L1; + * costs negligible amount of power savings. + * If not (unlikely), enable L0S, so there is at least some + * power savings, even without L1. + */ + if (il->cfg->base_params->set_l0s) { + lctl = il_pcie_link_ctl(il); + if ((lctl & PCI_CFG_LINK_CTRL_VAL_L1_EN) == + PCI_CFG_LINK_CTRL_VAL_L1_EN) { + /* L1-ASPM enabled; disable(!) L0S */ + il_set_bit(il, CSR_GIO_REG, + CSR_GIO_REG_VAL_L0S_ENABLED); + D_POWER("L1 Enabled; Disabling L0S\n"); + } else { + /* L1-ASPM disabled; enable(!) L0S */ + il_clear_bit(il, CSR_GIO_REG, + CSR_GIO_REG_VAL_L0S_ENABLED); + D_POWER("L1 Disabled; Enabling L0S\n"); + } + } + + /* Configure analog phase-lock-loop before activating to D0A */ + if (il->cfg->base_params->pll_cfg_val) + il_set_bit(il, CSR_ANA_PLL_CFG, + il->cfg->base_params->pll_cfg_val); + + /* + * Set "initialization complete" bit to move adapter from + * D0U* --> D0A* (powered-up active) state. + */ + il_set_bit(il, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE); + + /* + * Wait for clock stabilization; once stabilized, access to + * device-internal resources is supported, e.g. il_wr_prph() + * and accesses to uCode SRAM. + */ + ret = _il_poll_bit(il, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, + CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, 25000); + if (ret < 0) { + D_INFO("Failed to init the card\n"); + goto out; + } + + /* + * Enable DMA and BSM (if used) clocks, wait for them to stabilize. + * BSM (Boostrap State Machine) is only in 3945 and 4965. + * + * Write to "CLK_EN_REG"; "1" bits enable clocks, while "0" bits + * do not disable clocks. This preserves any hardware bits already + * set by default in "CLK_CTRL_REG" after reset. + */ + if (il->cfg->base_params->use_bsm) + il_wr_prph(il, APMG_CLK_EN_REG, + APMG_CLK_VAL_DMA_CLK_RQT | APMG_CLK_VAL_BSM_CLK_RQT); + else + il_wr_prph(il, APMG_CLK_EN_REG, + APMG_CLK_VAL_DMA_CLK_RQT); + udelay(20); + + /* Disable L1-Active */ + il_set_bits_prph(il, APMG_PCIDEV_STT_REG, + APMG_PCIDEV_STT_VAL_L1_ACT_DIS); + +out: + return ret; +} +EXPORT_SYMBOL(il_apm_init); + + +int il_set_tx_power(struct il_priv *il, s8 tx_power, bool force) +{ + int ret; + s8 prev_tx_power; + bool defer; + struct il_rxon_context *ctx = &il->ctx; + + lockdep_assert_held(&il->mutex); + + if (il->tx_power_user_lmt == tx_power && !force) + return 0; + + if (!il->cfg->ops->lib->send_tx_power) + return -EOPNOTSUPP; + + /* 0 dBm mean 1 milliwatt */ + if (tx_power < 0) { + IL_WARN( + "Requested user TXPOWER %d below 1 mW.\n", + tx_power); + return -EINVAL; + } + + if (tx_power > il->tx_power_device_lmt) { + IL_WARN( + "Requested user TXPOWER %d above upper limit %d.\n", + tx_power, il->tx_power_device_lmt); + return -EINVAL; + } + + if (!il_is_ready_rf(il)) + return -EIO; + + /* scan complete and commit_rxon use tx_power_next value, + * it always need to be updated for newest request */ + il->tx_power_next = tx_power; + + /* do not set tx power when scanning or channel changing */ + defer = test_bit(S_SCANNING, &il->status) || + memcmp(&ctx->active, &ctx->staging, sizeof(ctx->staging)); + if (defer && !force) { + D_INFO("Deferring tx power set\n"); + return 0; + } + + prev_tx_power = il->tx_power_user_lmt; + il->tx_power_user_lmt = tx_power; + + ret = il->cfg->ops->lib->send_tx_power(il); + + /* if fail to set tx_power, restore the orig. tx power */ + if (ret) { + il->tx_power_user_lmt = prev_tx_power; + il->tx_power_next = prev_tx_power; + } + return ret; +} +EXPORT_SYMBOL(il_set_tx_power); + +void il_send_bt_config(struct il_priv *il) +{ + struct il_bt_cmd bt_cmd = { + .lead_time = BT_LEAD_TIME_DEF, + .max_kill = BT_MAX_KILL_DEF, + .kill_ack_mask = 0, + .kill_cts_mask = 0, + }; + + if (!bt_coex_active) + bt_cmd.flags = BT_COEX_DISABLE; + else + bt_cmd.flags = BT_COEX_ENABLE; + + D_INFO("BT coex %s\n", + (bt_cmd.flags == BT_COEX_DISABLE) ? "disable" : "active"); + + if (il_send_cmd_pdu(il, C_BT_CONFIG, + sizeof(struct il_bt_cmd), &bt_cmd)) + IL_ERR("failed to send BT Coex Config\n"); +} +EXPORT_SYMBOL(il_send_bt_config); + +int il_send_stats_request(struct il_priv *il, u8 flags, bool clear) +{ + struct il_stats_cmd stats_cmd = { + .configuration_flags = + clear ? IL_STATS_CONF_CLEAR_STATS : 0, + }; + + if (flags & CMD_ASYNC) + return il_send_cmd_pdu_async(il, C_STATS, + sizeof(struct il_stats_cmd), + &stats_cmd, NULL); + else + return il_send_cmd_pdu(il, C_STATS, + sizeof(struct il_stats_cmd), + &stats_cmd); +} +EXPORT_SYMBOL(il_send_stats_request); + +void il_hdl_pm_sleep(struct il_priv *il, + struct il_rx_buf *rxb) +{ +#ifdef CONFIG_IWLEGACY_DEBUG + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il_sleep_notification *sleep = &(pkt->u.sleep_notif); + D_RX("sleep mode: %d, src: %d\n", + sleep->pm_sleep_mode, sleep->pm_wakeup_src); +#endif +} +EXPORT_SYMBOL(il_hdl_pm_sleep); + +void il_hdl_pm_debug_stats(struct il_priv *il, + struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + u32 len = le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; + D_RADIO("Dumping %d bytes of unhandled " + "notification for %s:\n", len, + il_get_cmd_string(pkt->hdr.cmd)); + il_print_hex_dump(il, IL_DL_RADIO, pkt->u.raw, len); +} +EXPORT_SYMBOL(il_hdl_pm_debug_stats); + +void il_hdl_error(struct il_priv *il, + struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + + IL_ERR("Error Reply type 0x%08X cmd %s (0x%02X) " + "seq 0x%04X ser 0x%08X\n", + le32_to_cpu(pkt->u.err_resp.error_type), + il_get_cmd_string(pkt->u.err_resp.cmd_id), + pkt->u.err_resp.cmd_id, + le16_to_cpu(pkt->u.err_resp.bad_cmd_seq_num), + le32_to_cpu(pkt->u.err_resp.error_info)); +} +EXPORT_SYMBOL(il_hdl_error); + +void il_clear_isr_stats(struct il_priv *il) +{ + memset(&il->isr_stats, 0, sizeof(il->isr_stats)); +} + +int il_mac_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct il_priv *il = hw->priv; + unsigned long flags; + int q; + + D_MAC80211("enter\n"); + + if (!il_is_ready_rf(il)) { + D_MAC80211("leave - RF not ready\n"); + return -EIO; + } + + if (queue >= AC_NUM) { + D_MAC80211("leave - queue >= AC_NUM %d\n", queue); + return 0; + } + + q = AC_NUM - 1 - queue; + + spin_lock_irqsave(&il->lock, flags); + + il->ctx.qos_data.def_qos_parm.ac[q].cw_min = + cpu_to_le16(params->cw_min); + il->ctx.qos_data.def_qos_parm.ac[q].cw_max = + cpu_to_le16(params->cw_max); + il->ctx.qos_data.def_qos_parm.ac[q].aifsn = params->aifs; + il->ctx.qos_data.def_qos_parm.ac[q].edca_txop = + cpu_to_le16((params->txop * 32)); + + il->ctx.qos_data.def_qos_parm.ac[q].reserved1 = 0; + + spin_unlock_irqrestore(&il->lock, flags); + + D_MAC80211("leave\n"); + return 0; +} +EXPORT_SYMBOL(il_mac_conf_tx); + +int il_mac_tx_last_beacon(struct ieee80211_hw *hw) +{ + struct il_priv *il = hw->priv; + + return il->ibss_manager == IL_IBSS_MANAGER; +} +EXPORT_SYMBOL_GPL(il_mac_tx_last_beacon); + +static int +il_set_mode(struct il_priv *il, struct il_rxon_context *ctx) +{ + il_connection_init_rx_config(il, ctx); + + if (il->cfg->ops->hcmd->set_rxon_chain) + il->cfg->ops->hcmd->set_rxon_chain(il, ctx); + + return il_commit_rxon(il, ctx); +} + +static int il_setup_interface(struct il_priv *il, + struct il_rxon_context *ctx) +{ + struct ieee80211_vif *vif = ctx->vif; + int err; + + lockdep_assert_held(&il->mutex); + + /* + * This variable will be correct only when there's just + * a single context, but all code using it is for hardware + * that supports only one context. + */ + il->iw_mode = vif->type; + + ctx->is_active = true; + + err = il_set_mode(il, ctx); + if (err) { + if (!ctx->always_active) + ctx->is_active = false; + return err; + } + + return 0; +} + +int +il_mac_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct il_priv *il = hw->priv; + struct il_vif_priv *vif_priv = (void *)vif->drv_priv; + int err; + u32 modes; + + D_MAC80211("enter: type %d, addr %pM\n", + vif->type, vif->addr); + + mutex_lock(&il->mutex); + + if (!il_is_ready_rf(il)) { + IL_WARN("Try to add interface when device not ready\n"); + err = -EINVAL; + goto out; + } + + + /* check if busy context is exclusive */ + if (il->ctx.vif && + (il->ctx.exclusive_interface_modes & BIT(il->ctx.vif->type))) { + err = -EINVAL; + goto out; + } + + modes = il->ctx.interface_modes | il->ctx.exclusive_interface_modes; + if (!(modes & BIT(vif->type))) { + err = -EOPNOTSUPP; + goto out; + } + + vif_priv->ctx = &il->ctx; + il->ctx.vif = vif; + + err = il_setup_interface(il, &il->ctx); + if (err) { + il->ctx.vif = NULL; + il->iw_mode = NL80211_IFTYPE_STATION; + } + + out: + mutex_unlock(&il->mutex); + + D_MAC80211("leave\n"); + return err; +} +EXPORT_SYMBOL(il_mac_add_interface); + +static void il_teardown_interface(struct il_priv *il, + struct ieee80211_vif *vif, + bool mode_change) +{ + struct il_rxon_context *ctx = il_rxon_ctx_from_vif(vif); + + lockdep_assert_held(&il->mutex); + + if (il->scan_vif == vif) { + il_scan_cancel_timeout(il, 200); + il_force_scan_end(il); + } + + if (!mode_change) { + il_set_mode(il, ctx); + if (!ctx->always_active) + ctx->is_active = false; + } +} + +void il_mac_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct il_priv *il = hw->priv; + struct il_rxon_context *ctx = il_rxon_ctx_from_vif(vif); + + D_MAC80211("enter\n"); + + mutex_lock(&il->mutex); + + WARN_ON(ctx->vif != vif); + ctx->vif = NULL; + + il_teardown_interface(il, vif, false); + + memset(il->bssid, 0, ETH_ALEN); + mutex_unlock(&il->mutex); + + D_MAC80211("leave\n"); + +} +EXPORT_SYMBOL(il_mac_remove_interface); + +int il_alloc_txq_mem(struct il_priv *il) +{ + if (!il->txq) + il->txq = kzalloc( + sizeof(struct il_tx_queue) * + il->cfg->base_params->num_of_queues, + GFP_KERNEL); + if (!il->txq) { + IL_ERR("Not enough memory for txq\n"); + return -ENOMEM; + } + return 0; +} +EXPORT_SYMBOL(il_alloc_txq_mem); + +void il_txq_mem(struct il_priv *il) +{ + kfree(il->txq); + il->txq = NULL; +} +EXPORT_SYMBOL(il_txq_mem); + +#ifdef CONFIG_IWLEGACY_DEBUGFS + +#define IL_TRAFFIC_DUMP_SIZE (IL_TRAFFIC_ENTRY_SIZE * IL_TRAFFIC_ENTRIES) + +void il_reset_traffic_log(struct il_priv *il) +{ + il->tx_traffic_idx = 0; + il->rx_traffic_idx = 0; + if (il->tx_traffic) + memset(il->tx_traffic, 0, IL_TRAFFIC_DUMP_SIZE); + if (il->rx_traffic) + memset(il->rx_traffic, 0, IL_TRAFFIC_DUMP_SIZE); +} + +int il_alloc_traffic_mem(struct il_priv *il) +{ + u32 traffic_size = IL_TRAFFIC_DUMP_SIZE; + + if (il_debug_level & IL_DL_TX) { + if (!il->tx_traffic) { + il->tx_traffic = + kzalloc(traffic_size, GFP_KERNEL); + if (!il->tx_traffic) + return -ENOMEM; + } + } + if (il_debug_level & IL_DL_RX) { + if (!il->rx_traffic) { + il->rx_traffic = + kzalloc(traffic_size, GFP_KERNEL); + if (!il->rx_traffic) + return -ENOMEM; + } + } + il_reset_traffic_log(il); + return 0; +} +EXPORT_SYMBOL(il_alloc_traffic_mem); + +void il_free_traffic_mem(struct il_priv *il) +{ + kfree(il->tx_traffic); + il->tx_traffic = NULL; + + kfree(il->rx_traffic); + il->rx_traffic = NULL; +} +EXPORT_SYMBOL(il_free_traffic_mem); + +void il_dbg_log_tx_data_frame(struct il_priv *il, + u16 length, struct ieee80211_hdr *header) +{ + __le16 fc; + u16 len; + + if (likely(!(il_debug_level & IL_DL_TX))) + return; + + if (!il->tx_traffic) + return; + + fc = header->frame_control; + if (ieee80211_is_data(fc)) { + len = (length > IL_TRAFFIC_ENTRY_SIZE) + ? IL_TRAFFIC_ENTRY_SIZE : length; + memcpy((il->tx_traffic + + (il->tx_traffic_idx * IL_TRAFFIC_ENTRY_SIZE)), + header, len); + il->tx_traffic_idx = + (il->tx_traffic_idx + 1) % IL_TRAFFIC_ENTRIES; + } +} +EXPORT_SYMBOL(il_dbg_log_tx_data_frame); + +void il_dbg_log_rx_data_frame(struct il_priv *il, + u16 length, struct ieee80211_hdr *header) +{ + __le16 fc; + u16 len; + + if (likely(!(il_debug_level & IL_DL_RX))) + return; + + if (!il->rx_traffic) + return; + + fc = header->frame_control; + if (ieee80211_is_data(fc)) { + len = (length > IL_TRAFFIC_ENTRY_SIZE) + ? IL_TRAFFIC_ENTRY_SIZE : length; + memcpy((il->rx_traffic + + (il->rx_traffic_idx * IL_TRAFFIC_ENTRY_SIZE)), + header, len); + il->rx_traffic_idx = + (il->rx_traffic_idx + 1) % IL_TRAFFIC_ENTRIES; + } +} +EXPORT_SYMBOL(il_dbg_log_rx_data_frame); + +const char *il_get_mgmt_string(int cmd) +{ + switch (cmd) { + IL_CMD(MANAGEMENT_ASSOC_REQ); + IL_CMD(MANAGEMENT_ASSOC_RESP); + IL_CMD(MANAGEMENT_REASSOC_REQ); + IL_CMD(MANAGEMENT_REASSOC_RESP); + IL_CMD(MANAGEMENT_PROBE_REQ); + IL_CMD(MANAGEMENT_PROBE_RESP); + IL_CMD(MANAGEMENT_BEACON); + IL_CMD(MANAGEMENT_ATIM); + IL_CMD(MANAGEMENT_DISASSOC); + IL_CMD(MANAGEMENT_AUTH); + IL_CMD(MANAGEMENT_DEAUTH); + IL_CMD(MANAGEMENT_ACTION); + default: + return "UNKNOWN"; + + } +} + +const char *il_get_ctrl_string(int cmd) +{ + switch (cmd) { + IL_CMD(CONTROL_BACK_REQ); + IL_CMD(CONTROL_BACK); + IL_CMD(CONTROL_PSPOLL); + IL_CMD(CONTROL_RTS); + IL_CMD(CONTROL_CTS); + IL_CMD(CONTROL_ACK); + IL_CMD(CONTROL_CFEND); + IL_CMD(CONTROL_CFENDACK); + default: + return "UNKNOWN"; + + } +} + +void il_clear_traffic_stats(struct il_priv *il) +{ + memset(&il->tx_stats, 0, sizeof(struct traffic_stats)); + memset(&il->rx_stats, 0, sizeof(struct traffic_stats)); +} + +/* + * if CONFIG_IWLEGACY_DEBUGFS defined, + * il_update_stats function will + * record all the MGMT, CTRL and DATA pkt for both TX and Rx pass + * Use debugFs to display the rx/rx_stats + * if CONFIG_IWLEGACY_DEBUGFS not being defined, then no MGMT and CTRL + * information will be recorded, but DATA pkt still will be recorded + * for the reason of il_led.c need to control the led blinking based on + * number of tx and rx data. + * + */ +void +il_update_stats(struct il_priv *il, bool is_tx, __le16 fc, u16 len) +{ + struct traffic_stats *stats; + + if (is_tx) + stats = &il->tx_stats; + else + stats = &il->rx_stats; + + if (ieee80211_is_mgmt(fc)) { + switch (fc & cpu_to_le16(IEEE80211_FCTL_STYPE)) { + case cpu_to_le16(IEEE80211_STYPE_ASSOC_REQ): + stats->mgmt[MANAGEMENT_ASSOC_REQ]++; + break; + case cpu_to_le16(IEEE80211_STYPE_ASSOC_RESP): + stats->mgmt[MANAGEMENT_ASSOC_RESP]++; + break; + case cpu_to_le16(IEEE80211_STYPE_REASSOC_REQ): + stats->mgmt[MANAGEMENT_REASSOC_REQ]++; + break; + case cpu_to_le16(IEEE80211_STYPE_REASSOC_RESP): + stats->mgmt[MANAGEMENT_REASSOC_RESP]++; + break; + case cpu_to_le16(IEEE80211_STYPE_PROBE_REQ): + stats->mgmt[MANAGEMENT_PROBE_REQ]++; + break; + case cpu_to_le16(IEEE80211_STYPE_PROBE_RESP): + stats->mgmt[MANAGEMENT_PROBE_RESP]++; + break; + case cpu_to_le16(IEEE80211_STYPE_BEACON): + stats->mgmt[MANAGEMENT_BEACON]++; + break; + case cpu_to_le16(IEEE80211_STYPE_ATIM): + stats->mgmt[MANAGEMENT_ATIM]++; + break; + case cpu_to_le16(IEEE80211_STYPE_DISASSOC): + stats->mgmt[MANAGEMENT_DISASSOC]++; + break; + case cpu_to_le16(IEEE80211_STYPE_AUTH): + stats->mgmt[MANAGEMENT_AUTH]++; + break; + case cpu_to_le16(IEEE80211_STYPE_DEAUTH): + stats->mgmt[MANAGEMENT_DEAUTH]++; + break; + case cpu_to_le16(IEEE80211_STYPE_ACTION): + stats->mgmt[MANAGEMENT_ACTION]++; + break; + } + } else if (ieee80211_is_ctl(fc)) { + switch (fc & cpu_to_le16(IEEE80211_FCTL_STYPE)) { + case cpu_to_le16(IEEE80211_STYPE_BACK_REQ): + stats->ctrl[CONTROL_BACK_REQ]++; + break; + case cpu_to_le16(IEEE80211_STYPE_BACK): + stats->ctrl[CONTROL_BACK]++; + break; + case cpu_to_le16(IEEE80211_STYPE_PSPOLL): + stats->ctrl[CONTROL_PSPOLL]++; + break; + case cpu_to_le16(IEEE80211_STYPE_RTS): + stats->ctrl[CONTROL_RTS]++; + break; + case cpu_to_le16(IEEE80211_STYPE_CTS): + stats->ctrl[CONTROL_CTS]++; + break; + case cpu_to_le16(IEEE80211_STYPE_ACK): + stats->ctrl[CONTROL_ACK]++; + break; + case cpu_to_le16(IEEE80211_STYPE_CFEND): + stats->ctrl[CONTROL_CFEND]++; + break; + case cpu_to_le16(IEEE80211_STYPE_CFENDACK): + stats->ctrl[CONTROL_CFENDACK]++; + break; + } + } else { + /* data */ + stats->data_cnt++; + stats->data_bytes += len; + } +} +EXPORT_SYMBOL(il_update_stats); +#endif + +int il_force_reset(struct il_priv *il, bool external) +{ + struct il_force_reset *force_reset; + + if (test_bit(S_EXIT_PENDING, &il->status)) + return -EINVAL; + + force_reset = &il->force_reset; + force_reset->reset_request_count++; + if (!external) { + if (force_reset->last_force_reset_jiffies && + time_after(force_reset->last_force_reset_jiffies + + force_reset->reset_duration, jiffies)) { + D_INFO("force reset rejected\n"); + force_reset->reset_reject_count++; + return -EAGAIN; + } + } + force_reset->reset_success_count++; + force_reset->last_force_reset_jiffies = jiffies; + + /* + * if the request is from external(ex: debugfs), + * then always perform the request in regardless the module + * parameter setting + * if the request is from internal (uCode error or driver + * detect failure), then fw_restart module parameter + * need to be check before performing firmware reload + */ + + if (!external && !il->cfg->mod_params->restart_fw) { + D_INFO("Cancel firmware reload based on " + "module parameter setting\n"); + return 0; + } + + IL_ERR("On demand firmware reload\n"); + + /* Set the FW error flag -- cleared on il_down */ + set_bit(S_FW_ERROR, &il->status); + wake_up(&il->wait_command_queue); + /* + * Keep the restart process from trying to send host + * commands by clearing the INIT status bit + */ + clear_bit(S_READY, &il->status); + queue_work(il->workqueue, &il->restart); + + return 0; +} + +int +il_mac_change_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum nl80211_iftype newtype, bool newp2p) +{ + struct il_priv *il = hw->priv; + struct il_rxon_context *ctx = il_rxon_ctx_from_vif(vif); + u32 modes; + int err; + + newtype = ieee80211_iftype_p2p(newtype, newp2p); + + mutex_lock(&il->mutex); + + if (!ctx->vif || !il_is_ready_rf(il)) { + /* + * Huh? But wait ... this can maybe happen when + * we're in the middle of a firmware restart! + */ + err = -EBUSY; + goto out; + } + + modes = ctx->interface_modes | ctx->exclusive_interface_modes; + if (!(modes & BIT(newtype))) { + err = -EOPNOTSUPP; + goto out; + } + + if ((il->ctx.exclusive_interface_modes & BIT(il->ctx.vif->type)) || + (il->ctx.exclusive_interface_modes & BIT(newtype))) { + err = -EINVAL; + goto out; + } + + /* success */ + il_teardown_interface(il, vif, true); + vif->type = newtype; + vif->p2p = newp2p; + err = il_setup_interface(il, ctx); + WARN_ON(err); + /* + * We've switched internally, but submitting to the + * device may have failed for some reason. Mask this + * error, because otherwise mac80211 will not switch + * (and set the interface type back) and we'll be + * out of sync with it. + */ + err = 0; + + out: + mutex_unlock(&il->mutex); + return err; +} +EXPORT_SYMBOL(il_mac_change_interface); + +/* + * On every watchdog tick we check (latest) time stamp. If it does not + * change during timeout period and queue is not empty we reset firmware. + */ +static int il_check_stuck_queue(struct il_priv *il, int cnt) +{ + struct il_tx_queue *txq = &il->txq[cnt]; + struct il_queue *q = &txq->q; + unsigned long timeout; + int ret; + + if (q->read_ptr == q->write_ptr) { + txq->time_stamp = jiffies; + return 0; + } + + timeout = txq->time_stamp + + msecs_to_jiffies(il->cfg->base_params->wd_timeout); + + if (time_after(jiffies, timeout)) { + IL_ERR("Queue %d stuck for %u ms.\n", + q->id, il->cfg->base_params->wd_timeout); + ret = il_force_reset(il, false); + return (ret == -EAGAIN) ? 0 : 1; + } + + return 0; +} + +/* + * Making watchdog tick be a quarter of timeout assure we will + * discover the queue hung between timeout and 1.25*timeout + */ +#define IL_WD_TICK(timeout) ((timeout) / 4) + +/* + * Watchdog timer callback, we check each tx queue for stuck, if if hung + * we reset the firmware. If everything is fine just rearm the timer. + */ +void il_bg_watchdog(unsigned long data) +{ + struct il_priv *il = (struct il_priv *)data; + int cnt; + unsigned long timeout; + + if (test_bit(S_EXIT_PENDING, &il->status)) + return; + + timeout = il->cfg->base_params->wd_timeout; + if (timeout == 0) + return; + + /* monitor and check for stuck cmd queue */ + if (il_check_stuck_queue(il, il->cmd_queue)) + return; + + /* monitor and check for other stuck queues */ + if (il_is_any_associated(il)) { + for (cnt = 0; cnt < il->hw_params.max_txq_num; cnt++) { + /* skip as we already checked the command queue */ + if (cnt == il->cmd_queue) + continue; + if (il_check_stuck_queue(il, cnt)) + return; + } + } + + mod_timer(&il->watchdog, jiffies + + msecs_to_jiffies(IL_WD_TICK(timeout))); +} +EXPORT_SYMBOL(il_bg_watchdog); + +void il_setup_watchdog(struct il_priv *il) +{ + unsigned int timeout = il->cfg->base_params->wd_timeout; + + if (timeout) + mod_timer(&il->watchdog, + jiffies + msecs_to_jiffies(IL_WD_TICK(timeout))); + else + del_timer(&il->watchdog); +} +EXPORT_SYMBOL(il_setup_watchdog); + +/* + * extended beacon time format + * time in usec will be changed into a 32-bit value in extended:internal format + * the extended part is the beacon counts + * the internal part is the time in usec within one beacon interval + */ +u32 +il_usecs_to_beacons(struct il_priv *il, + u32 usec, u32 beacon_interval) +{ + u32 quot; + u32 rem; + u32 interval = beacon_interval * TIME_UNIT; + + if (!interval || !usec) + return 0; + + quot = (usec / interval) & + (il_beacon_time_mask_high(il, + il->hw_params.beacon_time_tsf_bits) >> + il->hw_params.beacon_time_tsf_bits); + rem = (usec % interval) & il_beacon_time_mask_low(il, + il->hw_params.beacon_time_tsf_bits); + + return (quot << il->hw_params.beacon_time_tsf_bits) + rem; +} +EXPORT_SYMBOL(il_usecs_to_beacons); + +/* base is usually what we get from ucode with each received frame, + * the same as HW timer counter counting down + */ +__le32 il_add_beacon_time(struct il_priv *il, u32 base, + u32 addon, u32 beacon_interval) +{ + u32 base_low = base & il_beacon_time_mask_low(il, + il->hw_params.beacon_time_tsf_bits); + u32 addon_low = addon & il_beacon_time_mask_low(il, + il->hw_params.beacon_time_tsf_bits); + u32 interval = beacon_interval * TIME_UNIT; + u32 res = (base & il_beacon_time_mask_high(il, + il->hw_params.beacon_time_tsf_bits)) + + (addon & il_beacon_time_mask_high(il, + il->hw_params.beacon_time_tsf_bits)); + + if (base_low > addon_low) + res += base_low - addon_low; + else if (base_low < addon_low) { + res += interval + base_low - addon_low; + res += (1 << il->hw_params.beacon_time_tsf_bits); + } else + res += (1 << il->hw_params.beacon_time_tsf_bits); + + return cpu_to_le32(res); +} +EXPORT_SYMBOL(il_add_beacon_time); + +#ifdef CONFIG_PM + +int il_pci_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct il_priv *il = pci_get_drvdata(pdev); + + /* + * This function is called when system goes into suspend state + * mac80211 will call il_mac_stop() from the mac80211 suspend function + * first but since il_mac_stop() has no knowledge of who the caller is, + * it will not call apm_ops.stop() to stop the DMA operation. + * Calling apm_ops.stop here to make sure we stop the DMA. + */ + il_apm_stop(il); + + return 0; +} +EXPORT_SYMBOL(il_pci_suspend); + +int il_pci_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct il_priv *il = pci_get_drvdata(pdev); + bool hw_rfkill = false; + + /* + * We disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state. + */ + pci_write_config_byte(pdev, PCI_CFG_RETRY_TIMEOUT, 0x00); + + il_enable_interrupts(il); + + if (!(_il_rd(il, CSR_GP_CNTRL) & + CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW)) + hw_rfkill = true; + + if (hw_rfkill) + set_bit(S_RF_KILL_HW, &il->status); + else + clear_bit(S_RF_KILL_HW, &il->status); + + wiphy_rfkill_set_hw_state(il->hw->wiphy, hw_rfkill); + + return 0; +} +EXPORT_SYMBOL(il_pci_resume); + +const struct dev_pm_ops il_pm_ops = { + .suspend = il_pci_suspend, + .resume = il_pci_resume, + .freeze = il_pci_suspend, + .thaw = il_pci_resume, + .poweroff = il_pci_suspend, + .restore = il_pci_resume, +}; +EXPORT_SYMBOL(il_pm_ops); + +#endif /* CONFIG_PM */ + +static void +il_update_qos(struct il_priv *il, struct il_rxon_context *ctx) +{ + if (test_bit(S_EXIT_PENDING, &il->status)) + return; + + if (!ctx->is_active) + return; + + ctx->qos_data.def_qos_parm.qos_flags = 0; + + if (ctx->qos_data.qos_active) + ctx->qos_data.def_qos_parm.qos_flags |= + QOS_PARAM_FLG_UPDATE_EDCA_MSK; + + if (ctx->ht.enabled) + ctx->qos_data.def_qos_parm.qos_flags |= QOS_PARAM_FLG_TGN_MSK; + + D_QOS("send QoS cmd with Qos active=%d FLAGS=0x%X\n", + ctx->qos_data.qos_active, + ctx->qos_data.def_qos_parm.qos_flags); + + il_send_cmd_pdu_async(il, ctx->qos_cmd, + sizeof(struct il_qosparam_cmd), + &ctx->qos_data.def_qos_parm, NULL); +} + +/** + * il_mac_config - mac80211 config callback + */ +int il_mac_config(struct ieee80211_hw *hw, u32 changed) +{ + struct il_priv *il = hw->priv; + const struct il_channel_info *ch_info; + struct ieee80211_conf *conf = &hw->conf; + struct ieee80211_channel *channel = conf->channel; + struct il_ht_config *ht_conf = &il->current_ht_config; + struct il_rxon_context *ctx = &il->ctx; + unsigned long flags = 0; + int ret = 0; + u16 ch; + int scan_active = 0; + bool ht_changed = false; + + if (WARN_ON(!il->cfg->ops->legacy)) + return -EOPNOTSUPP; + + mutex_lock(&il->mutex); + + D_MAC80211("enter to channel %d changed 0x%X\n", + channel->hw_value, changed); + + if (unlikely(test_bit(S_SCANNING, &il->status))) { + scan_active = 1; + D_MAC80211("scan active\n"); + } + + if (changed & (IEEE80211_CONF_CHANGE_SMPS | + IEEE80211_CONF_CHANGE_CHANNEL)) { + /* mac80211 uses static for non-HT which is what we want */ + il->current_ht_config.smps = conf->smps_mode; + + /* + * Recalculate chain counts. + * + * If monitor mode is enabled then mac80211 will + * set up the SM PS mode to OFF if an HT channel is + * configured. + */ + if (il->cfg->ops->hcmd->set_rxon_chain) + il->cfg->ops->hcmd->set_rxon_chain(il, &il->ctx); + } + + /* during scanning mac80211 will delay channel setting until + * scan finish with changed = 0 + */ + if (!changed || (changed & IEEE80211_CONF_CHANGE_CHANNEL)) { + + if (scan_active) + goto set_ch_out; + + ch = channel->hw_value; + ch_info = il_get_channel_info(il, channel->band, ch); + if (!il_is_channel_valid(ch_info)) { + D_MAC80211("leave - invalid channel\n"); + ret = -EINVAL; + goto set_ch_out; + } + + if (il->iw_mode == NL80211_IFTYPE_ADHOC && + !il_is_channel_ibss(ch_info)) { + D_MAC80211("leave - not IBSS channel\n"); + ret = -EINVAL; + goto set_ch_out; + } + + spin_lock_irqsave(&il->lock, flags); + + /* Configure HT40 channels */ + if (ctx->ht.enabled != conf_is_ht(conf)) { + ctx->ht.enabled = conf_is_ht(conf); + ht_changed = true; + } + if (ctx->ht.enabled) { + if (conf_is_ht40_minus(conf)) { + ctx->ht.extension_chan_offset = + IEEE80211_HT_PARAM_CHA_SEC_BELOW; + ctx->ht.is_40mhz = true; + } else if (conf_is_ht40_plus(conf)) { + ctx->ht.extension_chan_offset = + IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + ctx->ht.is_40mhz = true; + } else { + ctx->ht.extension_chan_offset = + IEEE80211_HT_PARAM_CHA_SEC_NONE; + ctx->ht.is_40mhz = false; + } + } else + ctx->ht.is_40mhz = false; + + /* + * Default to no protection. Protection mode will + * later be set from BSS config in il_ht_conf + */ + ctx->ht.protection = + IEEE80211_HT_OP_MODE_PROTECTION_NONE; + + /* if we are switching from ht to 2.4 clear flags + * from any ht related info since 2.4 does not + * support ht */ + if ((le16_to_cpu(ctx->staging.channel) != ch)) + ctx->staging.flags = 0; + + il_set_rxon_channel(il, channel, ctx); + il_set_rxon_ht(il, ht_conf); + + il_set_flags_for_band(il, ctx, channel->band, + ctx->vif); + + spin_unlock_irqrestore(&il->lock, flags); + + if (il->cfg->ops->legacy->update_bcast_stations) + ret = + il->cfg->ops->legacy->update_bcast_stations(il); + + set_ch_out: + /* The list of supported rates and rate mask can be different + * for each band; since the band may have changed, reset + * the rate mask to what mac80211 lists */ + il_set_rate(il); + } + + if (changed & (IEEE80211_CONF_CHANGE_PS | + IEEE80211_CONF_CHANGE_IDLE)) { + ret = il_power_update_mode(il, false); + if (ret) + D_MAC80211("Error setting sleep level\n"); + } + + if (changed & IEEE80211_CONF_CHANGE_POWER) { + D_MAC80211("TX Power old=%d new=%d\n", + il->tx_power_user_lmt, conf->power_level); + + il_set_tx_power(il, conf->power_level, false); + } + + if (!il_is_ready(il)) { + D_MAC80211("leave - not ready\n"); + goto out; + } + + if (scan_active) + goto out; + + if (memcmp(&ctx->active, &ctx->staging, sizeof(ctx->staging))) + il_commit_rxon(il, ctx); + else + D_INFO("Not re-sending same RXON configuration.\n"); + if (ht_changed) + il_update_qos(il, ctx); + +out: + D_MAC80211("leave\n"); + mutex_unlock(&il->mutex); + return ret; +} +EXPORT_SYMBOL(il_mac_config); + +void il_mac_reset_tsf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct il_priv *il = hw->priv; + unsigned long flags; + struct il_rxon_context *ctx = &il->ctx; + + if (WARN_ON(!il->cfg->ops->legacy)) + return; + + mutex_lock(&il->mutex); + D_MAC80211("enter\n"); + + spin_lock_irqsave(&il->lock, flags); + memset(&il->current_ht_config, 0, sizeof(struct il_ht_config)); + spin_unlock_irqrestore(&il->lock, flags); + + spin_lock_irqsave(&il->lock, flags); + + /* new association get rid of ibss beacon skb */ + if (il->beacon_skb) + dev_kfree_skb(il->beacon_skb); + + il->beacon_skb = NULL; + + il->timestamp = 0; + + spin_unlock_irqrestore(&il->lock, flags); + + il_scan_cancel_timeout(il, 100); + if (!il_is_ready_rf(il)) { + D_MAC80211("leave - not ready\n"); + mutex_unlock(&il->mutex); + return; + } + + /* we are restarting association process + * clear RXON_FILTER_ASSOC_MSK bit + */ + ctx->staging.filter_flags &= ~RXON_FILTER_ASSOC_MSK; + il_commit_rxon(il, ctx); + + il_set_rate(il); + + mutex_unlock(&il->mutex); + + D_MAC80211("leave\n"); +} +EXPORT_SYMBOL(il_mac_reset_tsf); + +static void il_ht_conf(struct il_priv *il, + struct ieee80211_vif *vif) +{ + struct il_ht_config *ht_conf = &il->current_ht_config; + struct ieee80211_sta *sta; + struct ieee80211_bss_conf *bss_conf = &vif->bss_conf; + struct il_rxon_context *ctx = il_rxon_ctx_from_vif(vif); + + D_ASSOC("enter:\n"); + + if (!ctx->ht.enabled) + return; + + ctx->ht.protection = + bss_conf->ht_operation_mode & IEEE80211_HT_OP_MODE_PROTECTION; + ctx->ht.non_gf_sta_present = + !!(bss_conf->ht_operation_mode & + IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT); + + ht_conf->single_chain_sufficient = false; + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + rcu_read_lock(); + sta = ieee80211_find_sta(vif, bss_conf->bssid); + if (sta) { + struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + int maxstreams; + + maxstreams = (ht_cap->mcs.tx_params & + IEEE80211_HT_MCS_TX_MAX_STREAMS_MASK) + >> IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT; + maxstreams += 1; + + if (ht_cap->mcs.rx_mask[1] == 0 && + ht_cap->mcs.rx_mask[2] == 0) + ht_conf->single_chain_sufficient = true; + if (maxstreams <= 1) + ht_conf->single_chain_sufficient = true; + } else { + /* + * If at all, this can only happen through a race + * when the AP disconnects us while we're still + * setting up the connection, in that case mac80211 + * will soon tell us about that. + */ + ht_conf->single_chain_sufficient = true; + } + rcu_read_unlock(); + break; + case NL80211_IFTYPE_ADHOC: + ht_conf->single_chain_sufficient = true; + break; + default: + break; + } + + D_ASSOC("leave\n"); +} + +static inline void il_set_no_assoc(struct il_priv *il, + struct ieee80211_vif *vif) +{ + struct il_rxon_context *ctx = il_rxon_ctx_from_vif(vif); + + /* + * inform the ucode that there is no longer an + * association and that no more packets should be + * sent + */ + ctx->staging.filter_flags &= ~RXON_FILTER_ASSOC_MSK; + ctx->staging.assoc_id = 0; + il_commit_rxon(il, ctx); +} + +static void il_beacon_update(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct il_priv *il = hw->priv; + unsigned long flags; + __le64 timestamp; + struct sk_buff *skb = ieee80211_beacon_get(hw, vif); + + if (!skb) + return; + + D_MAC80211("enter\n"); + + lockdep_assert_held(&il->mutex); + + if (!il->beacon_ctx) { + IL_ERR("update beacon but no beacon context!\n"); + dev_kfree_skb(skb); + return; + } + + spin_lock_irqsave(&il->lock, flags); + + if (il->beacon_skb) + dev_kfree_skb(il->beacon_skb); + + il->beacon_skb = skb; + + timestamp = ((struct ieee80211_mgmt *)skb->data)->u.beacon.timestamp; + il->timestamp = le64_to_cpu(timestamp); + + D_MAC80211("leave\n"); + spin_unlock_irqrestore(&il->lock, flags); + + if (!il_is_ready_rf(il)) { + D_MAC80211("leave - RF not ready\n"); + return; + } + + il->cfg->ops->legacy->post_associate(il); +} + +void il_mac_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changes) +{ + struct il_priv *il = hw->priv; + struct il_rxon_context *ctx = il_rxon_ctx_from_vif(vif); + int ret; + + if (WARN_ON(!il->cfg->ops->legacy)) + return; + + D_MAC80211("changes = 0x%X\n", changes); + + mutex_lock(&il->mutex); + + if (!il_is_alive(il)) { + mutex_unlock(&il->mutex); + return; + } + + if (changes & BSS_CHANGED_QOS) { + unsigned long flags; + + spin_lock_irqsave(&il->lock, flags); + ctx->qos_data.qos_active = bss_conf->qos; + il_update_qos(il, ctx); + spin_unlock_irqrestore(&il->lock, flags); + } + + if (changes & BSS_CHANGED_BEACON_ENABLED) { + /* + * the add_interface code must make sure we only ever + * have a single interface that could be beaconing at + * any time. + */ + if (vif->bss_conf.enable_beacon) + il->beacon_ctx = ctx; + else + il->beacon_ctx = NULL; + } + + if (changes & BSS_CHANGED_BSSID) { + D_MAC80211("BSSID %pM\n", bss_conf->bssid); + + /* + * If there is currently a HW scan going on in the + * background then we need to cancel it else the RXON + * below/in post_associate will fail. + */ + if (il_scan_cancel_timeout(il, 100)) { + IL_WARN( + "Aborted scan still in progress after 100ms\n"); + D_MAC80211( + "leaving - scan abort failed.\n"); + mutex_unlock(&il->mutex); + return; + } + + /* mac80211 only sets assoc when in STATION mode */ + if (vif->type == NL80211_IFTYPE_ADHOC || bss_conf->assoc) { + memcpy(ctx->staging.bssid_addr, + bss_conf->bssid, ETH_ALEN); + + /* currently needed in a few places */ + memcpy(il->bssid, bss_conf->bssid, ETH_ALEN); + } else { + ctx->staging.filter_flags &= + ~RXON_FILTER_ASSOC_MSK; + } + + } + + /* + * This needs to be after setting the BSSID in case + * mac80211 decides to do both changes at once because + * it will invoke post_associate. + */ + if (vif->type == NL80211_IFTYPE_ADHOC && (changes & BSS_CHANGED_BEACON)) + il_beacon_update(hw, vif); + + if (changes & BSS_CHANGED_ERP_PREAMBLE) { + D_MAC80211("ERP_PREAMBLE %d\n", + bss_conf->use_short_preamble); + if (bss_conf->use_short_preamble) + ctx->staging.flags |= RXON_FLG_SHORT_PREAMBLE_MSK; + else + ctx->staging.flags &= ~RXON_FLG_SHORT_PREAMBLE_MSK; + } + + if (changes & BSS_CHANGED_ERP_CTS_PROT) { + D_MAC80211( + "ERP_CTS %d\n", bss_conf->use_cts_prot); + if (bss_conf->use_cts_prot && il->band != IEEE80211_BAND_5GHZ) + ctx->staging.flags |= RXON_FLG_TGG_PROTECT_MSK; + else + ctx->staging.flags &= ~RXON_FLG_TGG_PROTECT_MSK; + if (bss_conf->use_cts_prot) + ctx->staging.flags |= RXON_FLG_SELF_CTS_EN; + else + ctx->staging.flags &= ~RXON_FLG_SELF_CTS_EN; + } + + if (changes & BSS_CHANGED_BASIC_RATES) { + /* XXX use this information + * + * To do that, remove code from il_set_rate() and put something + * like this here: + * + if (A-band) + ctx->staging.ofdm_basic_rates = + bss_conf->basic_rates; + else + ctx->staging.ofdm_basic_rates = + bss_conf->basic_rates >> 4; + ctx->staging.cck_basic_rates = + bss_conf->basic_rates & 0xF; + */ + } + + if (changes & BSS_CHANGED_HT) { + il_ht_conf(il, vif); + + if (il->cfg->ops->hcmd->set_rxon_chain) + il->cfg->ops->hcmd->set_rxon_chain(il, ctx); + } + + if (changes & BSS_CHANGED_ASSOC) { + D_MAC80211("ASSOC %d\n", bss_conf->assoc); + if (bss_conf->assoc) { + il->timestamp = bss_conf->timestamp; + + if (!il_is_rfkill(il)) + il->cfg->ops->legacy->post_associate(il); + } else + il_set_no_assoc(il, vif); + } + + if (changes && il_is_associated_ctx(ctx) && bss_conf->aid) { + D_MAC80211("Changes (%#x) while associated\n", + changes); + ret = il_send_rxon_assoc(il, ctx); + if (!ret) { + /* Sync active_rxon with latest change. */ + memcpy((void *)&ctx->active, + &ctx->staging, + sizeof(struct il_rxon_cmd)); + } + } + + if (changes & BSS_CHANGED_BEACON_ENABLED) { + if (vif->bss_conf.enable_beacon) { + memcpy(ctx->staging.bssid_addr, + bss_conf->bssid, ETH_ALEN); + memcpy(il->bssid, bss_conf->bssid, ETH_ALEN); + il->cfg->ops->legacy->config_ap(il); + } else + il_set_no_assoc(il, vif); + } + + if (changes & BSS_CHANGED_IBSS) { + ret = il->cfg->ops->legacy->manage_ibss_station(il, vif, + bss_conf->ibss_joined); + if (ret) + IL_ERR("failed to %s IBSS station %pM\n", + bss_conf->ibss_joined ? "add" : "remove", + bss_conf->bssid); + } + + mutex_unlock(&il->mutex); + + D_MAC80211("leave\n"); +} +EXPORT_SYMBOL(il_mac_bss_info_changed); + +irqreturn_t il_isr(int irq, void *data) +{ + struct il_priv *il = data; + u32 inta, inta_mask; + u32 inta_fh; + unsigned long flags; + if (!il) + return IRQ_NONE; + + spin_lock_irqsave(&il->lock, flags); + + /* Disable (but don't clear!) interrupts here to avoid + * back-to-back ISRs and sporadic interrupts from our NIC. + * If we have something to service, the tasklet will re-enable ints. + * If we *don't* have something, we'll re-enable before leaving here. */ + inta_mask = _il_rd(il, CSR_INT_MASK); /* just for debug */ + _il_wr(il, CSR_INT_MASK, 0x00000000); + + /* Discover which interrupts are active/pending */ + inta = _il_rd(il, CSR_INT); + inta_fh = _il_rd(il, CSR_FH_INT_STATUS); + + /* Ignore interrupt if there's nothing in NIC to service. + * This may be due to IRQ shared with another device, + * or due to sporadic interrupts thrown from our NIC. */ + if (!inta && !inta_fh) { + D_ISR( + "Ignore interrupt, inta == 0, inta_fh == 0\n"); + goto none; + } + + if (inta == 0xFFFFFFFF || (inta & 0xFFFFFFF0) == 0xa5a5a5a0) { + /* Hardware disappeared. It might have already raised + * an interrupt */ + IL_WARN("HARDWARE GONE?? INTA == 0x%08x\n", inta); + goto unplugged; + } + + D_ISR("ISR inta 0x%08x, enabled 0x%08x, fh 0x%08x\n", + inta, inta_mask, inta_fh); + + inta &= ~CSR_INT_BIT_SCD; + + /* il_irq_tasklet() will service interrupts and re-enable them */ + if (likely(inta || inta_fh)) + tasklet_schedule(&il->irq_tasklet); + +unplugged: + spin_unlock_irqrestore(&il->lock, flags); + return IRQ_HANDLED; + +none: + /* re-enable interrupts here since we don't have anything to service. */ + /* only Re-enable if disabled by irq */ + if (test_bit(S_INT_ENABLED, &il->status)) + il_enable_interrupts(il); + spin_unlock_irqrestore(&il->lock, flags); + return IRQ_NONE; +} +EXPORT_SYMBOL(il_isr); + +/* + * il_tx_cmd_protection: Set rts/cts. 3945 and 4965 only share this + * function. + */ +void il_tx_cmd_protection(struct il_priv *il, + struct ieee80211_tx_info *info, + __le16 fc, __le32 *tx_flags) +{ + if (info->control.rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS) { + *tx_flags |= TX_CMD_FLG_RTS_MSK; + *tx_flags &= ~TX_CMD_FLG_CTS_MSK; + *tx_flags |= TX_CMD_FLG_FULL_TXOP_PROT_MSK; + + if (!ieee80211_is_mgmt(fc)) + return; + + switch (fc & cpu_to_le16(IEEE80211_FCTL_STYPE)) { + case cpu_to_le16(IEEE80211_STYPE_AUTH): + case cpu_to_le16(IEEE80211_STYPE_DEAUTH): + case cpu_to_le16(IEEE80211_STYPE_ASSOC_REQ): + case cpu_to_le16(IEEE80211_STYPE_REASSOC_REQ): + *tx_flags &= ~TX_CMD_FLG_RTS_MSK; + *tx_flags |= TX_CMD_FLG_CTS_MSK; + break; + } + } else if (info->control.rates[0].flags & + IEEE80211_TX_RC_USE_CTS_PROTECT) { + *tx_flags &= ~TX_CMD_FLG_RTS_MSK; + *tx_flags |= TX_CMD_FLG_CTS_MSK; + *tx_flags |= TX_CMD_FLG_FULL_TXOP_PROT_MSK; + } +} +EXPORT_SYMBOL(il_tx_cmd_protection); |