Commit | Line | Data |
---|---|---|
bb9f8692 ZY |
1 | /* |
2 | * Intel Wireless Multicomm 3200 WiFi driver | |
3 | * | |
4 | * Copyright (C) 2009 Intel Corporation <ilw@linux.intel.com> | |
5 | * Samuel Ortiz <samuel.ortiz@intel.com> | |
6 | * Zhu Yi <yi.zhu@intel.com> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU General Public License version | |
10 | * 2 as published by the Free Software Foundation. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
20 | * 02110-1301, USA. | |
21 | * | |
22 | */ | |
23 | ||
24 | #include <linux/kernel.h> | |
25 | #include <linux/netdevice.h> | |
26 | #include <linux/wireless.h> | |
27 | #include <linux/if_arp.h> | |
28 | #include <linux/etherdevice.h> | |
29 | #include <net/cfg80211.h> | |
30 | #include <net/iw_handler.h> | |
31 | ||
32 | #include "iwm.h" | |
33 | #include "umac.h" | |
34 | #include "commands.h" | |
35 | #include "debug.h" | |
36 | ||
37 | static struct iw_statistics *iwm_get_wireless_stats(struct net_device *dev) | |
38 | { | |
39 | struct iwm_priv *iwm = ndev_to_iwm(dev); | |
40 | struct iw_statistics *wstats = &iwm->wstats; | |
41 | ||
42 | if (!test_bit(IWM_STATUS_ASSOCIATED, &iwm->status)) { | |
43 | memset(wstats, 0, sizeof(struct iw_statistics)); | |
44 | wstats->qual.updated = IW_QUAL_ALL_INVALID; | |
45 | } | |
46 | ||
47 | return wstats; | |
48 | } | |
49 | ||
50 | static int iwm_wext_siwfreq(struct net_device *dev, | |
51 | struct iw_request_info *info, | |
52 | struct iw_freq *freq, char *extra) | |
53 | { | |
54 | struct iwm_priv *iwm = ndev_to_iwm(dev); | |
55 | ||
56 | if (freq->flags == IW_FREQ_AUTO) | |
57 | return 0; | |
58 | ||
59 | /* frequency/channel can only be set in IBSS mode */ | |
60 | if (iwm->conf.mode != UMAC_MODE_IBSS) | |
61 | return -EOPNOTSUPP; | |
62 | ||
63 | return cfg80211_ibss_wext_siwfreq(dev, info, freq, extra); | |
64 | } | |
65 | ||
66 | static int iwm_wext_giwfreq(struct net_device *dev, | |
67 | struct iw_request_info *info, | |
68 | struct iw_freq *freq, char *extra) | |
69 | { | |
70 | struct iwm_priv *iwm = ndev_to_iwm(dev); | |
71 | ||
72 | if (iwm->conf.mode == UMAC_MODE_IBSS) | |
73 | return cfg80211_ibss_wext_giwfreq(dev, info, freq, extra); | |
74 | ||
75 | freq->e = 0; | |
76 | freq->m = iwm->channel; | |
77 | ||
78 | return 0; | |
79 | } | |
80 | ||
81 | static int iwm_wext_siwap(struct net_device *dev, struct iw_request_info *info, | |
82 | struct sockaddr *ap_addr, char *extra) | |
83 | { | |
84 | struct iwm_priv *iwm = ndev_to_iwm(dev); | |
0c5553b1 | 85 | int ret; |
bb9f8692 | 86 | |
13e0fe70 SO |
87 | IWM_DBG_WEXT(iwm, DBG, "Set BSSID: %pM\n", ap_addr->sa_data); |
88 | ||
bb9f8692 ZY |
89 | if (iwm->conf.mode == UMAC_MODE_IBSS) |
90 | return cfg80211_ibss_wext_siwap(dev, info, ap_addr, extra); | |
91 | ||
92 | if (!test_bit(IWM_STATUS_READY, &iwm->status)) | |
93 | return -EIO; | |
94 | ||
95 | if (is_zero_ether_addr(ap_addr->sa_data) || | |
96 | is_broadcast_ether_addr(ap_addr->sa_data)) { | |
97 | IWM_DBG_WEXT(iwm, DBG, "clear mandatory bssid %pM\n", | |
98 | iwm->umac_profile->bssid[0]); | |
99 | memset(&iwm->umac_profile->bssid[0], 0, ETH_ALEN); | |
100 | iwm->umac_profile->bss_num = 0; | |
101 | } else { | |
102 | IWM_DBG_WEXT(iwm, DBG, "add mandatory bssid %pM\n", | |
103 | ap_addr->sa_data); | |
104 | memcpy(&iwm->umac_profile->bssid[0], ap_addr->sa_data, | |
105 | ETH_ALEN); | |
106 | iwm->umac_profile->bss_num = 1; | |
107 | } | |
108 | ||
109 | if (iwm->umac_profile_active) { | |
0c5553b1 SO |
110 | int i; |
111 | ||
bb9f8692 ZY |
112 | if (!memcmp(&iwm->umac_profile->bssid[0], iwm->bssid, ETH_ALEN)) |
113 | return 0; | |
114 | ||
0c5553b1 SO |
115 | /* |
116 | * If we're clearing the BSSID, and we're associated, | |
117 | * we have to clear the keys as they're no longer valid. | |
118 | */ | |
119 | if (is_zero_ether_addr(ap_addr->sa_data)) { | |
120 | for (i = 0; i < IWM_NUM_KEYS; i++) | |
13e0fe70 | 121 | iwm->keys[i].key_len = 0; |
0c5553b1 SO |
122 | } |
123 | ||
124 | ret = iwm_invalidate_mlme_profile(iwm); | |
125 | if (ret < 0) { | |
126 | IWM_ERR(iwm, "Couldn't invalidate profile\n"); | |
127 | return ret; | |
128 | } | |
bb9f8692 ZY |
129 | } |
130 | ||
131 | if (iwm->umac_profile->ssid.ssid_len) | |
132 | return iwm_send_mlme_profile(iwm); | |
133 | ||
134 | return 0; | |
135 | } | |
136 | ||
137 | static int iwm_wext_giwap(struct net_device *dev, struct iw_request_info *info, | |
138 | struct sockaddr *ap_addr, char *extra) | |
139 | { | |
140 | struct iwm_priv *iwm = ndev_to_iwm(dev); | |
141 | ||
142 | switch (iwm->conf.mode) { | |
143 | case UMAC_MODE_IBSS: | |
144 | return cfg80211_ibss_wext_giwap(dev, info, ap_addr, extra); | |
145 | case UMAC_MODE_BSS: | |
146 | if (test_bit(IWM_STATUS_ASSOCIATED, &iwm->status)) { | |
147 | ap_addr->sa_family = ARPHRD_ETHER; | |
148 | memcpy(&ap_addr->sa_data, iwm->bssid, ETH_ALEN); | |
149 | } else | |
150 | memset(&ap_addr->sa_data, 0, ETH_ALEN); | |
151 | break; | |
152 | default: | |
153 | return -EOPNOTSUPP; | |
154 | } | |
155 | ||
156 | return 0; | |
157 | } | |
158 | ||
159 | static int iwm_wext_siwessid(struct net_device *dev, | |
160 | struct iw_request_info *info, | |
161 | struct iw_point *data, char *ssid) | |
162 | { | |
163 | struct iwm_priv *iwm = ndev_to_iwm(dev); | |
164 | size_t len = data->length; | |
165 | int ret; | |
166 | ||
13e0fe70 SO |
167 | IWM_DBG_WEXT(iwm, DBG, "Set ESSID: >%s<\n", ssid); |
168 | ||
bb9f8692 ZY |
169 | if (iwm->conf.mode == UMAC_MODE_IBSS) |
170 | return cfg80211_ibss_wext_siwessid(dev, info, data, ssid); | |
171 | ||
172 | if (!test_bit(IWM_STATUS_READY, &iwm->status)) | |
173 | return -EIO; | |
174 | ||
175 | if (len > 0 && ssid[len - 1] == '\0') | |
176 | len--; | |
177 | ||
178 | if (iwm->umac_profile_active) { | |
179 | if (iwm->umac_profile->ssid.ssid_len == len && | |
180 | !memcmp(iwm->umac_profile->ssid.ssid, ssid, len)) | |
181 | return 0; | |
182 | ||
183 | ret = iwm_invalidate_mlme_profile(iwm); | |
184 | if (ret < 0) { | |
185 | IWM_ERR(iwm, "Couldn't invalidate profile\n"); | |
186 | return ret; | |
187 | } | |
188 | } | |
189 | ||
190 | iwm->umac_profile->ssid.ssid_len = len; | |
191 | memcpy(iwm->umac_profile->ssid.ssid, ssid, len); | |
192 | ||
193 | return iwm_send_mlme_profile(iwm); | |
194 | } | |
195 | ||
196 | static int iwm_wext_giwessid(struct net_device *dev, | |
197 | struct iw_request_info *info, | |
198 | struct iw_point *data, char *ssid) | |
199 | { | |
200 | struct iwm_priv *iwm = ndev_to_iwm(dev); | |
201 | ||
202 | if (iwm->conf.mode == UMAC_MODE_IBSS) | |
203 | return cfg80211_ibss_wext_giwessid(dev, info, data, ssid); | |
204 | ||
205 | if (!test_bit(IWM_STATUS_READY, &iwm->status)) | |
206 | return -EIO; | |
207 | ||
208 | data->length = iwm->umac_profile->ssid.ssid_len; | |
209 | if (data->length) { | |
210 | memcpy(ssid, iwm->umac_profile->ssid.ssid, data->length); | |
211 | data->flags = 1; | |
212 | } else | |
213 | data->flags = 0; | |
214 | ||
215 | return 0; | |
216 | } | |
217 | ||
bb9f8692 ZY |
218 | static int iwm_wext_giwrate(struct net_device *dev, |
219 | struct iw_request_info *info, | |
220 | struct iw_param *rate, char *extra) | |
221 | { | |
222 | struct iwm_priv *iwm = ndev_to_iwm(dev); | |
223 | ||
224 | rate->value = iwm->rate * 1000000; | |
225 | ||
226 | return 0; | |
227 | } | |
228 | ||
bb9f8692 ZY |
229 | static int iwm_set_wpa_version(struct iwm_priv *iwm, u8 wpa_version) |
230 | { | |
231 | if (wpa_version & IW_AUTH_WPA_VERSION_WPA2) | |
232 | iwm->umac_profile->sec.flags = UMAC_SEC_FLG_RSNA_ON_MSK; | |
233 | else if (wpa_version & IW_AUTH_WPA_VERSION_WPA) | |
234 | iwm->umac_profile->sec.flags = UMAC_SEC_FLG_WPA_ON_MSK; | |
235 | else | |
236 | iwm->umac_profile->sec.flags = UMAC_SEC_FLG_LEGACY_PROFILE; | |
237 | ||
238 | return 0; | |
239 | } | |
240 | ||
bb9f8692 ZY |
241 | static int iwm_set_key_mgt(struct iwm_priv *iwm, u8 key_mgt) |
242 | { | |
243 | u8 *auth_type = &iwm->umac_profile->sec.auth_type; | |
244 | ||
13e0fe70 SO |
245 | IWM_DBG_WEXT(iwm, DBG, "key_mgt: 0x%x\n", key_mgt); |
246 | ||
bb9f8692 ZY |
247 | if (key_mgt == IW_AUTH_KEY_MGMT_802_1X) |
248 | *auth_type = UMAC_AUTH_TYPE_8021X; | |
249 | else if (key_mgt == IW_AUTH_KEY_MGMT_PSK) { | |
250 | if (iwm->umac_profile->sec.flags & | |
251 | (UMAC_SEC_FLG_WPA_ON_MSK | UMAC_SEC_FLG_RSNA_ON_MSK)) | |
252 | *auth_type = UMAC_AUTH_TYPE_RSNA_PSK; | |
253 | else | |
254 | *auth_type = UMAC_AUTH_TYPE_LEGACY_PSK; | |
255 | } else { | |
256 | IWM_ERR(iwm, "Invalid key mgt: 0x%x\n", key_mgt); | |
257 | return -EINVAL; | |
258 | } | |
259 | ||
260 | return 0; | |
261 | } | |
262 | ||
263 | static int iwm_set_cipher(struct iwm_priv *iwm, u8 cipher, u8 ucast) | |
264 | { | |
265 | u8 *profile_cipher = ucast ? &iwm->umac_profile->sec.ucast_cipher : | |
266 | &iwm->umac_profile->sec.mcast_cipher; | |
267 | ||
268 | switch (cipher) { | |
269 | case IW_AUTH_CIPHER_NONE: | |
270 | *profile_cipher = UMAC_CIPHER_TYPE_NONE; | |
271 | break; | |
272 | case IW_AUTH_CIPHER_WEP40: | |
273 | *profile_cipher = UMAC_CIPHER_TYPE_WEP_40; | |
274 | break; | |
275 | case IW_AUTH_CIPHER_TKIP: | |
276 | *profile_cipher = UMAC_CIPHER_TYPE_TKIP; | |
277 | break; | |
278 | case IW_AUTH_CIPHER_CCMP: | |
279 | *profile_cipher = UMAC_CIPHER_TYPE_CCMP; | |
280 | break; | |
281 | case IW_AUTH_CIPHER_WEP104: | |
282 | *profile_cipher = UMAC_CIPHER_TYPE_WEP_104; | |
283 | break; | |
284 | default: | |
285 | IWM_ERR(iwm, "Unsupported cipher: 0x%x\n", cipher); | |
286 | return -ENOTSUPP; | |
287 | } | |
288 | ||
289 | return 0; | |
290 | } | |
291 | ||
292 | static int iwm_set_auth_alg(struct iwm_priv *iwm, u8 auth_alg) | |
293 | { | |
294 | u8 *auth_type = &iwm->umac_profile->sec.auth_type; | |
295 | ||
13e0fe70 SO |
296 | IWM_DBG_WEXT(iwm, DBG, "auth_alg: 0x%x\n", auth_alg); |
297 | ||
bb9f8692 ZY |
298 | switch (auth_alg) { |
299 | case IW_AUTH_ALG_OPEN_SYSTEM: | |
300 | *auth_type = UMAC_AUTH_TYPE_OPEN; | |
301 | break; | |
302 | case IW_AUTH_ALG_SHARED_KEY: | |
303 | if (iwm->umac_profile->sec.flags & | |
304 | (UMAC_SEC_FLG_WPA_ON_MSK | UMAC_SEC_FLG_RSNA_ON_MSK)) { | |
305 | if (*auth_type == UMAC_AUTH_TYPE_8021X) | |
306 | return -EINVAL; | |
307 | *auth_type = UMAC_AUTH_TYPE_RSNA_PSK; | |
308 | } else { | |
13e0fe70 | 309 | IWM_DBG_WEXT(iwm, DBG, "WEP shared key\n"); |
bb9f8692 ZY |
310 | *auth_type = UMAC_AUTH_TYPE_LEGACY_PSK; |
311 | } | |
312 | break; | |
313 | case IW_AUTH_ALG_LEAP: | |
314 | default: | |
315 | IWM_ERR(iwm, "Unsupported auth alg: 0x%x\n", auth_alg); | |
316 | return -ENOTSUPP; | |
317 | } | |
318 | ||
319 | return 0; | |
320 | } | |
321 | ||
322 | static int iwm_wext_siwauth(struct net_device *dev, | |
323 | struct iw_request_info *info, | |
324 | struct iw_param *data, char *extra) | |
325 | { | |
326 | struct iwm_priv *iwm = ndev_to_iwm(dev); | |
327 | int ret; | |
328 | ||
329 | if ((data->flags) & | |
330 | (IW_AUTH_WPA_VERSION | IW_AUTH_KEY_MGMT | | |
331 | IW_AUTH_WPA_ENABLED | IW_AUTH_80211_AUTH_ALG)) { | |
332 | /* We need to invalidate the current profile */ | |
333 | if (iwm->umac_profile_active) { | |
334 | ret = iwm_invalidate_mlme_profile(iwm); | |
335 | if (ret < 0) { | |
336 | IWM_ERR(iwm, "Couldn't invalidate profile\n"); | |
337 | return ret; | |
338 | } | |
339 | } | |
340 | } | |
341 | ||
342 | switch (data->flags & IW_AUTH_INDEX) { | |
343 | case IW_AUTH_WPA_VERSION: | |
344 | return iwm_set_wpa_version(iwm, data->value); | |
345 | break; | |
346 | case IW_AUTH_CIPHER_PAIRWISE: | |
347 | return iwm_set_cipher(iwm, data->value, 1); | |
348 | break; | |
349 | case IW_AUTH_CIPHER_GROUP: | |
350 | return iwm_set_cipher(iwm, data->value, 0); | |
351 | break; | |
352 | case IW_AUTH_KEY_MGMT: | |
353 | return iwm_set_key_mgt(iwm, data->value); | |
354 | break; | |
355 | case IW_AUTH_80211_AUTH_ALG: | |
356 | return iwm_set_auth_alg(iwm, data->value); | |
357 | break; | |
358 | default: | |
359 | return -ENOTSUPP; | |
360 | } | |
361 | ||
362 | return 0; | |
363 | } | |
364 | ||
365 | static int iwm_wext_giwauth(struct net_device *dev, | |
366 | struct iw_request_info *info, | |
367 | struct iw_param *data, char *extra) | |
368 | { | |
369 | return 0; | |
370 | } | |
371 | ||
bb9f8692 ZY |
372 | static const iw_handler iwm_handlers[] = |
373 | { | |
374 | (iw_handler) NULL, /* SIOCSIWCOMMIT */ | |
375 | (iw_handler) cfg80211_wext_giwname, /* SIOCGIWNAME */ | |
376 | (iw_handler) NULL, /* SIOCSIWNWID */ | |
377 | (iw_handler) NULL, /* SIOCGIWNWID */ | |
378 | (iw_handler) iwm_wext_siwfreq, /* SIOCSIWFREQ */ | |
379 | (iw_handler) iwm_wext_giwfreq, /* SIOCGIWFREQ */ | |
380 | (iw_handler) cfg80211_wext_siwmode, /* SIOCSIWMODE */ | |
381 | (iw_handler) cfg80211_wext_giwmode, /* SIOCGIWMODE */ | |
382 | (iw_handler) NULL, /* SIOCSIWSENS */ | |
383 | (iw_handler) NULL, /* SIOCGIWSENS */ | |
384 | (iw_handler) NULL /* not used */, /* SIOCSIWRANGE */ | |
385 | (iw_handler) cfg80211_wext_giwrange, /* SIOCGIWRANGE */ | |
386 | (iw_handler) NULL /* not used */, /* SIOCSIWPRIV */ | |
387 | (iw_handler) NULL /* kernel code */, /* SIOCGIWPRIV */ | |
388 | (iw_handler) NULL /* not used */, /* SIOCSIWSTATS */ | |
389 | (iw_handler) NULL /* kernel code */, /* SIOCGIWSTATS */ | |
390 | (iw_handler) NULL, /* SIOCSIWSPY */ | |
391 | (iw_handler) NULL, /* SIOCGIWSPY */ | |
392 | (iw_handler) NULL, /* SIOCSIWTHRSPY */ | |
393 | (iw_handler) NULL, /* SIOCGIWTHRSPY */ | |
394 | (iw_handler) iwm_wext_siwap, /* SIOCSIWAP */ | |
395 | (iw_handler) iwm_wext_giwap, /* SIOCGIWAP */ | |
396 | (iw_handler) NULL, /* SIOCSIWMLME */ | |
397 | (iw_handler) NULL, /* SIOCGIWAPLIST */ | |
398 | (iw_handler) cfg80211_wext_siwscan, /* SIOCSIWSCAN */ | |
399 | (iw_handler) cfg80211_wext_giwscan, /* SIOCGIWSCAN */ | |
400 | (iw_handler) iwm_wext_siwessid, /* SIOCSIWESSID */ | |
401 | (iw_handler) iwm_wext_giwessid, /* SIOCGIWESSID */ | |
402 | (iw_handler) NULL, /* SIOCSIWNICKN */ | |
403 | (iw_handler) NULL, /* SIOCGIWNICKN */ | |
404 | (iw_handler) NULL, /* -- hole -- */ | |
405 | (iw_handler) NULL, /* -- hole -- */ | |
406 | (iw_handler) NULL, /* SIOCSIWRATE */ | |
407 | (iw_handler) iwm_wext_giwrate, /* SIOCGIWRATE */ | |
408 | (iw_handler) cfg80211_wext_siwrts, /* SIOCSIWRTS */ | |
409 | (iw_handler) cfg80211_wext_giwrts, /* SIOCGIWRTS */ | |
410 | (iw_handler) cfg80211_wext_siwfrag, /* SIOCSIWFRAG */ | |
411 | (iw_handler) cfg80211_wext_giwfrag, /* SIOCGIWFRAG */ | |
257862f3 ZY |
412 | (iw_handler) cfg80211_wext_siwtxpower, /* SIOCSIWTXPOW */ |
413 | (iw_handler) cfg80211_wext_giwtxpower, /* SIOCGIWTXPOW */ | |
bb9f8692 ZY |
414 | (iw_handler) NULL, /* SIOCSIWRETRY */ |
415 | (iw_handler) NULL, /* SIOCGIWRETRY */ | |
13e0fe70 SO |
416 | (iw_handler) cfg80211_wext_siwencode, /* SIOCSIWENCODE */ |
417 | (iw_handler) cfg80211_wext_giwencode, /* SIOCGIWENCODE */ | |
bc92afd9 JB |
418 | (iw_handler) cfg80211_wext_siwpower, /* SIOCSIWPOWER */ |
419 | (iw_handler) cfg80211_wext_giwpower, /* SIOCGIWPOWER */ | |
bb9f8692 ZY |
420 | (iw_handler) NULL, /* -- hole -- */ |
421 | (iw_handler) NULL, /* -- hole -- */ | |
422 | (iw_handler) NULL, /* SIOCSIWGENIE */ | |
423 | (iw_handler) NULL, /* SIOCGIWGENIE */ | |
424 | (iw_handler) iwm_wext_siwauth, /* SIOCSIWAUTH */ | |
425 | (iw_handler) iwm_wext_giwauth, /* SIOCGIWAUTH */ | |
13e0fe70 | 426 | (iw_handler) cfg80211_wext_siwencodeext, /* SIOCSIWENCODEEXT */ |
bb9f8692 ZY |
427 | (iw_handler) NULL, /* SIOCGIWENCODEEXT */ |
428 | (iw_handler) NULL, /* SIOCSIWPMKSA */ | |
429 | (iw_handler) NULL, /* -- hole -- */ | |
430 | }; | |
431 | ||
432 | const struct iw_handler_def iwm_iw_handler_def = { | |
433 | .num_standard = ARRAY_SIZE(iwm_handlers), | |
434 | .standard = (iw_handler *) iwm_handlers, | |
435 | .get_wireless_stats = iwm_get_wireless_stats, | |
436 | }; | |
437 |