/* * mac80211 work implementation * * Copyright 2003-2008, Jouni Malinen <j@w1.fi> * Copyright 2004, Instant802 Networks, Inc. * Copyright 2005, Devicescape Software, Inc. * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz> * Copyright 2007, Michael Wu <flamingice@sourmilk.net> * Copyright 2009, Johannes Berg <johannes@sipsolutions.net> * * 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 <linux/delay.h>#include <linux/if_ether.h>#include <linux/skbuff.h>#include <linux/if_arp.h>#include <linux/etherdevice.h>#include <linux/crc32.h>#include <linux/slab.h>#include <net/mac80211.h>#include <asm/unaligned.h> #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_checkieee80211_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_checkieee80211_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;}
/*
* mac80211 work implementation
*
* Copyright 2003-2008, Jouni Malinen <j@w1.fi>
* Copyright 2004, Instant802 Networks, Inc.
* Copyright 2005, Devicescape Software, Inc.
* Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
* Copyright 2007, Michael Wu <flamingice@sourmilk.net>
* Copyright 2009, Johannes Berg <johannes@sipsolutions.net>
*
* 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 <linux/delay.h>
#include <linux/if_ether.h>
#include <linux/skbuff.h>
#include <linux/if_arp.h>
#include <linux/etherdevice.h>
#include <linux/crc32.h>
#include <linux/slab.h>
#include <net/mac80211.h>
#include <asm/unaligned.h>
#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;
}