From e3192690a3c889767d1161b228374f4926d92af0 Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Sun, 3 Jun 2012 17:41:40 +0000 Subject: net: Remove casts to same type Adding casts of objects to the same type is unnecessary and confusing for a human reader. For example, this cast: int y; int *p = (int *)&y; I used the coccinelle script below to find and remove these unnecessary casts. I manually removed the conversions this script produces of casts with __force and __user. @@ type T; T *p; @@ - (T *)p + p Signed-off-by: Joe Perches Signed-off-by: David S. Miller --- net/mac80211/scan.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'net/mac80211/scan.c') diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index 169da0742c8..6d90a562669 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -114,8 +114,7 @@ ieee80211_bss_info_update(struct ieee80211_local *local, if (elems->tim && (!elems->parse_error || !(bss->valid_data & IEEE80211_BSS_VALID_DTIM))) { - struct ieee80211_tim_ie *tim_ie = - (struct ieee80211_tim_ie *)elems->tim; + struct ieee80211_tim_ie *tim_ie = elems->tim; bss->dtim_period = tim_ie->dtim_period; if (!elems->parse_error) bss->valid_data |= IEEE80211_BSS_VALID_DTIM; -- cgit v1.2.3-70-g09d2 From 2eb278e083549f4eb29838037004054b3b55df62 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 5 Jun 2012 14:28:42 +0200 Subject: mac80211: unify SW/offload remain-on-channel Redesign all the off-channel code, getting rid of the generic off-channel work concept, replacing it with a simple remain-on-channel list. This fixes a number of small issues with the ROC implementation: * offloaded remain-on-channel couldn't be queued, now we can queue it as well, if needed * in iwlwifi (the only user) offloaded ROC is mutually exclusive with scanning, use the new queue to handle that case -- I expect that it will later depend on a HW flag The bigger issue though is that there's a bad bug in the current implementation: if we get a mgmt TX request while HW roc is active, and this new request has a wait time, we actually schedule a software ROC instead since we can't guarantee the existing offloaded ROC will still be that long. To fix this, the queuing mechanism was needed. The queuing mechanism for offloaded ROC isn't yet optimal, ideally we should add API to have the HW extend the ROC if needed. We could add that later but for now use a software implementation. Overall, this unifies the behaviour between the offloaded and software-implemented case as much as possible. Signed-off-by: Johannes Berg Signed-off-by: John W. Linville --- include/net/mac80211.h | 3 - net/mac80211/Makefile | 1 - net/mac80211/cfg.c | 478 ++++++++++++++++++++++++--------------------- net/mac80211/ieee80211_i.h | 89 +++------ net/mac80211/iface.c | 23 +-- net/mac80211/main.c | 10 +- net/mac80211/offchannel.c | 280 ++++++++++++++++++++++---- net/mac80211/scan.c | 4 +- net/mac80211/status.c | 28 +-- net/mac80211/work.c | 370 ----------------------------------- 10 files changed, 532 insertions(+), 754 deletions(-) delete mode 100644 net/mac80211/work.c (limited to 'net/mac80211/scan.c') diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 6e700bf8d4a..d152f54064f 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -2184,9 +2184,6 @@ enum ieee80211_rate_control_changed { * normally except for the %IEEE80211_TX_CTL_TX_OFFCHAN flag. When the * duration (which will always be non-zero) expires, the driver must call * ieee80211_remain_on_channel_expired(). - * The driver must not call ieee80211_remain_on_channel_expired() before - * the TX status for a frame that was sent off-channel, otherwise the TX - * status is reported to userspace in an invalid way. * Note that this callback may be called while the device is in IDLE and * must be accepted in this case. * This callback may sleep. diff --git a/net/mac80211/Makefile b/net/mac80211/Makefile index 3e9d931bba3..2b1470bac17 100644 --- a/net/mac80211/Makefile +++ b/net/mac80211/Makefile @@ -9,7 +9,6 @@ mac80211-y := \ scan.o offchannel.o \ ht.o agg-tx.o agg-rx.o \ ibss.o \ - work.o \ iface.o \ rate.o \ michael.o \ diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index a1690791970..498c94e3442 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -2112,35 +2112,171 @@ static int ieee80211_set_bitrate_mask(struct wiphy *wiphy, return 0; } -static int ieee80211_remain_on_channel_hw(struct ieee80211_local *local, - struct net_device *dev, - struct ieee80211_channel *chan, - enum nl80211_channel_type chantype, - unsigned int duration, u64 *cookie) -{ +static int ieee80211_start_roc_work(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_channel *channel, + enum nl80211_channel_type channel_type, + unsigned int duration, u64 *cookie, + struct sk_buff *txskb) +{ + struct ieee80211_roc_work *roc, *tmp; + bool queued = false; int ret; - u32 random_cookie; lockdep_assert_held(&local->mtx); - if (local->hw_roc_cookie) - return -EBUSY; - /* must be nonzero */ - random_cookie = random32() | 1; - - *cookie = random_cookie; - local->hw_roc_dev = dev; - local->hw_roc_cookie = random_cookie; - local->hw_roc_channel = chan; - local->hw_roc_channel_type = chantype; - local->hw_roc_duration = duration; - ret = drv_remain_on_channel(local, chan, chantype, duration); + roc = kzalloc(sizeof(*roc), GFP_KERNEL); + if (!roc) + return -ENOMEM; + + roc->chan = channel; + roc->chan_type = channel_type; + roc->duration = duration; + roc->req_duration = duration; + roc->frame = txskb; + roc->mgmt_tx_cookie = (unsigned long)txskb; + roc->sdata = sdata; + INIT_DELAYED_WORK(&roc->work, ieee80211_sw_roc_work); + INIT_LIST_HEAD(&roc->dependents); + + /* if there's one pending or we're scanning, queue this one */ + if (!list_empty(&local->roc_list) || local->scanning) + goto out_check_combine; + + /* if not HW assist, just queue & schedule work */ + if (!local->ops->remain_on_channel) { + ieee80211_queue_delayed_work(&local->hw, &roc->work, 0); + goto out_queue; + } + + /* otherwise actually kick it off here (for error handling) */ + + /* + * If the duration is zero, then the driver + * wouldn't actually do anything. Set it to + * 10 for now. + * + * TODO: cancel the off-channel operation + * when we get the SKB's TX status and + * the wait time was zero before. + */ + if (!duration) + duration = 10; + + ret = drv_remain_on_channel(local, channel, channel_type, duration); if (ret) { - local->hw_roc_channel = NULL; - local->hw_roc_cookie = 0; + kfree(roc); + return ret; } - return ret; + roc->started = true; + goto out_queue; + + out_check_combine: + list_for_each_entry(tmp, &local->roc_list, list) { + if (tmp->chan != channel || tmp->chan_type != channel_type) + continue; + + /* + * Extend this ROC if possible: + * + * If it hasn't started yet, just increase the duration + * and add the new one to the list of dependents. + */ + if (!tmp->started) { + list_add_tail(&roc->list, &tmp->dependents); + tmp->duration = max(tmp->duration, roc->duration); + queued = true; + break; + } + + /* If it has already started, it's more difficult ... */ + if (local->ops->remain_on_channel) { + unsigned long j = jiffies; + + /* + * In the offloaded ROC case, if it hasn't begun, add + * this new one to the dependent list to be handled + * when the the master one begins. If it has begun, + * check that there's still a minimum time left and + * if so, start this one, transmitting the frame, but + * add it to the list directly after this one with a + * a reduced time so we'll ask the driver to execute + * it right after finishing the previous one, in the + * hope that it'll also be executed right afterwards, + * effectively extending the old one. + * If there's no minimum time left, just add it to the + * normal list. + */ + if (!tmp->hw_begun) { + list_add_tail(&roc->list, &tmp->dependents); + queued = true; + break; + } + + if (time_before(j + IEEE80211_ROC_MIN_LEFT, + tmp->hw_start_time + + msecs_to_jiffies(tmp->duration))) { + int new_dur; + + ieee80211_handle_roc_started(roc); + + new_dur = roc->duration - + jiffies_to_msecs(tmp->hw_start_time + + msecs_to_jiffies( + tmp->duration) - + j); + + if (new_dur > 0) { + /* add right after tmp */ + list_add(&roc->list, &tmp->list); + } else { + list_add_tail(&roc->list, + &tmp->dependents); + } + queued = true; + } + } else if (del_timer_sync(&tmp->work.timer)) { + unsigned long new_end; + + /* + * In the software ROC case, cancel the timer, if + * that fails then the finish work is already + * queued/pending and thus we queue the new ROC + * normally, if that succeeds then we can extend + * the timer duration and TX the frame (if any.) + */ + + list_add_tail(&roc->list, &tmp->dependents); + queued = true; + + new_end = jiffies + msecs_to_jiffies(roc->duration); + + /* ok, it was started & we canceled timer */ + if (time_after(new_end, tmp->work.timer.expires)) + mod_timer(&tmp->work.timer, new_end); + else + add_timer(&tmp->work.timer); + + ieee80211_handle_roc_started(roc); + } + break; + } + + out_queue: + if (!queued) + list_add_tail(&roc->list, &local->roc_list); + + /* + * cookie is either the roc (for normal roc) + * or the SKB (for mgmt TX) + */ + if (txskb) + *cookie = (unsigned long)txskb; + else + *cookie = (unsigned long)roc; + + return 0; } static int ieee80211_remain_on_channel(struct wiphy *wiphy, @@ -2152,84 +2288,76 @@ static int ieee80211_remain_on_channel(struct wiphy *wiphy, { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; + int ret; - if (local->ops->remain_on_channel) { - int ret; - - mutex_lock(&local->mtx); - ret = ieee80211_remain_on_channel_hw(local, dev, - chan, channel_type, - duration, cookie); - local->hw_roc_for_tx = false; - mutex_unlock(&local->mtx); - - return ret; - } + mutex_lock(&local->mtx); + ret = ieee80211_start_roc_work(local, sdata, chan, channel_type, + duration, cookie, NULL); + mutex_unlock(&local->mtx); - return ieee80211_wk_remain_on_channel(sdata, chan, channel_type, - duration, cookie); + return ret; } -static int ieee80211_cancel_remain_on_channel_hw(struct ieee80211_local *local, - u64 cookie) +static int ieee80211_cancel_roc(struct ieee80211_local *local, + u64 cookie, bool mgmt_tx) { + struct ieee80211_roc_work *roc, *tmp, *found = NULL; int ret; - lockdep_assert_held(&local->mtx); + mutex_lock(&local->mtx); + list_for_each_entry_safe(roc, tmp, &local->roc_list, list) { + if (!mgmt_tx && (unsigned long)roc != cookie) + continue; + else if (mgmt_tx && roc->mgmt_tx_cookie != cookie) + continue; - if (local->hw_roc_cookie != cookie) - return -ENOENT; + found = roc; + break; + } - ret = drv_cancel_remain_on_channel(local); - if (ret) - return ret; + if (!found) { + mutex_unlock(&local->mtx); + return -ENOENT; + } - local->hw_roc_cookie = 0; - local->hw_roc_channel = NULL; + if (local->ops->remain_on_channel) { + if (found->started) { + ret = drv_cancel_remain_on_channel(local); + if (WARN_ON_ONCE(ret)) { + mutex_unlock(&local->mtx); + return ret; + } + } - return 0; -} + list_del(&found->list); -static int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy, - struct net_device *dev, - u64 cookie) -{ - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); - struct ieee80211_local *local = sdata->local; + ieee80211_run_deferred_scan(local); + ieee80211_start_next_roc(local); + mutex_unlock(&local->mtx); - if (local->ops->cancel_remain_on_channel) { - int ret; + ieee80211_roc_notify_destroy(found); + } else { + /* work may be pending so use it all the time */ + found->abort = true; + ieee80211_queue_delayed_work(&local->hw, &found->work, 0); - mutex_lock(&local->mtx); - ret = ieee80211_cancel_remain_on_channel_hw(local, cookie); mutex_unlock(&local->mtx); - return ret; + /* work will clean up etc */ + flush_delayed_work(&found->work); } - return ieee80211_wk_cancel_remain_on_channel(sdata, cookie); + return 0; } -static enum work_done_result -ieee80211_offchan_tx_done(struct ieee80211_work *wk, struct sk_buff *skb) +static int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy, + struct net_device *dev, + u64 cookie) { - /* - * Use the data embedded in the work struct for reporting - * here so if the driver mangled the SKB before dropping - * it (which is the only way we really should get here) - * then we don't report mangled data. - * - * If there was no wait time, then by the time we get here - * the driver will likely not have reported the status yet, - * so in that case userspace will have to deal with it. - */ - - if (wk->offchan_tx.wait && !wk->offchan_tx.status) - cfg80211_mgmt_tx_status(wk->sdata->dev, - (unsigned long) wk->offchan_tx.frame, - wk->data, wk->data_len, false, GFP_KERNEL); + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_local *local = sdata->local; - return WORK_DONE_DESTROY; + return ieee80211_cancel_roc(local, cookie, false); } static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev, @@ -2243,10 +2371,10 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev, struct ieee80211_local *local = sdata->local; struct sk_buff *skb; struct sta_info *sta; - struct ieee80211_work *wk; const struct ieee80211_mgmt *mgmt = (void *)buf; + bool need_offchan = false; u32 flags; - bool is_offchan = false, in_hw_roc = false; + int ret; if (dont_wait_for_ack) flags = IEEE80211_TX_CTL_NO_ACK; @@ -2254,34 +2382,28 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev, flags = IEEE80211_TX_INTFL_NL80211_FRAME_TX | IEEE80211_TX_CTL_REQ_TX_STATUS; - /* Check that we are on the requested channel for transmission */ - if (chan != local->tmp_channel && - chan != local->oper_channel) - is_offchan = true; - if (channel_type_valid && - (channel_type != local->tmp_channel_type && - channel_type != local->_oper_channel_type)) - is_offchan = true; - - if (chan == local->hw_roc_channel) { - /* TODO: check channel type? */ - is_offchan = false; - in_hw_roc = true; - flags |= IEEE80211_TX_CTL_TX_OFFCHAN; - } - if (no_cck) flags |= IEEE80211_TX_CTL_NO_CCK_RATE; - if (is_offchan && !offchan) - return -EBUSY; - switch (sdata->vif.type) { case NL80211_IFTYPE_ADHOC: + if (!sdata->vif.bss_conf.ibss_joined) + need_offchan = true; + /* fall through */ +#ifdef CONFIG_MAC80211_MESH + case NL80211_IFTYPE_MESH_POINT: + if (ieee80211_vif_is_mesh(&sdata->vif) && + !sdata->u.mesh.mesh_id_len) + need_offchan = true; + /* fall through */ +#endif case NL80211_IFTYPE_AP: case NL80211_IFTYPE_AP_VLAN: case NL80211_IFTYPE_P2P_GO: - case NL80211_IFTYPE_MESH_POINT: + if (sdata->vif.type != NL80211_IFTYPE_ADHOC && + !ieee80211_vif_is_mesh(&sdata->vif) && + !rcu_access_pointer(sdata->bss->beacon)) + need_offchan = true; if (!ieee80211_is_action(mgmt->frame_control) || mgmt->u.action.category == WLAN_CATEGORY_PUBLIC) break; @@ -2293,105 +2415,60 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev, break; case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_P2P_CLIENT: + if (!sdata->u.mgd.associated) + need_offchan = true; break; default: return -EOPNOTSUPP; } + mutex_lock(&local->mtx); + + /* Check if the operating channel is the requested channel */ + if (!need_offchan) { + need_offchan = chan != local->oper_channel; + if (channel_type_valid && + channel_type != local->_oper_channel_type) + need_offchan = true; + } + + if (need_offchan && !offchan) { + ret = -EBUSY; + goto out_unlock; + } + skb = dev_alloc_skb(local->hw.extra_tx_headroom + len); - if (!skb) - return -ENOMEM; + if (!skb) { + ret = -ENOMEM; + goto out_unlock; + } skb_reserve(skb, local->hw.extra_tx_headroom); memcpy(skb_put(skb, len), buf, len); IEEE80211_SKB_CB(skb)->flags = flags; - if (local->hw.flags & IEEE80211_HW_QUEUE_CONTROL && - flags & IEEE80211_TX_CTL_TX_OFFCHAN) - IEEE80211_SKB_CB(skb)->hw_queue = - local->hw.offchannel_tx_hw_queue; - skb->dev = sdata->dev; - *cookie = (unsigned long) skb; - - if (is_offchan && local->ops->remain_on_channel) { - unsigned int duration; - int ret; - - mutex_lock(&local->mtx); - /* - * If the duration is zero, then the driver - * wouldn't actually do anything. Set it to - * 100 for now. - * - * TODO: cancel the off-channel operation - * when we get the SKB's TX status and - * the wait time was zero before. - */ - duration = 100; - if (wait) - duration = wait; - ret = ieee80211_remain_on_channel_hw(local, dev, chan, - channel_type, - duration, cookie); - if (ret) { - kfree_skb(skb); - mutex_unlock(&local->mtx); - return ret; - } - - local->hw_roc_for_tx = true; - local->hw_roc_duration = wait; - - /* - * queue up frame for transmission after - * ieee80211_ready_on_channel call - */ - - /* modify cookie to prevent API mismatches */ - *cookie ^= 2; - IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_CTL_TX_OFFCHAN; - if (local->hw.flags & IEEE80211_HW_QUEUE_CONTROL) - IEEE80211_SKB_CB(skb)->hw_queue = - local->hw.offchannel_tx_hw_queue; - local->hw_roc_skb = skb; - local->hw_roc_skb_for_status = skb; - mutex_unlock(&local->mtx); - - return 0; - } - - /* - * Can transmit right away if the channel was the - * right one and there's no wait involved... If a - * wait is involved, we might otherwise not be on - * the right channel for long enough! - */ - if (!is_offchan && !wait && (in_hw_roc || !sdata->vif.bss_conf.idle)) { + if (!need_offchan) { ieee80211_tx_skb(sdata, skb); - return 0; - } - - wk = kzalloc(sizeof(*wk) + len, GFP_KERNEL); - if (!wk) { - kfree_skb(skb); - return -ENOMEM; + ret = 0; + goto out_unlock; } - wk->type = IEEE80211_WORK_OFFCHANNEL_TX; - wk->chan = chan; - wk->chan_type = channel_type; - wk->sdata = sdata; - wk->done = ieee80211_offchan_tx_done; - wk->offchan_tx.frame = skb; - wk->offchan_tx.wait = wait; - wk->data_len = len; - memcpy(wk->data, buf, len); + IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_CTL_TX_OFFCHAN; + if (local->hw.flags & IEEE80211_HW_QUEUE_CONTROL) + IEEE80211_SKB_CB(skb)->hw_queue = + local->hw.offchannel_tx_hw_queue; - ieee80211_add_work(wk); - return 0; + /* This will handle all kinds of coalescing and immediate TX */ + ret = ieee80211_start_roc_work(local, sdata, chan, channel_type, + wait, cookie, skb); + if (ret) + kfree_skb(skb); + out_unlock: + mutex_unlock(&local->mtx); + return ret; } static int ieee80211_mgmt_tx_cancel_wait(struct wiphy *wiphy, @@ -2400,45 +2477,8 @@ static int ieee80211_mgmt_tx_cancel_wait(struct wiphy *wiphy, { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; - struct ieee80211_work *wk; - int ret = -ENOENT; - mutex_lock(&local->mtx); - - if (local->ops->cancel_remain_on_channel) { - cookie ^= 2; - ret = ieee80211_cancel_remain_on_channel_hw(local, cookie); - - if (ret == 0) { - kfree_skb(local->hw_roc_skb); - local->hw_roc_skb = NULL; - local->hw_roc_skb_for_status = NULL; - } - - mutex_unlock(&local->mtx); - - return ret; - } - - list_for_each_entry(wk, &local->work_list, list) { - if (wk->sdata != sdata) - continue; - - if (wk->type != IEEE80211_WORK_OFFCHANNEL_TX) - continue; - - if (cookie != (unsigned long) wk->offchan_tx.frame) - continue; - - wk->timeout = jiffies; - - ieee80211_queue_work(&local->hw, &local->work_work); - ret = 0; - break; - } - mutex_unlock(&local->mtx); - - return ret; + return ieee80211_cancel_roc(local, cookie, true); } static void ieee80211_mgmt_frame_register(struct wiphy *wiphy, diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 8c026abcb8d..e6cbf5b68c8 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -317,55 +317,30 @@ struct mesh_preq_queue { u8 flags; }; -enum ieee80211_work_type { - IEEE80211_WORK_ABORT, - IEEE80211_WORK_REMAIN_ON_CHANNEL, - IEEE80211_WORK_OFFCHANNEL_TX, -}; - -/** - * enum work_done_result - indicates what to do after work was done - * - * @WORK_DONE_DESTROY: This work item is no longer needed, destroy. - * @WORK_DONE_REQUEUE: This work item was reset to be reused, and - * should be requeued. - */ -enum work_done_result { - WORK_DONE_DESTROY, - WORK_DONE_REQUEUE, -}; +#if HZ/100 == 0 +#define IEEE80211_ROC_MIN_LEFT 1 +#else +#define IEEE80211_ROC_MIN_LEFT (HZ/100) +#endif -struct ieee80211_work { +struct ieee80211_roc_work { struct list_head list; + struct list_head dependents; - struct rcu_head rcu_head; + struct delayed_work work; struct ieee80211_sub_if_data *sdata; - enum work_done_result (*done)(struct ieee80211_work *wk, - struct sk_buff *skb); - struct ieee80211_channel *chan; enum nl80211_channel_type chan_type; - unsigned long timeout; - enum ieee80211_work_type type; + bool started, abort, hw_begun, notified; - bool started; + unsigned long hw_start_time; - union { - struct { - u32 duration; - } remain; - struct { - struct sk_buff *frame; - u32 wait; - bool status; - } offchan_tx; - }; - - size_t data_len; - u8 data[]; + u32 duration, req_duration; + struct sk_buff *frame; + u64 mgmt_tx_cookie; }; /* flags used in struct ieee80211_if_managed.flags */ @@ -847,13 +822,6 @@ struct ieee80211_local { const struct ieee80211_ops *ops; - /* - * work stuff, potentially off-channel (in the future) - */ - struct list_head work_list; - struct timer_list work_timer; - struct work_struct work_work; - /* * private workqueue to mac80211. mac80211 makes this accessible * via ieee80211_queue_work() @@ -1088,14 +1056,12 @@ struct ieee80211_local { } debugfs; #endif - struct ieee80211_channel *hw_roc_channel; - struct net_device *hw_roc_dev; - struct sk_buff *hw_roc_skb, *hw_roc_skb_for_status; + /* + * Remain-on-channel support + */ + struct list_head roc_list; struct work_struct hw_roc_start, hw_roc_done; - enum nl80211_channel_type hw_roc_channel_type; - unsigned int hw_roc_duration; - u32 hw_roc_cookie; - bool hw_roc_for_tx; + unsigned long hw_roc_start_time; struct idr ack_status_frames; spinlock_t ack_status_lock; @@ -1291,7 +1257,12 @@ void ieee80211_offchannel_stop_vifs(struct ieee80211_local *local, bool offchannel_ps_enable); void ieee80211_offchannel_return(struct ieee80211_local *local, bool offchannel_ps_disable); -void ieee80211_hw_roc_setup(struct ieee80211_local *local); +void ieee80211_roc_setup(struct ieee80211_local *local); +void ieee80211_start_next_roc(struct ieee80211_local *local); +void ieee80211_roc_purge(struct ieee80211_sub_if_data *sdata); +void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc); +void ieee80211_sw_roc_work(struct work_struct *work); +void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc); /* interface handling */ int ieee80211_iface_init(void); @@ -1501,18 +1472,6 @@ u8 *ieee80211_ie_build_ht_oper(u8 *pos, struct ieee80211_sta_ht_cap *ht_cap, enum nl80211_channel_type channel_type, u16 prot_mode); -/* internal work items */ -void ieee80211_work_init(struct ieee80211_local *local); -void ieee80211_add_work(struct ieee80211_work *wk); -void free_work(struct ieee80211_work *wk); -void ieee80211_work_purge(struct ieee80211_sub_if_data *sdata); -int ieee80211_wk_remain_on_channel(struct ieee80211_sub_if_data *sdata, - struct ieee80211_channel *chan, - enum nl80211_channel_type channel_type, - unsigned int duration, u64 *cookie); -int ieee80211_wk_cancel_remain_on_channel( - struct ieee80211_sub_if_data *sdata, u64 cookie); - /* channel management */ enum ieee80211_chan_mode { CHAN_MODE_UNDEFINED, diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 968d71c5071..87aeb4f21ff 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -528,10 +528,7 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, */ netif_tx_stop_all_queues(sdata->dev); - /* - * Purge work for this interface. - */ - ieee80211_work_purge(sdata); + ieee80211_roc_purge(sdata); /* * Remove all stations associated with this interface. @@ -637,18 +634,6 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, ieee80211_configure_filter(local); break; default: - mutex_lock(&local->mtx); - if (local->hw_roc_dev == sdata->dev && - local->hw_roc_channel) { - /* ignore return value since this is racy */ - drv_cancel_remain_on_channel(local); - ieee80211_queue_work(&local->hw, &local->hw_roc_done); - } - mutex_unlock(&local->mtx); - - flush_work(&local->hw_roc_start); - flush_work(&local->hw_roc_done); - flush_work(&sdata->work); /* * When we get here, the interface is marked down. @@ -1457,8 +1442,8 @@ u32 __ieee80211_recalc_idle(struct ieee80211_local *local) struct ieee80211_sub_if_data *sdata; int count = 0; bool working = false, scanning = false; - struct ieee80211_work *wk; unsigned int led_trig_start = 0, led_trig_stop = 0; + struct ieee80211_roc_work *roc; #ifdef CONFIG_PROVE_LOCKING WARN_ON(debug_locks && !lockdep_rtnl_is_held() && @@ -1494,9 +1479,9 @@ u32 __ieee80211_recalc_idle(struct ieee80211_local *local) } if (!local->ops->remain_on_channel) { - list_for_each_entry(wk, &local->work_list, list) { + list_for_each_entry(roc, &local->roc_list, list) { working = true; - wk->sdata->vif.bss_conf.idle = false; + roc->sdata->vif.bss_conf.idle = false; } } diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 779ac613ee5..d81c178c771 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -625,8 +625,6 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len, INIT_DELAYED_WORK(&local->scan_work, ieee80211_scan_work); - ieee80211_work_init(local); - INIT_WORK(&local->restart_work, ieee80211_restart_work); INIT_WORK(&local->reconfig_filter, ieee80211_reconfig_filter); @@ -669,7 +667,7 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len, ieee80211_led_names(local); - ieee80211_hw_roc_setup(local); + ieee80211_roc_setup(local); return &local->hw; } @@ -1016,12 +1014,6 @@ void ieee80211_unregister_hw(struct ieee80211_hw *hw) rtnl_unlock(); - /* - * Now all work items will be gone, but the - * timer might still be armed, so delete it - */ - del_timer_sync(&local->work_timer); - cancel_work_sync(&local->restart_work); cancel_work_sync(&local->reconfig_filter); diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c index 8f482b15bc5..abb226dc475 100644 --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c @@ -16,6 +16,7 @@ #include #include "ieee80211_i.h" #include "driver-trace.h" +#include "driver-ops.h" /* * Tell our hardware to disable PS. @@ -181,32 +182,58 @@ void ieee80211_offchannel_return(struct ieee80211_local *local, mutex_unlock(&local->iflist_mtx); } +void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc) +{ + if (roc->notified) + return; + + if (roc->mgmt_tx_cookie) { + if (!WARN_ON(!roc->frame)) { + ieee80211_tx_skb(roc->sdata, roc->frame); + roc->frame = NULL; + } + } else { + cfg80211_ready_on_channel(roc->sdata->dev, (unsigned long)roc, + roc->chan, roc->chan_type, + roc->req_duration, GFP_KERNEL); + } + + roc->notified = true; +} + static void ieee80211_hw_roc_start(struct work_struct *work) { struct ieee80211_local *local = container_of(work, struct ieee80211_local, hw_roc_start); - struct ieee80211_sub_if_data *sdata; + struct ieee80211_roc_work *roc, *dep, *tmp; mutex_lock(&local->mtx); - if (!local->hw_roc_channel) { - mutex_unlock(&local->mtx); - return; - } + if (list_empty(&local->roc_list)) + goto out_unlock; - if (local->hw_roc_skb) { - sdata = IEEE80211_DEV_TO_SUB_IF(local->hw_roc_dev); - ieee80211_tx_skb(sdata, local->hw_roc_skb); - local->hw_roc_skb = NULL; - } else { - cfg80211_ready_on_channel(local->hw_roc_dev, - local->hw_roc_cookie, - local->hw_roc_channel, - local->hw_roc_channel_type, - local->hw_roc_duration, - GFP_KERNEL); - } + roc = list_first_entry(&local->roc_list, struct ieee80211_roc_work, + list); + + if (!roc->started) + goto out_unlock; + + roc->hw_begun = true; + roc->hw_start_time = local->hw_roc_start_time; + ieee80211_handle_roc_started(roc); + list_for_each_entry_safe(dep, tmp, &roc->dependents, list) { + ieee80211_handle_roc_started(dep); + + if (dep->duration > roc->duration) { + u32 dur = dep->duration; + dep->duration = dur - roc->duration; + roc->duration = dur; + list_del(&dep->list); + list_add(&dep->list, &roc->list); + } + } + out_unlock: mutex_unlock(&local->mtx); } @@ -214,50 +241,179 @@ void ieee80211_ready_on_channel(struct ieee80211_hw *hw) { struct ieee80211_local *local = hw_to_local(hw); + local->hw_roc_start_time = jiffies; + trace_api_ready_on_channel(local); ieee80211_queue_work(hw, &local->hw_roc_start); } EXPORT_SYMBOL_GPL(ieee80211_ready_on_channel); -static void ieee80211_hw_roc_done(struct work_struct *work) +void ieee80211_start_next_roc(struct ieee80211_local *local) { - struct ieee80211_local *local = - container_of(work, struct ieee80211_local, hw_roc_done); + struct ieee80211_roc_work *roc; - mutex_lock(&local->mtx); + lockdep_assert_held(&local->mtx); - if (!local->hw_roc_channel) { - mutex_unlock(&local->mtx); + if (list_empty(&local->roc_list)) { + ieee80211_run_deferred_scan(local); return; } - /* was never transmitted */ - if (local->hw_roc_skb) { - u64 cookie; + roc = list_first_entry(&local->roc_list, struct ieee80211_roc_work, + list); - cookie = local->hw_roc_cookie ^ 2; + if (local->ops->remain_on_channel) { + int ret, duration = roc->duration; - cfg80211_mgmt_tx_status(local->hw_roc_dev, cookie, - local->hw_roc_skb->data, - local->hw_roc_skb->len, false, - GFP_KERNEL); + /* XXX: duplicated, see ieee80211_start_roc_work() */ + if (!duration) + duration = 10; - kfree_skb(local->hw_roc_skb); - local->hw_roc_skb = NULL; - local->hw_roc_skb_for_status = NULL; + ret = drv_remain_on_channel(local, roc->chan, + roc->chan_type, + duration); + + roc->started = true; + + if (ret) { + wiphy_warn(local->hw.wiphy, + "failed to start next HW ROC (%d)\n", ret); + /* + * queue the work struct again to avoid recursion + * when multiple failures occur + */ + ieee80211_remain_on_channel_expired(&local->hw); + } + } else { + /* delay it a bit */ + ieee80211_queue_delayed_work(&local->hw, &roc->work, + round_jiffies_relative(HZ/2)); + } +} + +void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc) +{ + struct ieee80211_roc_work *dep, *tmp; + + /* was never transmitted */ + if (roc->frame) { + cfg80211_mgmt_tx_status(roc->sdata->dev, + (unsigned long)roc->frame, + roc->frame->data, roc->frame->len, + false, GFP_KERNEL); + kfree_skb(roc->frame); } - if (!local->hw_roc_for_tx) - cfg80211_remain_on_channel_expired(local->hw_roc_dev, - local->hw_roc_cookie, - local->hw_roc_channel, - local->hw_roc_channel_type, + if (!roc->mgmt_tx_cookie) + cfg80211_remain_on_channel_expired(roc->sdata->dev, + (unsigned long)roc, + roc->chan, roc->chan_type, GFP_KERNEL); - local->hw_roc_channel = NULL; - local->hw_roc_cookie = 0; + list_for_each_entry_safe(dep, tmp, &roc->dependents, list) + ieee80211_roc_notify_destroy(dep); + + kfree(roc); +} + +void ieee80211_sw_roc_work(struct work_struct *work) +{ + struct ieee80211_roc_work *roc = + container_of(work, struct ieee80211_roc_work, work.work); + struct ieee80211_sub_if_data *sdata = roc->sdata; + struct ieee80211_local *local = sdata->local; + + mutex_lock(&local->mtx); + + if (roc->abort) + goto finish; + + if (WARN_ON(list_empty(&local->roc_list))) + goto out_unlock; + + if (WARN_ON(roc != list_first_entry(&local->roc_list, + struct ieee80211_roc_work, + list))) + goto out_unlock; + + if (!roc->started) { + struct ieee80211_roc_work *dep; + + /* start this ROC */ + /* switch channel etc */ + ieee80211_recalc_idle(local); + + local->tmp_channel = roc->chan; + local->tmp_channel_type = roc->chan_type; + ieee80211_hw_config(local, 0); + + /* tell userspace or send frame */ + ieee80211_handle_roc_started(roc); + list_for_each_entry(dep, &roc->dependents, list) + ieee80211_handle_roc_started(dep); + + /* if it was pure TX, just finish right away */ + if (!roc->duration) + goto finish; + + roc->started = true; + ieee80211_queue_delayed_work(&local->hw, &roc->work, + msecs_to_jiffies(roc->duration)); + } else { + /* finish this ROC */ + finish: + list_del(&roc->list); + ieee80211_roc_notify_destroy(roc); + + if (roc->started) { + drv_flush(local, false); + + local->tmp_channel = NULL; + ieee80211_hw_config(local, 0); + + ieee80211_offchannel_return(local, true); + } + + ieee80211_recalc_idle(local); + + ieee80211_start_next_roc(local); + ieee80211_run_deferred_scan(local); + } + + out_unlock: + mutex_unlock(&local->mtx); +} + +static void ieee80211_hw_roc_done(struct work_struct *work) +{ + struct ieee80211_local *local = + container_of(work, struct ieee80211_local, hw_roc_done); + struct ieee80211_roc_work *roc; + + mutex_lock(&local->mtx); + + if (list_empty(&local->roc_list)) + goto out_unlock; + + roc = list_first_entry(&local->roc_list, struct ieee80211_roc_work, + list); + + if (!roc->started) + goto out_unlock; + + list_del(&roc->list); + + ieee80211_roc_notify_destroy(roc); + + /* if there's another roc, start it now */ + ieee80211_start_next_roc(local); + + /* or scan maybe */ + ieee80211_run_deferred_scan(local); + + out_unlock: mutex_unlock(&local->mtx); } @@ -271,8 +427,48 @@ void ieee80211_remain_on_channel_expired(struct ieee80211_hw *hw) } EXPORT_SYMBOL_GPL(ieee80211_remain_on_channel_expired); -void ieee80211_hw_roc_setup(struct ieee80211_local *local) +void ieee80211_roc_setup(struct ieee80211_local *local) { INIT_WORK(&local->hw_roc_start, ieee80211_hw_roc_start); INIT_WORK(&local->hw_roc_done, ieee80211_hw_roc_done); + INIT_LIST_HEAD(&local->roc_list); +} + +void ieee80211_roc_purge(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_roc_work *roc, *tmp; + LIST_HEAD(tmp_list); + + mutex_lock(&local->mtx); + list_for_each_entry_safe(roc, tmp, &local->roc_list, list) { + if (roc->sdata != sdata) + continue; + + if (roc->started && local->ops->remain_on_channel) { + /* can race, so ignore return value */ + drv_cancel_remain_on_channel(local); + } + + list_move_tail(&roc->list, &tmp_list); + roc->abort = true; + } + + ieee80211_start_next_roc(local); + ieee80211_run_deferred_scan(local); + mutex_unlock(&local->mtx); + + list_for_each_entry_safe(roc, tmp, &tmp_list, list) { + if (local->ops->remain_on_channel) { + list_del(&roc->list); + ieee80211_roc_notify_destroy(roc); + } else { + ieee80211_queue_delayed_work(&local->hw, &roc->work, 0); + + /* work will clean up etc */ + flush_delayed_work(&roc->work); + } + } + + WARN_ON_ONCE(!list_empty(&tmp_list)); } diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index 169da0742c8..379f178eab5 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -323,7 +323,7 @@ static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted, ieee80211_mlme_notify_scan_completed(local); ieee80211_ibss_notify_scan_completed(local); ieee80211_mesh_notify_scan_completed(local); - ieee80211_queue_work(&local->hw, &local->work_work); + ieee80211_start_next_roc(local); } void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted) @@ -376,7 +376,7 @@ static int ieee80211_start_sw_scan(struct ieee80211_local *local) static bool ieee80211_can_scan(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata) { - if (!list_empty(&local->work_list)) + if (!list_empty(&local->roc_list)) return false; if (sdata->vif.type == NL80211_IFTYPE_STATION && diff --git a/net/mac80211/status.c b/net/mac80211/status.c index 63a76901506..6b4f4252788 100644 --- a/net/mac80211/status.c +++ b/net/mac80211/status.c @@ -520,36 +520,16 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb) if (info->flags & IEEE80211_TX_INTFL_NL80211_FRAME_TX) { u64 cookie = (unsigned long)skb; + acked = info->flags & IEEE80211_TX_STAT_ACK; if (ieee80211_is_nullfunc(hdr->frame_control) || - ieee80211_is_qos_nullfunc(hdr->frame_control)) { - acked = info->flags & IEEE80211_TX_STAT_ACK; - + ieee80211_is_qos_nullfunc(hdr->frame_control)) cfg80211_probe_status(skb->dev, hdr->addr1, cookie, acked, GFP_ATOMIC); - } else { - struct ieee80211_work *wk; - - rcu_read_lock(); - list_for_each_entry_rcu(wk, &local->work_list, list) { - if (wk->type != IEEE80211_WORK_OFFCHANNEL_TX) - continue; - if (wk->offchan_tx.frame != skb) - continue; - wk->offchan_tx.status = true; - break; - } - rcu_read_unlock(); - if (local->hw_roc_skb_for_status == skb) { - cookie = local->hw_roc_cookie ^ 2; - local->hw_roc_skb_for_status = NULL; - } - + else cfg80211_mgmt_tx_status( skb->dev, cookie, skb->data, skb->len, - !!(info->flags & IEEE80211_TX_STAT_ACK), - GFP_ATOMIC); - } + acked, GFP_ATOMIC); } if (unlikely(info->ack_frame_id)) { diff --git a/net/mac80211/work.c b/net/mac80211/work.c deleted file mode 100644 index b2650a9d45f..00000000000 --- a/net/mac80211/work.c +++ /dev/null @@ -1,370 +0,0 @@ -/* - * mac80211 work implementation - * - * Copyright 2003-2008, Jouni Malinen - * Copyright 2004, Instant802 Networks, Inc. - * Copyright 2005, Devicescape Software, Inc. - * Copyright 2006-2007 Jiri Benc - * Copyright 2007, Michael Wu - * Copyright 2009, Johannes Berg - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ieee80211_i.h" -#include "rate.h" -#include "driver-ops.h" - -enum work_action { - WORK_ACT_NONE, - WORK_ACT_TIMEOUT, -}; - - -/* utils */ -static inline void ASSERT_WORK_MTX(struct ieee80211_local *local) -{ - lockdep_assert_held(&local->mtx); -} - -/* - * We can have multiple work items (and connection probing) - * scheduling this timer, but we need to take care to only - * reschedule it when it should fire _earlier_ than it was - * asked for before, or if it's not pending right now. This - * function ensures that. Note that it then is required to - * run this function for all timeouts after the first one - * has happened -- the work that runs from this timer will - * do that. - */ -static void run_again(struct ieee80211_local *local, - unsigned long timeout) -{ - ASSERT_WORK_MTX(local); - - if (!timer_pending(&local->work_timer) || - time_before(timeout, local->work_timer.expires)) - mod_timer(&local->work_timer, timeout); -} - -void free_work(struct ieee80211_work *wk) -{ - kfree_rcu(wk, rcu_head); -} - -static enum work_action __must_check -ieee80211_remain_on_channel_timeout(struct ieee80211_work *wk) -{ - /* - * First time we run, do nothing -- the generic code will - * have switched to the right channel etc. - */ - if (!wk->started) { - wk->timeout = jiffies + msecs_to_jiffies(wk->remain.duration); - - cfg80211_ready_on_channel(wk->sdata->dev, (unsigned long) wk, - wk->chan, wk->chan_type, - wk->remain.duration, GFP_KERNEL); - - return WORK_ACT_NONE; - } - - return WORK_ACT_TIMEOUT; -} - -static enum work_action __must_check -ieee80211_offchannel_tx(struct ieee80211_work *wk) -{ - if (!wk->started) { - wk->timeout = jiffies + msecs_to_jiffies(wk->offchan_tx.wait); - - /* - * After this, offchan_tx.frame remains but now is no - * longer a valid pointer -- we still need it as the - * cookie for canceling this work/status matching. - */ - ieee80211_tx_skb(wk->sdata, wk->offchan_tx.frame); - - return WORK_ACT_NONE; - } - - return WORK_ACT_TIMEOUT; -} - -static void ieee80211_work_timer(unsigned long data) -{ - struct ieee80211_local *local = (void *) data; - - if (local->quiescing) - return; - - ieee80211_queue_work(&local->hw, &local->work_work); -} - -static void ieee80211_work_work(struct work_struct *work) -{ - struct ieee80211_local *local = - container_of(work, struct ieee80211_local, work_work); - struct ieee80211_work *wk, *tmp; - LIST_HEAD(free_work); - enum work_action rma; - bool remain_off_channel = false; - - /* - * ieee80211_queue_work() should have picked up most cases, - * here we'll pick the rest. - */ - if (WARN(local->suspended, "work scheduled while going to suspend\n")) - return; - - mutex_lock(&local->mtx); - - if (local->scanning) { - mutex_unlock(&local->mtx); - return; - } - - ieee80211_recalc_idle(local); - - list_for_each_entry_safe(wk, tmp, &local->work_list, list) { - bool started = wk->started; - - /* mark work as started if it's on the current off-channel */ - if (!started && local->tmp_channel && - wk->chan == local->tmp_channel && - wk->chan_type == local->tmp_channel_type) { - started = true; - wk->timeout = jiffies; - } - - if (!started && !local->tmp_channel) { - ieee80211_offchannel_stop_vifs(local, true); - - local->tmp_channel = wk->chan; - local->tmp_channel_type = wk->chan_type; - - ieee80211_hw_config(local, 0); - - started = true; - wk->timeout = jiffies; - } - - /* don't try to work with items that aren't started */ - if (!started) - continue; - - if (time_is_after_jiffies(wk->timeout)) { - /* - * This work item isn't supposed to be worked on - * right now, but take care to adjust the timer - * properly. - */ - run_again(local, wk->timeout); - continue; - } - - switch (wk->type) { - default: - WARN_ON(1); - /* nothing */ - rma = WORK_ACT_NONE; - break; - case IEEE80211_WORK_ABORT: - rma = WORK_ACT_TIMEOUT; - break; - case IEEE80211_WORK_REMAIN_ON_CHANNEL: - rma = ieee80211_remain_on_channel_timeout(wk); - break; - case IEEE80211_WORK_OFFCHANNEL_TX: - rma = ieee80211_offchannel_tx(wk); - break; - } - - wk->started = started; - - switch (rma) { - case WORK_ACT_NONE: - /* might have changed the timeout */ - run_again(local, wk->timeout); - break; - case WORK_ACT_TIMEOUT: - list_del_rcu(&wk->list); - synchronize_rcu(); - list_add(&wk->list, &free_work); - break; - default: - WARN(1, "unexpected: %d", rma); - } - } - - list_for_each_entry(wk, &local->work_list, list) { - if (!wk->started) - continue; - if (wk->chan != local->tmp_channel || - wk->chan_type != local->tmp_channel_type) - continue; - remain_off_channel = true; - } - - if (!remain_off_channel && local->tmp_channel) { - local->tmp_channel = NULL; - ieee80211_hw_config(local, 0); - - ieee80211_offchannel_return(local, true); - - /* give connection some time to breathe */ - run_again(local, jiffies + HZ/2); - } - - ieee80211_recalc_idle(local); - ieee80211_run_deferred_scan(local); - - mutex_unlock(&local->mtx); - - list_for_each_entry_safe(wk, tmp, &free_work, list) { - wk->done(wk, NULL); - list_del(&wk->list); - kfree(wk); - } -} - -void ieee80211_add_work(struct ieee80211_work *wk) -{ - struct ieee80211_local *local; - - if (WARN_ON(!wk->chan)) - return; - - if (WARN_ON(!wk->sdata)) - return; - - if (WARN_ON(!wk->done)) - return; - - if (WARN_ON(!ieee80211_sdata_running(wk->sdata))) - return; - - wk->started = false; - - local = wk->sdata->local; - mutex_lock(&local->mtx); - list_add_tail(&wk->list, &local->work_list); - mutex_unlock(&local->mtx); - - ieee80211_queue_work(&local->hw, &local->work_work); -} - -void ieee80211_work_init(struct ieee80211_local *local) -{ - INIT_LIST_HEAD(&local->work_list); - setup_timer(&local->work_timer, ieee80211_work_timer, - (unsigned long)local); - INIT_WORK(&local->work_work, ieee80211_work_work); -} - -void ieee80211_work_purge(struct ieee80211_sub_if_data *sdata) -{ - struct ieee80211_local *local = sdata->local; - struct ieee80211_work *wk; - bool cleanup = false; - - mutex_lock(&local->mtx); - list_for_each_entry(wk, &local->work_list, list) { - if (wk->sdata != sdata) - continue; - cleanup = true; - wk->type = IEEE80211_WORK_ABORT; - wk->started = true; - wk->timeout = jiffies; - } - mutex_unlock(&local->mtx); - - /* run cleanups etc. */ - if (cleanup) - ieee80211_work_work(&local->work_work); - - mutex_lock(&local->mtx); - list_for_each_entry(wk, &local->work_list, list) { - if (wk->sdata != sdata) - continue; - WARN_ON(1); - break; - } - mutex_unlock(&local->mtx); -} - -static enum work_done_result ieee80211_remain_done(struct ieee80211_work *wk, - struct sk_buff *skb) -{ - /* - * We are done serving the remain-on-channel command. - */ - cfg80211_remain_on_channel_expired(wk->sdata->dev, (unsigned long) wk, - wk->chan, wk->chan_type, - GFP_KERNEL); - - return WORK_DONE_DESTROY; -} - -int ieee80211_wk_remain_on_channel(struct ieee80211_sub_if_data *sdata, - struct ieee80211_channel *chan, - enum nl80211_channel_type channel_type, - unsigned int duration, u64 *cookie) -{ - struct ieee80211_work *wk; - - wk = kzalloc(sizeof(*wk), GFP_KERNEL); - if (!wk) - return -ENOMEM; - - wk->type = IEEE80211_WORK_REMAIN_ON_CHANNEL; - wk->chan = chan; - wk->chan_type = channel_type; - wk->sdata = sdata; - wk->done = ieee80211_remain_done; - - wk->remain.duration = duration; - - *cookie = (unsigned long) wk; - - ieee80211_add_work(wk); - - return 0; -} - -int ieee80211_wk_cancel_remain_on_channel(struct ieee80211_sub_if_data *sdata, - u64 cookie) -{ - struct ieee80211_local *local = sdata->local; - struct ieee80211_work *wk, *tmp; - bool found = false; - - mutex_lock(&local->mtx); - list_for_each_entry_safe(wk, tmp, &local->work_list, list) { - if ((unsigned long) wk == cookie) { - wk->timeout = jiffies; - found = true; - break; - } - } - mutex_unlock(&local->mtx); - - if (!found) - return -ENOENT; - - ieee80211_queue_work(&local->hw, &local->work_work); - - return 0; -} -- cgit v1.2.3-70-g09d2 From d811b3d5566f1441b321a1219c260124b209e0bd Mon Sep 17 00:00:00 2001 From: Arik Nemtsov Date: Mon, 9 Jul 2012 19:57:28 +0300 Subject: mac80211: fix invalid band deref building preq IEs The function building probe-request IEs does not validate the band is supported before dereferencing it. This can result in a panic when all bands are traversed, as done during sched-scan start. Warn when this happens and return an empty probe request. Also fix sched-scan to not waste memory on unsupported bands. Signed-off-by: Arik Nemtsov Signed-off-by: Johannes Berg --- net/mac80211/scan.c | 3 +++ net/mac80211/util.c | 2 ++ 2 files changed, 5 insertions(+) (limited to 'net/mac80211/scan.c') diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index 379f178eab5..1ff04f689d1 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -928,6 +928,9 @@ int ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata, } for (i = 0; i < IEEE80211_NUM_BANDS; i++) { + if (!local->hw.wiphy->bands[i]) + continue; + local->sched_scan_ies.ie[i] = kzalloc(2 + IEEE80211_MAX_SSID_LEN + local->scan_ies_len + diff --git a/net/mac80211/util.c b/net/mac80211/util.c index 64493a7bef1..596db0c2a11 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -999,6 +999,8 @@ int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer, int ext_rates_len; sband = local->hw.wiphy->bands[band]; + if (WARN_ON_ONCE(!sband)) + return 0; pos = buffer; -- cgit v1.2.3-70-g09d2 From e2fd5dbc1c7031be5b5de043bcc0a18c7a59a68a Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 6 Jul 2012 21:39:28 +0200 Subject: mac80211: make scan_sdata pointer usable with RCU Making the scan_sdata pointer usable with RCU makes it possible to dereference it in the RX path to see if a received frame actually matches the interface that is scanning. This is just preparations, making the pointer __rcu. Signed-off-by: Johannes Berg --- net/mac80211/ieee80211_i.h | 2 +- net/mac80211/iface.c | 9 +++++---- net/mac80211/scan.c | 33 ++++++++++++++++++++++++--------- 3 files changed, 30 insertions(+), 14 deletions(-) (limited to 'net/mac80211/scan.c') diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index c3241c3ec6d..9f2534a4124 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -972,7 +972,7 @@ struct ieee80211_local { unsigned long leave_oper_channel_time; enum mac80211_scan_state next_scan_state; struct delayed_work scan_work; - struct ieee80211_sub_if_data *scan_sdata; + struct ieee80211_sub_if_data __rcu *scan_sdata; enum nl80211_channel_type _oper_channel_type; struct ieee80211_channel *oper_channel, *csa_channel; diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index b1edf60fbba..e3c49748ce8 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -112,10 +112,11 @@ static u32 __ieee80211_recalc_idle(struct ieee80211_local *local) } } - if (local->scan_sdata && - !(local->hw.flags & IEEE80211_HW_SCAN_WHILE_IDLE)) { + sdata = rcu_dereference_protected(local->scan_sdata, + lockdep_is_held(&local->mtx)); + if (sdata && !(local->hw.flags & IEEE80211_HW_SCAN_WHILE_IDLE)) { scanning = true; - local->scan_sdata->vif.bss_conf.idle = false; + sdata->vif.bss_conf.idle = false; } list_for_each_entry(sdata, &local->interfaces, list) { @@ -628,7 +629,7 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, clear_bit(SDATA_STATE_RUNNING, &sdata->state); - if (local->scan_sdata == sdata) + if (rcu_access_pointer(local->scan_sdata) == sdata) ieee80211_scan_cancel(local); /* diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index 1ff04f689d1..704dcf84776 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -293,7 +293,13 @@ static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted, return; if (was_hw_scan && !aborted && ieee80211_prep_hw_scan(local)) { - int rc = drv_hw_scan(local, local->scan_sdata, local->hw_scan_req); + int rc; + + rc = drv_hw_scan(local, + rcu_dereference_protected(local->scan_sdata, + lockdep_is_held(&local->mtx)), + local->hw_scan_req); + if (rc == 0) return; } @@ -394,7 +400,10 @@ void ieee80211_run_deferred_scan(struct ieee80211_local *local) if (!local->scan_req || local->scanning) return; - if (!ieee80211_can_scan(local, local->scan_sdata)) + if (!ieee80211_can_scan(local, + rcu_dereference_protected( + local->scan_sdata, + lockdep_is_held(&local->mtx)))) return; ieee80211_queue_delayed_work(&local->hw, &local->scan_work, @@ -405,9 +414,12 @@ static void ieee80211_scan_state_send_probe(struct ieee80211_local *local, unsigned long *next_delay) { int i; - struct ieee80211_sub_if_data *sdata = local->scan_sdata; + struct ieee80211_sub_if_data *sdata; enum ieee80211_band band = local->hw.conf.channel->band; + sdata = rcu_dereference_protected(local->scan_sdata, + lockdep_is_held(&local->mtx));; + for (i = 0; i < local->scan_req->n_ssids; i++) ieee80211_send_probe_req( sdata, NULL, @@ -439,7 +451,7 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, if (!ieee80211_can_scan(local, sdata)) { /* wait for the work to finish/time out */ local->scan_req = req; - local->scan_sdata = sdata; + rcu_assign_pointer(local->scan_sdata, sdata); return 0; } @@ -473,7 +485,7 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, } local->scan_req = req; - local->scan_sdata = sdata; + rcu_assign_pointer(local->scan_sdata, sdata); if (local->ops->hw_scan) { __set_bit(SCAN_HW_SCANNING, &local->scanning); @@ -533,7 +545,7 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, ieee80211_recalc_idle(local); local->scan_req = NULL; - local->scan_sdata = NULL; + rcu_assign_pointer(local->scan_sdata, NULL); } return rc; @@ -720,7 +732,8 @@ void ieee80211_scan_work(struct work_struct *work) mutex_lock(&local->mtx); - sdata = local->scan_sdata; + sdata = rcu_dereference_protected(local->scan_sdata, + lockdep_is_held(&local->mtx)); /* When scanning on-channel, the first-callback means completed. */ if (test_bit(SCAN_ONCHANNEL_SCANNING, &local->scanning)) { @@ -741,7 +754,7 @@ void ieee80211_scan_work(struct work_struct *work) int rc; local->scan_req = NULL; - local->scan_sdata = NULL; + rcu_assign_pointer(local->scan_sdata, NULL); rc = __ieee80211_start_scan(sdata, req); if (rc) { @@ -893,7 +906,9 @@ void ieee80211_scan_cancel(struct ieee80211_local *local) if (test_bit(SCAN_HW_SCANNING, &local->scanning)) { if (local->ops->cancel_hw_scan) - drv_cancel_hw_scan(local, local->scan_sdata); + drv_cancel_hw_scan(local, + rcu_dereference_protected(local->scan_sdata, + lockdep_is_held(&local->mtx))); goto out; } -- cgit v1.2.3-70-g09d2 From 5260a5b2c3524f198ea062fe0a6a4faa724e6a9d Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 6 Jul 2012 21:55:11 +0200 Subject: mac80211: track scheduled scan virtual interface Instead of tracking whether or not we're in a scheduled scan, track the virtual interface (sdata) in an RCU-protected pointer to make it usable from RX to check the MAC address. Signed-off-by: Johannes Berg --- net/mac80211/ieee80211_i.h | 2 +- net/mac80211/main.c | 3 ++- net/mac80211/rx.c | 4 ++-- net/mac80211/scan.c | 20 ++++++++++---------- 4 files changed, 15 insertions(+), 14 deletions(-) (limited to 'net/mac80211/scan.c') diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 9f2534a4124..e973a8f96c9 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -965,9 +965,9 @@ struct ieee80211_local { int scan_channel_idx; int scan_ies_len; - bool sched_scanning; struct ieee80211_sched_scan_ies sched_scan_ies; struct work_struct sched_scan_stopped_work; + struct ieee80211_sub_if_data __rcu *sched_scan_sdata; unsigned long leave_oper_channel_time; enum mac80211_scan_state next_scan_state; diff --git a/net/mac80211/main.c b/net/mac80211/main.c index c794101f898..c26e231c733 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -322,7 +322,8 @@ static void ieee80211_restart_work(struct work_struct *work) mutex_lock(&local->mtx); WARN(test_bit(SCAN_HW_SCANNING, &local->scanning) || - local->sched_scanning, + rcu_dereference_protected(local->sched_scan_sdata, + lockdep_is_held(&local->mtx)), "%s called with hardware scan in progress\n", __func__); mutex_unlock(&local->mtx); diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index f8cf9e7477a..17a56151be7 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -421,13 +421,13 @@ ieee80211_rx_h_passive_scan(struct ieee80211_rx_data *rx) struct sk_buff *skb = rx->skb; if (likely(!(status->rx_flags & IEEE80211_RX_IN_SCAN) && - !local->sched_scanning)) + !rcu_access_pointer(local->sched_scan_sdata))) return RX_CONTINUE; if (test_bit(SCAN_HW_SCANNING, &local->scanning) || test_bit(SCAN_SW_SCANNING, &local->scanning) || test_bit(SCAN_ONCHANNEL_SCANNING, &local->scanning) || - local->sched_scanning) + rcu_access_pointer(local->sched_scan_sdata)) return ieee80211_scan_rx(rx->sdata, skb); /* scanning finished during invoking of handlers */ diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index 704dcf84776..a619c1ea9bd 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -930,9 +930,9 @@ int ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata, struct ieee80211_local *local = sdata->local; int ret, i; - mutex_lock(&sdata->local->mtx); + mutex_lock(&local->mtx); - if (local->sched_scanning) { + if (rcu_access_pointer(local->sched_scan_sdata)) { ret = -EBUSY; goto out; } @@ -966,7 +966,7 @@ int ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata, ret = drv_sched_scan_start(local, sdata, req, &local->sched_scan_ies); if (ret == 0) { - local->sched_scanning = true; + rcu_assign_pointer(local->sched_scan_sdata, sdata); goto out; } @@ -974,7 +974,7 @@ out_free: while (i > 0) kfree(local->sched_scan_ies.ie[--i]); out: - mutex_unlock(&sdata->local->mtx); + mutex_unlock(&local->mtx); return ret; } @@ -983,22 +983,22 @@ int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata) struct ieee80211_local *local = sdata->local; int ret = 0, i; - mutex_lock(&sdata->local->mtx); + mutex_lock(&local->mtx); if (!local->ops->sched_scan_stop) { ret = -ENOTSUPP; goto out; } - if (local->sched_scanning) { + if (rcu_access_pointer(local->sched_scan_sdata)) { for (i = 0; i < IEEE80211_NUM_BANDS; i++) kfree(local->sched_scan_ies.ie[i]); drv_sched_scan_stop(local, sdata); - local->sched_scanning = false; + rcu_assign_pointer(local->sched_scan_sdata, NULL); } out: - mutex_unlock(&sdata->local->mtx); + mutex_unlock(&local->mtx); return ret; } @@ -1022,7 +1022,7 @@ void ieee80211_sched_scan_stopped_work(struct work_struct *work) mutex_lock(&local->mtx); - if (!local->sched_scanning) { + if (!rcu_access_pointer(local->sched_scan_sdata)) { mutex_unlock(&local->mtx); return; } @@ -1030,7 +1030,7 @@ void ieee80211_sched_scan_stopped_work(struct work_struct *work) for (i = 0; i < IEEE80211_NUM_BANDS; i++) kfree(local->sched_scan_ies.ie[i]); - local->sched_scanning = false; + rcu_assign_pointer(local->sched_scan_sdata, NULL); mutex_unlock(&local->mtx); -- cgit v1.2.3-70-g09d2 From d48b296850f25cb559cb9b907d6d8c09eca3e89d Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 6 Jul 2012 22:19:27 +0200 Subject: mac80211: redesign scan RX Scan receive is rather inefficient when there are multiple virtual interfaces. We iterate all of the virtual interfaces and then notify cfg80211 about each beacon many times. Redesign scan RX to happen before everything else. Then we can also get rid of IEEE80211_RX_IN_SCAN since we don't have to accept frames into the RX handlers for scanning or scheduled scanning any more. Overall, this simplifies the code. Signed-off-by: Johannes Berg --- net/mac80211/debugfs.c | 2 -- net/mac80211/ieee80211_i.h | 6 +---- net/mac80211/rx.c | 47 +++++++------------------------------- net/mac80211/scan.c | 57 +++++++++++++++++++--------------------------- 4 files changed, 32 insertions(+), 80 deletions(-) (limited to 'net/mac80211/scan.c') diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c index 778e5916d7c..b8dfb440c8e 100644 --- a/net/mac80211/debugfs.c +++ b/net/mac80211/debugfs.c @@ -325,8 +325,6 @@ void debugfs_hw_add(struct ieee80211_local *local) local->rx_handlers_drop_defrag); DEBUGFS_STATS_ADD(rx_handlers_drop_short, local->rx_handlers_drop_short); - DEBUGFS_STATS_ADD(rx_handlers_drop_passive_scan, - local->rx_handlers_drop_passive_scan); DEBUGFS_STATS_ADD(tx_expand_skb_head, local->tx_expand_skb_head); DEBUGFS_STATS_ADD(tx_expand_skb_head_cloned, diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index e973a8f96c9..2a97d668d2d 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -207,7 +207,6 @@ typedef unsigned __bitwise__ ieee80211_rx_result; * enum ieee80211_packet_rx_flags - packet RX flags * @IEEE80211_RX_RA_MATCH: frame is destined to interface currently processed * (incl. multicast frames) - * @IEEE80211_RX_IN_SCAN: received while scanning * @IEEE80211_RX_FRAGMENTED: fragmented frame * @IEEE80211_RX_AMSDU: a-MSDU packet * @IEEE80211_RX_MALFORMED_ACTION_FRM: action frame is malformed @@ -217,7 +216,6 @@ typedef unsigned __bitwise__ ieee80211_rx_result; * @rx_flags field of &struct ieee80211_rx_status. */ enum ieee80211_packet_rx_flags { - IEEE80211_RX_IN_SCAN = BIT(0), IEEE80211_RX_RA_MATCH = BIT(1), IEEE80211_RX_FRAGMENTED = BIT(2), IEEE80211_RX_AMSDU = BIT(3), @@ -1014,7 +1012,6 @@ struct ieee80211_local { unsigned int rx_handlers_drop_nullfunc; unsigned int rx_handlers_drop_defrag; unsigned int rx_handlers_drop_short; - unsigned int rx_handlers_drop_passive_scan; unsigned int tx_expand_skb_head; unsigned int tx_expand_skb_head_cloned; unsigned int rx_expand_skb_head; @@ -1247,8 +1244,7 @@ int ieee80211_request_scan(struct ieee80211_sub_if_data *sdata, struct cfg80211_scan_request *req); void ieee80211_scan_cancel(struct ieee80211_local *local); void ieee80211_run_deferred_scan(struct ieee80211_local *local); -ieee80211_rx_result -ieee80211_scan_rx(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb); +void ieee80211_scan_rx(struct ieee80211_local *local, struct sk_buff *skb); void ieee80211_mlme_notify_scan_completed(struct ieee80211_local *local); struct ieee80211_bss * diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 17a56151be7..1d7a58098e3 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -413,29 +413,6 @@ static void ieee80211_verify_alignment(struct ieee80211_rx_data *rx) /* rx handlers */ -static ieee80211_rx_result debug_noinline -ieee80211_rx_h_passive_scan(struct ieee80211_rx_data *rx) -{ - struct ieee80211_local *local = rx->local; - struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx->skb); - struct sk_buff *skb = rx->skb; - - if (likely(!(status->rx_flags & IEEE80211_RX_IN_SCAN) && - !rcu_access_pointer(local->sched_scan_sdata))) - return RX_CONTINUE; - - if (test_bit(SCAN_HW_SCANNING, &local->scanning) || - test_bit(SCAN_SW_SCANNING, &local->scanning) || - test_bit(SCAN_ONCHANNEL_SCANNING, &local->scanning) || - rcu_access_pointer(local->sched_scan_sdata)) - return ieee80211_scan_rx(rx->sdata, skb); - - /* scanning finished during invoking of handlers */ - I802_DEBUG_INC(local->rx_handlers_drop_passive_scan); - return RX_DROP_UNUSABLE; -} - - static int ieee80211_is_unicast_robust_mgmt_frame(struct sk_buff *skb) { struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; @@ -2692,7 +2669,6 @@ static void ieee80211_invoke_rx_handlers(struct ieee80211_rx_data *rx) goto rxh_next; \ } while (0); - CALL_RXH(ieee80211_rx_h_passive_scan) CALL_RXH(ieee80211_rx_h_check) ieee80211_rx_reorder_ampdu(rx); @@ -2762,11 +2738,8 @@ static int prepare_for_handlers(struct ieee80211_rx_data *rx, return 0; if (ieee80211_is_beacon(hdr->frame_control)) { return 1; - } - else if (!ieee80211_bssid_match(bssid, sdata->u.ibss.bssid)) { - if (!(status->rx_flags & IEEE80211_RX_IN_SCAN)) - return 0; - status->rx_flags &= ~IEEE80211_RX_RA_MATCH; + } else if (!ieee80211_bssid_match(bssid, sdata->u.ibss.bssid)) { + return 0; } else if (!multicast && !ether_addr_equal(sdata->vif.addr, hdr->addr1)) { if (!(sdata->dev->flags & IFF_PROMISC)) @@ -2804,11 +2777,9 @@ static int prepare_for_handlers(struct ieee80211_rx_data *rx, * and location updates. Note that mac80211 * itself never looks at these frames. */ - if (!(status->rx_flags & IEEE80211_RX_IN_SCAN) && - ieee80211_is_public_action(hdr, skb->len)) + if (ieee80211_is_public_action(hdr, skb->len)) return 1; - if (!(status->rx_flags & IEEE80211_RX_IN_SCAN) && - !ieee80211_is_beacon(hdr->frame_control)) + if (!ieee80211_is_beacon(hdr->frame_control)) return 0; status->rx_flags &= ~IEEE80211_RX_RA_MATCH; } @@ -2874,7 +2845,6 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx, static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, struct sk_buff *skb) { - struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); struct ieee80211_local *local = hw_to_local(hw); struct ieee80211_sub_if_data *sdata; struct ieee80211_hdr *hdr; @@ -2892,11 +2862,6 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, if (ieee80211_is_data(fc) || ieee80211_is_mgmt(fc)) local->dot11ReceivedFragmentCount++; - if (unlikely(test_bit(SCAN_HW_SCANNING, &local->scanning) || - test_bit(SCAN_ONCHANNEL_SCANNING, &local->scanning) || - test_bit(SCAN_SW_SCANNING, &local->scanning))) - status->rx_flags |= IEEE80211_RX_IN_SCAN; - if (ieee80211_is_mgmt(fc)) err = skb_linearize(skb); else @@ -2911,6 +2876,10 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, ieee80211_parse_qos(&rx); ieee80211_verify_alignment(&rx); + if (unlikely(ieee80211_is_probe_resp(hdr->frame_control) || + ieee80211_is_beacon(hdr->frame_control))) + ieee80211_scan_rx(local, skb); + if (ieee80211_is_data(fc)) { prev_sta = NULL; diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index a619c1ea9bd..1a893f3637c 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -165,52 +165,47 @@ ieee80211_bss_info_update(struct ieee80211_local *local, return bss; } -ieee80211_rx_result -ieee80211_scan_rx(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb) +void ieee80211_scan_rx(struct ieee80211_local *local, struct sk_buff *skb) { struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); - struct ieee80211_mgmt *mgmt; + struct ieee80211_sub_if_data *sdata1, *sdata2; + struct ieee80211_mgmt *mgmt = (void *)skb->data; struct ieee80211_bss *bss; u8 *elements; struct ieee80211_channel *channel; size_t baselen; int freq; - __le16 fc; - bool presp, beacon = false; + bool beacon; struct ieee802_11_elems elems; - if (skb->len < 2) - return RX_DROP_UNUSABLE; - - mgmt = (struct ieee80211_mgmt *) skb->data; - fc = mgmt->frame_control; + if (skb->len < 24 || + (!ieee80211_is_probe_resp(mgmt->frame_control) && + !ieee80211_is_beacon(mgmt->frame_control))) + return; - if (ieee80211_is_ctl(fc)) - return RX_CONTINUE; + sdata1 = rcu_dereference(local->scan_sdata); + sdata2 = rcu_dereference(local->sched_scan_sdata); - if (skb->len < 24) - return RX_CONTINUE; + if (likely(!sdata1 && !sdata2)) + return; - presp = ieee80211_is_probe_resp(fc); - if (presp) { + if (ieee80211_is_probe_resp(mgmt->frame_control)) { /* ignore ProbeResp to foreign address */ - if (!ether_addr_equal(mgmt->da, sdata->vif.addr)) - return RX_DROP_MONITOR; + if ((!sdata1 || !ether_addr_equal(mgmt->da, sdata1->vif.addr)) && + (!sdata2 || !ether_addr_equal(mgmt->da, sdata2->vif.addr))) + return; - presp = true; elements = mgmt->u.probe_resp.variable; baselen = offsetof(struct ieee80211_mgmt, u.probe_resp.variable); + beacon = false; } else { - beacon = ieee80211_is_beacon(fc); baselen = offsetof(struct ieee80211_mgmt, u.beacon.variable); elements = mgmt->u.beacon.variable; + beacon = true; } - if (!presp && !beacon) - return RX_CONTINUE; - if (baselen > skb->len) - return RX_DROP_MONITOR; + return; ieee802_11_parse_elems(elements, skb->len - baselen, &elems); @@ -220,22 +215,16 @@ ieee80211_scan_rx(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb) else freq = rx_status->freq; - channel = ieee80211_get_channel(sdata->local->hw.wiphy, freq); + channel = ieee80211_get_channel(local->hw.wiphy, freq); if (!channel || channel->flags & IEEE80211_CHAN_DISABLED) - return RX_DROP_MONITOR; + return; - bss = ieee80211_bss_info_update(sdata->local, rx_status, + bss = ieee80211_bss_info_update(local, rx_status, mgmt, skb->len, &elems, channel, beacon); if (bss) - ieee80211_rx_bss_put(sdata->local, bss); - - if (channel == sdata->local->oper_channel) - return RX_CONTINUE; - - dev_kfree_skb(skb); - return RX_QUEUED; + ieee80211_rx_bss_put(local, bss); } /* return false if no more work */ -- cgit v1.2.3-70-g09d2 From 8c358bcd097fa1f63e57fb82525ba52f4a537bfa Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 22 May 2012 22:13:05 +0200 Subject: mac80211: add time synchronisation with BSS for assoc Some drivers (iwlegacy, iwlwifi and rt2x00) today use the bss_conf.last_tsf value. By itself though that value is completely worthless since it may be ancient. What really is needed is synchronisation between some device time and the TSF. To clarify this, rename bss_conf.last_tsf to sync_tsf and add sync_device_ts which is obtained from rx_status which gets a new field device_timestamp for this purpose. This is intentionally not using the mactime field since that is used for other things and in IBSS is expected to sync with the IBSS's TSF which isn't necessarily true for the device timestamp. Also, since we have the information and it's useful even before the connection has been established, give all the timing details to the driver before authenticating. Reviewed-by: Emmanuel Grumbach Signed-off-by: Johannes Berg --- drivers/net/wireless/iwlegacy/common.c | 2 +- drivers/net/wireless/iwlwifi/dvm/rxon.c | 2 +- drivers/net/wireless/rt2x00/rt2x00config.c | 2 +- include/net/mac80211.h | 10 ++++++++-- net/mac80211/ieee80211_i.h | 2 ++ net/mac80211/mlme.c | 15 ++++++++------- net/mac80211/scan.c | 3 ++- net/mac80211/trace.h | 6 ++++-- 8 files changed, 27 insertions(+), 15 deletions(-) (limited to 'net/mac80211/scan.c') diff --git a/drivers/net/wireless/iwlegacy/common.c b/drivers/net/wireless/iwlegacy/common.c index cbf2dc18341..9cbed0b15b0 100644 --- a/drivers/net/wireless/iwlegacy/common.c +++ b/drivers/net/wireless/iwlegacy/common.c @@ -5360,7 +5360,7 @@ il_mac_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, if (changes & BSS_CHANGED_ASSOC) { D_MAC80211("ASSOC %d\n", bss_conf->assoc); if (bss_conf->assoc) { - il->timestamp = bss_conf->last_tsf; + il->timestamp = bss_conf->sync_tsf; if (!il_is_rfkill(il)) il->ops->post_associate(il); diff --git a/drivers/net/wireless/iwlwifi/dvm/rxon.c b/drivers/net/wireless/iwlwifi/dvm/rxon.c index 6ee940f497f..10896393e5a 100644 --- a/drivers/net/wireless/iwlwifi/dvm/rxon.c +++ b/drivers/net/wireless/iwlwifi/dvm/rxon.c @@ -1447,7 +1447,7 @@ void iwlagn_bss_info_changed(struct ieee80211_hw *hw, if (changes & BSS_CHANGED_ASSOC) { if (bss_conf->assoc) { - priv->timestamp = bss_conf->last_tsf; + priv->timestamp = bss_conf->sync_tsf; ctx->staging.filter_flags |= RXON_FILTER_ASSOC_MSK; } else { /* diff --git a/drivers/net/wireless/rt2x00/rt2x00config.c b/drivers/net/wireless/rt2x00/rt2x00config.c index e7361d913e8..49a63e97393 100644 --- a/drivers/net/wireless/rt2x00/rt2x00config.c +++ b/drivers/net/wireless/rt2x00/rt2x00config.c @@ -102,7 +102,7 @@ void rt2x00lib_config_erp(struct rt2x00_dev *rt2x00dev, /* Update the AID, this is needed for dynamic PS support */ rt2x00dev->aid = bss_conf->assoc ? bss_conf->aid : 0; - rt2x00dev->last_beacon = bss_conf->last_tsf; + rt2x00dev->last_beacon = bss_conf->sync_tsf; /* Update global beacon interval time, this is needed for PS support */ rt2x00dev->beacon_int = bss_conf->beacon_int; diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 7300cb51dd5..bb86aa6f98d 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -233,8 +233,10 @@ enum ieee80211_rssi_event { * valid in station mode only while @assoc is true and if also * requested by %IEEE80211_HW_NEED_DTIM_PERIOD (cf. also hw conf * @ps_dtim_period) - * @last_tsf: last beacon's/probe response's TSF timestamp (could be old + * @sync_tsf: last beacon's/probe response's TSF timestamp (could be old * as it may have been received during scanning long ago) + * @sync_device_ts: the device timestamp corresponding to the sync_tsf, + * the driver/device can use this to calculate synchronisation * @beacon_int: beacon interval * @assoc_capability: capabilities taken from assoc resp * @basic_rates: bitmap of basic rates, each bit stands for an @@ -281,7 +283,8 @@ struct ieee80211_bss_conf { u8 dtim_period; u16 beacon_int; u16 assoc_capability; - u64 last_tsf; + u64 sync_tsf; + u32 sync_device_ts; u32 basic_rates; int mcast_rate[IEEE80211_NUM_BANDS]; u16 ht_operation_mode; @@ -696,6 +699,8 @@ enum mac80211_rx_flags { * * @mactime: value in microseconds of the 64-bit Time Synchronization Function * (TSF) timer when the first data symbol (MPDU) arrived at the hardware. + * @device_timestamp: arbitrary timestamp for the device, mac80211 doesn't use + * it but can store it and pass it back to the driver for synchronisation * @band: the active band when this frame was received * @freq: frequency the radio was tuned to when receiving this frame, in MHz * @signal: signal strength when receiving this frame, either in dBm, in dB or @@ -709,6 +714,7 @@ enum mac80211_rx_flags { */ struct ieee80211_rx_status { u64 mactime; + u32 device_timestamp; u16 flag; u16 freq; u8 rate_idx; diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 2a97d668d2d..7998513ec83 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -85,6 +85,8 @@ struct ieee80211_bss { size_t ssid_len; u8 ssid[IEEE80211_MAX_SSID_LEN]; + u32 device_ts; + u8 dtim_period; bool wmm_used; diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 4b503ce893d..4efcbf89a72 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -1269,11 +1269,6 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata, struct ieee80211_bss_conf *bss_conf = &sdata->vif.bss_conf; bss_info_changed |= BSS_CHANGED_ASSOC; - /* set timing information */ - bss_conf->beacon_int = cbss->beacon_interval; - bss_conf->last_tsf = cbss->tsf; - - bss_info_changed |= BSS_CHANGED_BEACON_INT; bss_info_changed |= ieee80211_handle_bss_capability(sdata, bss_conf->assoc_capability, bss->has_erp_value, bss->erp_value); @@ -3135,9 +3130,15 @@ static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata, memcpy(ifmgd->bssid, cbss->bssid, ETH_ALEN); - /* tell driver about BSSID and basic rates */ + /* set timing information */ + sdata->vif.bss_conf.beacon_int = cbss->beacon_interval; + sdata->vif.bss_conf.sync_tsf = cbss->tsf; + sdata->vif.bss_conf.sync_device_ts = bss->device_ts; + + /* tell driver about BSSID, basic rates and timing */ ieee80211_bss_info_change_notify(sdata, - BSS_CHANGED_BSSID | BSS_CHANGED_BASIC_RATES); + BSS_CHANGED_BSSID | BSS_CHANGED_BASIC_RATES | + BSS_CHANGED_BEACON_INT); if (assoc) sta_info_pre_move_state(sta, IEEE80211_STA_AUTH); diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index 1a893f3637c..e80a8b644aa 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -83,13 +83,14 @@ ieee80211_bss_info_update(struct ieee80211_local *local, cbss = cfg80211_inform_bss_frame(local->hw.wiphy, channel, mgmt, len, signal, GFP_ATOMIC); - if (!cbss) return NULL; cbss->free_priv = ieee80211_rx_bss_free; bss = (void *)cbss->priv; + bss->device_ts = rx_status->device_timestamp; + if (elems->parse_error) { if (beacon) bss->corrupt_data |= IEEE80211_BSS_CORRUPT_BEACON; diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h index e1e9d10ec2e..c6d33b55b2d 100644 --- a/net/mac80211/trace.h +++ b/net/mac80211/trace.h @@ -306,7 +306,8 @@ TRACE_EVENT(drv_bss_info_changed, __field(u8, dtimper) __field(u16, bcnint) __field(u16, assoc_cap) - __field(u64, timestamp) + __field(u64, sync_tsf) + __field(u32, sync_device_ts) __field(u32, basic_rates) __field(u32, changed) __field(bool, enable_beacon) @@ -325,7 +326,8 @@ TRACE_EVENT(drv_bss_info_changed, __entry->dtimper = info->dtim_period; __entry->bcnint = info->beacon_int; __entry->assoc_cap = info->assoc_capability; - __entry->timestamp = info->last_tsf; + __entry->sync_tsf = info->sync_tsf; + __entry->sync_device_ts = info->sync_device_ts; __entry->basic_rates = info->basic_rates; __entry->enable_beacon = info->enable_beacon; __entry->ht_operation_mode = info->ht_operation_mode; -- cgit v1.2.3-70-g09d2