iwlwifi: add function to reset/tune radio if needed
authorWey-Yi Guy <wey-yi.w.guy@intel.com>
Fri, 22 Jan 2010 22:22:43 +0000 (14:22 -0800)
committerJohn W. Linville <linville@tuxdriver.com>
Mon, 25 Jan 2010 21:36:19 +0000 (16:36 -0500)
Adding "radio reset" function to help reset and stabilize the radio.

During normal operation, sometime for unknown reason, radio encounter
problem and can not recover by itself; the best way to
recover from it is to reset and re-tune the radio. Currently, there is
no RF reset command available, but since radio will get reset when
switching channel, use internal hw scan request to force radio
reset and get back to normal operation state.

The internal hw scan will only perform passive scan on the first
available channel (not the channel being used) in associated state. The
request should be ignored if already performing scan operation or STA is
not in associated state.

Also include an "internal_scan" debugfs file to help trigger the
internal scan from user mode.

Signed-off-by: Wey-Yi Guy <wey-yi.w.guy@intel.com>
Signed-off-by: Reinette Chatre <reinette.chatre@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
drivers/net/wireless/iwlwifi/iwl-core.c
drivers/net/wireless/iwlwifi/iwl-core.h
drivers/net/wireless/iwlwifi/iwl-debug.h
drivers/net/wireless/iwlwifi/iwl-debugfs.c
drivers/net/wireless/iwlwifi/iwl-dev.h
drivers/net/wireless/iwlwifi/iwl-scan.c

index bb3ed25..645bc13 100644 (file)
@@ -3343,6 +3343,30 @@ int iwl_dump_fh(struct iwl_priv *priv, char **buf, bool display)
 }
 EXPORT_SYMBOL(iwl_dump_fh);
 
+void iwl_force_rf_reset(struct iwl_priv *priv)
+{
+       if (test_bit(STATUS_EXIT_PENDING, &priv->status))
+               return;
+
+       if (!iwl_is_associated(priv)) {
+               IWL_DEBUG_SCAN(priv, "force reset rejected: not associated\n");
+               return;
+       }
+       /*
+        * There is no easy and better way to force reset the radio,
+        * the only known method is switching channel which will force to
+        * reset and tune the radio.
+        * Use internal short scan (single channel) operation to should
+        * achieve this objective.
+        * Driver should reset the radio when number of consecutive missed
+        * beacon, or any other uCode error condition detected.
+        */
+       IWL_DEBUG_INFO(priv, "perform radio reset.\n");
+       iwl_internal_short_hw_scan(priv);
+       return;
+}
+EXPORT_SYMBOL(iwl_force_rf_reset);
+
 #ifdef CONFIG_PM
 
 int iwl_pci_suspend(struct pci_dev *pdev, pm_message_t state)
index 785331a..6de83d1 100644 (file)
@@ -494,6 +494,8 @@ void iwl_init_scan_params(struct iwl_priv *priv);
 int iwl_scan_cancel(struct iwl_priv *priv);
 int iwl_scan_cancel_timeout(struct iwl_priv *priv, unsigned long ms);
 int iwl_mac_hw_scan(struct ieee80211_hw *hw, struct cfg80211_scan_request *req);
+int iwl_internal_short_hw_scan(struct iwl_priv *priv);
+void iwl_force_rf_reset(struct iwl_priv *priv);
 u16 iwl_fill_probe_req(struct iwl_priv *priv, struct ieee80211_mgmt *frame,
                       const u8 *ie, int ie_len, int left);
 void iwl_setup_rx_scan_handlers(struct iwl_priv *priv);
index 36b558f..d81b4f3 100644 (file)
@@ -113,6 +113,7 @@ struct iwl_debugfs {
                struct dentry *file_ucode_tracing;
                struct dentry *file_fh_reg;
                struct dentry *file_missed_beacon;
+               struct dentry *file_internal_scan;
        } dbgfs_debug_files;
        u32 sram_offset;
        u32 sram_len;
index 02f80bc..4944fdb 100644 (file)
@@ -2174,6 +2174,27 @@ static ssize_t iwl_dbgfs_missed_beacon_write(struct file *file,
        return count;
 }
 
+static ssize_t iwl_dbgfs_internal_scan_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 scan;
+
+       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", &scan) != 1)
+               return -EINVAL;
+
+       iwl_internal_short_hw_scan(priv);
+
+       return count;
+}
+
 DEBUGFS_READ_FILE_OPS(rx_statistics);
 DEBUGFS_READ_FILE_OPS(tx_statistics);
 DEBUGFS_READ_WRITE_FILE_OPS(traffic_log);
@@ -2192,6 +2213,7 @@ DEBUGFS_WRITE_FILE_OPS(csr);
 DEBUGFS_READ_WRITE_FILE_OPS(ucode_tracing);
 DEBUGFS_READ_FILE_OPS(fh_reg);
 DEBUGFS_READ_WRITE_FILE_OPS(missed_beacon);
+DEBUGFS_WRITE_FILE_OPS(internal_scan);
 
 /*
  * Create the debugfs files and directories
@@ -2245,6 +2267,7 @@ int iwl_dbgfs_register(struct iwl_priv *priv, const char *name)
        DEBUGFS_ADD_FILE(csr, debug, S_IWUSR);
        DEBUGFS_ADD_FILE(fh_reg, debug, S_IRUSR);
        DEBUGFS_ADD_FILE(missed_beacon, debug, S_IWUSR);
+       DEBUGFS_ADD_FILE(internal_scan, debug, S_IWUSR);
        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);
@@ -2306,6 +2329,7 @@ void iwl_dbgfs_unregister(struct iwl_priv *priv)
        DEBUGFS_REMOVE(priv->dbgfs->dbgfs_debug_files.file_csr);
        DEBUGFS_REMOVE(priv->dbgfs->dbgfs_debug_files.file_fh_reg);
        DEBUGFS_REMOVE(priv->dbgfs->dbgfs_debug_files.file_missed_beacon);
+       DEBUGFS_REMOVE(priv->dbgfs->dbgfs_debug_files.file_internal_scan);
        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);
index 5e06e66..502d7a6 100644 (file)
@@ -1080,6 +1080,7 @@ struct iwl_priv {
        void *scan;
        int scan_bands;
        struct cfg80211_scan_request *scan_request;
+       bool is_internal_short_scan;
        u8 scan_tx_ant[IEEE80211_NUM_BANDS];
        u8 mgmt_tx_ant;
 
index ceb91f9..fd6bafb 100644 (file)
@@ -314,6 +314,72 @@ u16 iwl_get_passive_dwell_time(struct iwl_priv *priv,
 }
 EXPORT_SYMBOL(iwl_get_passive_dwell_time);
 
+static int iwl_get_single_channel_for_scan(struct iwl_priv *priv,
+                                    enum ieee80211_band band,
+                                    struct iwl_scan_channel *scan_ch)
+{
+       const struct ieee80211_supported_band *sband;
+       const struct iwl_channel_info *ch_info;
+       u16 passive_dwell = 0;
+       u16 active_dwell = 0;
+       int i, added = 0;
+       u16 channel = 0;
+
+       sband = iwl_get_hw_mode(priv, band);
+       if (!sband) {
+               IWL_ERR(priv, "invalid band\n");
+               return added;
+       }
+
+       active_dwell = iwl_get_active_dwell_time(priv, band, 0);
+       passive_dwell = iwl_get_passive_dwell_time(priv, band);
+
+       if (passive_dwell <= active_dwell)
+               passive_dwell = active_dwell + 1;
+
+       /* only scan single channel, good enough to reset the RF */
+       /* pick the first valid not in-use channel */
+       if (band == IEEE80211_BAND_5GHZ) {
+               for (i = 14; i < priv->channel_count; i++) {
+                       if (priv->channel_info[i].channel !=
+                           le16_to_cpu(priv->staging_rxon.channel)) {
+                               channel = priv->channel_info[i].channel;
+                               ch_info = iwl_get_channel_info(priv,
+                                       band, channel);
+                               if (is_channel_valid(ch_info))
+                                       break;
+                       }
+               }
+       } else {
+               for (i = 0; i < 14; i++) {
+                       if (priv->channel_info[i].channel !=
+                           le16_to_cpu(priv->staging_rxon.channel)) {
+                                       channel =
+                                               priv->channel_info[i].channel;
+                                       ch_info = iwl_get_channel_info(priv,
+                                               band, channel);
+                                       if (is_channel_valid(ch_info))
+                                               break;
+                       }
+               }
+       }
+       if (channel) {
+               scan_ch->channel = cpu_to_le16(channel);
+               scan_ch->type = SCAN_CHANNEL_TYPE_PASSIVE;
+               scan_ch->active_dwell = cpu_to_le16(active_dwell);
+               scan_ch->passive_dwell = cpu_to_le16(passive_dwell);
+               /* Set txpower levels to defaults */
+               scan_ch->dsp_atten = 110;
+               if (band == IEEE80211_BAND_5GHZ)
+                       scan_ch->tx_gain = ((1 << 5) | (3 << 3)) | 3;
+               else
+                       scan_ch->tx_gain = ((1 << 5) | (5 << 3));
+               added++;
+       } else
+               IWL_ERR(priv, "no valid channel found\n");
+       return added;
+}
+
 static int iwl_get_channels_for_scan(struct iwl_priv *priv,
                                     enum ieee80211_band band,
                                     u8 is_active, u8 n_probes,
@@ -421,6 +487,7 @@ static int iwl_scan_initiate(struct iwl_priv *priv)
 
        IWL_DEBUG_INFO(priv, "Starting scan...\n");
        set_bit(STATUS_SCANNING, &priv->status);
+       priv->is_internal_short_scan = false;
        priv->scan_start = jiffies;
        priv->scan_pass_start = priv->scan_start;
 
@@ -488,6 +555,45 @@ out_unlock:
 }
 EXPORT_SYMBOL(iwl_mac_hw_scan);
 
+/*
+ * internal short scan, this function should only been called while associated.
+ * It will reset and tune the radio to prevent possible RF related problem
+ */
+int iwl_internal_short_hw_scan(struct iwl_priv *priv)
+{
+       int ret = 0;
+
+       if (!iwl_is_ready_rf(priv)) {
+               ret = -EIO;
+               IWL_DEBUG_SCAN(priv, "not ready or exit pending\n");
+               goto out;
+       }
+       if (test_bit(STATUS_SCANNING, &priv->status)) {
+               IWL_DEBUG_SCAN(priv, "Scan already in progress.\n");
+               ret = -EAGAIN;
+               goto out;
+       }
+       if (test_bit(STATUS_SCAN_ABORTING, &priv->status)) {
+               IWL_DEBUG_SCAN(priv, "Scan request while abort pending\n");
+               ret = -EAGAIN;
+               goto out;
+       }
+       priv->scan_bands = 0;
+       if (priv->band == IEEE80211_BAND_5GHZ)
+               priv->scan_bands |= BIT(IEEE80211_BAND_5GHZ);
+       else
+               priv->scan_bands |= BIT(IEEE80211_BAND_2GHZ);
+
+       IWL_DEBUG_SCAN(priv, "Start internal short scan...\n");
+       set_bit(STATUS_SCANNING, &priv->status);
+       priv->is_internal_short_scan = true;
+       queue_work(priv->workqueue, &priv->request_scan);
+
+out:
+       return ret;
+}
+EXPORT_SYMBOL(iwl_internal_short_hw_scan);
+
 #define IWL_SCAN_CHECK_WATCHDOG (7 * HZ)
 
 void iwl_bg_scan_check(struct work_struct *data)
@@ -551,7 +657,8 @@ u16 iwl_fill_probe_req(struct iwl_priv *priv, struct ieee80211_mgmt *frame,
        if (WARN_ON(left < ie_len))
                return len;
 
-       memcpy(pos, ies, ie_len);
+       if (ies)
+               memcpy(pos, ies, ie_len);
        len += ie_len;
        left -= ie_len;
 
@@ -654,7 +761,6 @@ static void iwl_bg_request_scan(struct work_struct *data)
                unsigned long flags;
 
                IWL_DEBUG_INFO(priv, "Scanning while associated...\n");
-
                spin_lock_irqsave(&priv->lock, flags);
                interval = priv->beacon_int;
                spin_unlock_irqrestore(&priv->lock, flags);
@@ -672,7 +778,9 @@ static void iwl_bg_request_scan(struct work_struct *data)
                               scan_suspend_time, interval);
        }
 
-       if (priv->scan_request->n_ssids) {
+       if (priv->is_internal_short_scan) {
+               IWL_DEBUG_SCAN(priv, "Start internal passive scan.\n");
+       } else if (priv->scan_request->n_ssids) {
                int i, p = 0;
                IWL_DEBUG_SCAN(priv, "Kicking off active scan\n");
                for (i = 0; i < priv->scan_request->n_ssids; i++) {
@@ -753,24 +861,38 @@ static void iwl_bg_request_scan(struct work_struct *data)
        rx_chain |= rx_ant << RXON_RX_CHAIN_FORCE_SEL_POS;
        rx_chain |= 0x1 << RXON_RX_CHAIN_DRIVER_FORCE_POS;
        scan->rx_chain = cpu_to_le16(rx_chain);
-       cmd_len = iwl_fill_probe_req(priv,
-                               (struct ieee80211_mgmt *)scan->data,
-                               priv->scan_request->ie,
-                               priv->scan_request->ie_len,
-                               IWL_MAX_SCAN_SIZE - sizeof(*scan));
+       if (!priv->is_internal_short_scan) {
+               cmd_len = iwl_fill_probe_req(priv,
+                                       (struct ieee80211_mgmt *)scan->data,
+                                       priv->scan_request->ie,
+                                       priv->scan_request->ie_len,
+                                       IWL_MAX_SCAN_SIZE - sizeof(*scan));
+       } else {
+               cmd_len = iwl_fill_probe_req(priv,
+                                       (struct ieee80211_mgmt *)scan->data,
+                                       NULL, 0,
+                                       IWL_MAX_SCAN_SIZE - sizeof(*scan));
 
+       }
        scan->tx_cmd.len = cpu_to_le16(cmd_len);
-
        if (iwl_is_monitor_mode(priv))
                scan->filter_flags = RXON_FILTER_PROMISC_MSK;
 
        scan->filter_flags |= (RXON_FILTER_ACCEPT_GRP_MSK |
                               RXON_FILTER_BCON_AWARE_MSK);
 
-       scan->channel_count =
-               iwl_get_channels_for_scan(priv, band, is_active, n_probes,
-                       (void *)&scan->data[le16_to_cpu(scan->tx_cmd.len)]);
-
+       if (priv->is_internal_short_scan) {
+               scan->channel_count =
+                       iwl_get_single_channel_for_scan(priv, band,
+                               (void *)&scan->data[le16_to_cpu(
+                               scan->tx_cmd.len)]);
+       } else {
+               scan->channel_count =
+                       iwl_get_channels_for_scan(priv, band,
+                               is_active, n_probes,
+                               (void *)&scan->data[le16_to_cpu(
+                               scan->tx_cmd.len)]);
+       }
        if (scan->channel_count == 0) {
                IWL_DEBUG_SCAN(priv, "channel count %d\n", scan->channel_count);
                goto done;
@@ -831,7 +953,12 @@ void iwl_bg_scan_completed(struct work_struct *work)
 
        cancel_delayed_work(&priv->scan_check);
 
-       ieee80211_scan_completed(priv->hw, false);
+       if (!priv->is_internal_short_scan)
+               ieee80211_scan_completed(priv->hw, false);
+       else {
+               priv->is_internal_short_scan = false;
+               IWL_DEBUG_SCAN(priv, "internal short scan completed\n");
+       }
 
        if (test_bit(STATUS_EXIT_PENDING, &priv->status))
                return;