| Wey-Yi Guy | be663ab | 2011-02-21 11:27:26 -0800 | [diff] [blame] | 1 | /****************************************************************************** | 
|  | 2 | * | 
|  | 3 | * GPL LICENSE SUMMARY | 
|  | 4 | * | 
|  | 5 | * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. | 
|  | 6 | * | 
|  | 7 | * This program is free software; you can redistribute it and/or modify | 
|  | 8 | * it under the terms of version 2 of the GNU General Public License as | 
|  | 9 | * published by the Free Software Foundation. | 
|  | 10 | * | 
|  | 11 | * This program is distributed in the hope that it will be useful, but | 
|  | 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | 14 | * General Public License for more details. | 
|  | 15 | * | 
|  | 16 | * You should have received a copy of the GNU General Public License | 
|  | 17 | * along with this program; if not, write to the Free Software | 
|  | 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, | 
|  | 19 | * USA | 
|  | 20 | * | 
|  | 21 | * The full GNU General Public License is included in this distribution | 
|  | 22 | * in the file called LICENSE.GPL. | 
|  | 23 | * | 
|  | 24 | * Contact Information: | 
|  | 25 | *  Intel Linux Wireless <ilw@linux.intel.com> | 
|  | 26 | * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 | 
|  | 27 | * | 
|  | 28 | *****************************************************************************/ | 
|  | 29 |  | 
|  | 30 | #include <linux/kernel.h> | 
|  | 31 | #include <linux/module.h> | 
|  | 32 | #include <linux/init.h> | 
|  | 33 | #include <linux/sched.h> | 
|  | 34 |  | 
|  | 35 | #include "iwl-dev.h" | 
|  | 36 | #include "iwl-core.h" | 
|  | 37 | #include "iwl-4965-calib.h" | 
|  | 38 | #include "iwl-sta.h" | 
|  | 39 | #include "iwl-io.h" | 
|  | 40 | #include "iwl-helpers.h" | 
|  | 41 | #include "iwl-4965-hw.h" | 
|  | 42 | #include "iwl-4965.h" | 
|  | 43 |  | 
|  | 44 | void iwl4965_rx_missed_beacon_notif(struct iwl_priv *priv, | 
|  | 45 | struct iwl_rx_mem_buffer *rxb) | 
|  | 46 |  | 
|  | 47 | { | 
|  | 48 | struct iwl_rx_packet *pkt = rxb_addr(rxb); | 
|  | 49 | struct iwl_missed_beacon_notif *missed_beacon; | 
|  | 50 |  | 
|  | 51 | missed_beacon = &pkt->u.missed_beacon; | 
|  | 52 | if (le32_to_cpu(missed_beacon->consecutive_missed_beacons) > | 
|  | 53 | priv->missed_beacon_threshold) { | 
|  | 54 | IWL_DEBUG_CALIB(priv, | 
|  | 55 | "missed bcn cnsq %d totl %d rcd %d expctd %d\n", | 
|  | 56 | le32_to_cpu(missed_beacon->consecutive_missed_beacons), | 
|  | 57 | le32_to_cpu(missed_beacon->total_missed_becons), | 
|  | 58 | le32_to_cpu(missed_beacon->num_recvd_beacons), | 
|  | 59 | le32_to_cpu(missed_beacon->num_expected_beacons)); | 
|  | 60 | if (!test_bit(STATUS_SCANNING, &priv->status)) | 
|  | 61 | iwl4965_init_sensitivity(priv); | 
|  | 62 | } | 
|  | 63 | } | 
|  | 64 |  | 
|  | 65 | /* Calculate noise level, based on measurements during network silence just | 
|  | 66 | *   before arriving beacon.  This measurement can be done only if we know | 
|  | 67 | *   exactly when to expect beacons, therefore only when we're associated. */ | 
|  | 68 | static void iwl4965_rx_calc_noise(struct iwl_priv *priv) | 
|  | 69 | { | 
|  | 70 | struct statistics_rx_non_phy *rx_info; | 
|  | 71 | int num_active_rx = 0; | 
|  | 72 | int total_silence = 0; | 
|  | 73 | int bcn_silence_a, bcn_silence_b, bcn_silence_c; | 
|  | 74 | int last_rx_noise; | 
|  | 75 |  | 
|  | 76 | rx_info = &(priv->_4965.statistics.rx.general); | 
|  | 77 | bcn_silence_a = | 
|  | 78 | le32_to_cpu(rx_info->beacon_silence_rssi_a) & IN_BAND_FILTER; | 
|  | 79 | bcn_silence_b = | 
|  | 80 | le32_to_cpu(rx_info->beacon_silence_rssi_b) & IN_BAND_FILTER; | 
|  | 81 | bcn_silence_c = | 
|  | 82 | le32_to_cpu(rx_info->beacon_silence_rssi_c) & IN_BAND_FILTER; | 
|  | 83 |  | 
|  | 84 | if (bcn_silence_a) { | 
|  | 85 | total_silence += bcn_silence_a; | 
|  | 86 | num_active_rx++; | 
|  | 87 | } | 
|  | 88 | if (bcn_silence_b) { | 
|  | 89 | total_silence += bcn_silence_b; | 
|  | 90 | num_active_rx++; | 
|  | 91 | } | 
|  | 92 | if (bcn_silence_c) { | 
|  | 93 | total_silence += bcn_silence_c; | 
|  | 94 | num_active_rx++; | 
|  | 95 | } | 
|  | 96 |  | 
|  | 97 | /* Average among active antennas */ | 
|  | 98 | if (num_active_rx) | 
|  | 99 | last_rx_noise = (total_silence / num_active_rx) - 107; | 
|  | 100 | else | 
|  | 101 | last_rx_noise = IWL_NOISE_MEAS_NOT_AVAILABLE; | 
|  | 102 |  | 
|  | 103 | IWL_DEBUG_CALIB(priv, "inband silence a %u, b %u, c %u, dBm %d\n", | 
|  | 104 | bcn_silence_a, bcn_silence_b, bcn_silence_c, | 
|  | 105 | last_rx_noise); | 
|  | 106 | } | 
|  | 107 |  | 
|  | 108 | #ifdef CONFIG_IWLWIFI_LEGACY_DEBUGFS | 
|  | 109 | /* | 
|  | 110 | *  based on the assumption of all statistics counter are in DWORD | 
|  | 111 | *  FIXME: This function is for debugging, do not deal with | 
|  | 112 | *  the case of counters roll-over. | 
|  | 113 | */ | 
|  | 114 | static void iwl4965_accumulative_statistics(struct iwl_priv *priv, | 
|  | 115 | __le32 *stats) | 
|  | 116 | { | 
|  | 117 | int i, size; | 
|  | 118 | __le32 *prev_stats; | 
|  | 119 | u32 *accum_stats; | 
|  | 120 | u32 *delta, *max_delta; | 
|  | 121 | struct statistics_general_common *general, *accum_general; | 
|  | 122 | struct statistics_tx *tx, *accum_tx; | 
|  | 123 |  | 
|  | 124 | prev_stats = (__le32 *)&priv->_4965.statistics; | 
|  | 125 | accum_stats = (u32 *)&priv->_4965.accum_statistics; | 
|  | 126 | size = sizeof(struct iwl_notif_statistics); | 
|  | 127 | general = &priv->_4965.statistics.general.common; | 
|  | 128 | accum_general = &priv->_4965.accum_statistics.general.common; | 
|  | 129 | tx = &priv->_4965.statistics.tx; | 
|  | 130 | accum_tx = &priv->_4965.accum_statistics.tx; | 
|  | 131 | delta = (u32 *)&priv->_4965.delta_statistics; | 
|  | 132 | max_delta = (u32 *)&priv->_4965.max_delta; | 
|  | 133 |  | 
|  | 134 | for (i = sizeof(__le32); i < size; | 
|  | 135 | i += sizeof(__le32), stats++, prev_stats++, delta++, | 
|  | 136 | max_delta++, accum_stats++) { | 
|  | 137 | if (le32_to_cpu(*stats) > le32_to_cpu(*prev_stats)) { | 
|  | 138 | *delta = (le32_to_cpu(*stats) - | 
|  | 139 | le32_to_cpu(*prev_stats)); | 
|  | 140 | *accum_stats += *delta; | 
|  | 141 | if (*delta > *max_delta) | 
|  | 142 | *max_delta = *delta; | 
|  | 143 | } | 
|  | 144 | } | 
|  | 145 |  | 
|  | 146 | /* reset accumulative statistics for "no-counter" type statistics */ | 
|  | 147 | accum_general->temperature = general->temperature; | 
|  | 148 | accum_general->ttl_timestamp = general->ttl_timestamp; | 
|  | 149 | } | 
|  | 150 | #endif | 
|  | 151 |  | 
|  | 152 | #define REG_RECALIB_PERIOD (60) | 
|  | 153 |  | 
|  | 154 | /** | 
|  | 155 | * iwl4965_good_plcp_health - checks for plcp error. | 
|  | 156 | * | 
|  | 157 | * When the plcp error is exceeding the thresholds, reset the radio | 
|  | 158 | * to improve the throughput. | 
|  | 159 | */ | 
|  | 160 | bool iwl4965_good_plcp_health(struct iwl_priv *priv, | 
|  | 161 | struct iwl_rx_packet *pkt) | 
|  | 162 | { | 
|  | 163 | bool rc = true; | 
|  | 164 | int combined_plcp_delta; | 
|  | 165 | unsigned int plcp_msec; | 
|  | 166 | unsigned long plcp_received_jiffies; | 
|  | 167 |  | 
|  | 168 | if (priv->cfg->base_params->plcp_delta_threshold == | 
|  | 169 | IWL_MAX_PLCP_ERR_THRESHOLD_DISABLE) { | 
|  | 170 | IWL_DEBUG_RADIO(priv, "plcp_err check disabled\n"); | 
|  | 171 | return rc; | 
|  | 172 | } | 
|  | 173 |  | 
|  | 174 | /* | 
|  | 175 | * check for plcp_err and trigger radio reset if it exceeds | 
|  | 176 | * the plcp error threshold plcp_delta. | 
|  | 177 | */ | 
|  | 178 | plcp_received_jiffies = jiffies; | 
|  | 179 | plcp_msec = jiffies_to_msecs((long) plcp_received_jiffies - | 
|  | 180 | (long) priv->plcp_jiffies); | 
|  | 181 | priv->plcp_jiffies = plcp_received_jiffies; | 
|  | 182 | /* | 
|  | 183 | * check to make sure plcp_msec is not 0 to prevent division | 
|  | 184 | * by zero. | 
|  | 185 | */ | 
|  | 186 | if (plcp_msec) { | 
|  | 187 | struct statistics_rx_phy *ofdm; | 
|  | 188 | struct statistics_rx_ht_phy *ofdm_ht; | 
|  | 189 |  | 
|  | 190 | ofdm = &pkt->u.stats.rx.ofdm; | 
|  | 191 | ofdm_ht = &pkt->u.stats.rx.ofdm_ht; | 
|  | 192 | combined_plcp_delta = | 
|  | 193 | (le32_to_cpu(ofdm->plcp_err) - | 
|  | 194 | le32_to_cpu(priv->_4965.statistics. | 
|  | 195 | rx.ofdm.plcp_err)) + | 
|  | 196 | (le32_to_cpu(ofdm_ht->plcp_err) - | 
|  | 197 | le32_to_cpu(priv->_4965.statistics. | 
|  | 198 | rx.ofdm_ht.plcp_err)); | 
|  | 199 |  | 
|  | 200 | if ((combined_plcp_delta > 0) && | 
|  | 201 | ((combined_plcp_delta * 100) / plcp_msec) > | 
|  | 202 | priv->cfg->base_params->plcp_delta_threshold) { | 
|  | 203 | /* | 
|  | 204 | * if plcp_err exceed the threshold, | 
|  | 205 | * the following data is printed in csv format: | 
|  | 206 | *    Text: plcp_err exceeded %d, | 
|  | 207 | *    Received ofdm.plcp_err, | 
|  | 208 | *    Current ofdm.plcp_err, | 
|  | 209 | *    Received ofdm_ht.plcp_err, | 
|  | 210 | *    Current ofdm_ht.plcp_err, | 
|  | 211 | *    combined_plcp_delta, | 
|  | 212 | *    plcp_msec | 
|  | 213 | */ | 
|  | 214 | IWL_DEBUG_RADIO(priv, "plcp_err exceeded %u, " | 
|  | 215 | "%u, %u, %u, %u, %d, %u mSecs\n", | 
|  | 216 | priv->cfg->base_params->plcp_delta_threshold, | 
|  | 217 | le32_to_cpu(ofdm->plcp_err), | 
|  | 218 | le32_to_cpu(ofdm->plcp_err), | 
|  | 219 | le32_to_cpu(ofdm_ht->plcp_err), | 
|  | 220 | le32_to_cpu(ofdm_ht->plcp_err), | 
|  | 221 | combined_plcp_delta, plcp_msec); | 
|  | 222 |  | 
|  | 223 | rc = false; | 
|  | 224 | } | 
|  | 225 | } | 
|  | 226 | return rc; | 
|  | 227 | } | 
|  | 228 |  | 
|  | 229 | void iwl4965_rx_statistics(struct iwl_priv *priv, | 
|  | 230 | struct iwl_rx_mem_buffer *rxb) | 
|  | 231 | { | 
|  | 232 | int change; | 
|  | 233 | struct iwl_rx_packet *pkt = rxb_addr(rxb); | 
|  | 234 |  | 
|  | 235 | IWL_DEBUG_RX(priv, | 
|  | 236 | "Statistics notification received (%d vs %d).\n", | 
|  | 237 | (int)sizeof(struct iwl_notif_statistics), | 
|  | 238 | le32_to_cpu(pkt->len_n_flags) & | 
|  | 239 | FH_RSCSR_FRAME_SIZE_MSK); | 
|  | 240 |  | 
|  | 241 | change = ((priv->_4965.statistics.general.common.temperature != | 
|  | 242 | pkt->u.stats.general.common.temperature) || | 
|  | 243 | ((priv->_4965.statistics.flag & | 
|  | 244 | STATISTICS_REPLY_FLG_HT40_MODE_MSK) != | 
|  | 245 | (pkt->u.stats.flag & | 
|  | 246 | STATISTICS_REPLY_FLG_HT40_MODE_MSK))); | 
|  | 247 | #ifdef CONFIG_IWLWIFI_LEGACY_DEBUGFS | 
|  | 248 | iwl4965_accumulative_statistics(priv, (__le32 *)&pkt->u.stats); | 
|  | 249 | #endif | 
|  | 250 |  | 
|  | 251 | iwl_legacy_recover_from_statistics(priv, pkt); | 
|  | 252 |  | 
|  | 253 | memcpy(&priv->_4965.statistics, &pkt->u.stats, | 
|  | 254 | sizeof(priv->_4965.statistics)); | 
|  | 255 |  | 
|  | 256 | set_bit(STATUS_STATISTICS, &priv->status); | 
|  | 257 |  | 
|  | 258 | /* Reschedule the statistics timer to occur in | 
|  | 259 | * REG_RECALIB_PERIOD seconds to ensure we get a | 
|  | 260 | * thermal update even if the uCode doesn't give | 
|  | 261 | * us one */ | 
|  | 262 | mod_timer(&priv->statistics_periodic, jiffies + | 
|  | 263 | msecs_to_jiffies(REG_RECALIB_PERIOD * 1000)); | 
|  | 264 |  | 
|  | 265 | if (unlikely(!test_bit(STATUS_SCANNING, &priv->status)) && | 
|  | 266 | (pkt->hdr.cmd == STATISTICS_NOTIFICATION)) { | 
|  | 267 | iwl4965_rx_calc_noise(priv); | 
|  | 268 | queue_work(priv->workqueue, &priv->run_time_calib_work); | 
|  | 269 | } | 
|  | 270 | if (priv->cfg->ops->lib->temp_ops.temperature && change) | 
|  | 271 | priv->cfg->ops->lib->temp_ops.temperature(priv); | 
|  | 272 | } | 
|  | 273 |  | 
|  | 274 | void iwl4965_reply_statistics(struct iwl_priv *priv, | 
|  | 275 | struct iwl_rx_mem_buffer *rxb) | 
|  | 276 | { | 
|  | 277 | struct iwl_rx_packet *pkt = rxb_addr(rxb); | 
|  | 278 |  | 
|  | 279 | if (le32_to_cpu(pkt->u.stats.flag) & UCODE_STATISTICS_CLEAR_MSK) { | 
|  | 280 | #ifdef CONFIG_IWLWIFI_LEGACY_DEBUGFS | 
|  | 281 | memset(&priv->_4965.accum_statistics, 0, | 
|  | 282 | sizeof(struct iwl_notif_statistics)); | 
|  | 283 | memset(&priv->_4965.delta_statistics, 0, | 
|  | 284 | sizeof(struct iwl_notif_statistics)); | 
|  | 285 | memset(&priv->_4965.max_delta, 0, | 
|  | 286 | sizeof(struct iwl_notif_statistics)); | 
|  | 287 | #endif | 
|  | 288 | IWL_DEBUG_RX(priv, "Statistics have been cleared\n"); | 
|  | 289 | } | 
|  | 290 | iwl4965_rx_statistics(priv, rxb); | 
|  | 291 | } |