diff options
author | Jiri Benc <jbenc@suse.cz> | 2007-05-05 14:45:53 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2007-05-05 14:45:53 -0400 |
commit | f0706e828e96d0fa4e80c0d25aa98523f6d589a0 (patch) | |
tree | a03c7f94939d74c1e1b82fcd9a215871590d8b35 /net/mac80211/sta_info.c | |
parent | a9de8ce0943e03b425be18561f51159fcceb873d (diff) |
[MAC80211]: Add mac80211 wireless stack.
Add mac80211, the IEEE 802.11 software MAC layer.
Signed-off-by: Jiri Benc <jbenc@suse.cz>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'net/mac80211/sta_info.c')
-rw-r--r-- | net/mac80211/sta_info.c | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c new file mode 100644 index 000000000000..cddaf578dc8f --- /dev/null +++ b/net/mac80211/sta_info.c | |||
@@ -0,0 +1,386 @@ | |||
1 | /* | ||
2 | * Copyright 2002-2005, Instant802 Networks, Inc. | ||
3 | * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License version 2 as | ||
7 | * published by the Free Software Foundation. | ||
8 | */ | ||
9 | |||
10 | #include <linux/module.h> | ||
11 | #include <linux/init.h> | ||
12 | #include <linux/netdevice.h> | ||
13 | #include <linux/types.h> | ||
14 | #include <linux/slab.h> | ||
15 | #include <linux/skbuff.h> | ||
16 | #include <linux/if_arp.h> | ||
17 | |||
18 | #include <net/mac80211.h> | ||
19 | #include "ieee80211_i.h" | ||
20 | #include "ieee80211_rate.h" | ||
21 | #include "sta_info.h" | ||
22 | |||
23 | /* Caller must hold local->sta_lock */ | ||
24 | static void sta_info_hash_add(struct ieee80211_local *local, | ||
25 | struct sta_info *sta) | ||
26 | { | ||
27 | sta->hnext = local->sta_hash[STA_HASH(sta->addr)]; | ||
28 | local->sta_hash[STA_HASH(sta->addr)] = sta; | ||
29 | } | ||
30 | |||
31 | |||
32 | /* Caller must hold local->sta_lock */ | ||
33 | static void sta_info_hash_del(struct ieee80211_local *local, | ||
34 | struct sta_info *sta) | ||
35 | { | ||
36 | struct sta_info *s; | ||
37 | |||
38 | s = local->sta_hash[STA_HASH(sta->addr)]; | ||
39 | if (!s) | ||
40 | return; | ||
41 | if (memcmp(s->addr, sta->addr, ETH_ALEN) == 0) { | ||
42 | local->sta_hash[STA_HASH(sta->addr)] = s->hnext; | ||
43 | return; | ||
44 | } | ||
45 | |||
46 | while (s->hnext && memcmp(s->hnext->addr, sta->addr, ETH_ALEN) != 0) | ||
47 | s = s->hnext; | ||
48 | if (s->hnext) | ||
49 | s->hnext = s->hnext->hnext; | ||
50 | else | ||
51 | printk(KERN_ERR "%s: could not remove STA " MAC_FMT " from " | ||
52 | "hash table\n", local->mdev->name, MAC_ARG(sta->addr)); | ||
53 | } | ||
54 | |||
55 | static inline void __sta_info_get(struct sta_info *sta) | ||
56 | { | ||
57 | kref_get(&sta->kref); | ||
58 | } | ||
59 | |||
60 | struct sta_info *sta_info_get(struct ieee80211_local *local, u8 *addr) | ||
61 | { | ||
62 | struct sta_info *sta; | ||
63 | |||
64 | spin_lock_bh(&local->sta_lock); | ||
65 | sta = local->sta_hash[STA_HASH(addr)]; | ||
66 | while (sta) { | ||
67 | if (memcmp(sta->addr, addr, ETH_ALEN) == 0) { | ||
68 | __sta_info_get(sta); | ||
69 | break; | ||
70 | } | ||
71 | sta = sta->hnext; | ||
72 | } | ||
73 | spin_unlock_bh(&local->sta_lock); | ||
74 | |||
75 | return sta; | ||
76 | } | ||
77 | EXPORT_SYMBOL(sta_info_get); | ||
78 | |||
79 | int sta_info_min_txrate_get(struct ieee80211_local *local) | ||
80 | { | ||
81 | struct sta_info *sta; | ||
82 | struct ieee80211_hw_mode *mode; | ||
83 | int min_txrate = 9999999; | ||
84 | int i; | ||
85 | |||
86 | spin_lock_bh(&local->sta_lock); | ||
87 | mode = local->oper_hw_mode; | ||
88 | for (i = 0; i < STA_HASH_SIZE; i++) { | ||
89 | sta = local->sta_hash[i]; | ||
90 | while (sta) { | ||
91 | if (sta->txrate < min_txrate) | ||
92 | min_txrate = sta->txrate; | ||
93 | sta = sta->hnext; | ||
94 | } | ||
95 | } | ||
96 | spin_unlock_bh(&local->sta_lock); | ||
97 | if (min_txrate == 9999999) | ||
98 | min_txrate = 0; | ||
99 | |||
100 | return mode->rates[min_txrate].rate; | ||
101 | } | ||
102 | |||
103 | |||
104 | static void sta_info_release(struct kref *kref) | ||
105 | { | ||
106 | struct sta_info *sta = container_of(kref, struct sta_info, kref); | ||
107 | struct ieee80211_local *local = sta->local; | ||
108 | struct sk_buff *skb; | ||
109 | |||
110 | /* free sta structure; it has already been removed from | ||
111 | * hash table etc. external structures. Make sure that all | ||
112 | * buffered frames are release (one might have been added | ||
113 | * after sta_info_free() was called). */ | ||
114 | while ((skb = skb_dequeue(&sta->ps_tx_buf)) != NULL) { | ||
115 | local->total_ps_buffered--; | ||
116 | dev_kfree_skb_any(skb); | ||
117 | } | ||
118 | while ((skb = skb_dequeue(&sta->tx_filtered)) != NULL) { | ||
119 | dev_kfree_skb_any(skb); | ||
120 | } | ||
121 | rate_control_free_sta(sta->rate_ctrl, sta->rate_ctrl_priv); | ||
122 | rate_control_put(sta->rate_ctrl); | ||
123 | kfree(sta); | ||
124 | } | ||
125 | |||
126 | |||
127 | void sta_info_put(struct sta_info *sta) | ||
128 | { | ||
129 | kref_put(&sta->kref, sta_info_release); | ||
130 | } | ||
131 | EXPORT_SYMBOL(sta_info_put); | ||
132 | |||
133 | |||
134 | struct sta_info * sta_info_add(struct ieee80211_local *local, | ||
135 | struct net_device *dev, u8 *addr, gfp_t gfp) | ||
136 | { | ||
137 | struct sta_info *sta; | ||
138 | |||
139 | sta = kzalloc(sizeof(*sta), gfp); | ||
140 | if (!sta) | ||
141 | return NULL; | ||
142 | |||
143 | kref_init(&sta->kref); | ||
144 | |||
145 | sta->rate_ctrl = rate_control_get(local->rate_ctrl); | ||
146 | sta->rate_ctrl_priv = rate_control_alloc_sta(sta->rate_ctrl, gfp); | ||
147 | if (!sta->rate_ctrl_priv) { | ||
148 | rate_control_put(sta->rate_ctrl); | ||
149 | kref_put(&sta->kref, sta_info_release); | ||
150 | kfree(sta); | ||
151 | return NULL; | ||
152 | } | ||
153 | |||
154 | memcpy(sta->addr, addr, ETH_ALEN); | ||
155 | sta->local = local; | ||
156 | sta->dev = dev; | ||
157 | skb_queue_head_init(&sta->ps_tx_buf); | ||
158 | skb_queue_head_init(&sta->tx_filtered); | ||
159 | __sta_info_get(sta); /* sta used by caller, decremented by | ||
160 | * sta_info_put() */ | ||
161 | spin_lock_bh(&local->sta_lock); | ||
162 | list_add(&sta->list, &local->sta_list); | ||
163 | local->num_sta++; | ||
164 | sta_info_hash_add(local, sta); | ||
165 | spin_unlock_bh(&local->sta_lock); | ||
166 | if (local->ops->sta_table_notification) | ||
167 | local->ops->sta_table_notification(local_to_hw(local), | ||
168 | local->num_sta); | ||
169 | sta->key_idx_compression = HW_KEY_IDX_INVALID; | ||
170 | |||
171 | #ifdef CONFIG_MAC80211_VERBOSE_DEBUG | ||
172 | printk(KERN_DEBUG "%s: Added STA " MAC_FMT "\n", | ||
173 | local->mdev->name, MAC_ARG(addr)); | ||
174 | #endif /* CONFIG_MAC80211_VERBOSE_DEBUG */ | ||
175 | |||
176 | return sta; | ||
177 | } | ||
178 | |||
179 | static void sta_info_remove(struct sta_info *sta) | ||
180 | { | ||
181 | struct ieee80211_local *local = sta->local; | ||
182 | struct ieee80211_sub_if_data *sdata; | ||
183 | |||
184 | sta_info_hash_del(local, sta); | ||
185 | list_del(&sta->list); | ||
186 | sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); | ||
187 | if (sta->flags & WLAN_STA_PS) { | ||
188 | sta->flags &= ~WLAN_STA_PS; | ||
189 | if (sdata->bss) | ||
190 | atomic_dec(&sdata->bss->num_sta_ps); | ||
191 | } | ||
192 | local->num_sta--; | ||
193 | sta_info_remove_aid_ptr(sta); | ||
194 | } | ||
195 | |||
196 | void sta_info_free(struct sta_info *sta, int locked) | ||
197 | { | ||
198 | struct sk_buff *skb; | ||
199 | struct ieee80211_local *local = sta->local; | ||
200 | |||
201 | if (!locked) { | ||
202 | spin_lock_bh(&local->sta_lock); | ||
203 | sta_info_remove(sta); | ||
204 | spin_unlock_bh(&local->sta_lock); | ||
205 | } else { | ||
206 | sta_info_remove(sta); | ||
207 | } | ||
208 | if (local->ops->sta_table_notification) | ||
209 | local->ops->sta_table_notification(local_to_hw(local), | ||
210 | local->num_sta); | ||
211 | |||
212 | while ((skb = skb_dequeue(&sta->ps_tx_buf)) != NULL) { | ||
213 | local->total_ps_buffered--; | ||
214 | dev_kfree_skb_any(skb); | ||
215 | } | ||
216 | while ((skb = skb_dequeue(&sta->tx_filtered)) != NULL) { | ||
217 | dev_kfree_skb_any(skb); | ||
218 | } | ||
219 | |||
220 | if (sta->key) { | ||
221 | if (local->ops->set_key) { | ||
222 | struct ieee80211_key_conf *key; | ||
223 | key = ieee80211_key_data2conf(local, sta->key); | ||
224 | if (key) { | ||
225 | local->ops->set_key(local_to_hw(local), | ||
226 | DISABLE_KEY, | ||
227 | sta->addr, key, sta->aid); | ||
228 | kfree(key); | ||
229 | } | ||
230 | } | ||
231 | } else if (sta->key_idx_compression != HW_KEY_IDX_INVALID) { | ||
232 | struct ieee80211_key_conf conf; | ||
233 | memset(&conf, 0, sizeof(conf)); | ||
234 | conf.hw_key_idx = sta->key_idx_compression; | ||
235 | conf.alg = ALG_NULL; | ||
236 | conf.flags |= IEEE80211_KEY_FORCE_SW_ENCRYPT; | ||
237 | local->ops->set_key(local_to_hw(local), DISABLE_KEY, | ||
238 | sta->addr, &conf, sta->aid); | ||
239 | sta->key_idx_compression = HW_KEY_IDX_INVALID; | ||
240 | } | ||
241 | |||
242 | #ifdef CONFIG_MAC80211_VERBOSE_DEBUG | ||
243 | printk(KERN_DEBUG "%s: Removed STA " MAC_FMT "\n", | ||
244 | local->mdev->name, MAC_ARG(sta->addr)); | ||
245 | #endif /* CONFIG_MAC80211_VERBOSE_DEBUG */ | ||
246 | |||
247 | if (sta->key) { | ||
248 | ieee80211_key_free(sta->key); | ||
249 | sta->key = NULL; | ||
250 | } | ||
251 | |||
252 | sta_info_put(sta); | ||
253 | } | ||
254 | |||
255 | |||
256 | static inline int sta_info_buffer_expired(struct ieee80211_local *local, | ||
257 | struct sta_info *sta, | ||
258 | struct sk_buff *skb) | ||
259 | { | ||
260 | struct ieee80211_tx_packet_data *pkt_data; | ||
261 | int timeout; | ||
262 | |||
263 | if (!skb) | ||
264 | return 0; | ||
265 | |||
266 | pkt_data = (struct ieee80211_tx_packet_data *) skb->cb; | ||
267 | |||
268 | /* Timeout: (2 * listen_interval * beacon_int * 1024 / 1000000) sec */ | ||
269 | timeout = (sta->listen_interval * local->hw.conf.beacon_int * 32 / | ||
270 | 15625) * HZ; | ||
271 | if (timeout < STA_TX_BUFFER_EXPIRE) | ||
272 | timeout = STA_TX_BUFFER_EXPIRE; | ||
273 | return time_after(jiffies, pkt_data->jiffies + timeout); | ||
274 | } | ||
275 | |||
276 | |||
277 | static void sta_info_cleanup_expire_buffered(struct ieee80211_local *local, | ||
278 | struct sta_info *sta) | ||
279 | { | ||
280 | unsigned long flags; | ||
281 | struct sk_buff *skb; | ||
282 | |||
283 | if (skb_queue_empty(&sta->ps_tx_buf)) | ||
284 | return; | ||
285 | |||
286 | for (;;) { | ||
287 | spin_lock_irqsave(&sta->ps_tx_buf.lock, flags); | ||
288 | skb = skb_peek(&sta->ps_tx_buf); | ||
289 | if (sta_info_buffer_expired(local, sta, skb)) { | ||
290 | skb = __skb_dequeue(&sta->ps_tx_buf); | ||
291 | if (skb_queue_empty(&sta->ps_tx_buf)) | ||
292 | sta->flags &= ~WLAN_STA_TIM; | ||
293 | } else | ||
294 | skb = NULL; | ||
295 | spin_unlock_irqrestore(&sta->ps_tx_buf.lock, flags); | ||
296 | |||
297 | if (skb) { | ||
298 | local->total_ps_buffered--; | ||
299 | printk(KERN_DEBUG "Buffered frame expired (STA " | ||
300 | MAC_FMT ")\n", MAC_ARG(sta->addr)); | ||
301 | dev_kfree_skb(skb); | ||
302 | } else | ||
303 | break; | ||
304 | } | ||
305 | } | ||
306 | |||
307 | |||
308 | static void sta_info_cleanup(unsigned long data) | ||
309 | { | ||
310 | struct ieee80211_local *local = (struct ieee80211_local *) data; | ||
311 | struct sta_info *sta; | ||
312 | |||
313 | spin_lock_bh(&local->sta_lock); | ||
314 | list_for_each_entry(sta, &local->sta_list, list) { | ||
315 | __sta_info_get(sta); | ||
316 | sta_info_cleanup_expire_buffered(local, sta); | ||
317 | sta_info_put(sta); | ||
318 | } | ||
319 | spin_unlock_bh(&local->sta_lock); | ||
320 | |||
321 | local->sta_cleanup.expires = jiffies + STA_INFO_CLEANUP_INTERVAL; | ||
322 | add_timer(&local->sta_cleanup); | ||
323 | } | ||
324 | |||
325 | void sta_info_init(struct ieee80211_local *local) | ||
326 | { | ||
327 | spin_lock_init(&local->sta_lock); | ||
328 | INIT_LIST_HEAD(&local->sta_list); | ||
329 | INIT_LIST_HEAD(&local->deleted_sta_list); | ||
330 | |||
331 | init_timer(&local->sta_cleanup); | ||
332 | local->sta_cleanup.expires = jiffies + STA_INFO_CLEANUP_INTERVAL; | ||
333 | local->sta_cleanup.data = (unsigned long) local; | ||
334 | local->sta_cleanup.function = sta_info_cleanup; | ||
335 | } | ||
336 | |||
337 | int sta_info_start(struct ieee80211_local *local) | ||
338 | { | ||
339 | add_timer(&local->sta_cleanup); | ||
340 | return 0; | ||
341 | } | ||
342 | |||
343 | void sta_info_stop(struct ieee80211_local *local) | ||
344 | { | ||
345 | struct sta_info *sta, *tmp; | ||
346 | |||
347 | del_timer(&local->sta_cleanup); | ||
348 | |||
349 | list_for_each_entry_safe(sta, tmp, &local->sta_list, list) { | ||
350 | /* We don't need locking at this point. */ | ||
351 | sta_info_free(sta, 0); | ||
352 | } | ||
353 | } | ||
354 | |||
355 | void sta_info_remove_aid_ptr(struct sta_info *sta) | ||
356 | { | ||
357 | struct ieee80211_sub_if_data *sdata; | ||
358 | |||
359 | if (sta->aid <= 0) | ||
360 | return; | ||
361 | |||
362 | sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev); | ||
363 | |||
364 | if (sdata->local->ops->set_tim) | ||
365 | sdata->local->ops->set_tim(local_to_hw(sdata->local), | ||
366 | sta->aid, 0); | ||
367 | if (sdata->bss) | ||
368 | __bss_tim_clear(sdata->bss, sta->aid); | ||
369 | } | ||
370 | |||
371 | |||
372 | /** | ||
373 | * sta_info_flush - flush matching STA entries from the STA table | ||
374 | * @local: local interface data | ||
375 | * @dev: matching rule for the net device (sta->dev) or %NULL to match all STAs | ||
376 | */ | ||
377 | void sta_info_flush(struct ieee80211_local *local, struct net_device *dev) | ||
378 | { | ||
379 | struct sta_info *sta, *tmp; | ||
380 | |||
381 | spin_lock_bh(&local->sta_lock); | ||
382 | list_for_each_entry_safe(sta, tmp, &local->sta_list, list) | ||
383 | if (!dev || dev == sta->dev) | ||
384 | sta_info_free(sta, 1); | ||
385 | spin_unlock_bh(&local->sta_lock); | ||
386 | } | ||