diff options
Diffstat (limited to 'drivers/net/wireless/iwlwifi/mvm/ops.c')
-rw-r--r-- | drivers/net/wireless/iwlwifi/mvm/ops.c | 679 |
1 files changed, 679 insertions, 0 deletions
diff --git a/drivers/net/wireless/iwlwifi/mvm/ops.c b/drivers/net/wireless/iwlwifi/mvm/ops.c new file mode 100644 index 000000000000..983dca3f888a --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/ops.c | |||
@@ -0,0 +1,679 @@ | |||
1 | /****************************************************************************** | ||
2 | * | ||
3 | * This file is provided under a dual BSD/GPLv2 license. When using or | ||
4 | * redistributing this file, you may do so under either license. | ||
5 | * | ||
6 | * GPL LICENSE SUMMARY | ||
7 | * | ||
8 | * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of version 2 of the GNU General Public License as | ||
12 | * published by the Free Software Foundation. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, but | ||
15 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
17 | * General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with this program; if not, write to the Free Software | ||
21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, | ||
22 | * USA | ||
23 | * | ||
24 | * The full GNU General Public License is included in this distribution | ||
25 | * in the file called LICENSE.GPL. | ||
26 | * | ||
27 | * Contact Information: | ||
28 | * Intel Linux Wireless <ilw@linux.intel.com> | ||
29 | * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 | ||
30 | * | ||
31 | * BSD LICENSE | ||
32 | * | ||
33 | * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. | ||
34 | * All rights reserved. | ||
35 | * | ||
36 | * Redistribution and use in source and binary forms, with or without | ||
37 | * modification, are permitted provided that the following conditions | ||
38 | * are met: | ||
39 | * | ||
40 | * * Redistributions of source code must retain the above copyright | ||
41 | * notice, this list of conditions and the following disclaimer. | ||
42 | * * Redistributions in binary form must reproduce the above copyright | ||
43 | * notice, this list of conditions and the following disclaimer in | ||
44 | * the documentation and/or other materials provided with the | ||
45 | * distribution. | ||
46 | * * Neither the name Intel Corporation nor the names of its | ||
47 | * contributors may be used to endorse or promote products derived | ||
48 | * from this software without specific prior written permission. | ||
49 | * | ||
50 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
51 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
52 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
53 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
54 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
55 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
56 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
57 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
58 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
59 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
60 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
61 | * | ||
62 | *****************************************************************************/ | ||
63 | #include <linux/module.h> | ||
64 | #include <net/mac80211.h> | ||
65 | |||
66 | #include "iwl-notif-wait.h" | ||
67 | #include "iwl-trans.h" | ||
68 | #include "iwl-op-mode.h" | ||
69 | #include "iwl-fw.h" | ||
70 | #include "iwl-debug.h" | ||
71 | #include "iwl-drv.h" | ||
72 | #include "iwl-modparams.h" | ||
73 | #include "mvm.h" | ||
74 | #include "iwl-phy-db.h" | ||
75 | #include "iwl-eeprom-parse.h" | ||
76 | #include "iwl-csr.h" | ||
77 | #include "iwl-io.h" | ||
78 | #include "iwl-prph.h" | ||
79 | #include "rs.h" | ||
80 | #include "fw-api-scan.h" | ||
81 | #include "time-event.h" | ||
82 | |||
83 | /* | ||
84 | * module name, copyright, version, etc. | ||
85 | */ | ||
86 | #define DRV_DESCRIPTION "The new Intel(R) wireless AGN driver for Linux" | ||
87 | |||
88 | #define DRV_VERSION IWLWIFI_VERSION | ||
89 | |||
90 | MODULE_DESCRIPTION(DRV_DESCRIPTION); | ||
91 | MODULE_VERSION(DRV_VERSION); | ||
92 | MODULE_AUTHOR(DRV_COPYRIGHT " " DRV_AUTHOR); | ||
93 | MODULE_LICENSE("GPL"); | ||
94 | |||
95 | static const struct iwl_op_mode_ops iwl_mvm_ops; | ||
96 | |||
97 | struct iwl_mvm_mod_params iwlmvm_mod_params = { | ||
98 | .power_scheme = IWL_POWER_SCHEME_BPS, | ||
99 | /* rest of fields are 0 by default */ | ||
100 | }; | ||
101 | |||
102 | module_param_named(init_dbg, iwlmvm_mod_params.init_dbg, bool, S_IRUGO); | ||
103 | MODULE_PARM_DESC(init_dbg, | ||
104 | "set to true to debug an ASSERT in INIT fw (default: false"); | ||
105 | module_param_named(power_scheme, iwlmvm_mod_params.power_scheme, int, S_IRUGO); | ||
106 | MODULE_PARM_DESC(power_scheme, | ||
107 | "power management scheme: 1-active, 2-balanced, 3-low power, default: 2"); | ||
108 | |||
109 | /* | ||
110 | * module init and exit functions | ||
111 | */ | ||
112 | static int __init iwl_mvm_init(void) | ||
113 | { | ||
114 | int ret; | ||
115 | |||
116 | ret = iwl_mvm_rate_control_register(); | ||
117 | if (ret) { | ||
118 | pr_err("Unable to register rate control algorithm: %d\n", ret); | ||
119 | return ret; | ||
120 | } | ||
121 | |||
122 | ret = iwl_opmode_register("iwlmvm", &iwl_mvm_ops); | ||
123 | |||
124 | if (ret) { | ||
125 | pr_err("Unable to register MVM op_mode: %d\n", ret); | ||
126 | iwl_mvm_rate_control_unregister(); | ||
127 | } | ||
128 | |||
129 | return ret; | ||
130 | } | ||
131 | module_init(iwl_mvm_init); | ||
132 | |||
133 | static void __exit iwl_mvm_exit(void) | ||
134 | { | ||
135 | iwl_opmode_deregister("iwlmvm"); | ||
136 | iwl_mvm_rate_control_unregister(); | ||
137 | } | ||
138 | module_exit(iwl_mvm_exit); | ||
139 | |||
140 | static void iwl_mvm_nic_config(struct iwl_op_mode *op_mode) | ||
141 | { | ||
142 | struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); | ||
143 | u8 radio_cfg_type, radio_cfg_step, radio_cfg_dash; | ||
144 | u32 reg_val = 0; | ||
145 | |||
146 | /* | ||
147 | * We can't upload the correct value to the INIT image | ||
148 | * as we don't have nvm_data by that time. | ||
149 | * | ||
150 | * TODO: Figure out what we should do here | ||
151 | */ | ||
152 | if (mvm->nvm_data) { | ||
153 | radio_cfg_type = mvm->nvm_data->radio_cfg_type; | ||
154 | radio_cfg_step = mvm->nvm_data->radio_cfg_step; | ||
155 | radio_cfg_dash = mvm->nvm_data->radio_cfg_dash; | ||
156 | } else { | ||
157 | radio_cfg_type = 0; | ||
158 | radio_cfg_step = 0; | ||
159 | radio_cfg_dash = 0; | ||
160 | } | ||
161 | |||
162 | /* SKU control */ | ||
163 | reg_val |= CSR_HW_REV_STEP(mvm->trans->hw_rev) << | ||
164 | CSR_HW_IF_CONFIG_REG_POS_MAC_STEP; | ||
165 | reg_val |= CSR_HW_REV_DASH(mvm->trans->hw_rev) << | ||
166 | CSR_HW_IF_CONFIG_REG_POS_MAC_DASH; | ||
167 | |||
168 | /* radio configuration */ | ||
169 | reg_val |= radio_cfg_type << CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE; | ||
170 | reg_val |= radio_cfg_step << CSR_HW_IF_CONFIG_REG_POS_PHY_STEP; | ||
171 | reg_val |= radio_cfg_dash << CSR_HW_IF_CONFIG_REG_POS_PHY_DASH; | ||
172 | |||
173 | WARN_ON((radio_cfg_type << CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE) & | ||
174 | ~CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE); | ||
175 | |||
176 | /* silicon bits */ | ||
177 | reg_val |= CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI; | ||
178 | reg_val |= CSR_HW_IF_CONFIG_REG_BIT_MAC_SI; | ||
179 | |||
180 | iwl_trans_set_bits_mask(mvm->trans, CSR_HW_IF_CONFIG_REG, | ||
181 | CSR_HW_IF_CONFIG_REG_MSK_MAC_DASH | | ||
182 | CSR_HW_IF_CONFIG_REG_MSK_MAC_STEP | | ||
183 | CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE | | ||
184 | CSR_HW_IF_CONFIG_REG_MSK_PHY_STEP | | ||
185 | CSR_HW_IF_CONFIG_REG_MSK_PHY_DASH | | ||
186 | CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI | | ||
187 | CSR_HW_IF_CONFIG_REG_BIT_MAC_SI, | ||
188 | reg_val); | ||
189 | |||
190 | IWL_DEBUG_INFO(mvm, "Radio type=0x%x-0x%x-0x%x\n", radio_cfg_type, | ||
191 | radio_cfg_step, radio_cfg_dash); | ||
192 | |||
193 | /* | ||
194 | * W/A : NIC is stuck in a reset state after Early PCIe power off | ||
195 | * (PCIe power is lost before PERST# is asserted), causing ME FW | ||
196 | * to lose ownership and not being able to obtain it back. | ||
197 | */ | ||
198 | iwl_set_bits_mask_prph(mvm->trans, APMG_PS_CTRL_REG, | ||
199 | APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS, | ||
200 | ~APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS); | ||
201 | } | ||
202 | |||
203 | struct iwl_rx_handlers { | ||
204 | u8 cmd_id; | ||
205 | bool async; | ||
206 | int (*fn)(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, | ||
207 | struct iwl_device_cmd *cmd); | ||
208 | }; | ||
209 | |||
210 | #define RX_HANDLER(_cmd_id, _fn, _async) \ | ||
211 | { .cmd_id = _cmd_id , .fn = _fn , .async = _async } | ||
212 | |||
213 | /* | ||
214 | * Handlers for fw notifications | ||
215 | * Convention: RX_HANDLER(CMD_NAME, iwl_mvm_rx_CMD_NAME | ||
216 | * This list should be in order of frequency for performance purposes. | ||
217 | * | ||
218 | * The handler can be SYNC - this means that it will be called in the Rx path | ||
219 | * which can't acquire mvm->mutex. If the handler needs to hold mvm->mutex (and | ||
220 | * only in this case!), it should be set as ASYNC. In that case, it will be | ||
221 | * called from a worker with mvm->mutex held. | ||
222 | */ | ||
223 | static const struct iwl_rx_handlers iwl_mvm_rx_handlers[] = { | ||
224 | RX_HANDLER(REPLY_RX_MPDU_CMD, iwl_mvm_rx_rx_mpdu, false), | ||
225 | RX_HANDLER(REPLY_RX_PHY_CMD, iwl_mvm_rx_rx_phy_cmd, false), | ||
226 | RX_HANDLER(TX_CMD, iwl_mvm_rx_tx_cmd, false), | ||
227 | RX_HANDLER(BA_NOTIF, iwl_mvm_rx_ba_notif, false), | ||
228 | RX_HANDLER(TIME_EVENT_NOTIFICATION, iwl_mvm_rx_time_event_notif, false), | ||
229 | |||
230 | RX_HANDLER(SCAN_REQUEST_CMD, iwl_mvm_rx_scan_response, false), | ||
231 | RX_HANDLER(SCAN_COMPLETE_NOTIFICATION, iwl_mvm_rx_scan_complete, false), | ||
232 | |||
233 | RX_HANDLER(RADIO_VERSION_NOTIFICATION, iwl_mvm_rx_radio_ver, false), | ||
234 | RX_HANDLER(CARD_STATE_NOTIFICATION, iwl_mvm_rx_card_state_notif, false), | ||
235 | |||
236 | RX_HANDLER(REPLY_ERROR, iwl_mvm_rx_fw_error, false), | ||
237 | }; | ||
238 | #undef RX_HANDLER | ||
239 | #define CMD(x) [x] = #x | ||
240 | |||
241 | static const char *iwl_mvm_cmd_strings[REPLY_MAX] = { | ||
242 | CMD(MVM_ALIVE), | ||
243 | CMD(REPLY_ERROR), | ||
244 | CMD(INIT_COMPLETE_NOTIF), | ||
245 | CMD(PHY_CONTEXT_CMD), | ||
246 | CMD(MGMT_MCAST_KEY), | ||
247 | CMD(TX_CMD), | ||
248 | CMD(TXPATH_FLUSH), | ||
249 | CMD(MAC_CONTEXT_CMD), | ||
250 | CMD(TIME_EVENT_CMD), | ||
251 | CMD(TIME_EVENT_NOTIFICATION), | ||
252 | CMD(BINDING_CONTEXT_CMD), | ||
253 | CMD(TIME_QUOTA_CMD), | ||
254 | CMD(RADIO_VERSION_NOTIFICATION), | ||
255 | CMD(SCAN_REQUEST_CMD), | ||
256 | CMD(SCAN_ABORT_CMD), | ||
257 | CMD(SCAN_START_NOTIFICATION), | ||
258 | CMD(SCAN_RESULTS_NOTIFICATION), | ||
259 | CMD(SCAN_COMPLETE_NOTIFICATION), | ||
260 | CMD(NVM_ACCESS_CMD), | ||
261 | CMD(PHY_CONFIGURATION_CMD), | ||
262 | CMD(CALIB_RES_NOTIF_PHY_DB), | ||
263 | CMD(SET_CALIB_DEFAULT_CMD), | ||
264 | CMD(CALIBRATION_COMPLETE_NOTIFICATION), | ||
265 | CMD(ADD_STA), | ||
266 | CMD(REMOVE_STA), | ||
267 | CMD(LQ_CMD), | ||
268 | CMD(SCAN_OFFLOAD_CONFIG_CMD), | ||
269 | CMD(SCAN_OFFLOAD_REQUEST_CMD), | ||
270 | CMD(SCAN_OFFLOAD_ABORT_CMD), | ||
271 | CMD(SCAN_OFFLOAD_COMPLETE), | ||
272 | CMD(SCAN_OFFLOAD_UPDATE_PROFILES_CMD), | ||
273 | CMD(POWER_TABLE_CMD), | ||
274 | CMD(WEP_KEY), | ||
275 | CMD(REPLY_RX_PHY_CMD), | ||
276 | CMD(REPLY_RX_MPDU_CMD), | ||
277 | CMD(BEACON_TEMPLATE_CMD), | ||
278 | CMD(STATISTICS_NOTIFICATION), | ||
279 | CMD(TX_ANT_CONFIGURATION_CMD), | ||
280 | CMD(D3_CONFIG_CMD), | ||
281 | CMD(PROT_OFFLOAD_CONFIG_CMD), | ||
282 | CMD(OFFLOADS_QUERY_CMD), | ||
283 | CMD(REMOTE_WAKE_CONFIG_CMD), | ||
284 | CMD(WOWLAN_PATTERNS), | ||
285 | CMD(WOWLAN_CONFIGURATION), | ||
286 | CMD(WOWLAN_TSC_RSC_PARAM), | ||
287 | CMD(WOWLAN_TKIP_PARAM), | ||
288 | CMD(WOWLAN_KEK_KCK_MATERIAL), | ||
289 | CMD(WOWLAN_GET_STATUSES), | ||
290 | CMD(WOWLAN_TX_POWER_PER_DB), | ||
291 | CMD(NET_DETECT_CONFIG_CMD), | ||
292 | CMD(NET_DETECT_PROFILES_QUERY_CMD), | ||
293 | CMD(NET_DETECT_PROFILES_CMD), | ||
294 | CMD(NET_DETECT_HOTSPOTS_CMD), | ||
295 | CMD(NET_DETECT_HOTSPOTS_QUERY_CMD), | ||
296 | }; | ||
297 | #undef CMD | ||
298 | |||
299 | /* this forward declaration can avoid to export the function */ | ||
300 | static void iwl_mvm_async_handlers_wk(struct work_struct *wk); | ||
301 | |||
302 | static struct iwl_op_mode * | ||
303 | iwl_op_mode_mvm_start(struct iwl_trans *trans, const struct iwl_cfg *cfg, | ||
304 | const struct iwl_fw *fw, struct dentry *dbgfs_dir) | ||
305 | { | ||
306 | struct ieee80211_hw *hw; | ||
307 | struct iwl_op_mode *op_mode; | ||
308 | struct iwl_mvm *mvm; | ||
309 | struct iwl_trans_config trans_cfg = {}; | ||
310 | static const u8 no_reclaim_cmds[] = { | ||
311 | TX_CMD, | ||
312 | }; | ||
313 | int err, scan_size; | ||
314 | |||
315 | switch (cfg->device_family) { | ||
316 | case IWL_DEVICE_FAMILY_6030: | ||
317 | case IWL_DEVICE_FAMILY_6005: | ||
318 | case IWL_DEVICE_FAMILY_7000: | ||
319 | break; | ||
320 | default: | ||
321 | IWL_ERR(trans, "Trying to load mvm on an unsupported device\n"); | ||
322 | return NULL; | ||
323 | } | ||
324 | |||
325 | /******************************** | ||
326 | * 1. Allocating and configuring HW data | ||
327 | ********************************/ | ||
328 | hw = ieee80211_alloc_hw(sizeof(struct iwl_op_mode) + | ||
329 | sizeof(struct iwl_mvm), | ||
330 | &iwl_mvm_hw_ops); | ||
331 | if (!hw) | ||
332 | return NULL; | ||
333 | |||
334 | op_mode = hw->priv; | ||
335 | op_mode->ops = &iwl_mvm_ops; | ||
336 | op_mode->trans = trans; | ||
337 | |||
338 | mvm = IWL_OP_MODE_GET_MVM(op_mode); | ||
339 | mvm->dev = trans->dev; | ||
340 | mvm->trans = trans; | ||
341 | mvm->cfg = cfg; | ||
342 | mvm->fw = fw; | ||
343 | mvm->hw = hw; | ||
344 | |||
345 | mutex_init(&mvm->mutex); | ||
346 | spin_lock_init(&mvm->async_handlers_lock); | ||
347 | INIT_LIST_HEAD(&mvm->time_event_list); | ||
348 | INIT_LIST_HEAD(&mvm->async_handlers_list); | ||
349 | spin_lock_init(&mvm->time_event_lock); | ||
350 | |||
351 | INIT_WORK(&mvm->async_handlers_wk, iwl_mvm_async_handlers_wk); | ||
352 | INIT_WORK(&mvm->roc_done_wk, iwl_mvm_roc_done_wk); | ||
353 | INIT_WORK(&mvm->sta_drained_wk, iwl_mvm_sta_drained_wk); | ||
354 | |||
355 | SET_IEEE80211_DEV(mvm->hw, mvm->trans->dev); | ||
356 | |||
357 | /* | ||
358 | * Populate the state variables that the transport layer needs | ||
359 | * to know about. | ||
360 | */ | ||
361 | trans_cfg.op_mode = op_mode; | ||
362 | trans_cfg.no_reclaim_cmds = no_reclaim_cmds; | ||
363 | trans_cfg.n_no_reclaim_cmds = ARRAY_SIZE(no_reclaim_cmds); | ||
364 | trans_cfg.rx_buf_size_8k = iwlwifi_mod_params.amsdu_size_8K; | ||
365 | |||
366 | /* TODO: this should really be a TLV */ | ||
367 | if (cfg->device_family == IWL_DEVICE_FAMILY_7000) | ||
368 | trans_cfg.bc_table_dword = true; | ||
369 | |||
370 | if (!iwlwifi_mod_params.wd_disable) | ||
371 | trans_cfg.queue_watchdog_timeout = cfg->base_params->wd_timeout; | ||
372 | else | ||
373 | trans_cfg.queue_watchdog_timeout = IWL_WATCHDOG_DISABLED; | ||
374 | |||
375 | trans_cfg.command_names = iwl_mvm_cmd_strings; | ||
376 | |||
377 | trans_cfg.cmd_queue = IWL_MVM_CMD_QUEUE; | ||
378 | trans_cfg.cmd_fifo = IWL_MVM_CMD_FIFO; | ||
379 | |||
380 | snprintf(mvm->hw->wiphy->fw_version, | ||
381 | sizeof(mvm->hw->wiphy->fw_version), | ||
382 | "%s", fw->fw_version); | ||
383 | |||
384 | /* Configure transport layer */ | ||
385 | iwl_trans_configure(mvm->trans, &trans_cfg); | ||
386 | |||
387 | trans->rx_mpdu_cmd = REPLY_RX_MPDU_CMD; | ||
388 | trans->rx_mpdu_cmd_hdr_size = sizeof(struct iwl_rx_mpdu_res_start); | ||
389 | |||
390 | /* set up notification wait support */ | ||
391 | iwl_notification_wait_init(&mvm->notif_wait); | ||
392 | |||
393 | /* Init phy db */ | ||
394 | mvm->phy_db = iwl_phy_db_init(trans); | ||
395 | if (!mvm->phy_db) { | ||
396 | IWL_ERR(mvm, "Cannot init phy_db\n"); | ||
397 | goto out_free; | ||
398 | } | ||
399 | |||
400 | IWL_INFO(mvm, "Detected %s, REV=0x%X\n", | ||
401 | mvm->cfg->name, mvm->trans->hw_rev); | ||
402 | |||
403 | err = iwl_trans_start_hw(mvm->trans); | ||
404 | if (err) | ||
405 | goto out_free; | ||
406 | |||
407 | mutex_lock(&mvm->mutex); | ||
408 | err = iwl_run_init_mvm_ucode(mvm, true); | ||
409 | mutex_unlock(&mvm->mutex); | ||
410 | if (err && !iwlmvm_mod_params.init_dbg) { | ||
411 | IWL_ERR(mvm, "Failed to run INIT ucode: %d\n", err); | ||
412 | goto out_free; | ||
413 | } | ||
414 | |||
415 | /* Stop the hw after the ALIVE and NVM has been read */ | ||
416 | if (!iwlmvm_mod_params.init_dbg) | ||
417 | iwl_trans_stop_hw(mvm->trans, false); | ||
418 | |||
419 | scan_size = sizeof(struct iwl_scan_cmd) + | ||
420 | mvm->fw->ucode_capa.max_probe_length + | ||
421 | (MAX_NUM_SCAN_CHANNELS * sizeof(struct iwl_scan_channel)); | ||
422 | mvm->scan_cmd = kmalloc(scan_size, GFP_KERNEL); | ||
423 | if (!mvm->scan_cmd) | ||
424 | goto out_free; | ||
425 | |||
426 | err = iwl_mvm_mac_setup_register(mvm); | ||
427 | if (err) | ||
428 | goto out_free; | ||
429 | |||
430 | err = iwl_mvm_dbgfs_register(mvm, dbgfs_dir); | ||
431 | if (err) | ||
432 | goto out_unregister; | ||
433 | |||
434 | return op_mode; | ||
435 | |||
436 | out_unregister: | ||
437 | ieee80211_unregister_hw(mvm->hw); | ||
438 | out_free: | ||
439 | iwl_phy_db_free(mvm->phy_db); | ||
440 | kfree(mvm->scan_cmd); | ||
441 | kfree(mvm->eeprom_blob); | ||
442 | iwl_trans_stop_hw(trans, true); | ||
443 | ieee80211_free_hw(mvm->hw); | ||
444 | return NULL; | ||
445 | } | ||
446 | |||
447 | static void iwl_op_mode_mvm_stop(struct iwl_op_mode *op_mode) | ||
448 | { | ||
449 | struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); | ||
450 | int i; | ||
451 | |||
452 | iwl_mvm_leds_exit(mvm); | ||
453 | |||
454 | ieee80211_unregister_hw(mvm->hw); | ||
455 | |||
456 | kfree(mvm->scan_cmd); | ||
457 | |||
458 | iwl_trans_stop_hw(mvm->trans, true); | ||
459 | |||
460 | iwl_phy_db_free(mvm->phy_db); | ||
461 | mvm->phy_db = NULL; | ||
462 | |||
463 | kfree(mvm->eeprom_blob); | ||
464 | iwl_free_nvm_data(mvm->nvm_data); | ||
465 | for (i = 0; i < NVM_NUM_OF_SECTIONS; i++) | ||
466 | kfree(mvm->nvm_sections[i].data); | ||
467 | |||
468 | ieee80211_free_hw(mvm->hw); | ||
469 | } | ||
470 | |||
471 | struct iwl_async_handler_entry { | ||
472 | struct list_head list; | ||
473 | struct iwl_rx_cmd_buffer rxb; | ||
474 | int (*fn)(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, | ||
475 | struct iwl_device_cmd *cmd); | ||
476 | }; | ||
477 | |||
478 | void iwl_mvm_async_handlers_purge(struct iwl_mvm *mvm) | ||
479 | { | ||
480 | struct iwl_async_handler_entry *entry, *tmp; | ||
481 | |||
482 | spin_lock_bh(&mvm->async_handlers_lock); | ||
483 | list_for_each_entry_safe(entry, tmp, &mvm->async_handlers_list, list) { | ||
484 | iwl_free_rxb(&entry->rxb); | ||
485 | list_del(&entry->list); | ||
486 | kfree(entry); | ||
487 | } | ||
488 | spin_unlock_bh(&mvm->async_handlers_lock); | ||
489 | } | ||
490 | |||
491 | static void iwl_mvm_async_handlers_wk(struct work_struct *wk) | ||
492 | { | ||
493 | struct iwl_mvm *mvm = | ||
494 | container_of(wk, struct iwl_mvm, async_handlers_wk); | ||
495 | struct iwl_async_handler_entry *entry, *tmp; | ||
496 | struct list_head local_list; | ||
497 | |||
498 | INIT_LIST_HEAD(&local_list); | ||
499 | |||
500 | /* Ensure that we are not in stop flow (check iwl_mvm_mac_stop) */ | ||
501 | mutex_lock(&mvm->mutex); | ||
502 | |||
503 | /* | ||
504 | * Sync with Rx path with a lock. Remove all the entries from this list, | ||
505 | * add them to a local one (lock free), and then handle them. | ||
506 | */ | ||
507 | spin_lock_bh(&mvm->async_handlers_lock); | ||
508 | list_splice_init(&mvm->async_handlers_list, &local_list); | ||
509 | spin_unlock_bh(&mvm->async_handlers_lock); | ||
510 | |||
511 | list_for_each_entry_safe(entry, tmp, &local_list, list) { | ||
512 | if (entry->fn(mvm, &entry->rxb, NULL)) | ||
513 | IWL_WARN(mvm, | ||
514 | "returned value from ASYNC handlers are ignored\n"); | ||
515 | iwl_free_rxb(&entry->rxb); | ||
516 | list_del(&entry->list); | ||
517 | kfree(entry); | ||
518 | } | ||
519 | mutex_unlock(&mvm->mutex); | ||
520 | } | ||
521 | |||
522 | static int iwl_mvm_rx_dispatch(struct iwl_op_mode *op_mode, | ||
523 | struct iwl_rx_cmd_buffer *rxb, | ||
524 | struct iwl_device_cmd *cmd) | ||
525 | { | ||
526 | struct iwl_rx_packet *pkt = rxb_addr(rxb); | ||
527 | struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); | ||
528 | u8 i; | ||
529 | |||
530 | /* | ||
531 | * Do the notification wait before RX handlers so | ||
532 | * even if the RX handler consumes the RXB we have | ||
533 | * access to it in the notification wait entry. | ||
534 | */ | ||
535 | iwl_notification_wait_notify(&mvm->notif_wait, pkt); | ||
536 | |||
537 | for (i = 0; i < ARRAY_SIZE(iwl_mvm_rx_handlers); i++) { | ||
538 | const struct iwl_rx_handlers *rx_h = &iwl_mvm_rx_handlers[i]; | ||
539 | if (rx_h->cmd_id == pkt->hdr.cmd) { | ||
540 | struct iwl_async_handler_entry *entry; | ||
541 | if (!rx_h->async) | ||
542 | return rx_h->fn(mvm, rxb, cmd); | ||
543 | |||
544 | entry = kzalloc(sizeof(*entry), GFP_ATOMIC); | ||
545 | /* we can't do much... */ | ||
546 | if (!entry) | ||
547 | return 0; | ||
548 | |||
549 | entry->rxb._page = rxb_steal_page(rxb); | ||
550 | entry->rxb._offset = rxb->_offset; | ||
551 | entry->rxb._rx_page_order = rxb->_rx_page_order; | ||
552 | entry->fn = rx_h->fn; | ||
553 | spin_lock(&mvm->async_handlers_lock); | ||
554 | list_add_tail(&entry->list, &mvm->async_handlers_list); | ||
555 | spin_unlock(&mvm->async_handlers_lock); | ||
556 | schedule_work(&mvm->async_handlers_wk); | ||
557 | } | ||
558 | } | ||
559 | |||
560 | return 0; | ||
561 | } | ||
562 | |||
563 | static void iwl_mvm_stop_sw_queue(struct iwl_op_mode *op_mode, int queue) | ||
564 | { | ||
565 | struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); | ||
566 | int mq = mvm->queue_to_mac80211[queue]; | ||
567 | |||
568 | if (WARN_ON_ONCE(mq == IWL_INVALID_MAC80211_QUEUE)) | ||
569 | return; | ||
570 | |||
571 | if (atomic_inc_return(&mvm->queue_stop_count[mq]) > 1) { | ||
572 | IWL_DEBUG_TX_QUEUES(mvm, | ||
573 | "queue %d (mac80211 %d) already stopped\n", | ||
574 | queue, mq); | ||
575 | return; | ||
576 | } | ||
577 | |||
578 | set_bit(mq, &mvm->transport_queue_stop); | ||
579 | ieee80211_stop_queue(mvm->hw, mq); | ||
580 | } | ||
581 | |||
582 | static void iwl_mvm_wake_sw_queue(struct iwl_op_mode *op_mode, int queue) | ||
583 | { | ||
584 | struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); | ||
585 | int mq = mvm->queue_to_mac80211[queue]; | ||
586 | |||
587 | if (WARN_ON_ONCE(mq == IWL_INVALID_MAC80211_QUEUE)) | ||
588 | return; | ||
589 | |||
590 | if (atomic_dec_return(&mvm->queue_stop_count[mq]) > 0) { | ||
591 | IWL_DEBUG_TX_QUEUES(mvm, | ||
592 | "queue %d (mac80211 %d) already awake\n", | ||
593 | queue, mq); | ||
594 | return; | ||
595 | } | ||
596 | |||
597 | clear_bit(mq, &mvm->transport_queue_stop); | ||
598 | |||
599 | ieee80211_wake_queue(mvm->hw, mq); | ||
600 | } | ||
601 | |||
602 | static void iwl_mvm_set_hw_rfkill_state(struct iwl_op_mode *op_mode, bool state) | ||
603 | { | ||
604 | struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); | ||
605 | |||
606 | if (state) | ||
607 | set_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status); | ||
608 | else | ||
609 | clear_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status); | ||
610 | |||
611 | wiphy_rfkill_set_hw_state(mvm->hw->wiphy, state); | ||
612 | } | ||
613 | |||
614 | static void iwl_mvm_free_skb(struct iwl_op_mode *op_mode, struct sk_buff *skb) | ||
615 | { | ||
616 | struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); | ||
617 | struct ieee80211_tx_info *info; | ||
618 | |||
619 | info = IEEE80211_SKB_CB(skb); | ||
620 | iwl_trans_free_tx_cmd(mvm->trans, info->driver_data[1]); | ||
621 | ieee80211_free_txskb(mvm->hw, skb); | ||
622 | } | ||
623 | |||
624 | static void iwl_mvm_nic_error(struct iwl_op_mode *op_mode) | ||
625 | { | ||
626 | struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); | ||
627 | |||
628 | iwl_mvm_dump_nic_error_log(mvm); | ||
629 | |||
630 | iwl_abort_notification_waits(&mvm->notif_wait); | ||
631 | |||
632 | /* | ||
633 | * If we're restarting already, don't cycle restarts. | ||
634 | * If INIT fw asserted, it will likely fail again. | ||
635 | * If WoWLAN fw asserted, don't restart either, mac80211 | ||
636 | * can't recover this since we're already half suspended. | ||
637 | */ | ||
638 | if (test_and_set_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { | ||
639 | IWL_ERR(mvm, "Firmware error during reconfiguration! Abort.\n"); | ||
640 | } else if (mvm->cur_ucode == IWL_UCODE_REGULAR && | ||
641 | iwlwifi_mod_params.restart_fw) { | ||
642 | /* | ||
643 | * This is a bit racy, but worst case we tell mac80211 about | ||
644 | * a stopped/aborted (sched) scan when that was already done | ||
645 | * which is not a problem. It is necessary to abort any scan | ||
646 | * here because mac80211 requires having the scan cleared | ||
647 | * before restarting. | ||
648 | * We'll reset the scan_status to NONE in restart cleanup in | ||
649 | * the next start() call from mac80211. | ||
650 | */ | ||
651 | switch (mvm->scan_status) { | ||
652 | case IWL_MVM_SCAN_NONE: | ||
653 | break; | ||
654 | case IWL_MVM_SCAN_OS: | ||
655 | ieee80211_scan_completed(mvm->hw, true); | ||
656 | break; | ||
657 | } | ||
658 | |||
659 | ieee80211_restart_hw(mvm->hw); | ||
660 | } | ||
661 | } | ||
662 | |||
663 | static void iwl_mvm_cmd_queue_full(struct iwl_op_mode *op_mode) | ||
664 | { | ||
665 | WARN_ON(1); | ||
666 | } | ||
667 | |||
668 | static const struct iwl_op_mode_ops iwl_mvm_ops = { | ||
669 | .start = iwl_op_mode_mvm_start, | ||
670 | .stop = iwl_op_mode_mvm_stop, | ||
671 | .rx = iwl_mvm_rx_dispatch, | ||
672 | .queue_full = iwl_mvm_stop_sw_queue, | ||
673 | .queue_not_full = iwl_mvm_wake_sw_queue, | ||
674 | .hw_rf_kill = iwl_mvm_set_hw_rfkill_state, | ||
675 | .free_skb = iwl_mvm_free_skb, | ||
676 | .nic_error = iwl_mvm_nic_error, | ||
677 | .cmd_queue_full = iwl_mvm_cmd_queue_full, | ||
678 | .nic_config = iwl_mvm_nic_config, | ||
679 | }; | ||