diff options
Diffstat (limited to 'drivers/net/wireless/iwlwifi/iwl-agn-rx.c')
| -rw-r--r-- | drivers/net/wireless/iwlwifi/iwl-agn-rx.c | 351 | 
1 files changed, 351 insertions, 0 deletions
diff --git a/drivers/net/wireless/iwlwifi/iwl-agn-rx.c b/drivers/net/wireless/iwlwifi/iwl-agn-rx.c new file mode 100644 index 00000000000..9490eced119 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-agn-rx.c @@ -0,0 +1,351 @@ +/****************************************************************************** + * + * 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 <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/init.h> +#include <linux/sched.h> + +#include "iwl-dev.h" +#include "iwl-core.h" +#include "iwl-calib.h" +#include "iwl-sta.h" +#include "iwl-io.h" +#include "iwl-helpers.h" +#include "iwl-agn-hw.h" +#include "iwl-agn.h" + +void iwl_rx_missed_beacon_notif(struct iwl_priv *priv, +				struct iwl_rx_mem_buffer *rxb) + +{ +	struct iwl_rx_packet *pkt = rxb_addr(rxb); +	struct iwl_missed_beacon_notif *missed_beacon; + +	missed_beacon = &pkt->u.missed_beacon; +	if (le32_to_cpu(missed_beacon->consecutive_missed_beacons) > +	    priv->missed_beacon_threshold) { +		IWL_DEBUG_CALIB(priv, +		    "missed bcn cnsq %d totl %d rcd %d expctd %d\n", +		    le32_to_cpu(missed_beacon->consecutive_missed_beacons), +		    le32_to_cpu(missed_beacon->total_missed_becons), +		    le32_to_cpu(missed_beacon->num_recvd_beacons), +		    le32_to_cpu(missed_beacon->num_expected_beacons)); +		if (!test_bit(STATUS_SCANNING, &priv->status)) +			iwl_init_sensitivity(priv); +	} +} + +/* Calculate noise level, based on measurements during network silence just + *   before arriving beacon.  This measurement can be done only if we know + *   exactly when to expect beacons, therefore only when we're associated. */ +static void iwl_rx_calc_noise(struct iwl_priv *priv) +{ +	struct statistics_rx_non_phy *rx_info; +	int num_active_rx = 0; +	int total_silence = 0; +	int bcn_silence_a, bcn_silence_b, bcn_silence_c; +	int last_rx_noise; + +	if (priv->cfg->bt_statistics) +		rx_info = &(priv->_agn.statistics_bt.rx.general.common); +	else +		rx_info = &(priv->_agn.statistics.rx.general); +	bcn_silence_a = +		le32_to_cpu(rx_info->beacon_silence_rssi_a) & IN_BAND_FILTER; +	bcn_silence_b = +		le32_to_cpu(rx_info->beacon_silence_rssi_b) & IN_BAND_FILTER; +	bcn_silence_c = +		le32_to_cpu(rx_info->beacon_silence_rssi_c) & IN_BAND_FILTER; + +	if (bcn_silence_a) { +		total_silence += bcn_silence_a; +		num_active_rx++; +	} +	if (bcn_silence_b) { +		total_silence += bcn_silence_b; +		num_active_rx++; +	} +	if (bcn_silence_c) { +		total_silence += bcn_silence_c; +		num_active_rx++; +	} + +	/* Average among active antennas */ +	if (num_active_rx) +		last_rx_noise = (total_silence / num_active_rx) - 107; +	else +		last_rx_noise = IWL_NOISE_MEAS_NOT_AVAILABLE; + +	IWL_DEBUG_CALIB(priv, "inband silence a %u, b %u, c %u, dBm %d\n", +			bcn_silence_a, bcn_silence_b, bcn_silence_c, +			last_rx_noise); +} + +#ifdef CONFIG_IWLWIFI_DEBUGFS +/* + *  based on the assumption of all statistics counter are in DWORD + *  FIXME: This function is for debugging, do not deal with + *  the case of counters roll-over. + */ +static void iwl_accumulative_statistics(struct iwl_priv *priv, +					__le32 *stats) +{ +	int i, size; +	__le32 *prev_stats; +	u32 *accum_stats; +	u32 *delta, *max_delta; +	struct statistics_general_common *general, *accum_general; +	struct statistics_tx *tx, *accum_tx; + +	if (priv->cfg->bt_statistics) { +		prev_stats = (__le32 *)&priv->_agn.statistics_bt; +		accum_stats = (u32 *)&priv->_agn.accum_statistics_bt; +		size = sizeof(struct iwl_bt_notif_statistics); +		general = &priv->_agn.statistics_bt.general.common; +		accum_general = &priv->_agn.accum_statistics_bt.general.common; +		tx = &priv->_agn.statistics_bt.tx; +		accum_tx = &priv->_agn.accum_statistics_bt.tx; +		delta = (u32 *)&priv->_agn.delta_statistics_bt; +		max_delta = (u32 *)&priv->_agn.max_delta_bt; +	} else { +		prev_stats = (__le32 *)&priv->_agn.statistics; +		accum_stats = (u32 *)&priv->_agn.accum_statistics; +		size = sizeof(struct iwl_notif_statistics); +		general = &priv->_agn.statistics.general.common; +		accum_general = &priv->_agn.accum_statistics.general.common; +		tx = &priv->_agn.statistics.tx; +		accum_tx = &priv->_agn.accum_statistics.tx; +		delta = (u32 *)&priv->_agn.delta_statistics; +		max_delta = (u32 *)&priv->_agn.max_delta; +	} +	for (i = sizeof(__le32); i < size; +	     i += sizeof(__le32), stats++, prev_stats++, delta++, +	     max_delta++, accum_stats++) { +		if (le32_to_cpu(*stats) > le32_to_cpu(*prev_stats)) { +			*delta = (le32_to_cpu(*stats) - +				le32_to_cpu(*prev_stats)); +			*accum_stats += *delta; +			if (*delta > *max_delta) +				*max_delta = *delta; +		} +	} + +	/* reset accumulative statistics for "no-counter" type statistics */ +	accum_general->temperature = general->temperature; +	accum_general->temperature_m = general->temperature_m; +	accum_general->ttl_timestamp = general->ttl_timestamp; +	accum_tx->tx_power.ant_a = tx->tx_power.ant_a; +	accum_tx->tx_power.ant_b = tx->tx_power.ant_b; +	accum_tx->tx_power.ant_c = tx->tx_power.ant_c; +} +#endif + +#define REG_RECALIB_PERIOD (60) + +/** + * iwl_good_plcp_health - checks for plcp error. + * + * When the plcp error is exceeding the thresholds, reset the radio + * to improve the throughput. + */ +bool iwl_good_plcp_health(struct iwl_priv *priv, +				struct iwl_rx_packet *pkt) +{ +	bool rc = true; +	int combined_plcp_delta; +	unsigned int plcp_msec; +	unsigned long plcp_received_jiffies; + +	if (priv->cfg->plcp_delta_threshold == +	    IWL_MAX_PLCP_ERR_THRESHOLD_DISABLE) { +		IWL_DEBUG_RADIO(priv, "plcp_err check disabled\n"); +		return rc; +	} + +	/* +	 * check for plcp_err and trigger radio reset if it exceeds +	 * the plcp error threshold plcp_delta. +	 */ +	plcp_received_jiffies = jiffies; +	plcp_msec = jiffies_to_msecs((long) plcp_received_jiffies - +					(long) priv->plcp_jiffies); +	priv->plcp_jiffies = plcp_received_jiffies; +	/* +	 * check to make sure plcp_msec is not 0 to prevent division +	 * by zero. +	 */ +	if (plcp_msec) { +		struct statistics_rx_phy *ofdm; +		struct statistics_rx_ht_phy *ofdm_ht; + +		if (priv->cfg->bt_statistics) { +			ofdm = &pkt->u.stats_bt.rx.ofdm; +			ofdm_ht = &pkt->u.stats_bt.rx.ofdm_ht; +			combined_plcp_delta = +			   (le32_to_cpu(ofdm->plcp_err) - +			   le32_to_cpu(priv->_agn.statistics_bt. +				       rx.ofdm.plcp_err)) + +			   (le32_to_cpu(ofdm_ht->plcp_err) - +			   le32_to_cpu(priv->_agn.statistics_bt. +				       rx.ofdm_ht.plcp_err)); +		} else { +			ofdm = &pkt->u.stats.rx.ofdm; +			ofdm_ht = &pkt->u.stats.rx.ofdm_ht; +			combined_plcp_delta = +			    (le32_to_cpu(ofdm->plcp_err) - +			    le32_to_cpu(priv->_agn.statistics. +					rx.ofdm.plcp_err)) + +			    (le32_to_cpu(ofdm_ht->plcp_err) - +			    le32_to_cpu(priv->_agn.statistics. +					rx.ofdm_ht.plcp_err)); +		} + +		if ((combined_plcp_delta > 0) && +		    ((combined_plcp_delta * 100) / plcp_msec) > +			priv->cfg->plcp_delta_threshold) { +			/* +			 * if plcp_err exceed the threshold, +			 * the following data is printed in csv format: +			 *    Text: plcp_err exceeded %d, +			 *    Received ofdm.plcp_err, +			 *    Current ofdm.plcp_err, +			 *    Received ofdm_ht.plcp_err, +			 *    Current ofdm_ht.plcp_err, +			 *    combined_plcp_delta, +			 *    plcp_msec +			 */ +			IWL_DEBUG_RADIO(priv, "plcp_err exceeded %u, " +				    "%u, %u, %u, %u, %d, %u mSecs\n", +				    priv->cfg->plcp_delta_threshold, +				    le32_to_cpu(ofdm->plcp_err), +				    le32_to_cpu(ofdm->plcp_err), +				    le32_to_cpu(ofdm_ht->plcp_err), +				    le32_to_cpu(ofdm_ht->plcp_err), +				    combined_plcp_delta, plcp_msec); + +			rc = false; +		} +	} +	return rc; +} + +void iwl_rx_statistics(struct iwl_priv *priv, +			      struct iwl_rx_mem_buffer *rxb) +{ +	int change; +	struct iwl_rx_packet *pkt = rxb_addr(rxb); + +	if (priv->cfg->bt_statistics) { +		IWL_DEBUG_RX(priv, +			     "Statistics notification received (%d vs %d).\n", +			     (int)sizeof(struct iwl_bt_notif_statistics), +			     le32_to_cpu(pkt->len_n_flags) & +			     FH_RSCSR_FRAME_SIZE_MSK); + +		change = ((priv->_agn.statistics_bt.general.common.temperature != +			   pkt->u.stats_bt.general.common.temperature) || +			   ((priv->_agn.statistics_bt.flag & +			   STATISTICS_REPLY_FLG_HT40_MODE_MSK) != +			   (pkt->u.stats_bt.flag & +			   STATISTICS_REPLY_FLG_HT40_MODE_MSK))); +#ifdef CONFIG_IWLWIFI_DEBUGFS +		iwl_accumulative_statistics(priv, (__le32 *)&pkt->u.stats_bt); +#endif + +	} else { +		IWL_DEBUG_RX(priv, +			     "Statistics notification received (%d vs %d).\n", +			     (int)sizeof(struct iwl_notif_statistics), +			     le32_to_cpu(pkt->len_n_flags) & +			     FH_RSCSR_FRAME_SIZE_MSK); + +		change = ((priv->_agn.statistics.general.common.temperature != +			   pkt->u.stats.general.common.temperature) || +			   ((priv->_agn.statistics.flag & +			   STATISTICS_REPLY_FLG_HT40_MODE_MSK) != +			   (pkt->u.stats.flag & +			   STATISTICS_REPLY_FLG_HT40_MODE_MSK))); +#ifdef CONFIG_IWLWIFI_DEBUGFS +		iwl_accumulative_statistics(priv, (__le32 *)&pkt->u.stats); +#endif + +	} + +	iwl_recover_from_statistics(priv, pkt); + +	if (priv->cfg->bt_statistics) +		memcpy(&priv->_agn.statistics_bt, &pkt->u.stats_bt, +			sizeof(priv->_agn.statistics_bt)); +	else +		memcpy(&priv->_agn.statistics, &pkt->u.stats, +			sizeof(priv->_agn.statistics)); + +	set_bit(STATUS_STATISTICS, &priv->status); + +	/* Reschedule the statistics timer to occur in +	 * REG_RECALIB_PERIOD seconds to ensure we get a +	 * thermal update even if the uCode doesn't give +	 * us one */ +	mod_timer(&priv->statistics_periodic, jiffies + +		  msecs_to_jiffies(REG_RECALIB_PERIOD * 1000)); + +	if (unlikely(!test_bit(STATUS_SCANNING, &priv->status)) && +	    (pkt->hdr.cmd == STATISTICS_NOTIFICATION)) { +		iwl_rx_calc_noise(priv); +		queue_work(priv->workqueue, &priv->run_time_calib_work); +	} +	if (priv->cfg->ops->lib->temp_ops.temperature && change) +		priv->cfg->ops->lib->temp_ops.temperature(priv); +} + +void iwl_reply_statistics(struct iwl_priv *priv, +			      struct iwl_rx_mem_buffer *rxb) +{ +	struct iwl_rx_packet *pkt = rxb_addr(rxb); + +	if (le32_to_cpu(pkt->u.stats.flag) & UCODE_STATISTICS_CLEAR_MSK) { +#ifdef CONFIG_IWLWIFI_DEBUGFS +		memset(&priv->_agn.accum_statistics, 0, +			sizeof(struct iwl_notif_statistics)); +		memset(&priv->_agn.delta_statistics, 0, +			sizeof(struct iwl_notif_statistics)); +		memset(&priv->_agn.max_delta, 0, +			sizeof(struct iwl_notif_statistics)); +		memset(&priv->_agn.accum_statistics_bt, 0, +			sizeof(struct iwl_bt_notif_statistics)); +		memset(&priv->_agn.delta_statistics_bt, 0, +			sizeof(struct iwl_bt_notif_statistics)); +		memset(&priv->_agn.max_delta_bt, 0, +			sizeof(struct iwl_bt_notif_statistics)); +#endif +		IWL_DEBUG_RX(priv, "Statistics have been cleared\n"); +	} +	iwl_rx_statistics(priv, rxb); +}  |