/****************************************************************************** * * GPL LICENSE SUMMARY * * Copyright(c) 2008 - 2010 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 * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 *****************************************************************************/ #include #include #include "iwl-dev.h" #include "iwl-core.h" #include "iwl-helpers.h" #include "iwl-legacy.h" static void iwl_update_qos(struct iwl_priv *priv, struct iwl_rxon_context *ctx) { if (test_bit(STATUS_EXIT_PENDING, &priv->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; IWL_DEBUG_QOS(priv, "send QoS cmd with Qos active=%d FLAGS=0x%X\n", ctx->qos_data.qos_active, ctx->qos_data.def_qos_parm.qos_flags); iwl_send_cmd_pdu_async(priv, ctx->qos_cmd, sizeof(struct iwl_qosparam_cmd), &ctx->qos_data.def_qos_parm, NULL); } /** * iwl_legacy_mac_config - mac80211 config callback */ int iwl_legacy_mac_config(struct ieee80211_hw *hw, u32 changed) { struct iwl_priv *priv = hw->priv; const struct iwl_channel_info *ch_info; struct ieee80211_conf *conf = &hw->conf; struct ieee80211_channel *channel = conf->channel; struct iwl_ht_config *ht_conf = &priv->current_ht_config; struct iwl_rxon_context *ctx; unsigned long flags = 0; int ret = 0; u16 ch; int scan_active = 0; bool ht_changed[NUM_IWL_RXON_CTX] = {}; if (WARN_ON(!priv->cfg->ops->legacy)) return -EOPNOTSUPP; mutex_lock(&priv->mutex); IWL_DEBUG_MAC80211(priv, "enter to channel %d changed 0x%X\n", channel->hw_value, changed); if (unlikely(test_bit(STATUS_SCANNING, &priv->status))) { scan_active = 1; IWL_DEBUG_MAC80211(priv, "scan active\n"); } if (changed & (IEEE80211_CONF_CHANGE_SMPS | IEEE80211_CONF_CHANGE_CHANNEL)) { /* mac80211 uses static for non-HT which is what we want */ priv->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 (priv->cfg->ops->hcmd->set_rxon_chain) for_each_context(priv, ctx) priv->cfg->ops->hcmd->set_rxon_chain(priv, 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 = iwl_get_channel_info(priv, channel->band, ch); if (!is_channel_valid(ch_info)) { IWL_DEBUG_MAC80211(priv, "leave - invalid channel\n"); ret = -EINVAL; goto set_ch_out; } spin_lock_irqsave(&priv->lock, flags); for_each_context(priv, ctx) { /* Configure HT40 channels */ if (ctx->ht.enabled != conf_is_ht(conf)) { ctx->ht.enabled = conf_is_ht(conf); ht_changed[ctx->ctxid] = 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 iwl_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; iwl_set_rxon_channel(priv, channel, ctx); iwl_set_rxon_ht(priv, ht_conf); iwl_set_flags_for_band(priv, ctx, channel->band, ctx->vif); } spin_unlock_irqrestore(&priv->lock, flags); if (priv->cfg->ops->legacy->update_bcast_stations) ret = priv->cfg->ops->legacy->update_bcast_stations(priv); 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 */ iwl_set_rate(priv); } if (changed & (IEEE80211_CONF_CHANGE_PS | IEEE80211_CONF_CHANGE_IDLE)) { ret = iwl_power_update_mode(priv, false); if (ret) IWL_DEBUG_MAC80211(priv, "Error setting sleep level\n"); } if (changed & IEEE80211_CONF_CHANGE_POWER) { IWL_DEBUG_MAC80211(priv, "TX Power old=%d new=%d\n", priv->tx_power_user_lmt, conf->power_level); iwl_set_tx_power(priv, conf->power_level, false); } if (!iwl_is_ready(priv)) { IWL_DEBUG_MAC80211(priv, "leave - not ready\n"); goto out; } if (scan_active) goto out; for_each_context(priv, ctx) { if (memcmp(&ctx->active, &ctx->staging, sizeof(ctx->staging))) iwlcore_commit_rxon(priv, ctx); else IWL_DEBUG_INFO(priv, "Not re-sending same RXON configuration.\n"); if (ht_changed[ctx->ctxid]) iwl_update_qos(priv, ctx); } out: IWL_DEBUG_MAC80211(priv, "leave\n"); mutex_unlock(&priv->mutex); return ret; } EXPORT_SYMBOL(iwl_legacy_mac_config); void iwl_legacy_mac_reset_tsf(struct ieee80211_hw *hw) { struct iwl_priv *priv = hw->priv; unsigned long flags; /* IBSS can only be the IWL_RXON_CTX_BSS context */ struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; if (WARN_ON(!priv->cfg->ops->legacy)) return; mutex_lock(&priv->mutex); IWL_DEBUG_MAC80211(priv, "enter\n"); spin_lock_irqsave(&priv->lock, flags); memset(&priv->current_ht_config, 0, sizeof(struct iwl_ht_config)); spin_unlock_irqrestore(&priv->lock, flags); spin_lock_irqsave(&priv->lock, flags); /* new association get rid of ibss beacon skb */ if (priv->beacon_skb) dev_kfree_skb(priv->beacon_skb); priv->beacon_skb = NULL; priv->timestamp = 0; spin_unlock_irqrestore(&priv->lock, flags); iwl_scan_cancel_timeout(priv, 100); if (!iwl_is_ready_rf(priv)) { IWL_DEBUG_MAC80211(priv, "leave - not ready\n"); mutex_unlock(&priv->mutex); return; } /* we are restarting association process * clear RXON_FILTER_ASSOC_MSK bit */ ctx->staging.filter_flags &= ~RXON_FILTER_ASSOC_MSK; iwlcore_commit_rxon(priv, ctx); iwl_set_rate(priv); mutex_unlock(&priv->mutex); IWL_DEBUG_MAC80211(priv, "leave\n"); } EXPORT_SYMBOL(iwl_legacy_mac_reset_tsf); static void iwl_ht_conf(struct iwl_priv *priv, struct ieee80211_vif *vif) { struct iwl_ht_config *ht_conf = &priv->current_ht_config; struct ieee80211_sta *sta; struct ieee80211_bss_conf *bss_conf = &vif->bss_conf; struct iwl_rxon_context *ctx = iwl_rxon_ctx_from_vif(vif); IWL_DEBUG_ASSOC(priv, "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; } IWL_DEBUG_ASSOC(priv, "leave\n"); } static inline void iwl_set_no_assoc(struct iwl_priv *priv, struct ieee80211_vif *vif) { struct iwl_rxon_context *ctx = iwl_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; iwlcore_commit_rxon(priv, ctx); } static void iwlcore_beacon_update(struct ieee80211_hw *hw, struct ieee80211_vif *vif) { struct iwl_priv *priv = hw->priv; unsigned long flags; __le64 timestamp; struct sk_buff *skb = ieee80211_beacon_get(hw, vif); if (!skb) return; IWL_DEBUG_MAC80211(priv, "enter\n"); lockdep_assert_held(&priv->mutex); if (!priv->beacon_ctx) { IWL_ERR(priv, "update beacon but no beacon context!\n"); dev_kfree_skb(skb); return; } spin_lock_irqsave(&priv->lock, flags); if (priv->beacon_skb) dev_kfree_skb(priv->beacon_skb); priv->beacon_skb = skb; timestamp = ((struct ieee80211_mgmt *)skb->data)->u.beacon.timestamp; priv->timestamp = le64_to_cpu(timestamp); IWL_DEBUG_MAC80211(priv, "leave\n"); spin_unlock_irqrestore(&priv->lock, flags); if (!iwl_is_ready_rf(priv)) { IWL_DEBUG_MAC80211(priv, "leave - RF not ready\n"); return; } priv->cfg->ops->legacy->post_associate(priv); } void iwl_legacy_mac_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_bss_conf *bss_conf, u32 changes) { struct iwl_priv *priv = hw->priv; struct iwl_rxon_context *ctx = iwl_rxon_ctx_from_vif(vif); int ret; if (WARN_ON(!priv->cfg->ops->legacy)) return; IWL_DEBUG_MAC80211(priv, "changes = 0x%X\n", changes); if (!iwl_is_alive(priv)) return; mutex_lock(&priv->mutex); if (changes & BSS_CHANGED_QOS) { unsigned long flags; spin_lock_irqsave(&priv->lock, flags); ctx->qos_data.qos_active = bss_conf->qos; iwl_update_qos(priv, ctx); spin_unlock_irqrestore(&priv->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) priv->beacon_ctx = ctx; else priv->beacon_ctx = NULL; } if (changes & BSS_CHANGED_BEACON && vif->type == NL80211_IFTYPE_AP) { dev_kfree_skb(priv->beacon_skb); priv->beacon_skb = ieee80211_beacon_get(hw, vif); } if (changes & BSS_CHANGED_BEACON_INT && vif->type == NL80211_IFTYPE_AP) iwl_send_rxon_timing(priv, ctx); if (changes & BSS_CHANGED_BSSID) { IWL_DEBUG_MAC80211(priv, "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 (iwl_scan_cancel_timeout(priv, 100)) { IWL_WARN(priv, "Aborted scan still in progress after 100ms\n"); IWL_DEBUG_MAC80211(priv, "leaving - scan abort failed.\n"); mutex_unlock(&priv->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(priv->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) iwlcore_beacon_update(hw, vif); if (changes & BSS_CHANGED_ERP_PREAMBLE) { IWL_DEBUG_MAC80211(priv, "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) { IWL_DEBUG_MAC80211(priv, "ERP_CTS %d\n", bss_conf->use_cts_prot); if (bss_conf->use_cts_prot && (priv->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 iwl_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) { iwl_ht_conf(priv, vif); if (priv->cfg->ops->hcmd->set_rxon_chain) priv->cfg->ops->hcmd->set_rxon_chain(priv, ctx); } if (changes & BSS_CHANGED_ASSOC) { IWL_DEBUG_MAC80211(priv, "ASSOC %d\n", bss_conf->assoc); if (bss_conf->assoc) { priv->timestamp = bss_conf->timestamp; if (!iwl_is_rfkill(priv)) priv->cfg->ops->legacy->post_associate(priv); } else iwl_set_no_assoc(priv, vif); } if (changes && iwl_is_associated_ctx(ctx) && bss_conf->aid) { IWL_DEBUG_MAC80211(priv, "Changes (%#x) while associated\n", changes); ret = iwl_send_rxon_assoc(priv, ctx); if (!ret) { /* Sync active_rxon with latest change. */ memcpy((void *)&ctx->active, &ctx->staging, sizeof(struct iwl_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(priv->bssid, bss_conf->bssid, ETH_ALEN); priv->cfg->ops->legacy->config_ap(priv); } else iwl_set_no_assoc(priv, vif); } if (changes & BSS_CHANGED_IBSS) { ret = priv->cfg->ops->legacy->manage_ibss_station(priv, vif, bss_conf->ibss_joined); if (ret) IWL_ERR(priv, "failed to %s IBSS station %pM\n", bss_conf->ibss_joined ? "add" : "remove", bss_conf->bssid); } mutex_unlock(&priv->mutex); IWL_DEBUG_MAC80211(priv, "leave\n"); } EXPORT_SYMBOL(iwl_legacy_mac_bss_info_changed); irqreturn_t iwl_isr_legacy(int irq, void *data) { struct iwl_priv *priv = data; u32 inta, inta_mask; u32 inta_fh; unsigned long flags; if (!priv) return IRQ_NONE; spin_lock_irqsave(&priv->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 = iwl_read32(priv, CSR_INT_MASK); /* just for debug */ iwl_write32(priv, CSR_INT_MASK, 0x00000000); /* Discover which interrupts are active/pending */ inta = iwl_read32(priv, CSR_INT); inta_fh = iwl_read32(priv, 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) { IWL_DEBUG_ISR(priv, "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 */ IWL_WARN(priv, "HARDWARE GONE?? INTA == 0x%08x\n", inta); goto unplugged; } IWL_DEBUG_ISR(priv, "ISR inta 0x%08x, enabled 0x%08x, fh 0x%08x\n", inta, inta_mask, inta_fh); inta &= ~CSR_INT_BIT_SCD; /* iwl_irq_tasklet() will service interrupts and re-enable them */ if (likely(inta || inta_fh)) tasklet_schedule(&priv->irq_tasklet); unplugged: spin_unlock_irqrestore(&priv->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(STATUS_INT_ENABLED, &priv->status)) iwl_enable_interrupts(priv); spin_unlock_irqrestore(&priv->lock, flags); return IRQ_NONE; } EXPORT_SYMBOL(iwl_isr_legacy); /* * iwl_legacy_tx_cmd_protection: Set rts/cts. 3945 and 4965 only share this * function. */ void iwl_legacy_tx_cmd_protection(struct iwl_priv *priv, 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(iwl_legacy_tx_cmd_protection);