Skip to content

Commit

Permalink
mac80211: fix on-channel remain-on-channel
Browse files Browse the repository at this point in the history
Jouni reported that if a remain-on-channel was active on the
same channel as the current operating channel, then the ROC
would start, but any frames transmitted using mgmt-tx on the
same channel would get delayed until after the ROC.

The reason for this is that the ROC starts, but doesn't have
any handling for "remain on the same channel", so it stops
the interface queues. The later mgmt-tx then puts the frame
on the interface queues (since it's on the current operating
channel) and thus they get delayed until after the ROC.

To fix this, add some logic to handle remaining on the same
channel specially and not stop the queues etc. in this case.
This not only fixes the bug but also improves behaviour in
this case as data frames etc. can continue to flow.

Cc: stable@vger.kernel.org
Reported-by: Jouni Malinen <j@w1.fi>
Tested-by: Jouni Malinen <j@w1.fi>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
  • Loading branch information
Johannes Berg committed May 14, 2014
1 parent f565198 commit b4b177a
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 7 deletions.
1 change: 1 addition & 0 deletions net/mac80211/ieee80211_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ struct ieee80211_roc_work {

bool started, abort, hw_begun, notified;
bool to_be_freed;
bool on_channel;

unsigned long hw_start_time;

Expand Down
27 changes: 20 additions & 7 deletions net/mac80211/offchannel.c
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ void ieee80211_sw_roc_work(struct work_struct *work)
container_of(work, struct ieee80211_roc_work, work.work);
struct ieee80211_sub_if_data *sdata = roc->sdata;
struct ieee80211_local *local = sdata->local;
bool started;
bool started, on_channel;

mutex_lock(&local->mtx);

Expand All @@ -354,14 +354,26 @@ void ieee80211_sw_roc_work(struct work_struct *work)
if (!roc->started) {
struct ieee80211_roc_work *dep;

/* start this ROC */
ieee80211_offchannel_stop_vifs(local);
WARN_ON(local->use_chanctx);

/* If actually operating on the desired channel (with at least
* 20 MHz channel width) don't stop all the operations but still
* treat it as though the ROC operation started properly, so
* other ROC operations won't interfere with this one.
*/
roc->on_channel = roc->chan == local->_oper_chandef.chan &&
local->_oper_chandef.width != NL80211_CHAN_WIDTH_5 &&
local->_oper_chandef.width != NL80211_CHAN_WIDTH_10;

/* switch channel etc */
/* start this ROC */
ieee80211_recalc_idle(local);

local->tmp_channel = roc->chan;
ieee80211_hw_config(local, 0);
if (!roc->on_channel) {
ieee80211_offchannel_stop_vifs(local);

local->tmp_channel = roc->chan;
ieee80211_hw_config(local, 0);
}

/* tell userspace or send frame */
ieee80211_handle_roc_started(roc);
Expand All @@ -380,9 +392,10 @@ void ieee80211_sw_roc_work(struct work_struct *work)
finish:
list_del(&roc->list);
started = roc->started;
on_channel = roc->on_channel;
ieee80211_roc_notify_destroy(roc, !roc->abort);

if (started) {
if (started && !on_channel) {
ieee80211_flush_queues(local, NULL);

local->tmp_channel = NULL;
Expand Down

0 comments on commit b4b177a

Please sign in to comment.