summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Berg <johannes.berg@intel.com>2010-08-26 13:30:26 +0200
committerJohn W. Linville <linville@tuxdriver.com>2010-08-27 13:27:06 -0400
commit8789d459bc5e837bf37d261453df96ef54018d7b (patch)
tree5dbcabe5807de84f9119ab3654b998fd65ac0a40
parent5f33c92d188add2a22ec524c03e0ab097e303d52 (diff)
mac80211: allow scan to complete from any context
The ieee80211_scan_completed() function was a frequent source of potential deadlocks, since it is called by drivers but may call back into drivers, so drivers had to make sure to call it without any locks held, which frequently lead to more complex code in drivers. Avoid that problem by allowing the function to be called in any context, and queueing the actual work it does. Also update the documentation for it to indicate this. Signed-off-by: Johannes Berg <johannes.berg@intel.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
-rw-r--r--include/net/mac80211.h3
-rw-r--r--net/mac80211/ieee80211_i.h6
-rw-r--r--net/mac80211/scan.c34
3 files changed, 34 insertions, 9 deletions
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index dcc8c2bf986e..8f97548b6d80 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -2268,7 +2268,8 @@ void ieee80211_wake_queues(struct ieee80211_hw *hw);
*
* When hardware scan offload is used (i.e. the hw_scan() callback is
* assigned) this function needs to be called by the driver to notify
- * mac80211 that the scan finished.
+ * mac80211 that the scan finished. This function can be called from
+ * any context, including hardirq context.
*
* @hw: the hardware that finished the scan
* @aborted: set to true if scan was aborted
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 9e225f01497b..31713320258c 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -596,11 +596,17 @@ enum queue_stop_reason {
* determine if we are on the operating channel or not
* @SCAN_OFF_CHANNEL: We're off our operating channel for scanning,
* gets only set in conjunction with SCAN_SW_SCANNING
+ * @SCAN_COMPLETED: Set for our scan work function when the driver reported
+ * that the scan completed.
+ * @SCAN_ABORTED: Set for our scan work function when the driver reported
+ * a scan complete for an aborted scan.
*/
enum {
SCAN_SW_SCANNING,
SCAN_HW_SCANNING,
SCAN_OFF_CHANNEL,
+ SCAN_COMPLETED,
+ SCAN_ABORTED,
};
/**
diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c
index 31f233f7f51a..d60389ba9b95 100644
--- a/net/mac80211/scan.c
+++ b/net/mac80211/scan.c
@@ -248,13 +248,11 @@ static bool ieee80211_prep_hw_scan(struct ieee80211_local *local)
return true;
}
-void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
+static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
{
struct ieee80211_local *local = hw_to_local(hw);
bool was_hw_scan;
- trace_api_scan_completed(local, aborted);
-
mutex_lock(&local->mtx);
/*
@@ -312,6 +310,18 @@ void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
ieee80211_mesh_notify_scan_completed(local);
ieee80211_queue_work(&local->hw, &local->work_work);
}
+
+void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
+{
+ struct ieee80211_local *local = hw_to_local(hw);
+
+ trace_api_scan_completed(local, aborted);
+
+ set_bit(SCAN_COMPLETED, &local->scanning);
+ if (aborted)
+ set_bit(SCAN_ABORTED, &local->scanning);
+ ieee80211_queue_delayed_work(&local->hw, &local->scan_work, 0);
+}
EXPORT_SYMBOL(ieee80211_scan_completed);
static int ieee80211_start_sw_scan(struct ieee80211_local *local)
@@ -449,7 +459,7 @@ static int ieee80211_scan_state_decision(struct ieee80211_local *local,
/* if no more bands/channels left, complete scan and advance to the idle state */
if (local->scan_channel_idx >= local->scan_req->n_channels) {
- ieee80211_scan_completed(&local->hw, false);
+ __ieee80211_scan_completed(&local->hw, false);
return 1;
}
@@ -641,6 +651,14 @@ void ieee80211_scan_work(struct work_struct *work)
struct ieee80211_sub_if_data *sdata = local->scan_sdata;
unsigned long next_delay = 0;
+ if (test_and_clear_bit(SCAN_COMPLETED, &local->scanning)) {
+ bool aborted;
+
+ aborted = test_and_clear_bit(SCAN_ABORTED, &local->scanning);
+ __ieee80211_scan_completed(&local->hw, aborted);
+ return;
+ }
+
mutex_lock(&local->mtx);
if (!sdata || !local->scan_req) {
mutex_unlock(&local->mtx);
@@ -651,7 +669,7 @@ void ieee80211_scan_work(struct work_struct *work)
int rc = drv_hw_scan(local, sdata, local->hw_scan_req);
mutex_unlock(&local->mtx);
if (rc)
- ieee80211_scan_completed(&local->hw, true);
+ __ieee80211_scan_completed(&local->hw, true);
return;
}
@@ -666,7 +684,7 @@ void ieee80211_scan_work(struct work_struct *work)
mutex_unlock(&local->mtx);
if (rc)
- ieee80211_scan_completed(&local->hw, true);
+ __ieee80211_scan_completed(&local->hw, true);
return;
}
@@ -676,7 +694,7 @@ void ieee80211_scan_work(struct work_struct *work)
* Avoid re-scheduling when the sdata is going away.
*/
if (!ieee80211_sdata_running(sdata)) {
- ieee80211_scan_completed(&local->hw, true);
+ __ieee80211_scan_completed(&local->hw, true);
return;
}
@@ -783,5 +801,5 @@ void ieee80211_scan_cancel(struct ieee80211_local *local)
mutex_unlock(&local->mtx);
if (abortscan)
- ieee80211_scan_completed(&local->hw, true);
+ __ieee80211_scan_completed(&local->hw, true);
}