Backport of the following upstream commit... commit d5a0ffa3eaf9e898f25a925813f1a723be7808f8 Author: Wey-Yi Guy Date: Thu Mar 4 13:38:59 2010 -0800 iwlwifi: Recover TX flow failure Monitors the tx statistics to detect the drop in throughput. When the throughput drops, the ratio of the actual_ack_count and the expected_ack_count also drops. At the same time, the aggregated ba_timeout (the number of ba timeout retries) also rises. If the actual_ack_count/expected_ack_count ratio is 0 and the number of ba timeout retries rises to BA_TIMEOUT_MAX, no tx packets can be delivered. Reloading the uCode and bring the system back to normal operational state. Signed-off-by: Trieu 'Andrew' Nguyen Signed-off-by: Wey-Yi Guy Signed-off-by: Reinette Chatre diff -up linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-agn.c.orig linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-agn.c --- linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-agn.c.orig 2010-04-13 14:44:38.000000000 -0400 +++ linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-agn.c 2010-04-13 14:53:47.000000000 -0400 @@ -2783,10 +2783,21 @@ static int iwl_mac_ampdu_action(struct i return ret; case IEEE80211_AMPDU_TX_START: IWL_DEBUG_HT(priv, "start Tx\n"); - return iwl_tx_agg_start(priv, sta->addr, tid, ssn); + ret = iwl_tx_agg_start(priv, sta->addr, tid, ssn); + if (ret == 0) { + priv->agg_tids_count++; + IWL_DEBUG_HT(priv, "priv->agg_tids_count = %u\n", + priv->agg_tids_count); + } + return ret; case IEEE80211_AMPDU_TX_STOP: IWL_DEBUG_HT(priv, "stop Tx\n"); ret = iwl_tx_agg_stop(priv, sta->addr, tid); + if ((ret == 0) && (priv->agg_tids_count > 0)) { + priv->agg_tids_count--; + IWL_DEBUG_HT(priv, "priv->agg_tids_count = %u\n", + priv->agg_tids_count); + } if (test_bit(STATUS_EXIT_PENDING, &priv->status)) return 0; else @@ -3204,6 +3215,7 @@ static int iwl_init_drv(struct iwl_priv priv->band = IEEE80211_BAND_2GHZ; priv->iw_mode = NL80211_IFTYPE_STATION; + priv->agg_tids_count = 0; /* initialize force reset */ priv->force_reset[IWL_RF_RESET].reset_duration = 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:44:38.000000000 -0400 +++ linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-dev.h 2010-04-13 14:53:19.000000000 -0400 @@ -1239,6 +1239,11 @@ struct iwl_priv { void *ict_tbl_vir; u32 inta; bool use_ict; + /* + * reporting the number of tids has AGG on. 0 means + * no AGGREGATION + */ + u8 agg_tids_count; u32 inta_mask; /* Current association information needed to configure the 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:44:38.000000000 -0400 +++ linux-2.6.33.noarch/drivers/net/wireless/iwlwifi/iwl-rx.c 2010-04-13 14:56:17.000000000 -0400 @@ -593,9 +593,18 @@ static void iwl_accumulative_statistics( #define REG_RECALIB_PERIOD (60) +/* the threshold ratio of actual_ack_cnt to expected_ack_cnt in percent */ +#define ACK_CNT_RATIO (50) +#define BA_TIMEOUT_CNT (5) +#define BA_TIMEOUT_MAX (16) + #define PLCP_MSG "plcp_err exceeded %u, %u, %u, %u, %u, %d, %u mSecs\n" /* - * This function checks for plcp error. + * This function checks for plcp error, ACK count ratios, aggregated BA + * timeout retries. + * - When the ACK count ratio is 0 and aggregated BA timeout retries is + * exceeding the BA_TIMEOUT_MAX, it will recover the failure by resetting + * the firmware. * - When the plcp error is exceeding the thresholds, it will reset the radio * to improve the throughput. */ @@ -605,6 +614,37 @@ void iwl_recover_from_statistics(struct int combined_plcp_delta; unsigned int plcp_msec; unsigned long plcp_received_jiffies; + int actual_ack_cnt_delta; + int expected_ack_cnt_delta; + int ba_timeout_delta; + + actual_ack_cnt_delta = + le32_to_cpu(pkt->u.stats.tx.actual_ack_cnt) - + le32_to_cpu(priv->statistics.tx.actual_ack_cnt); + expected_ack_cnt_delta = + le32_to_cpu(pkt->u.stats.tx.expected_ack_cnt) - + le32_to_cpu(priv->statistics.tx.expected_ack_cnt); + ba_timeout_delta = + le32_to_cpu(pkt->u.stats.tx.agg.ba_timeout) - + le32_to_cpu(priv->statistics.tx.agg.ba_timeout); + if ((priv->agg_tids_count > 0) && + (expected_ack_cnt_delta > 0) && + (((actual_ack_cnt_delta * 100) / expected_ack_cnt_delta) + < ACK_CNT_RATIO) && + (ba_timeout_delta > BA_TIMEOUT_CNT)) { + IWL_DEBUG_RADIO(priv, "actual_ack_cnt delta = %d," + " expected_ack_cnt = %d\n", + actual_ack_cnt_delta, expected_ack_cnt_delta); + + IWL_DEBUG_RADIO(priv, "agg ba_timeout delta = %d\n", + ba_timeout_delta); + if ((actual_ack_cnt_delta == 0) && + (ba_timeout_delta >= BA_TIMEOUT_MAX)) { + IWL_DEBUG_RADIO(priv, + "call iwl_force_reset(IWL_FW_RESET)\n"); + iwl_force_reset(priv, IWL_FW_RESET); + } + } /* * check for plcp_err and trigger radio reset if it exceeds