Commit 88be0264 authored by Johannes Berg's avatar Johannes Berg Committed by Reinette Chatre

iwlwifi: fix scan races

When an internal scan is started, nothing protects the
is_internal_short_scan variable which can cause crashes,
cf. https://bugzilla.kernel.org/show_bug.cgi?id=15667.
Fix this by making the short scan request use the mutex
for locking, which requires making the request go to a
work struct so that it can sleep.
Reported-by: default avatarPeter Zijlstra <peterz@infradead.org>
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
Signed-off-by: default avatarReinette Chatre <reinette.chatre@intel.com>
parent 8b9fce77
...@@ -3330,6 +3330,7 @@ static void iwl_cancel_deferred_work(struct iwl_priv *priv) ...@@ -3330,6 +3330,7 @@ static void iwl_cancel_deferred_work(struct iwl_priv *priv)
cancel_delayed_work_sync(&priv->init_alive_start); cancel_delayed_work_sync(&priv->init_alive_start);
cancel_delayed_work(&priv->scan_check); cancel_delayed_work(&priv->scan_check);
cancel_work_sync(&priv->start_internal_scan);
cancel_delayed_work(&priv->alive_start); cancel_delayed_work(&priv->alive_start);
cancel_work_sync(&priv->beacon_update); cancel_work_sync(&priv->beacon_update);
del_timer_sync(&priv->statistics_periodic); del_timer_sync(&priv->statistics_periodic);
......
...@@ -3357,7 +3357,6 @@ static void iwl_force_rf_reset(struct iwl_priv *priv) ...@@ -3357,7 +3357,6 @@ static void iwl_force_rf_reset(struct iwl_priv *priv)
*/ */
IWL_DEBUG_INFO(priv, "perform radio reset.\n"); IWL_DEBUG_INFO(priv, "perform radio reset.\n");
iwl_internal_short_hw_scan(priv); iwl_internal_short_hw_scan(priv);
return;
} }
......
...@@ -506,7 +506,7 @@ void iwl_init_scan_params(struct iwl_priv *priv); ...@@ -506,7 +506,7 @@ void iwl_init_scan_params(struct iwl_priv *priv);
int iwl_scan_cancel(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_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_mac_hw_scan(struct ieee80211_hw *hw, struct cfg80211_scan_request *req);
int iwl_internal_short_hw_scan(struct iwl_priv *priv); void iwl_internal_short_hw_scan(struct iwl_priv *priv);
int iwl_force_reset(struct iwl_priv *priv, int mode); int iwl_force_reset(struct iwl_priv *priv, int mode);
u16 iwl_fill_probe_req(struct iwl_priv *priv, struct ieee80211_mgmt *frame, u16 iwl_fill_probe_req(struct iwl_priv *priv, struct ieee80211_mgmt *frame,
const u8 *ie, int ie_len, int left); const u8 *ie, int ie_len, int left);
......
...@@ -1296,6 +1296,7 @@ struct iwl_priv { ...@@ -1296,6 +1296,7 @@ struct iwl_priv {
struct work_struct tt_work; struct work_struct tt_work;
struct work_struct ct_enter; struct work_struct ct_enter;
struct work_struct ct_exit; struct work_struct ct_exit;
struct work_struct start_internal_scan;
struct tasklet_struct irq_tasklet; struct tasklet_struct irq_tasklet;
......
...@@ -469,6 +469,8 @@ EXPORT_SYMBOL(iwl_init_scan_params); ...@@ -469,6 +469,8 @@ EXPORT_SYMBOL(iwl_init_scan_params);
static int iwl_scan_initiate(struct iwl_priv *priv) static int iwl_scan_initiate(struct iwl_priv *priv)
{ {
WARN_ON(!mutex_is_locked(&priv->mutex));
IWL_DEBUG_INFO(priv, "Starting scan...\n"); IWL_DEBUG_INFO(priv, "Starting scan...\n");
set_bit(STATUS_SCANNING, &priv->status); set_bit(STATUS_SCANNING, &priv->status);
priv->is_internal_short_scan = false; priv->is_internal_short_scan = false;
...@@ -546,24 +548,31 @@ EXPORT_SYMBOL(iwl_mac_hw_scan); ...@@ -546,24 +548,31 @@ EXPORT_SYMBOL(iwl_mac_hw_scan);
* internal short scan, this function should only been called while associated. * internal short scan, this function should only been called while associated.
* It will reset and tune the radio to prevent possible RF related problem * It will reset and tune the radio to prevent possible RF related problem
*/ */
int iwl_internal_short_hw_scan(struct iwl_priv *priv) void iwl_internal_short_hw_scan(struct iwl_priv *priv)
{ {
int ret = 0; queue_work(priv->workqueue, &priv->start_internal_scan);
}
static void iwl_bg_start_internal_scan(struct work_struct *work)
{
struct iwl_priv *priv =
container_of(work, struct iwl_priv, start_internal_scan);
mutex_lock(&priv->mutex);
if (!iwl_is_ready_rf(priv)) { if (!iwl_is_ready_rf(priv)) {
ret = -EIO;
IWL_DEBUG_SCAN(priv, "not ready or exit pending\n"); IWL_DEBUG_SCAN(priv, "not ready or exit pending\n");
goto out; goto unlock;
} }
if (test_bit(STATUS_SCANNING, &priv->status)) { if (test_bit(STATUS_SCANNING, &priv->status)) {
IWL_DEBUG_SCAN(priv, "Scan already in progress.\n"); IWL_DEBUG_SCAN(priv, "Scan already in progress.\n");
ret = -EAGAIN; goto unlock;
goto out;
} }
if (test_bit(STATUS_SCAN_ABORTING, &priv->status)) { if (test_bit(STATUS_SCAN_ABORTING, &priv->status)) {
IWL_DEBUG_SCAN(priv, "Scan request while abort pending\n"); IWL_DEBUG_SCAN(priv, "Scan request while abort pending\n");
ret = -EAGAIN; goto unlock;
goto out;
} }
priv->scan_bands = 0; priv->scan_bands = 0;
...@@ -576,9 +585,8 @@ int iwl_internal_short_hw_scan(struct iwl_priv *priv) ...@@ -576,9 +585,8 @@ int iwl_internal_short_hw_scan(struct iwl_priv *priv)
set_bit(STATUS_SCANNING, &priv->status); set_bit(STATUS_SCANNING, &priv->status);
priv->is_internal_short_scan = true; priv->is_internal_short_scan = true;
queue_work(priv->workqueue, &priv->request_scan); queue_work(priv->workqueue, &priv->request_scan);
unlock:
out: mutex_unlock(&priv->mutex);
return ret;
} }
EXPORT_SYMBOL(iwl_internal_short_hw_scan); EXPORT_SYMBOL(iwl_internal_short_hw_scan);
...@@ -964,6 +972,7 @@ void iwl_setup_scan_deferred_work(struct iwl_priv *priv) ...@@ -964,6 +972,7 @@ void iwl_setup_scan_deferred_work(struct iwl_priv *priv)
INIT_WORK(&priv->scan_completed, iwl_bg_scan_completed); INIT_WORK(&priv->scan_completed, iwl_bg_scan_completed);
INIT_WORK(&priv->request_scan, iwl_bg_request_scan); INIT_WORK(&priv->request_scan, iwl_bg_request_scan);
INIT_WORK(&priv->abort_scan, iwl_bg_abort_scan); INIT_WORK(&priv->abort_scan, iwl_bg_abort_scan);
INIT_WORK(&priv->start_internal_scan, iwl_bg_start_internal_scan);
INIT_DELAYED_WORK(&priv->scan_check, iwl_bg_scan_check); INIT_DELAYED_WORK(&priv->scan_check, iwl_bg_scan_check);
} }
EXPORT_SYMBOL(iwl_setup_scan_deferred_work); EXPORT_SYMBOL(iwl_setup_scan_deferred_work);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment