/****************************************************************************** * * Copyright(c) 2003 - 2008 Intel Corporation. All rights reserved. * * Portions of this file are derived from the ipw3945 project, as well * as portions of the ieee80211 subsystem header files. * * This program is free software; you can redistribute it and/or modify it * under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA * * The full GNU General Public License is included in this distribution in the * file called LICENSE. * * Contact Information: * James P. Ketrenos * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 * *****************************************************************************/ #include #include "iwl-eeprom.h" #include "iwl-4965.h" #include "iwl-core.h" #include "iwl-sta.h" #include "iwl-io.h" #include "iwl-helpers.h" #include "iwl-4965.h" #include "iwl-sta.h" int iwl_get_free_ucode_key_index(struct iwl_priv *priv) { int i; for (i = 0; i < STA_KEY_MAX_NUM; i++) if (!test_and_set_bit(i, &priv->ucode_key_table)) return i; return -1; } int iwl_send_static_wepkey_cmd(struct iwl_priv *priv, u8 send_if_empty) { int i, not_empty = 0; u8 buff[sizeof(struct iwl_wep_cmd) + sizeof(struct iwl_wep_key) * WEP_KEYS_MAX]; struct iwl_wep_cmd *wep_cmd = (struct iwl_wep_cmd *)buff; size_t cmd_size = sizeof(struct iwl_wep_cmd); struct iwl_host_cmd cmd = { .id = REPLY_WEPKEY, .data = wep_cmd, .meta.flags = CMD_ASYNC, }; memset(wep_cmd, 0, cmd_size + (sizeof(struct iwl_wep_key) * WEP_KEYS_MAX)); for (i = 0; i < WEP_KEYS_MAX ; i++) { wep_cmd->key[i].key_index = i; if (priv->wep_keys[i].key_size) { wep_cmd->key[i].key_offset = i; not_empty = 1; } else { wep_cmd->key[i].key_offset = WEP_INVALID_OFFSET; } wep_cmd->key[i].key_size = priv->wep_keys[i].key_size; memcpy(&wep_cmd->key[i].key[3], priv->wep_keys[i].key, priv->wep_keys[i].key_size); } wep_cmd->global_key_type = WEP_KEY_WEP_TYPE; wep_cmd->num_keys = WEP_KEYS_MAX; cmd_size += sizeof(struct iwl_wep_key) * WEP_KEYS_MAX; cmd.len = cmd_size; if (not_empty || send_if_empty) return iwl_send_cmd(priv, &cmd); else return 0; } int iwl_remove_default_wep_key(struct iwl_priv *priv, struct ieee80211_key_conf *keyconf) { int ret; unsigned long flags; spin_lock_irqsave(&priv->sta_lock, flags); if (!test_and_clear_bit(keyconf->keyidx, &priv->ucode_key_table)) IWL_ERROR("index %d not used in uCode key table.\n", keyconf->keyidx); priv->default_wep_key--; memset(&priv->wep_keys[keyconf->keyidx], 0, sizeof(priv->wep_keys[0])); ret = iwl_send_static_wepkey_cmd(priv, 1); spin_unlock_irqrestore(&priv->sta_lock, flags); return ret; } int iwl_set_default_wep_key(struct iwl_priv *priv, struct ieee80211_key_conf *keyconf) { int ret; unsigned long flags; keyconf->flags &= ~IEEE80211_KEY_FLAG_GENERATE_IV; keyconf->hw_key_idx = keyconf->keyidx; priv->stations[IWL_AP_ID].keyinfo.alg = ALG_WEP; spin_lock_irqsave(&priv->sta_lock, flags); priv->default_wep_key++; if (test_and_set_bit(keyconf->keyidx, &priv->ucode_key_table)) IWL_ERROR("index %d already used in uCode key table.\n", keyconf->keyidx); priv->wep_keys[keyconf->keyidx].key_size = keyconf->keylen; memcpy(&priv->wep_keys[keyconf->keyidx].key, &keyconf->key, keyconf->keylen); ret = iwl_send_static_wepkey_cmd(priv, 0); spin_unlock_irqrestore(&priv->sta_lock, flags); return ret; } static int iwl_set_wep_dynamic_key_info(struct iwl_priv *priv, struct ieee80211_key_conf *keyconf, u8 sta_id) { unsigned long flags; __le16 key_flags = 0; int ret; keyconf->flags &= ~IEEE80211_KEY_FLAG_GENERATE_IV; keyconf->hw_key_idx = keyconf->keyidx; key_flags |= (STA_KEY_FLG_WEP | STA_KEY_FLG_MAP_KEY_MSK); key_flags |= cpu_to_le16(keyconf->keyidx << STA_KEY_FLG_KEYID_POS); key_flags &= ~STA_KEY_FLG_INVALID; if (keyconf->keylen == WEP_KEY_LEN_128) key_flags |= STA_KEY_FLG_KEY_SIZE_MSK; if (sta_id == priv->hw_params.bcast_sta_id) key_flags |= STA_KEY_MULTICAST_MSK; spin_lock_irqsave(&priv->sta_lock, flags); priv->stations[sta_id].keyinfo.alg = keyconf->alg; priv->stations[sta_id].keyinfo.keylen = keyconf->keylen; priv->stations[sta_id].keyinfo.keyidx = keyconf->keyidx; memcpy(priv->stations[sta_id].keyinfo.key, keyconf->key, keyconf->keylen); memcpy(&priv->stations[sta_id].sta.key.key[3], keyconf->key, keyconf->keylen); priv->stations[sta_id].sta.key.key_offset = iwl_get_free_ucode_key_index(priv); priv->stations[sta_id].sta.key.key_flags = key_flags; priv->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_KEY_MASK; priv->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; ret = iwl4965_send_add_station(priv, &priv->stations[sta_id].sta, CMD_ASYNC); spin_unlock_irqrestore(&priv->sta_lock, flags); return ret; } static int iwl_set_ccmp_dynamic_key_info(struct iwl_priv *priv, struct ieee80211_key_conf *keyconf, u8 sta_id) { unsigned long flags; __le16 key_flags = 0; key_flags |= (STA_KEY_FLG_CCMP | STA_KEY_FLG_MAP_KEY_MSK); key_flags |= cpu_to_le16(keyconf->keyidx << STA_KEY_FLG_KEYID_POS); key_flags &= ~STA_KEY_FLG_INVALID; if (sta_id == priv->hw_params.bcast_sta_id) key_flags |= STA_KEY_MULTICAST_MSK; keyconf->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; keyconf->hw_key_idx = keyconf->keyidx; spin_lock_irqsave(&priv->sta_lock, flags); priv->stations[sta_id].keyinfo.alg = keyconf->alg; priv->stations[sta_id].keyinfo.keylen = keyconf->keylen; memcpy(priv->stations[sta_id].keyinfo.key, keyconf->key, keyconf->keylen); memcpy(priv->stations[sta_id].sta.key.key, keyconf->key, keyconf->keylen); priv->stations[sta_id].sta.key.key_offset = iwl_get_free_ucode_key_index(priv); priv->stations[sta_id].sta.key.key_flags = key_flags; priv->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_KEY_MASK; priv->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; spin_unlock_irqrestore(&priv->sta_lock, flags); IWL_DEBUG_INFO("hwcrypto: modify ucode station key info\n"); return iwl4965_send_add_station(priv, &priv->stations[sta_id].sta, CMD_ASYNC); } static int iwl_set_tkip_dynamic_key_info(struct iwl_priv *priv, struct ieee80211_key_conf *keyconf, u8 sta_id) { unsigned long flags; int ret = 0; keyconf->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; keyconf->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; keyconf->hw_key_idx = keyconf->keyidx; spin_lock_irqsave(&priv->sta_lock, flags); priv->stations[sta_id].keyinfo.alg = keyconf->alg; priv->stations[sta_id].keyinfo.conf = keyconf; priv->stations[sta_id].keyinfo.keylen = 16; priv->stations[sta_id].sta.key.key_offset = iwl_get_free_ucode_key_index(priv); /* This copy is acutally not needed: we get the key with each TX */ memcpy(priv->stations[sta_id].keyinfo.key, keyconf->key, 16); memcpy(priv->stations[sta_id].sta.key.key, keyconf->key, 16); spin_unlock_irqrestore(&priv->sta_lock, flags); return ret; } int iwl_remove_dynamic_key(struct iwl_priv *priv, u8 sta_id) { unsigned long flags; priv->key_mapping_key = 0; spin_lock_irqsave(&priv->sta_lock, flags); if (!test_and_clear_bit(priv->stations[sta_id].sta.key.key_offset, &priv->ucode_key_table)) IWL_ERROR("index %d not used in uCode key table.\n", priv->stations[sta_id].sta.key.key_offset); memset(&priv->stations[sta_id].keyinfo, 0, sizeof(struct iwl4965_hw_key)); memset(&priv->stations[sta_id].sta.key, 0, sizeof(struct iwl4965_keyinfo)); priv->stations[sta_id].sta.key.key_flags = STA_KEY_FLG_NO_ENC; priv->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_KEY_MASK; priv->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; spin_unlock_irqrestore(&priv->sta_lock, flags); IWL_DEBUG_INFO("hwcrypto: clear ucode station key info\n"); return iwl4965_send_add_station(priv, &priv->stations[sta_id].sta, 0); } int iwl_set_dynamic_key(struct iwl_priv *priv, struct ieee80211_key_conf *key, u8 sta_id) { int ret; priv->key_mapping_key = 1; switch (key->alg) { case ALG_CCMP: ret = iwl_set_ccmp_dynamic_key_info(priv, key, sta_id); break; case ALG_TKIP: ret = iwl_set_tkip_dynamic_key_info(priv, key, sta_id); break; case ALG_WEP: ret = iwl_set_wep_dynamic_key_info(priv, key, sta_id); break; default: IWL_ERROR("Unknown alg: %s alg = %d\n", __func__, key->alg); ret = -EINVAL; } return ret; } #ifdef CONFIG_IWLWIFI_DEBUG static void iwl_dump_lq_cmd(struct iwl_priv *priv, struct iwl_link_quality_cmd *lq) { int i; IWL_DEBUG_RATE("lq station id 0x%x\n", lq->sta_id); IWL_DEBUG_RATE("lq dta 0x%X 0x%X\n", lq->general_params.single_stream_ant_msk, lq->general_params.dual_stream_ant_msk); for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) IWL_DEBUG_RATE("lq index %d 0x%X\n", i, lq->rs_table[i].rate_n_flags); } #else static inline void iwl_dump_lq_cmd(struct iwl_priv *priv, struct iwl_link_quality_cmd *lq) { } #endif int iwl_send_lq_cmd(struct iwl_priv *priv, struct iwl_link_quality_cmd *lq, u8 flags) { struct iwl_host_cmd cmd = { .id = REPLY_TX_LINK_QUALITY_CMD, .len = sizeof(struct iwl_link_quality_cmd), .meta.flags = flags, .data = lq, }; if ((lq->sta_id == 0xFF) && (priv->iw_mode == IEEE80211_IF_TYPE_IBSS)) return -EINVAL; if (lq->sta_id == 0xFF) lq->sta_id = IWL_AP_ID; iwl_dump_lq_cmd(priv,lq); if (iwl_is_associated(priv) && priv->assoc_station_added && priv->lq_mngr.lq_ready) return iwl_send_cmd(priv, &cmd); return 0; } EXPORT_SYMBOL(iwl_send_lq_cmd);