summaryrefslogtreecommitdiffstats
path: root/iwlwifi_-add-function-to-reset_tune-radio-if-needed.patch
blob: f83e12abc8055bde82b86b4f1272532b823fa9ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
Back-port of the following upstream commit...

commit afbdd69af0e6a0c40676d4d4b94a0a4414708eaa
Author: Wey-Yi Guy <wey-yi.w.guy@intel.com>
Date:   Fri Jan 22 14:22:43 2010 -0800

    iwlwifi: add function to reset/tune radio if needed
    
    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>

diff -up linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-core.c.orig linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-core.c
--- linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-core.c.orig	2010-03-22 10:23:59.000000000 -0400
+++ linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-core.c	2010-03-22 11:26:18.000000000 -0400
@@ -3035,6 +3035,30 @@ void iwl_update_stats(struct iwl_priv *p
 EXPORT_SYMBOL(iwl_update_stats);
 #endif
 
+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)
diff -up linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-core.h.orig linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-core.h
--- linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-core.h.orig	2010-03-22 10:23:59.000000000 -0400
+++ linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-core.h	2010-03-22 11:26:18.000000000 -0400
@@ -461,6 +461,8 @@ void iwl_init_scan_params(struct iwl_pri
 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);
diff -up linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-debugfs.c.orig linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-debugfs.c
--- linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-debugfs.c.orig	2009-12-02 22:51:21.000000000 -0500
+++ linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-debugfs.c	2010-03-22 11:33:02.000000000 -0400
@@ -1614,6 +1614,27 @@ static ssize_t iwl_dbgfs_tx_power_read(s
 	return simple_read_from_buffer(user_buf, count, ppos, buf, pos);
 }
 
+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_WRITE_FILE_OPS(rx_statistics);
 DEBUGFS_READ_WRITE_FILE_OPS(tx_statistics);
 DEBUGFS_READ_WRITE_FILE_OPS(traffic_log);
@@ -1625,6 +1646,7 @@ DEBUGFS_READ_FILE_OPS(ucode_general_stat
 DEBUGFS_READ_FILE_OPS(sensitivity);
 DEBUGFS_READ_FILE_OPS(chain_noise);
 DEBUGFS_READ_FILE_OPS(tx_power);
+DEBUGFS_WRITE_FILE_OPS(internal_scan);
 
 /*
  * Create the debugfs files and directories
@@ -1674,6 +1696,7 @@ int iwl_dbgfs_register(struct iwl_priv *
 	DEBUGFS_ADD_FILE(rx_queue, debug);
 	DEBUGFS_ADD_FILE(tx_queue, debug);
 	DEBUGFS_ADD_FILE(tx_power, debug);
+	DEBUGFS_ADD_FILE(internal_scan, debug);
 	if ((priv->hw_rev & CSR_HW_REV_TYPE_MSK) != CSR_HW_REV_TYPE_3945) {
 		DEBUGFS_ADD_FILE(ucode_rx_stats, debug);
 		DEBUGFS_ADD_FILE(ucode_tx_stats, debug);
@@ -1728,6 +1751,7 @@ void iwl_dbgfs_unregister(struct iwl_pri
 	DEBUGFS_REMOVE(priv->dbgfs->dbgfs_debug_files.file_rx_queue);
 	DEBUGFS_REMOVE(priv->dbgfs->dbgfs_debug_files.file_tx_queue);
 	DEBUGFS_REMOVE(priv->dbgfs->dbgfs_debug_files.file_tx_power);
+	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);
diff -up linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-debug.h.orig linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-debug.h
--- linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-debug.h.orig	2009-12-02 22:51:21.000000000 -0500
+++ linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-debug.h	2010-03-22 11:27:31.000000000 -0400
@@ -108,6 +108,7 @@ struct iwl_debugfs {
 		struct dentry *file_sensitivity;
 		struct dentry *file_chain_noise;
 		struct dentry *file_tx_power;
+		struct dentry *file_internal_scan;
 	} dbgfs_debug_files;
 	u32 sram_offset;
 	u32 sram_len;
diff -up linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-dev.h.orig linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-dev.h
--- linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-dev.h.orig	2010-03-22 10:23:59.000000000 -0400
+++ linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-dev.h	2010-03-22 11:26:18.000000000 -0400
@@ -1016,6 +1016,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;
 
diff -up linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-scan.c.orig linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-scan.c
--- linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-scan.c.orig	2009-12-02 22:51:21.000000000 -0500
+++ linux-2.6.32.noarch/drivers/net/wireless/iwlwifi/iwl-scan.c	2010-03-22 11:26:18.000000000 -0400
@@ -316,6 +316,72 @@ u16 iwl_get_passive_dwell_time(struct iw
 }
 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,
@@ -422,6 +488,7 @@ static int iwl_scan_initiate(struct iwl_
 
 	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;
 
@@ -489,6 +556,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)
@@ -552,7 +658,8 @@ u16 iwl_fill_probe_req(struct iwl_priv *
 	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 w
 		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 w
 			       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++) {
@@ -740,24 +848,38 @@ static void iwl_bg_request_scan(struct w
 	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;
@@ -818,7 +940,12 @@ void iwl_bg_scan_completed(struct work_s
 
 	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;