Commit | Line | Data |
---|---|---|
6974e363 EG |
1 | /****************************************************************************** |
2 | * | |
3 | * Copyright(c) 2003 - 2008 Intel Corporation. All rights reserved. | |
4 | * | |
5 | * Portions of this file are derived from the ipw3945 project, as well | |
6 | * as portions of the ieee80211 subsystem header files. | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify it | |
9 | * under the terms of version 2 of the GNU General Public License as | |
10 | * published by the Free Software Foundation. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
14 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
15 | * more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License along with | |
18 | * this program; if not, write to the Free Software Foundation, Inc., | |
19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA | |
20 | * | |
21 | * The full GNU General Public License is included in this distribution in the | |
22 | * file called LICENSE. | |
23 | * | |
24 | * Contact Information: | |
25 | * James P. Ketrenos <ipw2100-admin@linux.intel.com> | |
26 | * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 | |
27 | * | |
28 | *****************************************************************************/ | |
29 | ||
30 | #include <net/mac80211.h> | |
947b13a7 | 31 | #include <linux/etherdevice.h> |
6974e363 EG |
32 | |
33 | #include "iwl-eeprom.h" | |
3e0d4cb1 | 34 | #include "iwl-dev.h" |
6974e363 EG |
35 | #include "iwl-core.h" |
36 | #include "iwl-sta.h" | |
37 | #include "iwl-io.h" | |
38 | #include "iwl-helpers.h" | |
80fb47a1 | 39 | |
947b13a7 TW |
40 | u8 iwl_find_station(struct iwl_priv *priv, const u8 *addr) |
41 | { | |
42 | int i; | |
43 | int start = 0; | |
44 | int ret = IWL_INVALID_STATION; | |
45 | unsigned long flags; | |
46 | DECLARE_MAC_BUF(mac); | |
47 | ||
48 | if ((priv->iw_mode == IEEE80211_IF_TYPE_IBSS) || | |
49 | (priv->iw_mode == IEEE80211_IF_TYPE_AP)) | |
50 | start = IWL_STA_ID; | |
51 | ||
52 | if (is_broadcast_ether_addr(addr)) | |
53 | return priv->hw_params.bcast_sta_id; | |
54 | ||
55 | spin_lock_irqsave(&priv->sta_lock, flags); | |
56 | for (i = start; i < priv->hw_params.max_stations; i++) | |
57 | if (priv->stations[i].used && | |
58 | (!compare_ether_addr(priv->stations[i].sta.sta.addr, | |
59 | addr))) { | |
60 | ret = i; | |
61 | goto out; | |
62 | } | |
63 | ||
64 | IWL_DEBUG_ASSOC_LIMIT("can not find STA %s total %d\n", | |
65 | print_mac(mac, addr), priv->num_stations); | |
66 | ||
67 | out: | |
68 | spin_unlock_irqrestore(&priv->sta_lock, flags); | |
69 | return ret; | |
70 | } | |
71 | EXPORT_SYMBOL(iwl_find_station); | |
72 | ||
133636de TW |
73 | int iwl_send_add_sta(struct iwl_priv *priv, |
74 | struct iwl_addsta_cmd *sta, u8 flags) | |
75 | { | |
76 | struct iwl_rx_packet *res = NULL; | |
77 | int ret = 0; | |
78 | u8 data[sizeof(*sta)]; | |
79 | struct iwl_host_cmd cmd = { | |
80 | .id = REPLY_ADD_STA, | |
81 | .meta.flags = flags, | |
82 | .data = data, | |
83 | }; | |
84 | ||
85 | if (!(flags & CMD_ASYNC)) | |
86 | cmd.meta.flags |= CMD_WANT_SKB; | |
87 | ||
88 | cmd.len = priv->cfg->ops->utils->build_addsta_hcmd(sta, data); | |
89 | ret = iwl_send_cmd(priv, &cmd); | |
90 | ||
91 | if (ret || (flags & CMD_ASYNC)) | |
92 | return ret; | |
93 | ||
94 | res = (struct iwl_rx_packet *)cmd.meta.u.skb->data; | |
95 | if (res->hdr.flags & IWL_CMD_FAILED_MSK) { | |
96 | IWL_ERROR("Bad return from REPLY_ADD_STA (0x%08X)\n", | |
97 | res->hdr.flags); | |
98 | ret = -EIO; | |
99 | } | |
100 | ||
101 | if (ret == 0) { | |
102 | switch (res->u.add_sta.status) { | |
103 | case ADD_STA_SUCCESS_MSK: | |
104 | IWL_DEBUG_INFO("REPLY_ADD_STA PASSED\n"); | |
105 | break; | |
106 | default: | |
107 | ret = -EIO; | |
108 | IWL_WARNING("REPLY_ADD_STA failed\n"); | |
109 | break; | |
110 | } | |
111 | } | |
112 | ||
113 | priv->alloc_rxb_skb--; | |
114 | dev_kfree_skb_any(cmd.meta.u.skb); | |
115 | ||
116 | return ret; | |
117 | } | |
118 | EXPORT_SYMBOL(iwl_send_add_sta); | |
947b13a7 | 119 | |
80fb47a1 EG |
120 | int iwl_get_free_ucode_key_index(struct iwl_priv *priv) |
121 | { | |
122 | int i; | |
123 | ||
124 | for (i = 0; i < STA_KEY_MAX_NUM; i++) | |
77bab602 | 125 | if (!test_and_set_bit(i, &priv->ucode_key_table)) |
80fb47a1 EG |
126 | return i; |
127 | ||
128 | return -1; | |
129 | } | |
6974e363 EG |
130 | |
131 | int iwl_send_static_wepkey_cmd(struct iwl_priv *priv, u8 send_if_empty) | |
132 | { | |
133 | int i, not_empty = 0; | |
134 | u8 buff[sizeof(struct iwl_wep_cmd) + | |
135 | sizeof(struct iwl_wep_key) * WEP_KEYS_MAX]; | |
136 | struct iwl_wep_cmd *wep_cmd = (struct iwl_wep_cmd *)buff; | |
137 | size_t cmd_size = sizeof(struct iwl_wep_cmd); | |
138 | struct iwl_host_cmd cmd = { | |
139 | .id = REPLY_WEPKEY, | |
140 | .data = wep_cmd, | |
141 | .meta.flags = CMD_ASYNC, | |
142 | }; | |
143 | ||
144 | memset(wep_cmd, 0, cmd_size + | |
145 | (sizeof(struct iwl_wep_key) * WEP_KEYS_MAX)); | |
146 | ||
147 | for (i = 0; i < WEP_KEYS_MAX ; i++) { | |
148 | wep_cmd->key[i].key_index = i; | |
149 | if (priv->wep_keys[i].key_size) { | |
150 | wep_cmd->key[i].key_offset = i; | |
151 | not_empty = 1; | |
152 | } else { | |
153 | wep_cmd->key[i].key_offset = WEP_INVALID_OFFSET; | |
154 | } | |
155 | ||
156 | wep_cmd->key[i].key_size = priv->wep_keys[i].key_size; | |
157 | memcpy(&wep_cmd->key[i].key[3], priv->wep_keys[i].key, | |
158 | priv->wep_keys[i].key_size); | |
159 | } | |
160 | ||
161 | wep_cmd->global_key_type = WEP_KEY_WEP_TYPE; | |
162 | wep_cmd->num_keys = WEP_KEYS_MAX; | |
163 | ||
164 | cmd_size += sizeof(struct iwl_wep_key) * WEP_KEYS_MAX; | |
165 | ||
166 | cmd.len = cmd_size; | |
167 | ||
168 | if (not_empty || send_if_empty) | |
169 | return iwl_send_cmd(priv, &cmd); | |
170 | else | |
171 | return 0; | |
172 | } | |
173 | ||
174 | int iwl_remove_default_wep_key(struct iwl_priv *priv, | |
80fb47a1 | 175 | struct ieee80211_key_conf *keyconf) |
6974e363 EG |
176 | { |
177 | int ret; | |
178 | unsigned long flags; | |
179 | ||
180 | spin_lock_irqsave(&priv->sta_lock, flags); | |
80fb47a1 EG |
181 | |
182 | if (!test_and_clear_bit(keyconf->keyidx, &priv->ucode_key_table)) | |
183 | IWL_ERROR("index %d not used in uCode key table.\n", | |
184 | keyconf->keyidx); | |
185 | ||
6974e363 | 186 | priv->default_wep_key--; |
80fb47a1 | 187 | memset(&priv->wep_keys[keyconf->keyidx], 0, sizeof(priv->wep_keys[0])); |
6974e363 EG |
188 | ret = iwl_send_static_wepkey_cmd(priv, 1); |
189 | spin_unlock_irqrestore(&priv->sta_lock, flags); | |
190 | ||
191 | return ret; | |
192 | } | |
193 | ||
194 | int iwl_set_default_wep_key(struct iwl_priv *priv, | |
195 | struct ieee80211_key_conf *keyconf) | |
196 | { | |
197 | int ret; | |
198 | unsigned long flags; | |
199 | ||
200 | keyconf->flags &= ~IEEE80211_KEY_FLAG_GENERATE_IV; | |
201 | keyconf->hw_key_idx = keyconf->keyidx; | |
202 | priv->stations[IWL_AP_ID].keyinfo.alg = ALG_WEP; | |
203 | ||
204 | spin_lock_irqsave(&priv->sta_lock, flags); | |
205 | priv->default_wep_key++; | |
206 | ||
80fb47a1 EG |
207 | if (test_and_set_bit(keyconf->keyidx, &priv->ucode_key_table)) |
208 | IWL_ERROR("index %d already used in uCode key table.\n", | |
209 | keyconf->keyidx); | |
210 | ||
6974e363 EG |
211 | priv->wep_keys[keyconf->keyidx].key_size = keyconf->keylen; |
212 | memcpy(&priv->wep_keys[keyconf->keyidx].key, &keyconf->key, | |
213 | keyconf->keylen); | |
214 | ||
215 | ret = iwl_send_static_wepkey_cmd(priv, 0); | |
216 | spin_unlock_irqrestore(&priv->sta_lock, flags); | |
217 | ||
218 | return ret; | |
219 | } | |
220 | ||
7480513f | 221 | static int iwl_set_wep_dynamic_key_info(struct iwl_priv *priv, |
0211ddda EG |
222 | struct ieee80211_key_conf *keyconf, |
223 | u8 sta_id) | |
224 | { | |
225 | unsigned long flags; | |
226 | __le16 key_flags = 0; | |
227 | int ret; | |
228 | ||
229 | keyconf->flags &= ~IEEE80211_KEY_FLAG_GENERATE_IV; | |
230 | keyconf->hw_key_idx = keyconf->keyidx; | |
231 | ||
232 | key_flags |= (STA_KEY_FLG_WEP | STA_KEY_FLG_MAP_KEY_MSK); | |
233 | key_flags |= cpu_to_le16(keyconf->keyidx << STA_KEY_FLG_KEYID_POS); | |
234 | key_flags &= ~STA_KEY_FLG_INVALID; | |
235 | ||
236 | if (keyconf->keylen == WEP_KEY_LEN_128) | |
237 | key_flags |= STA_KEY_FLG_KEY_SIZE_MSK; | |
238 | ||
5425e490 | 239 | if (sta_id == priv->hw_params.bcast_sta_id) |
0211ddda EG |
240 | key_flags |= STA_KEY_MULTICAST_MSK; |
241 | ||
242 | spin_lock_irqsave(&priv->sta_lock, flags); | |
243 | ||
244 | priv->stations[sta_id].keyinfo.alg = keyconf->alg; | |
245 | priv->stations[sta_id].keyinfo.keylen = keyconf->keylen; | |
246 | priv->stations[sta_id].keyinfo.keyidx = keyconf->keyidx; | |
247 | ||
248 | memcpy(priv->stations[sta_id].keyinfo.key, | |
249 | keyconf->key, keyconf->keylen); | |
250 | ||
251 | memcpy(&priv->stations[sta_id].sta.key.key[3], | |
252 | keyconf->key, keyconf->keylen); | |
253 | ||
3ec47732 EG |
254 | if ((priv->stations[sta_id].sta.key.key_flags & STA_KEY_FLG_ENCRYPT_MSK) |
255 | == STA_KEY_FLG_NO_ENC) | |
256 | priv->stations[sta_id].sta.key.key_offset = | |
80fb47a1 | 257 | iwl_get_free_ucode_key_index(priv); |
3ec47732 EG |
258 | /* else, we are overriding an existing key => no need to allocated room |
259 | * in uCode. */ | |
0211ddda | 260 | |
3ec47732 | 261 | priv->stations[sta_id].sta.key.key_flags = key_flags; |
0211ddda EG |
262 | priv->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_KEY_MASK; |
263 | priv->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; | |
264 | ||
133636de | 265 | ret = iwl_send_add_sta(priv, &priv->stations[sta_id].sta, CMD_ASYNC); |
0211ddda EG |
266 | |
267 | spin_unlock_irqrestore(&priv->sta_lock, flags); | |
268 | ||
269 | return ret; | |
270 | } | |
7480513f EG |
271 | |
272 | static int iwl_set_ccmp_dynamic_key_info(struct iwl_priv *priv, | |
273 | struct ieee80211_key_conf *keyconf, | |
274 | u8 sta_id) | |
275 | { | |
276 | unsigned long flags; | |
277 | __le16 key_flags = 0; | |
278 | ||
279 | key_flags |= (STA_KEY_FLG_CCMP | STA_KEY_FLG_MAP_KEY_MSK); | |
280 | key_flags |= cpu_to_le16(keyconf->keyidx << STA_KEY_FLG_KEYID_POS); | |
281 | key_flags &= ~STA_KEY_FLG_INVALID; | |
282 | ||
5425e490 | 283 | if (sta_id == priv->hw_params.bcast_sta_id) |
7480513f EG |
284 | key_flags |= STA_KEY_MULTICAST_MSK; |
285 | ||
286 | keyconf->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; | |
287 | keyconf->hw_key_idx = keyconf->keyidx; | |
288 | ||
289 | spin_lock_irqsave(&priv->sta_lock, flags); | |
290 | priv->stations[sta_id].keyinfo.alg = keyconf->alg; | |
291 | priv->stations[sta_id].keyinfo.keylen = keyconf->keylen; | |
292 | ||
293 | memcpy(priv->stations[sta_id].keyinfo.key, keyconf->key, | |
294 | keyconf->keylen); | |
295 | ||
296 | memcpy(priv->stations[sta_id].sta.key.key, keyconf->key, | |
297 | keyconf->keylen); | |
298 | ||
3ec47732 EG |
299 | if ((priv->stations[sta_id].sta.key.key_flags & STA_KEY_FLG_ENCRYPT_MSK) |
300 | == STA_KEY_FLG_NO_ENC) | |
301 | priv->stations[sta_id].sta.key.key_offset = | |
302 | iwl_get_free_ucode_key_index(priv); | |
303 | /* else, we are overriding an existing key => no need to allocated room | |
304 | * in uCode. */ | |
305 | ||
7480513f EG |
306 | priv->stations[sta_id].sta.key.key_flags = key_flags; |
307 | priv->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_KEY_MASK; | |
308 | priv->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; | |
309 | ||
310 | spin_unlock_irqrestore(&priv->sta_lock, flags); | |
311 | ||
312 | IWL_DEBUG_INFO("hwcrypto: modify ucode station key info\n"); | |
133636de | 313 | return iwl_send_add_sta(priv, &priv->stations[sta_id].sta, CMD_ASYNC); |
7480513f EG |
314 | } |
315 | ||
316 | static int iwl_set_tkip_dynamic_key_info(struct iwl_priv *priv, | |
317 | struct ieee80211_key_conf *keyconf, | |
318 | u8 sta_id) | |
319 | { | |
320 | unsigned long flags; | |
321 | int ret = 0; | |
322 | ||
323 | keyconf->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; | |
324 | keyconf->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; | |
325 | keyconf->hw_key_idx = keyconf->keyidx; | |
326 | ||
327 | spin_lock_irqsave(&priv->sta_lock, flags); | |
328 | ||
329 | priv->stations[sta_id].keyinfo.alg = keyconf->alg; | |
330 | priv->stations[sta_id].keyinfo.conf = keyconf; | |
331 | priv->stations[sta_id].keyinfo.keylen = 16; | |
3ec47732 EG |
332 | |
333 | if ((priv->stations[sta_id].sta.key.key_flags & STA_KEY_FLG_ENCRYPT_MSK) | |
334 | == STA_KEY_FLG_NO_ENC) | |
335 | priv->stations[sta_id].sta.key.key_offset = | |
77bab602 | 336 | iwl_get_free_ucode_key_index(priv); |
3ec47732 EG |
337 | /* else, we are overriding an existing key => no need to allocated room |
338 | * in uCode. */ | |
7480513f EG |
339 | |
340 | /* This copy is acutally not needed: we get the key with each TX */ | |
341 | memcpy(priv->stations[sta_id].keyinfo.key, keyconf->key, 16); | |
342 | ||
343 | memcpy(priv->stations[sta_id].sta.key.key, keyconf->key, 16); | |
344 | ||
345 | spin_unlock_irqrestore(&priv->sta_lock, flags); | |
346 | ||
347 | return ret; | |
348 | } | |
349 | ||
3ec47732 EG |
350 | int iwl_remove_dynamic_key(struct iwl_priv *priv, |
351 | struct ieee80211_key_conf *keyconf, | |
352 | u8 sta_id) | |
7480513f EG |
353 | { |
354 | unsigned long flags; | |
3ec47732 EG |
355 | int ret = 0; |
356 | u16 key_flags; | |
357 | u8 keyidx; | |
7480513f EG |
358 | |
359 | priv->key_mapping_key = 0; | |
360 | ||
361 | spin_lock_irqsave(&priv->sta_lock, flags); | |
3ec47732 EG |
362 | key_flags = le16_to_cpu(priv->stations[sta_id].sta.key.key_flags); |
363 | keyidx = (key_flags >> STA_KEY_FLG_KEYID_POS) & 0x3; | |
364 | ||
365 | if (keyconf->keyidx != keyidx) { | |
366 | /* We need to remove a key with index different that the one | |
367 | * in the uCode. This means that the key we need to remove has | |
368 | * been replaced by another one with different index. | |
369 | * Don't do anything and return ok | |
370 | */ | |
371 | spin_unlock_irqrestore(&priv->sta_lock, flags); | |
372 | return 0; | |
373 | } | |
374 | ||
7480513f EG |
375 | if (!test_and_clear_bit(priv->stations[sta_id].sta.key.key_offset, |
376 | &priv->ucode_key_table)) | |
377 | IWL_ERROR("index %d not used in uCode key table.\n", | |
378 | priv->stations[sta_id].sta.key.key_offset); | |
379 | memset(&priv->stations[sta_id].keyinfo, 0, | |
6def9761 | 380 | sizeof(struct iwl_hw_key)); |
7480513f EG |
381 | memset(&priv->stations[sta_id].sta.key, 0, |
382 | sizeof(struct iwl4965_keyinfo)); | |
3ec47732 EG |
383 | priv->stations[sta_id].sta.key.key_flags = |
384 | STA_KEY_FLG_NO_ENC | STA_KEY_FLG_INVALID; | |
385 | priv->stations[sta_id].sta.key.key_offset = WEP_INVALID_OFFSET; | |
7480513f EG |
386 | priv->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_KEY_MASK; |
387 | priv->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; | |
7480513f EG |
388 | |
389 | IWL_DEBUG_INFO("hwcrypto: clear ucode station key info\n"); | |
133636de | 390 | ret = iwl_send_add_sta(priv, &priv->stations[sta_id].sta, 0); |
3ec47732 EG |
391 | spin_unlock_irqrestore(&priv->sta_lock, flags); |
392 | return ret; | |
7480513f EG |
393 | } |
394 | ||
395 | int iwl_set_dynamic_key(struct iwl_priv *priv, | |
396 | struct ieee80211_key_conf *key, u8 sta_id) | |
397 | { | |
398 | int ret; | |
399 | ||
400 | priv->key_mapping_key = 1; | |
401 | ||
402 | switch (key->alg) { | |
403 | case ALG_CCMP: | |
404 | ret = iwl_set_ccmp_dynamic_key_info(priv, key, sta_id); | |
405 | break; | |
406 | case ALG_TKIP: | |
407 | ret = iwl_set_tkip_dynamic_key_info(priv, key, sta_id); | |
408 | break; | |
409 | case ALG_WEP: | |
410 | ret = iwl_set_wep_dynamic_key_info(priv, key, sta_id); | |
411 | break; | |
412 | default: | |
413 | IWL_ERROR("Unknown alg: %s alg = %d\n", __func__, key->alg); | |
414 | ret = -EINVAL; | |
415 | } | |
416 | ||
417 | return ret; | |
418 | } | |
419 | ||
66c73db7 TW |
420 | #ifdef CONFIG_IWLWIFI_DEBUG |
421 | static void iwl_dump_lq_cmd(struct iwl_priv *priv, | |
422 | struct iwl_link_quality_cmd *lq) | |
423 | { | |
424 | int i; | |
425 | IWL_DEBUG_RATE("lq station id 0x%x\n", lq->sta_id); | |
426 | IWL_DEBUG_RATE("lq dta 0x%X 0x%X\n", | |
427 | lq->general_params.single_stream_ant_msk, | |
428 | lq->general_params.dual_stream_ant_msk); | |
429 | ||
430 | for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) | |
431 | IWL_DEBUG_RATE("lq index %d 0x%X\n", | |
432 | i, lq->rs_table[i].rate_n_flags); | |
433 | } | |
434 | #else | |
435 | static inline void iwl_dump_lq_cmd(struct iwl_priv *priv, | |
436 | struct iwl_link_quality_cmd *lq) | |
437 | { | |
438 | } | |
439 | #endif | |
440 | ||
441 | int iwl_send_lq_cmd(struct iwl_priv *priv, | |
442 | struct iwl_link_quality_cmd *lq, u8 flags) | |
443 | { | |
444 | struct iwl_host_cmd cmd = { | |
445 | .id = REPLY_TX_LINK_QUALITY_CMD, | |
446 | .len = sizeof(struct iwl_link_quality_cmd), | |
447 | .meta.flags = flags, | |
448 | .data = lq, | |
449 | }; | |
450 | ||
451 | if ((lq->sta_id == 0xFF) && | |
452 | (priv->iw_mode == IEEE80211_IF_TYPE_IBSS)) | |
453 | return -EINVAL; | |
454 | ||
455 | if (lq->sta_id == 0xFF) | |
456 | lq->sta_id = IWL_AP_ID; | |
457 | ||
458 | iwl_dump_lq_cmd(priv,lq); | |
459 | ||
460 | if (iwl_is_associated(priv) && priv->assoc_station_added && | |
461 | priv->lq_mngr.lq_ready) | |
462 | return iwl_send_cmd(priv, &cmd); | |
463 | ||
464 | return 0; | |
465 | } | |
466 | EXPORT_SYMBOL(iwl_send_lq_cmd); | |
467 |