[PATCH] libertas: make scan result handling more flexible
[linux-2.6] / drivers / net / wireless / libertas / assoc.c
1 /* Copyright (C) 2006, Red Hat, Inc. */
2
3 #include <linux/bitops.h>
4 #include <net/ieee80211.h>
5
6 #include "assoc.h"
7 #include "join.h"
8 #include "decl.h"
9 #include "hostcmd.h"
10 #include "host.h"
11
12
13 static const u8 bssid_any[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
14 static const u8 bssid_off[ETH_ALEN] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
15
16 static int assoc_helper_essid(wlan_private *priv,
17                               struct assoc_request * assoc_req)
18 {
19         wlan_adapter *adapter = priv->adapter;
20         int ret = 0;
21         struct bss_descriptor * bss;
22
23         lbs_deb_enter(LBS_DEB_ASSOC);
24
25         lbs_deb_assoc("New SSID requested: %s\n", assoc_req->ssid.ssid);
26         if (assoc_req->mode == IW_MODE_INFRA) {
27                 if (adapter->prescan) {
28                         libertas_send_specific_SSID_scan(priv, &assoc_req->ssid, 1);
29                 }
30
31                 bss = libertas_find_SSID_in_list(adapter, &assoc_req->ssid,
32                                 NULL, IW_MODE_INFRA);
33                 if (bss != NULL) {
34                         lbs_deb_assoc("SSID found in scan list, associating\n");
35                         ret = wlan_associate(priv, bss);
36                         if (ret == 0) {
37                                 memcpy(&assoc_req->bssid, bss->bssid, ETH_ALEN);
38                         }
39                 } else {
40                         lbs_deb_assoc("SSID '%s' not found; cannot associate\n",
41                                 assoc_req->ssid.ssid);
42                 }
43         } else if (assoc_req->mode == IW_MODE_ADHOC) {
44                 /* Scan for the network, do not save previous results.  Stale
45                  *   scan data will cause us to join a non-existant adhoc network
46                  */
47                 libertas_send_specific_SSID_scan(priv, &assoc_req->ssid, 0);
48
49                 /* Search for the requested SSID in the scan table */
50                 bss = libertas_find_SSID_in_list(adapter, &assoc_req->ssid, NULL,
51                                 IW_MODE_ADHOC);
52                 if (bss != NULL) {
53                         lbs_deb_assoc("SSID found joining\n");
54                         libertas_join_adhoc_network(priv, bss);
55                 } else {
56                         /* else send START command */
57                         lbs_deb_assoc("SSID not found in list, so creating adhoc"
58                                 " with SSID '%s'\n", assoc_req->ssid.ssid);
59                         libertas_start_adhoc_network(priv, &assoc_req->ssid);
60                 }
61                 memcpy(&assoc_req->bssid, &adapter->current_addr, ETH_ALEN);
62         }
63
64         lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret);
65         return ret;
66 }
67
68
69 static int assoc_helper_bssid(wlan_private *priv,
70                               struct assoc_request * assoc_req)
71 {
72         wlan_adapter *adapter = priv->adapter;
73         int ret = 0;
74         struct bss_descriptor * bss;
75
76         lbs_deb_enter_args(LBS_DEB_ASSOC, "BSSID" MAC_FMT "\n",
77                 MAC_ARG(assoc_req->bssid));
78
79         /* Search for index position in list for requested MAC */
80         bss = libertas_find_BSSID_in_list(adapter, assoc_req->bssid,
81                             assoc_req->mode);
82         if (bss == NULL) {
83                 lbs_deb_assoc("ASSOC: WAP: BSSID " MAC_FMT " not found, "
84                         "cannot associate.\n", MAC_ARG(assoc_req->bssid));
85                 goto out;
86         }
87
88         if (assoc_req->mode == IW_MODE_INFRA) {
89                 ret = wlan_associate(priv, bss);
90                 lbs_deb_assoc("ASSOC: wlan_associate(bssid) returned %d\n", ret);
91         } else if (assoc_req->mode == IW_MODE_ADHOC) {
92                 libertas_join_adhoc_network(priv, bss);
93         }
94         memcpy(&assoc_req->ssid, &bss->ssid, sizeof(struct WLAN_802_11_SSID));
95
96 out:
97         lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret);
98         return ret;
99 }
100
101
102 static int assoc_helper_associate(wlan_private *priv,
103                                   struct assoc_request * assoc_req)
104 {
105         int ret = 0, done = 0;
106
107         /* If we're given and 'any' BSSID, try associating based on SSID */
108
109         if (test_bit(ASSOC_FLAG_BSSID, &assoc_req->flags)) {
110                 if (memcmp(bssid_any, assoc_req->bssid, ETH_ALEN)
111                     && memcmp(bssid_off, assoc_req->bssid, ETH_ALEN)) {
112                         ret = assoc_helper_bssid(priv, assoc_req);
113                         done = 1;
114                         if (ret) {
115                                 lbs_deb_assoc("ASSOC: bssid: ret = %d\n", ret);
116                         }
117                 }
118         }
119
120         if (!done && test_bit(ASSOC_FLAG_SSID, &assoc_req->flags)) {
121                 ret = assoc_helper_essid(priv, assoc_req);
122                 if (ret) {
123                         lbs_deb_assoc("ASSOC: bssid: ret = %d\n", ret);
124                 }
125         }
126
127         return ret;
128 }
129
130
131 static int assoc_helper_mode(wlan_private *priv,
132                              struct assoc_request * assoc_req)
133 {
134         wlan_adapter *adapter = priv->adapter;
135         int ret = 0;
136
137         lbs_deb_enter(LBS_DEB_ASSOC);
138
139         if (assoc_req->mode == adapter->mode)
140                 goto done;
141
142         if (assoc_req->mode == IW_MODE_INFRA) {
143                 if (adapter->psstate != PS_STATE_FULL_POWER)
144                         libertas_ps_wakeup(priv, cmd_option_waitforrsp);
145                 adapter->psmode = wlan802_11powermodecam;
146         }
147
148         adapter->mode = assoc_req->mode;
149         ret = libertas_prepare_and_send_command(priv,
150                                     cmd_802_11_snmp_mib,
151                                     0, cmd_option_waitforrsp,
152                                     OID_802_11_INFRASTRUCTURE_MODE,
153                                     (void *) (size_t) assoc_req->mode);
154
155 done:
156         lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret);
157         return ret;
158 }
159
160
161 static int assoc_helper_wep_keys(wlan_private *priv,
162                                  struct assoc_request * assoc_req)
163 {
164         wlan_adapter *adapter = priv->adapter;
165         int i;
166         int ret = 0;
167
168         lbs_deb_enter(LBS_DEB_ASSOC);
169
170         /* Set or remove WEP keys */
171         if (   assoc_req->wep_keys[0].len
172             || assoc_req->wep_keys[1].len
173             || assoc_req->wep_keys[2].len
174             || assoc_req->wep_keys[3].len) {
175                 ret = libertas_prepare_and_send_command(priv,
176                                             cmd_802_11_set_wep,
177                                             cmd_act_add,
178                                             cmd_option_waitforrsp,
179                                             0, assoc_req);
180         } else {
181                 ret = libertas_prepare_and_send_command(priv,
182                                             cmd_802_11_set_wep,
183                                             cmd_act_remove,
184                                             cmd_option_waitforrsp,
185                                             0, NULL);
186         }
187
188         if (ret)
189                 goto out;
190
191         /* enable/disable the MAC's WEP packet filter */
192         if (assoc_req->secinfo.wep_enabled)
193                 adapter->currentpacketfilter |= cmd_act_mac_wep_enable;
194         else
195                 adapter->currentpacketfilter &= ~cmd_act_mac_wep_enable;
196         ret = libertas_set_mac_packet_filter(priv);
197         if (ret)
198                 goto out;
199
200         mutex_lock(&adapter->lock);
201
202         /* Copy WEP keys into adapter wep key fields */
203         for (i = 0; i < 4; i++) {
204                 memcpy(&adapter->wep_keys[i], &assoc_req->wep_keys[i],
205                         sizeof(struct WLAN_802_11_KEY));
206         }
207         adapter->wep_tx_keyidx = assoc_req->wep_tx_keyidx;
208
209         mutex_unlock(&adapter->lock);
210
211 out:
212         lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret);
213         return ret;
214 }
215
216 static int assoc_helper_secinfo(wlan_private *priv,
217                                 struct assoc_request * assoc_req)
218 {
219         wlan_adapter *adapter = priv->adapter;
220         int ret = 0;
221
222         lbs_deb_enter(LBS_DEB_ASSOC);
223
224         memcpy(&adapter->secinfo, &assoc_req->secinfo,
225                 sizeof(struct wlan_802_11_security));
226
227         ret = libertas_set_mac_packet_filter(priv);
228
229         lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret);
230         return ret;
231 }
232
233
234 static int assoc_helper_wpa_keys(wlan_private *priv,
235                                  struct assoc_request * assoc_req)
236 {
237         int ret = 0;
238
239         lbs_deb_enter(LBS_DEB_ASSOC);
240
241         /* enable/Disable RSN */
242         ret = libertas_prepare_and_send_command(priv,
243                                     cmd_802_11_enable_rsn,
244                                     cmd_act_set,
245                                     cmd_option_waitforrsp,
246                                     0, assoc_req);
247         if (ret)
248                 goto out;
249
250         ret = libertas_prepare_and_send_command(priv,
251                                     cmd_802_11_key_material,
252                                     cmd_act_set,
253                                     cmd_option_waitforrsp,
254                                     0, assoc_req);
255
256 out:
257         lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret);
258         return ret;
259 }
260
261
262 static int assoc_helper_wpa_ie(wlan_private *priv,
263                                struct assoc_request * assoc_req)
264 {
265         wlan_adapter *adapter = priv->adapter;
266         int ret = 0;
267
268         lbs_deb_enter(LBS_DEB_ASSOC);
269
270         if (assoc_req->secinfo.WPAenabled || assoc_req->secinfo.WPA2enabled) {
271                 memcpy(&adapter->wpa_ie, &assoc_req->wpa_ie, assoc_req->wpa_ie_len);
272                 adapter->wpa_ie_len = assoc_req->wpa_ie_len;
273         } else {
274                 memset(&adapter->wpa_ie, 0, MAX_WPA_IE_LEN);
275                 adapter->wpa_ie_len = 0;
276         }
277
278         lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret);
279         return ret;
280 }
281
282
283 static int should_deauth_infrastructure(wlan_adapter *adapter,
284                                         struct assoc_request * assoc_req)
285 {
286         if (adapter->connect_status != libertas_connected)
287                 return 0;
288
289         if (test_bit(ASSOC_FLAG_SSID, &assoc_req->flags)) {
290                 lbs_deb_assoc("Deauthenticating due to new SSID in "
291                         " configuration request.\n");
292                 return 1;
293         }
294
295         if (test_bit(ASSOC_FLAG_SECINFO, &assoc_req->flags)) {
296                 if (adapter->secinfo.auth_mode != assoc_req->secinfo.auth_mode) {
297                         lbs_deb_assoc("Deauthenticating due to updated security "
298                                 "info in configuration request.\n");
299                         return 1;
300                 }
301         }
302
303         if (test_bit(ASSOC_FLAG_BSSID, &assoc_req->flags)) {
304                 lbs_deb_assoc("Deauthenticating due to new BSSID in "
305                         " configuration request.\n");
306                 return 1;
307         }
308
309         /* FIXME: deal with 'auto' mode somehow */
310         if (test_bit(ASSOC_FLAG_MODE, &assoc_req->flags)) {
311                 if (assoc_req->mode != IW_MODE_INFRA)
312                         return 1;
313         }
314
315         return 0;
316 }
317
318
319 static int should_stop_adhoc(wlan_adapter *adapter,
320                              struct assoc_request * assoc_req)
321 {
322         if (adapter->connect_status != libertas_connected)
323                 return 0;
324
325         if (adapter->curbssparams.ssid.ssidlength != assoc_req->ssid.ssidlength)
326                 return 1;
327         if (memcmp(adapter->curbssparams.ssid.ssid, assoc_req->ssid.ssid,
328                         adapter->curbssparams.ssid.ssidlength))
329                 return 1;
330
331         /* FIXME: deal with 'auto' mode somehow */
332         if (test_bit(ASSOC_FLAG_MODE, &assoc_req->flags)) {
333                 if (assoc_req->mode != IW_MODE_ADHOC)
334                         return 1;
335         }
336
337         return 0;
338 }
339
340
341 void libertas_association_worker(struct work_struct *work)
342 {
343         wlan_private *priv = container_of(work, wlan_private, assoc_work.work);
344         wlan_adapter *adapter = priv->adapter;
345         struct assoc_request * assoc_req = NULL;
346         int ret = 0;
347         int find_any_ssid = 0;
348
349         lbs_deb_enter(LBS_DEB_ASSOC);
350
351         mutex_lock(&adapter->lock);
352         assoc_req = adapter->assoc_req;
353         adapter->assoc_req = NULL;
354         mutex_unlock(&adapter->lock);
355
356         if (!assoc_req)
357                 goto done;
358
359         lbs_deb_assoc("ASSOC: starting new association request: flags = 0x%lX\n",
360                 assoc_req->flags);
361
362         /* If 'any' SSID was specified, find an SSID to associate with */
363         if (test_bit(ASSOC_FLAG_SSID, &assoc_req->flags)
364             && !assoc_req->ssid.ssidlength)
365                 find_any_ssid = 1;
366
367         /* But don't use 'any' SSID if there's a valid locked BSSID to use */
368         if (test_bit(ASSOC_FLAG_BSSID, &assoc_req->flags)) {
369                 if (memcmp(&assoc_req->bssid, bssid_any, ETH_ALEN)
370                     && memcmp(&assoc_req->bssid, bssid_off, ETH_ALEN))
371                         find_any_ssid = 0;
372         }
373
374         if (find_any_ssid) {
375                 u8 new_mode;
376
377                 ret = libertas_find_best_network_SSID(priv, &assoc_req->ssid,
378                                 assoc_req->mode, &new_mode);
379                 if (ret) {
380                         lbs_deb_assoc("Could not find best network\n");
381                         ret = -ENETUNREACH;
382                         goto out;
383                 }
384
385                 /* Ensure we switch to the mode of the AP */
386                 if (assoc_req->mode == IW_MODE_AUTO) {
387                         set_bit(ASSOC_FLAG_MODE, &assoc_req->flags);
388                         assoc_req->mode = new_mode;
389                 }
390         }
391
392         /*
393          * Check if the attributes being changing require deauthentication
394          * from the currently associated infrastructure access point.
395          */
396         if (adapter->mode == IW_MODE_INFRA) {
397                 if (should_deauth_infrastructure(adapter, assoc_req)) {
398                         ret = libertas_send_deauthentication(priv);
399                         if (ret) {
400                                 lbs_deb_assoc("Deauthentication due to new "
401                                         "configuration request failed: %d\n",
402                                         ret);
403                         }
404                 }
405         } else if (adapter->mode == IW_MODE_ADHOC) {
406                 if (should_stop_adhoc(adapter, assoc_req)) {
407                         ret = libertas_stop_adhoc_network(priv);
408                         if (ret) {
409                                 lbs_deb_assoc("Teardown of AdHoc network due to "
410                                         "new configuration request failed: %d\n",
411                                         ret);
412                         }
413
414                 }
415         }
416
417         /* Send the various configuration bits to the firmware */
418         if (test_bit(ASSOC_FLAG_MODE, &assoc_req->flags)) {
419                 ret = assoc_helper_mode(priv, assoc_req);
420                 if (ret) {
421 lbs_deb_assoc("ASSOC(:%d) mode: ret = %d\n", __LINE__, ret);
422                         goto out;
423                 }
424         }
425
426         if (   test_bit(ASSOC_FLAG_WEP_KEYS, &assoc_req->flags)
427             || test_bit(ASSOC_FLAG_WEP_TX_KEYIDX, &assoc_req->flags)) {
428                 ret = assoc_helper_wep_keys(priv, assoc_req);
429                 if (ret) {
430 lbs_deb_assoc("ASSOC(:%d) wep_keys: ret = %d\n", __LINE__, ret);
431                         goto out;
432                 }
433         }
434
435         if (test_bit(ASSOC_FLAG_SECINFO, &assoc_req->flags)) {
436                 ret = assoc_helper_secinfo(priv, assoc_req);
437                 if (ret) {
438 lbs_deb_assoc("ASSOC(:%d) secinfo: ret = %d\n", __LINE__, ret);
439                         goto out;
440                 }
441         }
442
443         if (test_bit(ASSOC_FLAG_WPA_IE, &assoc_req->flags)) {
444                 ret = assoc_helper_wpa_ie(priv, assoc_req);
445                 if (ret) {
446 lbs_deb_assoc("ASSOC(:%d) wpa_ie: ret = %d\n", __LINE__, ret);
447                         goto out;
448                 }
449         }
450
451         if (test_bit(ASSOC_FLAG_WPA_MCAST_KEY, &assoc_req->flags)
452             || test_bit(ASSOC_FLAG_WPA_UCAST_KEY, &assoc_req->flags)) {
453                 ret = assoc_helper_wpa_keys(priv, assoc_req);
454                 if (ret) {
455 lbs_deb_assoc("ASSOC(:%d) wpa_keys: ret = %d\n", __LINE__, ret);
456                         goto out;
457                 }
458         }
459
460         /* SSID/BSSID should be the _last_ config option set, because they
461          * trigger the association attempt.
462          */
463         if (test_bit(ASSOC_FLAG_BSSID, &assoc_req->flags)
464             || test_bit(ASSOC_FLAG_SSID, &assoc_req->flags)) {
465                 int success = 1;
466
467                 ret = assoc_helper_associate(priv, assoc_req);
468                 if (ret) {
469                         lbs_deb_assoc("ASSOC: association attempt unsuccessful: %d\n",
470                                 ret);
471                         success = 0;
472                 }
473
474                 if (adapter->connect_status != libertas_connected) {
475                         lbs_deb_assoc("ASSOC: assoication attempt unsuccessful, "
476                                 "not connected.\n");
477                         success = 0;
478                 }
479
480                 if (success) {
481                         lbs_deb_assoc("ASSOC: association attempt successful. "
482                                 "Associated to '%s' (" MAC_FMT ")\n",
483                                 assoc_req->ssid.ssid, MAC_ARG(assoc_req->bssid));
484                         libertas_prepare_and_send_command(priv,
485                                 cmd_802_11_rssi,
486                                 0, cmd_option_waitforrsp, 0, NULL);
487
488                         libertas_prepare_and_send_command(priv,
489                                 cmd_802_11_get_log,
490                                 0, cmd_option_waitforrsp, 0, NULL);
491                 } else {
492
493                         ret = -1;
494                 }
495         }
496
497 out:
498         if (ret) {
499                 lbs_deb_assoc("ASSOC: reconfiguration attempt unsuccessful: %d\n",
500                         ret);
501         }
502         kfree(assoc_req);
503
504 done:
505         lbs_deb_leave(LBS_DEB_ASSOC);
506 }
507
508
509 /*
510  * Caller MUST hold any necessary locks
511  */
512 struct assoc_request * wlan_get_association_request(wlan_adapter *adapter)
513 {
514         struct assoc_request * assoc_req;
515
516         if (!adapter->assoc_req) {
517                 adapter->assoc_req = kzalloc(sizeof(struct assoc_request), GFP_KERNEL);
518                 if (!adapter->assoc_req) {
519                         lbs_pr_info("Not enough memory to allocate association"
520                                 " request!\n");
521                         return NULL;
522                 }
523         }
524
525         /* Copy current configuration attributes to the association request,
526          * but don't overwrite any that are already set.
527          */
528         assoc_req = adapter->assoc_req;
529         if (!test_bit(ASSOC_FLAG_SSID, &assoc_req->flags)) {
530                 memcpy(&assoc_req->ssid, adapter->curbssparams.ssid.ssid,
531                         adapter->curbssparams.ssid.ssidlength);
532         }
533
534         if (!test_bit(ASSOC_FLAG_CHANNEL, &assoc_req->flags))
535                 assoc_req->channel = adapter->curbssparams.channel;
536
537         if (!test_bit(ASSOC_FLAG_MODE, &assoc_req->flags))
538                 assoc_req->mode = adapter->mode;
539
540         if (!test_bit(ASSOC_FLAG_BSSID, &assoc_req->flags)) {
541                 memcpy(&assoc_req->bssid, adapter->curbssparams.bssid,
542                         ETH_ALEN);
543         }
544
545         if (!test_bit(ASSOC_FLAG_WEP_KEYS, &assoc_req->flags)) {
546                 int i;
547                 for (i = 0; i < 4; i++) {
548                         memcpy(&assoc_req->wep_keys[i], &adapter->wep_keys[i],
549                                 sizeof(struct WLAN_802_11_KEY));
550                 }
551         }
552
553         if (!test_bit(ASSOC_FLAG_WEP_TX_KEYIDX, &assoc_req->flags))
554                 assoc_req->wep_tx_keyidx = adapter->wep_tx_keyidx;
555
556         if (!test_bit(ASSOC_FLAG_WPA_MCAST_KEY, &assoc_req->flags)) {
557                 memcpy(&assoc_req->wpa_mcast_key, &adapter->wpa_mcast_key,
558                         sizeof(struct WLAN_802_11_KEY));
559         }
560
561         if (!test_bit(ASSOC_FLAG_WPA_UCAST_KEY, &assoc_req->flags)) {
562                 memcpy(&assoc_req->wpa_unicast_key, &adapter->wpa_unicast_key,
563                         sizeof(struct WLAN_802_11_KEY));
564         }
565
566         if (!test_bit(ASSOC_FLAG_SECINFO, &assoc_req->flags)) {
567                 memcpy(&assoc_req->secinfo, &adapter->secinfo,
568                         sizeof(struct wlan_802_11_security));
569         }
570
571         if (!test_bit(ASSOC_FLAG_WPA_IE, &assoc_req->flags)) {
572                 memcpy(&assoc_req->wpa_ie, &adapter->wpa_ie,
573                         MAX_WPA_IE_LEN);
574                 assoc_req->wpa_ie_len = adapter->wpa_ie_len;
575         }
576
577         return assoc_req;
578 }
579
580