| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 1 | /* | 
 | 2 |  * mac80211 work implementation | 
 | 3 |  * | 
 | 4 |  * Copyright 2003-2008, Jouni Malinen <j@w1.fi> | 
 | 5 |  * Copyright 2004, Instant802 Networks, Inc. | 
 | 6 |  * Copyright 2005, Devicescape Software, Inc. | 
 | 7 |  * Copyright 2006-2007	Jiri Benc <jbenc@suse.cz> | 
 | 8 |  * Copyright 2007, Michael Wu <flamingice@sourmilk.net> | 
 | 9 |  * Copyright 2009, Johannes Berg <johannes@sipsolutions.net> | 
 | 10 |  * | 
 | 11 |  * This program is free software; you can redistribute it and/or modify | 
 | 12 |  * it under the terms of the GNU General Public License version 2 as | 
 | 13 |  * published by the Free Software Foundation. | 
 | 14 |  */ | 
 | 15 |  | 
 | 16 | #include <linux/delay.h> | 
 | 17 | #include <linux/if_ether.h> | 
 | 18 | #include <linux/skbuff.h> | 
 | 19 | #include <linux/if_arp.h> | 
 | 20 | #include <linux/etherdevice.h> | 
 | 21 | #include <linux/crc32.h> | 
| Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 22 | #include <linux/slab.h> | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 23 | #include <net/mac80211.h> | 
 | 24 | #include <asm/unaligned.h> | 
 | 25 |  | 
 | 26 | #include "ieee80211_i.h" | 
 | 27 | #include "rate.h" | 
| Johannes Berg | b2abb6e | 2011-07-19 10:39:53 +0200 | [diff] [blame] | 28 | #include "driver-ops.h" | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 29 |  | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 30 | enum work_action { | 
 | 31 | 	WORK_ACT_NONE, | 
 | 32 | 	WORK_ACT_TIMEOUT, | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 33 | }; | 
 | 34 |  | 
 | 35 |  | 
 | 36 | /* utils */ | 
 | 37 | static inline void ASSERT_WORK_MTX(struct ieee80211_local *local) | 
 | 38 | { | 
| Johannes Berg | a1699b7 | 2010-07-30 16:46:07 +0200 | [diff] [blame] | 39 | 	lockdep_assert_held(&local->mtx); | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 40 | } | 
 | 41 |  | 
 | 42 | /* | 
 | 43 |  * We can have multiple work items (and connection probing) | 
 | 44 |  * scheduling this timer, but we need to take care to only | 
 | 45 |  * reschedule it when it should fire _earlier_ than it was | 
 | 46 |  * asked for before, or if it's not pending right now. This | 
 | 47 |  * function ensures that. Note that it then is required to | 
 | 48 |  * run this function for all timeouts after the first one | 
 | 49 |  * has happened -- the work that runs from this timer will | 
 | 50 |  * do that. | 
 | 51 |  */ | 
 | 52 | static void run_again(struct ieee80211_local *local, | 
 | 53 | 		      unsigned long timeout) | 
 | 54 | { | 
 | 55 | 	ASSERT_WORK_MTX(local); | 
 | 56 |  | 
 | 57 | 	if (!timer_pending(&local->work_timer) || | 
 | 58 | 	    time_before(timeout, local->work_timer.expires)) | 
 | 59 | 		mod_timer(&local->work_timer, timeout); | 
 | 60 | } | 
 | 61 |  | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 62 | void free_work(struct ieee80211_work *wk) | 
 | 63 | { | 
| Lai Jiangshan | a74ce14 | 2011-03-18 12:14:15 +0800 | [diff] [blame] | 64 | 	kfree_rcu(wk, rcu_head); | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 65 | } | 
 | 66 |  | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 67 | static enum work_action __must_check | 
 | 68 | ieee80211_remain_on_channel_timeout(struct ieee80211_work *wk) | 
 | 69 | { | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 70 | 	/* | 
 | 71 | 	 * First time we run, do nothing -- the generic code will | 
 | 72 | 	 * have switched to the right channel etc. | 
 | 73 | 	 */ | 
| Johannes Berg | 723bae7 | 2010-01-25 13:36:36 +0100 | [diff] [blame] | 74 | 	if (!wk->started) { | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 75 | 		wk->timeout = jiffies + msecs_to_jiffies(wk->remain.duration); | 
 | 76 |  | 
| Kalle Valo | 1990ca6 | 2009-12-30 14:42:20 +0200 | [diff] [blame] | 77 | 		cfg80211_ready_on_channel(wk->sdata->dev, (unsigned long) wk, | 
 | 78 | 					  wk->chan, wk->chan_type, | 
 | 79 | 					  wk->remain.duration, GFP_KERNEL); | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 80 |  | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 81 | 		return WORK_ACT_NONE; | 
 | 82 | 	} | 
 | 83 |  | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 84 | 	return WORK_ACT_TIMEOUT; | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 85 | } | 
 | 86 |  | 
| Johannes Berg | e5b900d | 2010-07-29 16:08:55 +0200 | [diff] [blame] | 87 | static enum work_action __must_check | 
| Johannes Berg | f30221e | 2010-11-25 10:02:30 +0100 | [diff] [blame] | 88 | ieee80211_offchannel_tx(struct ieee80211_work *wk) | 
 | 89 | { | 
 | 90 | 	if (!wk->started) { | 
 | 91 | 		wk->timeout = jiffies + msecs_to_jiffies(wk->offchan_tx.wait); | 
 | 92 |  | 
 | 93 | 		/* | 
 | 94 | 		 * After this, offchan_tx.frame remains but now is no | 
 | 95 | 		 * longer a valid pointer -- we still need it as the | 
| Johannes Berg | 28a1bcd | 2011-10-04 18:27:10 +0200 | [diff] [blame] | 96 | 		 * cookie for canceling this work/status matching. | 
| Johannes Berg | f30221e | 2010-11-25 10:02:30 +0100 | [diff] [blame] | 97 | 		 */ | 
 | 98 | 		ieee80211_tx_skb(wk->sdata, wk->offchan_tx.frame); | 
 | 99 |  | 
 | 100 | 		return WORK_ACT_NONE; | 
 | 101 | 	} | 
 | 102 |  | 
 | 103 | 	return WORK_ACT_TIMEOUT; | 
 | 104 | } | 
 | 105 |  | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 106 | static void ieee80211_work_timer(unsigned long data) | 
 | 107 | { | 
 | 108 | 	struct ieee80211_local *local = (void *) data; | 
 | 109 |  | 
 | 110 | 	if (local->quiescing) | 
 | 111 | 		return; | 
 | 112 |  | 
 | 113 | 	ieee80211_queue_work(&local->hw, &local->work_work); | 
 | 114 | } | 
 | 115 |  | 
 | 116 | static void ieee80211_work_work(struct work_struct *work) | 
 | 117 | { | 
 | 118 | 	struct ieee80211_local *local = | 
 | 119 | 		container_of(work, struct ieee80211_local, work_work); | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 120 | 	struct ieee80211_work *wk, *tmp; | 
 | 121 | 	LIST_HEAD(free_work); | 
 | 122 | 	enum work_action rma; | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 123 | 	bool remain_off_channel = false; | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 124 |  | 
 | 125 | 	if (local->scanning) | 
 | 126 | 		return; | 
 | 127 |  | 
 | 128 | 	/* | 
 | 129 | 	 * ieee80211_queue_work() should have picked up most cases, | 
| Walter Goldens | 77c2061 | 2010-05-18 04:44:54 -0700 | [diff] [blame] | 130 | 	 * here we'll pick the rest. | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 131 | 	 */ | 
 | 132 | 	if (WARN(local->suspended, "work scheduled while going to suspend\n")) | 
 | 133 | 		return; | 
 | 134 |  | 
| Johannes Berg | a1699b7 | 2010-07-30 16:46:07 +0200 | [diff] [blame] | 135 | 	mutex_lock(&local->mtx); | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 136 |  | 
| Johannes Berg | 7da7cc1 | 2010-08-05 17:02:38 +0200 | [diff] [blame] | 137 | 	ieee80211_recalc_idle(local); | 
 | 138 |  | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 139 | 	list_for_each_entry_safe(wk, tmp, &local->work_list, list) { | 
| Johannes Berg | 723bae7 | 2010-01-25 13:36:36 +0100 | [diff] [blame] | 140 | 		bool started = wk->started; | 
 | 141 |  | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 142 | 		/* mark work as started if it's on the current off-channel */ | 
| Johannes Berg | 723bae7 | 2010-01-25 13:36:36 +0100 | [diff] [blame] | 143 | 		if (!started && local->tmp_channel && | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 144 | 		    wk->chan == local->tmp_channel && | 
 | 145 | 		    wk->chan_type == local->tmp_channel_type) { | 
| Johannes Berg | 723bae7 | 2010-01-25 13:36:36 +0100 | [diff] [blame] | 146 | 			started = true; | 
| Johannes Berg | 81ac346 | 2010-01-06 15:30:58 +0100 | [diff] [blame] | 147 | 			wk->timeout = jiffies; | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 148 | 		} | 
 | 149 |  | 
| Johannes Berg | 723bae7 | 2010-01-25 13:36:36 +0100 | [diff] [blame] | 150 | 		if (!started && !local->tmp_channel) { | 
| Johannes Berg | e76aadc | 2011-11-29 10:20:02 +0100 | [diff] [blame] | 151 | 			ieee80211_offchannel_stop_vifs(local, true); | 
| Ben Greear | da2fd1f | 2011-02-07 13:44:36 -0800 | [diff] [blame] | 152 |  | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 153 | 			local->tmp_channel = wk->chan; | 
| Johannes Berg | e76aadc | 2011-11-29 10:20:02 +0100 | [diff] [blame] | 154 | 			local->tmp_channel_type = wk->chan_type; | 
 | 155 |  | 
 | 156 | 			ieee80211_hw_config(local, 0); | 
| Ben Greear | b23b025 | 2011-02-04 11:54:17 -0800 | [diff] [blame] | 157 |  | 
| Johannes Berg | 723bae7 | 2010-01-25 13:36:36 +0100 | [diff] [blame] | 158 | 			started = true; | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 159 | 			wk->timeout = jiffies; | 
 | 160 | 		} | 
 | 161 |  | 
 | 162 | 		/* don't try to work with items that aren't started */ | 
| Johannes Berg | 723bae7 | 2010-01-25 13:36:36 +0100 | [diff] [blame] | 163 | 		if (!started) | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 164 | 			continue; | 
 | 165 |  | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 166 | 		if (time_is_after_jiffies(wk->timeout)) { | 
 | 167 | 			/* | 
 | 168 | 			 * This work item isn't supposed to be worked on | 
 | 169 | 			 * right now, but take care to adjust the timer | 
 | 170 | 			 * properly. | 
 | 171 | 			 */ | 
 | 172 | 			run_again(local, wk->timeout); | 
 | 173 | 			continue; | 
 | 174 | 		} | 
 | 175 |  | 
 | 176 | 		switch (wk->type) { | 
 | 177 | 		default: | 
 | 178 | 			WARN_ON(1); | 
 | 179 | 			/* nothing */ | 
 | 180 | 			rma = WORK_ACT_NONE; | 
 | 181 | 			break; | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 182 | 		case IEEE80211_WORK_ABORT: | 
 | 183 | 			rma = WORK_ACT_TIMEOUT; | 
| Juuso Oikarinen | 0e0a228 | 2010-02-26 08:13:41 +0200 | [diff] [blame] | 184 | 			break; | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 185 | 		case IEEE80211_WORK_REMAIN_ON_CHANNEL: | 
 | 186 | 			rma = ieee80211_remain_on_channel_timeout(wk); | 
 | 187 | 			break; | 
| Johannes Berg | f30221e | 2010-11-25 10:02:30 +0100 | [diff] [blame] | 188 | 		case IEEE80211_WORK_OFFCHANNEL_TX: | 
 | 189 | 			rma = ieee80211_offchannel_tx(wk); | 
 | 190 | 			break; | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 191 | 		} | 
 | 192 |  | 
| Johannes Berg | 723bae7 | 2010-01-25 13:36:36 +0100 | [diff] [blame] | 193 | 		wk->started = started; | 
 | 194 |  | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 195 | 		switch (rma) { | 
 | 196 | 		case WORK_ACT_NONE: | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 197 | 			/* might have changed the timeout */ | 
 | 198 | 			run_again(local, wk->timeout); | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 199 | 			break; | 
 | 200 | 		case WORK_ACT_TIMEOUT: | 
 | 201 | 			list_del_rcu(&wk->list); | 
 | 202 | 			synchronize_rcu(); | 
 | 203 | 			list_add(&wk->list, &free_work); | 
 | 204 | 			break; | 
 | 205 | 		default: | 
 | 206 | 			WARN(1, "unexpected: %d", rma); | 
 | 207 | 		} | 
 | 208 | 	} | 
 | 209 |  | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 210 | 	list_for_each_entry(wk, &local->work_list, list) { | 
 | 211 | 		if (!wk->started) | 
 | 212 | 			continue; | 
| Johannes Berg | e76aadc | 2011-11-29 10:20:02 +0100 | [diff] [blame] | 213 | 		if (wk->chan != local->tmp_channel || | 
 | 214 | 		    wk->chan_type != local->tmp_channel_type) | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 215 | 			continue; | 
 | 216 | 		remain_off_channel = true; | 
 | 217 | 	} | 
 | 218 |  | 
 | 219 | 	if (!remain_off_channel && local->tmp_channel) { | 
 | 220 | 		local->tmp_channel = NULL; | 
| Johannes Berg | e76aadc | 2011-11-29 10:20:02 +0100 | [diff] [blame] | 221 | 		ieee80211_hw_config(local, 0); | 
| Ben Greear | b23b025 | 2011-02-04 11:54:17 -0800 | [diff] [blame] | 222 |  | 
| Johannes Berg | e76aadc | 2011-11-29 10:20:02 +0100 | [diff] [blame] | 223 | 		ieee80211_offchannel_return(local, true); | 
| Ben Greear | b23b025 | 2011-02-04 11:54:17 -0800 | [diff] [blame] | 224 |  | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 225 | 		/* give connection some time to breathe */ | 
 | 226 | 		run_again(local, jiffies + HZ/2); | 
 | 227 | 	} | 
 | 228 |  | 
| Teemu Paasikivi | 68dd5b7 | 2010-04-09 13:07:55 +0300 | [diff] [blame] | 229 | 	if (list_empty(&local->work_list) && local->scan_req && | 
 | 230 | 	    !local->scanning) | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 231 | 		ieee80211_queue_delayed_work(&local->hw, | 
 | 232 | 					     &local->scan_work, | 
 | 233 | 					     round_jiffies_relative(0)); | 
 | 234 |  | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 235 | 	ieee80211_recalc_idle(local); | 
 | 236 |  | 
| Johannes Berg | 7da7cc1 | 2010-08-05 17:02:38 +0200 | [diff] [blame] | 237 | 	mutex_unlock(&local->mtx); | 
 | 238 |  | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 239 | 	list_for_each_entry_safe(wk, tmp, &free_work, list) { | 
 | 240 | 		wk->done(wk, NULL); | 
 | 241 | 		list_del(&wk->list); | 
 | 242 | 		kfree(wk); | 
 | 243 | 	} | 
 | 244 | } | 
 | 245 |  | 
 | 246 | void ieee80211_add_work(struct ieee80211_work *wk) | 
 | 247 | { | 
 | 248 | 	struct ieee80211_local *local; | 
 | 249 |  | 
 | 250 | 	if (WARN_ON(!wk->chan)) | 
 | 251 | 		return; | 
 | 252 |  | 
 | 253 | 	if (WARN_ON(!wk->sdata)) | 
 | 254 | 		return; | 
 | 255 |  | 
 | 256 | 	if (WARN_ON(!wk->done)) | 
 | 257 | 		return; | 
 | 258 |  | 
| Johannes Berg | 81ac346 | 2010-01-06 15:30:58 +0100 | [diff] [blame] | 259 | 	if (WARN_ON(!ieee80211_sdata_running(wk->sdata))) | 
 | 260 | 		return; | 
 | 261 |  | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 262 | 	wk->started = false; | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 263 |  | 
 | 264 | 	local = wk->sdata->local; | 
| Johannes Berg | a1699b7 | 2010-07-30 16:46:07 +0200 | [diff] [blame] | 265 | 	mutex_lock(&local->mtx); | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 266 | 	list_add_tail(&wk->list, &local->work_list); | 
| Johannes Berg | a1699b7 | 2010-07-30 16:46:07 +0200 | [diff] [blame] | 267 | 	mutex_unlock(&local->mtx); | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 268 |  | 
 | 269 | 	ieee80211_queue_work(&local->hw, &local->work_work); | 
 | 270 | } | 
 | 271 |  | 
 | 272 | void ieee80211_work_init(struct ieee80211_local *local) | 
 | 273 | { | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 274 | 	INIT_LIST_HEAD(&local->work_list); | 
 | 275 | 	setup_timer(&local->work_timer, ieee80211_work_timer, | 
 | 276 | 		    (unsigned long)local); | 
 | 277 | 	INIT_WORK(&local->work_work, ieee80211_work_work); | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 278 | } | 
 | 279 |  | 
 | 280 | void ieee80211_work_purge(struct ieee80211_sub_if_data *sdata) | 
 | 281 | { | 
 | 282 | 	struct ieee80211_local *local = sdata->local; | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 283 | 	struct ieee80211_work *wk; | 
| Herton Ronaldo Krzesinski | 8808f64 | 2010-12-13 11:43:51 -0200 | [diff] [blame] | 284 | 	bool cleanup = false; | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 285 |  | 
| Johannes Berg | a1699b7 | 2010-07-30 16:46:07 +0200 | [diff] [blame] | 286 | 	mutex_lock(&local->mtx); | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 287 | 	list_for_each_entry(wk, &local->work_list, list) { | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 288 | 		if (wk->sdata != sdata) | 
 | 289 | 			continue; | 
| Herton Ronaldo Krzesinski | 8808f64 | 2010-12-13 11:43:51 -0200 | [diff] [blame] | 290 | 		cleanup = true; | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 291 | 		wk->type = IEEE80211_WORK_ABORT; | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 292 | 		wk->started = true; | 
 | 293 | 		wk->timeout = jiffies; | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 294 | 	} | 
| Johannes Berg | a1699b7 | 2010-07-30 16:46:07 +0200 | [diff] [blame] | 295 | 	mutex_unlock(&local->mtx); | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 296 |  | 
 | 297 | 	/* run cleanups etc. */ | 
| Herton Ronaldo Krzesinski | 8808f64 | 2010-12-13 11:43:51 -0200 | [diff] [blame] | 298 | 	if (cleanup) | 
 | 299 | 		ieee80211_work_work(&local->work_work); | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 300 |  | 
| Johannes Berg | a1699b7 | 2010-07-30 16:46:07 +0200 | [diff] [blame] | 301 | 	mutex_lock(&local->mtx); | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 302 | 	list_for_each_entry(wk, &local->work_list, list) { | 
 | 303 | 		if (wk->sdata != sdata) | 
 | 304 | 			continue; | 
 | 305 | 		WARN_ON(1); | 
 | 306 | 		break; | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 307 | 	} | 
| Johannes Berg | a1699b7 | 2010-07-30 16:46:07 +0200 | [diff] [blame] | 308 | 	mutex_unlock(&local->mtx); | 
| Johannes Berg | af6b637 | 2009-12-23 13:15:35 +0100 | [diff] [blame] | 309 | } | 
 | 310 |  | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 311 | static enum work_done_result ieee80211_remain_done(struct ieee80211_work *wk, | 
 | 312 | 						   struct sk_buff *skb) | 
 | 313 | { | 
 | 314 | 	/* | 
 | 315 | 	 * We are done serving the remain-on-channel command. | 
 | 316 | 	 */ | 
| Kalle Valo | 1990ca6 | 2009-12-30 14:42:20 +0200 | [diff] [blame] | 317 | 	cfg80211_remain_on_channel_expired(wk->sdata->dev, (unsigned long) wk, | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 318 | 					   wk->chan, wk->chan_type, | 
 | 319 | 					   GFP_KERNEL); | 
 | 320 |  | 
 | 321 | 	return WORK_DONE_DESTROY; | 
 | 322 | } | 
 | 323 |  | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 324 | int ieee80211_wk_remain_on_channel(struct ieee80211_sub_if_data *sdata, | 
 | 325 | 				   struct ieee80211_channel *chan, | 
 | 326 | 				   enum nl80211_channel_type channel_type, | 
 | 327 | 				   unsigned int duration, u64 *cookie) | 
 | 328 | { | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 329 | 	struct ieee80211_work *wk; | 
 | 330 |  | 
 | 331 | 	wk = kzalloc(sizeof(*wk), GFP_KERNEL); | 
 | 332 | 	if (!wk) | 
 | 333 | 		return -ENOMEM; | 
 | 334 |  | 
 | 335 | 	wk->type = IEEE80211_WORK_REMAIN_ON_CHANNEL; | 
 | 336 | 	wk->chan = chan; | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 337 | 	wk->chan_type = channel_type; | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 338 | 	wk->sdata = sdata; | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 339 | 	wk->done = ieee80211_remain_done; | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 340 |  | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 341 | 	wk->remain.duration = duration; | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 342 |  | 
| Kalle Valo | 1990ca6 | 2009-12-30 14:42:20 +0200 | [diff] [blame] | 343 | 	*cookie = (unsigned long) wk; | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 344 |  | 
 | 345 | 	ieee80211_add_work(wk); | 
 | 346 |  | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 347 | 	return 0; | 
 | 348 | } | 
 | 349 |  | 
 | 350 | int ieee80211_wk_cancel_remain_on_channel(struct ieee80211_sub_if_data *sdata, | 
 | 351 | 					  u64 cookie) | 
 | 352 | { | 
 | 353 | 	struct ieee80211_local *local = sdata->local; | 
 | 354 | 	struct ieee80211_work *wk, *tmp; | 
 | 355 | 	bool found = false; | 
 | 356 |  | 
| Johannes Berg | a1699b7 | 2010-07-30 16:46:07 +0200 | [diff] [blame] | 357 | 	mutex_lock(&local->mtx); | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 358 | 	list_for_each_entry_safe(wk, tmp, &local->work_list, list) { | 
| Kalle Valo | 1990ca6 | 2009-12-30 14:42:20 +0200 | [diff] [blame] | 359 | 		if ((unsigned long) wk == cookie) { | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 360 | 			wk->timeout = jiffies; | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 361 | 			found = true; | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 362 | 			break; | 
 | 363 | 		} | 
 | 364 | 	} | 
| Johannes Berg | a1699b7 | 2010-07-30 16:46:07 +0200 | [diff] [blame] | 365 | 	mutex_unlock(&local->mtx); | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 366 |  | 
 | 367 | 	if (!found) | 
 | 368 | 		return -ENOENT; | 
 | 369 |  | 
| Johannes Berg | e4da8c3 | 2009-12-23 13:15:43 +0100 | [diff] [blame] | 370 | 	ieee80211_queue_work(&local->hw, &local->work_work); | 
| Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame] | 371 |  | 
 | 372 | 	return 0; | 
 | 373 | } |