diff options
Diffstat (limited to 'drivers/net/wireless/iwlwifi/iwl-scan.c')
-rw-r--r-- | drivers/net/wireless/iwlwifi/iwl-scan.c | 381 |
1 files changed, 215 insertions, 166 deletions
diff --git a/drivers/net/wireless/iwlwifi/iwl-scan.c b/drivers/net/wireless/iwlwifi/iwl-scan.c index 7727f0966d31..c54c20023e7c 100644 --- a/drivers/net/wireless/iwlwifi/iwl-scan.c +++ b/drivers/net/wireless/iwlwifi/iwl-scan.c @@ -54,100 +54,134 @@ #define IWL_PASSIVE_DWELL_BASE (100) #define IWL_CHANNEL_TUNE_TIME 5 +static int iwl_send_scan_abort(struct iwl_priv *priv) +{ + int ret; + struct iwl_rx_packet *pkt; + struct iwl_host_cmd cmd = { + .id = REPLY_SCAN_ABORT_CMD, + .flags = CMD_WANT_SKB, + }; + /* Exit instantly with error when device is not ready + * to receive scan abort command or it does not perform + * hardware scan currently */ + if (!test_bit(STATUS_READY, &priv->status) || + !test_bit(STATUS_GEO_CONFIGURED, &priv->status) || + !test_bit(STATUS_SCAN_HW, &priv->status) || + test_bit(STATUS_FW_ERROR, &priv->status) || + test_bit(STATUS_EXIT_PENDING, &priv->status)) + return -EIO; -/** - * iwl_scan_cancel - Cancel any currently executing HW scan - * - * NOTE: priv->mutex is not required before calling this function - */ -int iwl_scan_cancel(struct iwl_priv *priv) + ret = iwl_send_cmd_sync(priv, &cmd); + if (ret) + return ret; + + pkt = (struct iwl_rx_packet *)cmd.reply_page; + if (pkt->u.status != CAN_ABORT_STATUS) { + /* The scan abort will return 1 for success or + * 2 for "failure". A failure condition can be + * due to simply not being in an active scan which + * can occur if we send the scan abort before we + * the microcode has notified us that a scan is + * completed. */ + IWL_DEBUG_SCAN(priv, "SCAN_ABORT ret %d.\n", pkt->u.status); + ret = -EIO; + } + + iwl_free_pages(priv, cmd.reply_page); + return ret; +} + +static void iwl_complete_scan(struct iwl_priv *priv, bool aborted) { - if (!test_bit(STATUS_SCAN_HW, &priv->status)) { - clear_bit(STATUS_SCANNING, &priv->status); - return 0; + /* check if scan was requested from mac80211 */ + if (priv->scan_request) { + IWL_DEBUG_SCAN(priv, "Complete scan in mac80211\n"); + ieee80211_scan_completed(priv->hw, aborted); } - if (test_bit(STATUS_SCANNING, &priv->status)) { - if (!test_and_set_bit(STATUS_SCAN_ABORTING, &priv->status)) { - IWL_DEBUG_SCAN(priv, "Queuing scan abort.\n"); - queue_work(priv->workqueue, &priv->abort_scan); + priv->is_internal_short_scan = false; + priv->scan_vif = NULL; + priv->scan_request = NULL; +} - } else - IWL_DEBUG_SCAN(priv, "Scan abort already in progress.\n"); +void iwl_force_scan_end(struct iwl_priv *priv) +{ + lockdep_assert_held(&priv->mutex); - return test_bit(STATUS_SCANNING, &priv->status); + if (!test_bit(STATUS_SCANNING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Forcing scan end while not scanning\n"); + return; } + IWL_DEBUG_SCAN(priv, "Forcing scan end\n"); + clear_bit(STATUS_SCANNING, &priv->status); + clear_bit(STATUS_SCAN_HW, &priv->status); + clear_bit(STATUS_SCAN_ABORTING, &priv->status); + iwl_complete_scan(priv, true); +} +EXPORT_SYMBOL(iwl_force_scan_end); + +static void iwl_do_scan_abort(struct iwl_priv *priv) +{ + int ret; + + lockdep_assert_held(&priv->mutex); + + if (!test_bit(STATUS_SCANNING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Not performing scan to abort\n"); + return; + } + + if (test_and_set_bit(STATUS_SCAN_ABORTING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Scan abort in progress\n"); + return; + } + + ret = iwl_send_scan_abort(priv); + if (ret) { + IWL_DEBUG_SCAN(priv, "Send scan abort failed %d\n", ret); + iwl_force_scan_end(priv); + } else + IWL_DEBUG_SCAN(priv, "Sucessfully send scan abort\n"); +} + +/** + * iwl_scan_cancel - Cancel any currently executing HW scan + */ +int iwl_scan_cancel(struct iwl_priv *priv) +{ + IWL_DEBUG_SCAN(priv, "Queuing abort scan\n"); + queue_work(priv->workqueue, &priv->abort_scan); return 0; } EXPORT_SYMBOL(iwl_scan_cancel); + /** * iwl_scan_cancel_timeout - Cancel any currently executing HW scan * @ms: amount of time to wait (in milliseconds) for scan to abort * - * NOTE: priv->mutex must be held before calling this function */ int iwl_scan_cancel_timeout(struct iwl_priv *priv, unsigned long ms) { - unsigned long now = jiffies; - int ret; - - ret = iwl_scan_cancel(priv); - if (ret && ms) { - mutex_unlock(&priv->mutex); - while (!time_after(jiffies, now + msecs_to_jiffies(ms)) && - test_bit(STATUS_SCANNING, &priv->status)) - msleep(1); - mutex_lock(&priv->mutex); - - return test_bit(STATUS_SCANNING, &priv->status); - } + unsigned long timeout = jiffies + msecs_to_jiffies(ms); - return ret; -} -EXPORT_SYMBOL(iwl_scan_cancel_timeout); + lockdep_assert_held(&priv->mutex); -static int iwl_send_scan_abort(struct iwl_priv *priv) -{ - int ret = 0; - struct iwl_rx_packet *pkt; - struct iwl_host_cmd cmd = { - .id = REPLY_SCAN_ABORT_CMD, - .flags = CMD_WANT_SKB, - }; + IWL_DEBUG_SCAN(priv, "Scan cancel timeout\n"); - /* If there isn't a scan actively going on in the hardware - * then we are in between scan bands and not actually - * actively scanning, so don't send the abort command */ - if (!test_bit(STATUS_SCAN_HW, &priv->status)) { - clear_bit(STATUS_SCAN_ABORTING, &priv->status); - return 0; - } - - ret = iwl_send_cmd_sync(priv, &cmd); - if (ret) { - clear_bit(STATUS_SCAN_ABORTING, &priv->status); - return ret; - } + iwl_do_scan_abort(priv); - pkt = (struct iwl_rx_packet *)cmd.reply_page; - if (pkt->u.status != CAN_ABORT_STATUS) { - /* The scan abort will return 1 for success or - * 2 for "failure". A failure condition can be - * due to simply not being in an active scan which - * can occur if we send the scan abort before we - * the microcode has notified us that a scan is - * completed. */ - IWL_DEBUG_INFO(priv, "SCAN_ABORT returned %d.\n", pkt->u.status); - clear_bit(STATUS_SCAN_ABORTING, &priv->status); - clear_bit(STATUS_SCAN_HW, &priv->status); + while (time_before_eq(jiffies, timeout)) { + if (!test_bit(STATUS_SCAN_HW, &priv->status)) + break; + msleep(20); } - iwl_free_pages(priv, cmd.reply_page); - - return ret; + return test_bit(STATUS_SCAN_HW, &priv->status); } +EXPORT_SYMBOL(iwl_scan_cancel_timeout); /* Service response to REPLY_SCAN_CMD (0x80) */ static void iwl_rx_reply_scan(struct iwl_priv *priv, @@ -158,7 +192,7 @@ static void iwl_rx_reply_scan(struct iwl_priv *priv, struct iwl_scanreq_notification *notif = (struct iwl_scanreq_notification *)pkt->u.raw; - IWL_DEBUG_RX(priv, "Scan request status = 0x%x\n", notif->status); + IWL_DEBUG_SCAN(priv, "Scan request status = 0x%x\n", notif->status); #endif } @@ -217,26 +251,16 @@ static void iwl_rx_scan_complete_notif(struct iwl_priv *priv, /* The HW is no longer scanning */ clear_bit(STATUS_SCAN_HW, &priv->status); - IWL_DEBUG_INFO(priv, "Scan on %sGHz took %dms\n", + IWL_DEBUG_SCAN(priv, "Scan on %sGHz took %dms\n", (priv->scan_band == IEEE80211_BAND_2GHZ) ? "2.4" : "5.2", jiffies_to_msecs(elapsed_jiffies (priv->scan_start, jiffies))); - /* - * If a request to abort was given, or the scan did not succeed - * then we reset the scan state machine and terminate, - * re-queuing another scan if one has been requested - */ - if (test_and_clear_bit(STATUS_SCAN_ABORTING, &priv->status)) - IWL_DEBUG_INFO(priv, "Aborted scan completed.\n"); - - IWL_DEBUG_INFO(priv, "Setting scan to off\n"); - - clear_bit(STATUS_SCANNING, &priv->status); + queue_work(priv->workqueue, &priv->scan_completed); if (priv->iw_mode != NL80211_IFTYPE_ADHOC && - priv->cfg->advanced_bt_coexist && priv->bt_status != - scan_notif->bt_status) { + priv->cfg->advanced_bt_coexist && + priv->bt_status != scan_notif->bt_status) { if (scan_notif->bt_status) { /* BT on */ if (!priv->bt_ch_announce) @@ -254,7 +278,6 @@ static void iwl_rx_scan_complete_notif(struct iwl_priv *priv, priv->bt_status = scan_notif->bt_status; queue_work(priv->workqueue, &priv->bt_traffic_change_work); } - queue_work(priv->workqueue, &priv->scan_completed); } void iwl_setup_rx_scan_handlers(struct iwl_priv *priv) @@ -324,19 +347,53 @@ void iwl_init_scan_params(struct iwl_priv *priv) } EXPORT_SYMBOL(iwl_init_scan_params); -static int iwl_scan_initiate(struct iwl_priv *priv, struct ieee80211_vif *vif) +static int __must_check iwl_scan_initiate(struct iwl_priv *priv, + struct ieee80211_vif *vif, + bool internal, + enum ieee80211_band band) { + int ret; + lockdep_assert_held(&priv->mutex); - IWL_DEBUG_INFO(priv, "Starting scan...\n"); + if (WARN_ON(!priv->cfg->ops->utils->request_scan)) + return -EOPNOTSUPP; + + cancel_delayed_work(&priv->scan_check); + + if (!iwl_is_ready_rf(priv)) { + IWL_WARN(priv, "Request scan called when driver not ready.\n"); + return -EIO; + } + + if (test_bit(STATUS_SCAN_HW, &priv->status)) { + IWL_DEBUG_SCAN(priv, + "Multiple concurrent scan requests in parallel.\n"); + return -EBUSY; + } + + if (test_bit(STATUS_SCAN_ABORTING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Scan request while abort pending.\n"); + return -EBUSY; + } + + IWL_DEBUG_SCAN(priv, "Starting %sscan...\n", + internal ? "internal short " : ""); + set_bit(STATUS_SCANNING, &priv->status); - priv->is_internal_short_scan = false; + priv->is_internal_short_scan = internal; priv->scan_start = jiffies; + priv->scan_band = band; - if (WARN_ON(!priv->cfg->ops->utils->request_scan)) - return -EOPNOTSUPP; + ret = priv->cfg->ops->utils->request_scan(priv, vif); + if (ret) { + clear_bit(STATUS_SCANNING, &priv->status); + priv->is_internal_short_scan = false; + return ret; + } - priv->cfg->ops->utils->request_scan(priv, vif); + queue_delayed_work(priv->workqueue, &priv->scan_check, + IWL_SCAN_CHECK_WATCHDOG); return 0; } @@ -355,12 +412,6 @@ int iwl_mac_hw_scan(struct ieee80211_hw *hw, mutex_lock(&priv->mutex); - if (!iwl_is_ready_rf(priv)) { - ret = -EIO; - IWL_DEBUG_MAC80211(priv, "leave - not ready or exit pending\n"); - goto out_unlock; - } - if (test_bit(STATUS_SCANNING, &priv->status) && !priv->is_internal_short_scan) { IWL_DEBUG_SCAN(priv, "Scan already in progress.\n"); @@ -368,14 +419,7 @@ int iwl_mac_hw_scan(struct ieee80211_hw *hw, goto out_unlock; } - if (test_bit(STATUS_SCAN_ABORTING, &priv->status)) { - IWL_DEBUG_SCAN(priv, "Scan request while abort pending\n"); - ret = -EAGAIN; - goto out_unlock; - } - /* mac80211 will only ask for one band at a time */ - priv->scan_band = req->channels[0]->band; priv->scan_request = req; priv->scan_vif = vif; @@ -383,10 +427,12 @@ int iwl_mac_hw_scan(struct ieee80211_hw *hw, * If an internal scan is in progress, just set * up the scan_request as per above. */ - if (priv->is_internal_short_scan) + if (priv->is_internal_short_scan) { + IWL_DEBUG_SCAN(priv, "SCAN request during internal scan\n"); ret = 0; - else - ret = iwl_scan_initiate(priv, vif); + } else + ret = iwl_scan_initiate(priv, vif, false, + req->channels[0]->band); IWL_DEBUG_MAC80211(priv, "leave\n"); @@ -411,6 +457,8 @@ static void iwl_bg_start_internal_scan(struct work_struct *work) struct iwl_priv *priv = container_of(work, struct iwl_priv, start_internal_scan); + IWL_DEBUG_SCAN(priv, "Start internal scan\n"); + mutex_lock(&priv->mutex); if (priv->is_internal_short_scan == true) { @@ -418,31 +466,13 @@ static void iwl_bg_start_internal_scan(struct work_struct *work) goto unlock; } - if (!iwl_is_ready_rf(priv)) { - IWL_DEBUG_SCAN(priv, "not ready or exit pending\n"); - goto unlock; - } - if (test_bit(STATUS_SCANNING, &priv->status)) { IWL_DEBUG_SCAN(priv, "Scan already in progress.\n"); goto unlock; } - if (test_bit(STATUS_SCAN_ABORTING, &priv->status)) { - IWL_DEBUG_SCAN(priv, "Scan request while abort pending\n"); - goto unlock; - } - - priv->scan_band = priv->band; - - IWL_DEBUG_SCAN(priv, "Start internal short scan...\n"); - set_bit(STATUS_SCANNING, &priv->status); - priv->is_internal_short_scan = true; - - if (WARN_ON(!priv->cfg->ops->utils->request_scan)) - goto unlock; - - priv->cfg->ops->utils->request_scan(priv, NULL); + if (iwl_scan_initiate(priv, NULL, true, priv->band)) + IWL_DEBUG_SCAN(priv, "failed to start internal short scan\n"); unlock: mutex_unlock(&priv->mutex); } @@ -452,18 +482,13 @@ static void iwl_bg_scan_check(struct work_struct *data) struct iwl_priv *priv = container_of(data, struct iwl_priv, scan_check.work); - if (test_bit(STATUS_EXIT_PENDING, &priv->status)) - return; + IWL_DEBUG_SCAN(priv, "Scan check work\n"); + /* Since we are here firmware does not finish scan and + * most likely is in bad shape, so we don't bother to + * send abort command, just force scan complete to mac80211 */ mutex_lock(&priv->mutex); - if (test_bit(STATUS_SCANNING, &priv->status) && - !test_bit(STATUS_SCAN_ABORTING, &priv->status)) { - IWL_DEBUG_SCAN(priv, "Scan completion watchdog (%dms)\n", - jiffies_to_msecs(IWL_SCAN_CHECK_WATCHDOG)); - - if (!test_bit(STATUS_EXIT_PENDING, &priv->status)) - iwl_send_scan_abort(priv); - } + iwl_force_scan_end(priv); mutex_unlock(&priv->mutex); } @@ -519,15 +544,12 @@ static void iwl_bg_abort_scan(struct work_struct *work) { struct iwl_priv *priv = container_of(work, struct iwl_priv, abort_scan); - if (!test_bit(STATUS_READY, &priv->status) || - !test_bit(STATUS_GEO_CONFIGURED, &priv->status)) - return; - - cancel_delayed_work(&priv->scan_check); + IWL_DEBUG_SCAN(priv, "Abort scan work\n"); + /* We keep scan_check work queued in case when firmware will not + * report back scan completed notification */ mutex_lock(&priv->mutex); - if (test_bit(STATUS_SCAN_ABORTING, &priv->status)) - iwl_send_scan_abort(priv); + iwl_scan_cancel_timeout(priv, 200); mutex_unlock(&priv->mutex); } @@ -535,30 +557,52 @@ static void iwl_bg_scan_completed(struct work_struct *work) { struct iwl_priv *priv = container_of(work, struct iwl_priv, scan_completed); - bool internal = false; - bool scan_completed = false; + bool aborted; struct iwl_rxon_context *ctx; - IWL_DEBUG_SCAN(priv, "SCAN complete scan\n"); + IWL_DEBUG_SCAN(priv, "Completed %sscan.\n", + priv->is_internal_short_scan ? "internal short " : ""); cancel_delayed_work(&priv->scan_check); mutex_lock(&priv->mutex); - if (priv->is_internal_short_scan) { - priv->is_internal_short_scan = false; - IWL_DEBUG_SCAN(priv, "internal short scan completed\n"); - internal = true; - } else if (priv->scan_request) { - scan_completed = true; - priv->scan_request = NULL; - priv->scan_vif = NULL; + + aborted = test_and_clear_bit(STATUS_SCAN_ABORTING, &priv->status); + if (aborted) + IWL_DEBUG_SCAN(priv, "Aborted scan completed.\n"); + + if (!test_and_clear_bit(STATUS_SCANNING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Scan already completed.\n"); + goto out_settings; } - if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + if (priv->is_internal_short_scan && !aborted) { + int err; + + /* Check if mac80211 requested scan during our internal scan */ + if (priv->scan_request == NULL) + goto out_complete; + + /* If so request a new scan */ + err = iwl_scan_initiate(priv, priv->scan_vif, false, + priv->scan_request->channels[0]->band); + if (err) { + IWL_DEBUG_SCAN(priv, + "failed to initiate pending scan: %d\n", err); + aborted = true; + goto out_complete; + } + goto out; + } + +out_complete: + iwl_complete_scan(priv, aborted); - if (internal && priv->scan_request) - iwl_scan_initiate(priv, priv->scan_vif); +out_settings: + /* Can we still talk to firmware ? */ + if (!iwl_is_ready_rf(priv)) + goto out; /* Since setting the TXPOWER may have been deferred while * performing the scan, fire one off */ @@ -571,19 +615,11 @@ static void iwl_bg_scan_completed(struct work_struct *work) for_each_context(priv, ctx) iwlcore_commit_rxon(priv, ctx); - out: if (priv->cfg->ops->hcmd->set_pan_params) priv->cfg->ops->hcmd->set_pan_params(priv); + out: mutex_unlock(&priv->mutex); - - /* - * Do not hold mutex here since this will cause mac80211 to call - * into driver again into functions that will attempt to take - * mutex. - */ - if (scan_completed) - ieee80211_scan_completed(priv->hw, false); } void iwl_setup_scan_deferred_work(struct iwl_priv *priv) @@ -595,3 +631,16 @@ void iwl_setup_scan_deferred_work(struct iwl_priv *priv) } EXPORT_SYMBOL(iwl_setup_scan_deferred_work); +void iwl_cancel_scan_deferred_work(struct iwl_priv *priv) +{ + cancel_work_sync(&priv->start_internal_scan); + cancel_work_sync(&priv->abort_scan); + cancel_work_sync(&priv->scan_completed); + + if (cancel_delayed_work_sync(&priv->scan_check)) { + mutex_lock(&priv->mutex); + iwl_force_scan_end(priv); + mutex_unlock(&priv->mutex); + } +} +EXPORT_SYMBOL(iwl_cancel_scan_deferred_work); |