Blob Blame History Raw
Backport of the following upstream commit...

commit 3e4fb5faefb57824f2e42305b3d5907845af978c
Author: Trieu 'Andrew' Nguyen <trieux.t.nguyen@intel.com>
Date:   Fri Jan 22 14:22:46 2010 -0800

    iwlwifi: Tune radio to prevent unexpected behavior
    
    We have seen the throughput dropped due to external noisy environment
    and the radio is out of tune.  There are lot of plcp errors indicating
    this condition. Eventually the station can get de-authenticated by the
    Access Point.  By resetting and tuning the radio, the plcp errors are
    reduced or eliminated and the throughput starts to rise.
    
    To prevent unexpected behavior such as drop in throughput or deauthentication,
    - The change provides the driver feature to monitor and tune the radio base on
    the statistics notification from the uCode.
    - It also allows the setting of the plcp error rate threshold via
    the plcp_delta under debugfs interface.
    
    Signed-off-by: Trieu 'Andrew' Nguyen <trieux.t.nguyen@intel.com>
    Signed-off-by: Reinette Chatre <reinette.chatre@intel.com>
    Signed-off-by: John W. Linville <linville@tuxdriver.com>

diff -up linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-1000.c.orig linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-1000.c
--- linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-1000.c.orig	2010-04-13 14:30:36.000000000 -0400
+++ linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-1000.c	2010-04-13 14:30:41.000000000 -0400
@@ -174,6 +174,7 @@ struct iwl_cfg iwl1000_bgn_cfg = {
 	.chain_noise_num_beacons = IWL_CAL_NUM_BEACONS,
 	.support_ct_kill_exit = true,
 	.sm_ps_mode = WLAN_HT_CAP_SM_PS_DISABLED,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF,
 };
 
 struct iwl_cfg iwl1000_bg_cfg = {
@@ -200,6 +201,7 @@ struct iwl_cfg iwl1000_bg_cfg = {
 	.led_compensation = 51,
 	.chain_noise_num_beacons = IWL_CAL_NUM_BEACONS,
 	.support_ct_kill_exit = true,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF,
 };
 
 MODULE_FIRMWARE(IWL1000_MODULE_FIRMWARE(IWL1000_UCODE_API_MAX));
diff -up linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-3945.c.orig linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-3945.c
--- linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-3945.c.orig	2010-04-13 14:30:36.000000000 -0400
+++ linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-3945.c	2010-04-13 14:30:41.000000000 -0400
@@ -2828,6 +2828,7 @@ static struct iwl_cfg iwl3945_bg_cfg = {
 	.ht_greenfield_support = false,
 	.led_compensation = 64,
 	.broken_powersave = true,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF,
 };
 
 static struct iwl_cfg iwl3945_abg_cfg = {
@@ -2845,6 +2846,7 @@ static struct iwl_cfg iwl3945_abg_cfg = 
 	.ht_greenfield_support = false,
 	.led_compensation = 64,
 	.broken_powersave = true,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF,
 };
 
 struct pci_device_id iwl3945_hw_card_ids[] = {
diff -up linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-4965.c.orig linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-4965.c
--- linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-4965.c.orig	2010-04-13 14:30:36.000000000 -0400
+++ linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-4965.c	2010-04-13 14:30:41.000000000 -0400
@@ -2247,6 +2247,7 @@ struct iwl_cfg iwl4965_agn_cfg = {
 	.led_compensation = 61,
 	.chain_noise_num_beacons = IWL4965_CAL_NUM_BEACONS,
 	.sm_ps_mode = WLAN_HT_CAP_SM_PS_DISABLED,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF,
 };
 
 /* Module firmware */
diff -up linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-5000.c.orig linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-5000.c
--- linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-5000.c.orig	2010-04-13 14:30:36.000000000 -0400
+++ linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-5000.c	2010-04-13 14:30:41.000000000 -0400
@@ -1606,6 +1606,7 @@ struct iwl_cfg iwl5300_agn_cfg = {
 	.use_rts_for_ht = true, /* use rts/cts protection */
 	.chain_noise_num_beacons = IWL_CAL_NUM_BEACONS,
 	.sm_ps_mode = WLAN_HT_CAP_SM_PS_DISABLED,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_LONG_THRESHOLD_DEF,
 };
 
 struct iwl_cfg iwl5100_bgn_cfg = {
@@ -1630,6 +1631,7 @@ struct iwl_cfg iwl5100_bgn_cfg = {
 	.led_compensation = 51,
 	.use_rts_for_ht = true, /* use rts/cts protection */
 	.chain_noise_num_beacons = IWL_CAL_NUM_BEACONS,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_LONG_THRESHOLD_DEF,
 };
 
 struct iwl_cfg iwl5100_abg_cfg = {
@@ -1652,6 +1654,7 @@ struct iwl_cfg iwl5100_abg_cfg = {
 	.use_bsm = false,
 	.led_compensation = 51,
 	.chain_noise_num_beacons = IWL_CAL_NUM_BEACONS,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_LONG_THRESHOLD_DEF,
 };
 
 struct iwl_cfg iwl5100_agn_cfg = {
@@ -1677,6 +1680,7 @@ struct iwl_cfg iwl5100_agn_cfg = {
 	.use_rts_for_ht = true, /* use rts/cts protection */
 	.chain_noise_num_beacons = IWL_CAL_NUM_BEACONS,
 	.sm_ps_mode = WLAN_HT_CAP_SM_PS_DISABLED,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_LONG_THRESHOLD_DEF,
 };
 
 struct iwl_cfg iwl5350_agn_cfg = {
@@ -1702,6 +1706,7 @@ struct iwl_cfg iwl5350_agn_cfg = {
 	.use_rts_for_ht = true, /* use rts/cts protection */
 	.chain_noise_num_beacons = IWL_CAL_NUM_BEACONS,
 	.sm_ps_mode = WLAN_HT_CAP_SM_PS_DISABLED,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_LONG_THRESHOLD_DEF,
 };
 
 struct iwl_cfg iwl5150_agn_cfg = {
@@ -1727,6 +1732,7 @@ struct iwl_cfg iwl5150_agn_cfg = {
 	.use_rts_for_ht = true, /* use rts/cts protection */
 	.chain_noise_num_beacons = IWL_CAL_NUM_BEACONS,
 	.sm_ps_mode = WLAN_HT_CAP_SM_PS_DISABLED,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_LONG_THRESHOLD_DEF,
 };
 
 struct iwl_cfg iwl5150_abg_cfg = {
@@ -1749,6 +1755,7 @@ struct iwl_cfg iwl5150_abg_cfg = {
 	.use_bsm = false,
 	.led_compensation = 51,
 	.chain_noise_num_beacons = IWL_CAL_NUM_BEACONS,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_LONG_THRESHOLD_DEF,
 };
 
 MODULE_FIRMWARE(IWL5000_MODULE_FIRMWARE(IWL5000_UCODE_API_MAX));
diff -up linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-6000.c.orig linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-6000.c
--- linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-6000.c.orig	2010-04-13 14:30:36.000000000 -0400
+++ linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-6000.c	2010-04-13 14:32:09.000000000 -0400
@@ -307,6 +307,7 @@ struct iwl_cfg iwl6000i_2agn_cfg = {
 	.adv_thermal_throttle = true,
 	.support_ct_kill_exit = true,
 	.sm_ps_mode = WLAN_HT_CAP_SM_PS_DISABLED,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF,
 };
 
 struct iwl_cfg iwl6000i_2abg_cfg = {
@@ -336,6 +337,7 @@ struct iwl_cfg iwl6000i_2abg_cfg = {
 	.supports_idle = true,
 	.adv_thermal_throttle = true,
 	.support_ct_kill_exit = true,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF,
 };
 
 struct iwl_cfg iwl6000i_2bg_cfg = {
@@ -365,6 +367,7 @@ struct iwl_cfg iwl6000i_2bg_cfg = {
 	.supports_idle = true,
 	.adv_thermal_throttle = true,
 	.support_ct_kill_exit = true,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF,
 };
 
 struct iwl_cfg iwl6050_2agn_cfg = {
@@ -396,6 +399,7 @@ struct iwl_cfg iwl6050_2agn_cfg = {
 	.adv_thermal_throttle = true,
 	.support_ct_kill_exit = true,
 	.sm_ps_mode = WLAN_HT_CAP_SM_PS_DYNAMIC,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF,
 };
 
 struct iwl_cfg iwl6050_2abg_cfg = {
@@ -425,6 +429,7 @@ struct iwl_cfg iwl6050_2abg_cfg = {
 	.supports_idle = true,
 	.adv_thermal_throttle = true,
 	.support_ct_kill_exit = true,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF,
 };
 
 struct iwl_cfg iwl6000_3agn_cfg = {
@@ -456,6 +461,7 @@ struct iwl_cfg iwl6000_3agn_cfg = {
 	.adv_thermal_throttle = true,
 	.support_ct_kill_exit = true,
 	.sm_ps_mode = WLAN_HT_CAP_SM_PS_DISABLED,
+	.plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF,
 };
 
 MODULE_FIRMWARE(IWL6000_MODULE_FIRMWARE(IWL6000_UCODE_API_MAX));
diff -up linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-core.h.orig linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-core.h
--- linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-core.h.orig	2010-04-13 14:30:36.000000000 -0400
+++ linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-core.h	2010-04-13 14:30:41.000000000 -0400
@@ -232,6 +232,8 @@ struct iwl_mod_params {
  * @support_ct_kill_exit: support ct kill exit condition
  * @sm_ps_mode: spatial multiplexing power save mode
  * @support_wimax_coexist: support wimax/wifi co-exist
+ * @plcp_delta_threshold: plcp error rate threshold used to trigger
+ *	radio tuning when there is a high receiving plcp error rate
  *
  * We enable the driver to be backward compatible wrt API version. The
  * driver specifies which APIs it supports (with @ucode_api_max being the
@@ -289,6 +291,7 @@ struct iwl_cfg {
 	bool support_ct_kill_exit;
 	u8 sm_ps_mode;
 	const bool support_wimax_coexist;
+	u8 plcp_delta_threshold;
 };
 
 /***************************
diff -up linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-debugfs.c.orig linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-debugfs.c
--- linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-debugfs.c.orig	2010-04-13 14:30:36.000000000 -0400
+++ linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-debugfs.c	2010-04-13 14:30:41.000000000 -0400
@@ -1866,6 +1866,47 @@ static ssize_t iwl_dbgfs_internal_scan_w
 	return count;
 }
 
+static ssize_t iwl_dbgfs_plcp_delta_read(struct file *file,
+					char __user *user_buf,
+					size_t count, loff_t *ppos) {
+
+	struct iwl_priv *priv = (struct iwl_priv *)file->private_data;
+	int pos = 0;
+	char buf[12];
+	const size_t bufsz = sizeof(buf);
+	ssize_t ret;
+
+	pos += scnprintf(buf + pos, bufsz - pos, "%u\n",
+			priv->cfg->plcp_delta_threshold);
+
+	ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos);
+	return ret;
+}
+
+static ssize_t iwl_dbgfs_plcp_delta_write(struct file *file,
+					const char __user *user_buf,
+					size_t count, loff_t *ppos) {
+
+	struct iwl_priv *priv = file->private_data;
+	char buf[8];
+	int buf_size;
+	int plcp;
+
+	memset(buf, 0, sizeof(buf));
+	buf_size = min(count, sizeof(buf) -  1);
+	if (copy_from_user(buf, user_buf, buf_size))
+		return -EFAULT;
+	if (sscanf(buf, "%d", &plcp) != 1)
+		return -EINVAL;
+	if ((plcp <= IWL_MAX_PLCP_ERR_THRESHOLD_MIN) ||
+		(plcp > IWL_MAX_PLCP_ERR_THRESHOLD_MAX))
+		priv->cfg->plcp_delta_threshold =
+			IWL_MAX_PLCP_ERR_THRESHOLD_DEF;
+	else
+		priv->cfg->plcp_delta_threshold = plcp;
+	return count;
+}
+
 DEBUGFS_READ_FILE_OPS(rx_statistics);
 DEBUGFS_READ_FILE_OPS(tx_statistics);
 DEBUGFS_READ_WRITE_FILE_OPS(traffic_log);
@@ -1881,6 +1922,7 @@ DEBUGFS_READ_FILE_OPS(power_save_status)
 DEBUGFS_WRITE_FILE_OPS(clear_ucode_statistics);
 DEBUGFS_WRITE_FILE_OPS(clear_traffic_statistics);
 DEBUGFS_WRITE_FILE_OPS(internal_scan);
+DEBUGFS_READ_WRITE_FILE_OPS(plcp_delta);
 
 /*
  * Create the debugfs files and directories
@@ -1932,6 +1974,7 @@ int iwl_dbgfs_register(struct iwl_priv *
 	DEBUGFS_ADD_FILE(clear_ucode_statistics, debug, S_IWUSR);
 	DEBUGFS_ADD_FILE(clear_traffic_statistics, debug, S_IWUSR);
 	DEBUGFS_ADD_FILE(internal_scan, debug, S_IWUSR);
+	DEBUGFS_ADD_FILE(plcp_delta, debug, S_IWUSR | S_IRUSR);
 	if ((priv->hw_rev & CSR_HW_REV_TYPE_MSK) != CSR_HW_REV_TYPE_3945) {
 		DEBUGFS_ADD_FILE(ucode_rx_stats, debug, S_IRUSR);
 		DEBUGFS_ADD_FILE(ucode_tx_stats, debug, S_IRUSR);
@@ -1990,6 +2033,7 @@ void iwl_dbgfs_unregister(struct iwl_pri
 	DEBUGFS_REMOVE(priv->dbgfs->dbgfs_debug_files.
 			file_clear_traffic_statistics);
 	DEBUGFS_REMOVE(priv->dbgfs->dbgfs_debug_files.file_internal_scan);
+	DEBUGFS_REMOVE(priv->dbgfs->dbgfs_debug_files.file_plcp_delta);
 	if ((priv->hw_rev & CSR_HW_REV_TYPE_MSK) != CSR_HW_REV_TYPE_3945) {
 		DEBUGFS_REMOVE(priv->dbgfs->dbgfs_debug_files.
 			file_ucode_rx_stats);
diff -up linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-debug.h.orig linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-debug.h
--- linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-debug.h.orig	2010-04-13 14:30:36.000000000 -0400
+++ linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-debug.h	2010-04-13 14:30:41.000000000 -0400
@@ -110,6 +110,7 @@ struct iwl_debugfs {
 		struct dentry *file_clear_ucode_statistics;
 		struct dentry *file_clear_traffic_statistics;
 		struct dentry *file_internal_scan;
+		struct dentry *file_plcp_delta;
 	} dbgfs_debug_files;
 	u32 sram_offset;
 	u32 sram_len;
diff -up linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-dev.h.orig linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-dev.h
--- linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-dev.h.orig	2010-04-13 14:30:36.000000000 -0400
+++ linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-dev.h	2010-04-13 14:30:41.000000000 -0400
@@ -984,6 +984,15 @@ struct iwl_switch_rxon {
 	__le16 channel;
 };
 
+/*
+ * This is the threshold value of plcp error rate per 100mSecs.  It is
+ * used to set and check for the validity of plcp_delta.
+ */
+#define IWL_MAX_PLCP_ERR_THRESHOLD_MIN	(0)
+#define IWL_MAX_PLCP_ERR_THRESHOLD_DEF	(50)
+#define IWL_MAX_PLCP_ERR_LONG_THRESHOLD_DEF	(100)
+#define IWL_MAX_PLCP_ERR_THRESHOLD_MAX	(255)
+
 struct iwl_priv {
 
 	/* ieee device used by generic ieee processing code */
@@ -1012,6 +1021,9 @@ struct iwl_priv {
 	/* ucode beacon time */
 	u32 ucode_beacon_time;
 
+	/* storing the jiffies when the plcp error rate is received */
+	unsigned long plcp_jiffies;
+
 	/* we allocate array of iwl4965_channel_info for NIC's valid channels.
 	 *    Access via channel # using indirect index array */
 	struct iwl_channel_info *channel_info;	/* channel info array */
diff -up linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-rx.c.orig linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-rx.c
--- linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-rx.c.orig	2010-04-13 14:30:36.000000000 -0400
+++ linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-rx.c	2010-04-13 14:30:41.000000000 -0400
@@ -593,11 +593,15 @@ static void iwl_accumulative_statistics(
 
 #define REG_RECALIB_PERIOD (60)
 
+#define PLCP_MSG "plcp_err exceeded %u, %u, %u, %u, %u, %d, %u mSecs\n"
 void iwl_rx_statistics(struct iwl_priv *priv,
 			      struct iwl_rx_mem_buffer *rxb)
 {
 	int change;
 	struct iwl_rx_packet *pkt = rxb_addr(rxb);
+	int combined_plcp_delta;
+	unsigned int plcp_msec;
+	unsigned long plcp_received_jiffies;
 
 	IWL_DEBUG_RX(priv, "Statistics notification received (%d vs %d).\n",
 		     (int)sizeof(priv->statistics),
@@ -612,6 +616,56 @@ void iwl_rx_statistics(struct iwl_priv *
 #ifdef CONFIG_IWLWIFI_DEBUG
 	iwl_accumulative_statistics(priv, (__le32 *)&pkt->u.stats);
 #endif
+	/*
+	 * 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) {
+		combined_plcp_delta =
+			(le32_to_cpu(pkt->u.stats.rx.ofdm.plcp_err) -
+			le32_to_cpu(priv->statistics.rx.ofdm.plcp_err)) +
+			(le32_to_cpu(pkt->u.stats.rx.ofdm_ht.plcp_err) -
+			le32_to_cpu(priv->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_MSG,
+				priv->cfg->plcp_delta_threshold,
+				le32_to_cpu(pkt->u.stats.rx.ofdm.plcp_err),
+				le32_to_cpu(priv->statistics.rx.ofdm.plcp_err),
+				le32_to_cpu(pkt->u.stats.rx.ofdm_ht.plcp_err),
+				le32_to_cpu(
+					priv->statistics.rx.ofdm_ht.plcp_err),
+				combined_plcp_delta, plcp_msec);
+
+			/*
+			 * Reset the RF radio due to the high plcp
+			 * error rate
+			 */
+			iwl_force_rf_reset(priv);
+		}
+	}
+
 	memcpy(&priv->statistics, &pkt->u.stats, sizeof(priv->statistics));
 
 	set_bit(STATUS_STATISTICS, &priv->status);