iwmc3200wifi: cfg80211 key hooks implemetation

This patch implements the new cfg80211 privacy related hooks: add/get/set_key
and the set_default_key one.
With this implementation we can now call the wext-compat *encode* routines and
reduce our own wext code.

Signed-off-by: Samuel Ortiz <samuel.ortiz@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/drivers/net/wireless/iwmc3200wifi/cfg80211.c b/drivers/net/wireless/iwmc3200wifi/cfg80211.c
index bc89b1e..739bd9b 100644
--- a/drivers/net/wireless/iwmc3200wifi/cfg80211.c
+++ b/drivers/net/wireless/iwmc3200wifi/cfg80211.c
@@ -23,6 +23,7 @@
 
 #include <linux/kernel.h>
 #include <linux/netdevice.h>
+#include <linux/etherdevice.h>
 #include <linux/wireless.h>
 #include <linux/ieee80211.h>
 #include <net/cfg80211.h>
@@ -130,6 +131,173 @@
 	.n_bitrates = iwm_a_rates_size,
 };
 
+static int iwm_key_init(struct iwm_key *key, u8 key_index,
+			const u8 *mac_addr, struct key_params *params)
+{
+	key->hdr.key_idx = key_index;
+	if (!mac_addr || is_broadcast_ether_addr(mac_addr)) {
+		key->hdr.multicast = 1;
+		memset(key->hdr.mac, 0xff, ETH_ALEN);
+	} else {
+		key->hdr.multicast = 0;
+		memcpy(key->hdr.mac, mac_addr, ETH_ALEN);
+	}
+
+	if (params) {
+		if (params->key_len > WLAN_MAX_KEY_LEN ||
+		    params->seq_len > IW_ENCODE_SEQ_MAX_SIZE)
+			return -EINVAL;
+
+		key->cipher = params->cipher;
+		key->key_len = params->key_len;
+		key->seq_len = params->seq_len;
+		memcpy(key->key, params->key, key->key_len);
+		memcpy(key->seq, params->seq, key->seq_len);
+	}
+
+	return 0;
+}
+
+static int iwm_reset_profile(struct iwm_priv *iwm)
+{
+	int ret;
+
+	if (!iwm->umac_profile_active)
+		return 0;
+
+	/*
+	 * If there is a current active profile, but no
+	 * default key, it's not worth trying to associate again.
+	 */
+	if (iwm->default_key < 0)
+		return 0;
+
+	/*
+	 * Here we have an active profile, but a key setting changed.
+	 * We thus have to invalidate the current profile, and push the
+	 * new one. Keys will be pushed when association takes place.
+	 */
+	ret = iwm_invalidate_mlme_profile(iwm);
+	if (ret < 0) {
+		IWM_ERR(iwm, "Couldn't invalidate profile\n");
+		return ret;
+	}
+
+	return iwm_send_mlme_profile(iwm);
+}
+
+static int iwm_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev,
+				u8 key_index, const u8 *mac_addr,
+				struct key_params *params)
+{
+	struct iwm_priv *iwm = ndev_to_iwm(ndev);
+	struct iwm_key *key = &iwm->keys[key_index];
+	int ret;
+
+	IWM_DBG_WEXT(iwm, DBG, "Adding key for %pM\n", mac_addr);
+
+	memset(key, 0, sizeof(struct iwm_key));
+	ret = iwm_key_init(key, key_index, mac_addr, params);
+	if (ret < 0) {
+		IWM_ERR(iwm, "Invalid key_params\n");
+		return ret;
+	}
+
+	/*
+	 * The WEP keys can be set before or after setting the essid.
+	 * We need to handle both cases by simply pushing the keys after
+	 * we send the profile.
+	 * If the profile is not set yet (i.e. we're pushing keys before
+	 * the essid), we set the cipher appropriately.
+	 * If the profile is set, we havent associated yet because our
+	 * cipher was incorrectly set. So we invalidate and send the
+	 * profile again.
+	 */
+	if (key->cipher == WLAN_CIPHER_SUITE_WEP40 ||
+	    key->cipher == WLAN_CIPHER_SUITE_WEP104) {
+		u8 *ucast_cipher = &iwm->umac_profile->sec.ucast_cipher;
+		u8 *mcast_cipher = &iwm->umac_profile->sec.mcast_cipher;
+
+		IWM_DBG_WEXT(iwm, DBG, "WEP key\n");
+
+		if (key->cipher == WLAN_CIPHER_SUITE_WEP40)
+			*ucast_cipher = *mcast_cipher = UMAC_CIPHER_TYPE_WEP_40;
+		if (key->cipher == WLAN_CIPHER_SUITE_WEP104)
+			*ucast_cipher = *mcast_cipher =
+				UMAC_CIPHER_TYPE_WEP_104;
+
+		return iwm_reset_profile(iwm);
+	}
+
+	return iwm_set_key(iwm, 0, key);
+}
+
+static int iwm_cfg80211_get_key(struct wiphy *wiphy, struct net_device *ndev,
+				u8 key_index, const u8 *mac_addr, void *cookie,
+				void (*callback)(void *cookie,
+						 struct key_params*))
+{
+	struct iwm_priv *iwm = ndev_to_iwm(ndev);
+	struct iwm_key *key = &iwm->keys[key_index];
+	struct key_params params;
+
+	IWM_DBG_WEXT(iwm, DBG, "Getting key %d\n", key_index);
+
+	memset(&params, 0, sizeof(params));
+
+	params.cipher = key->cipher;
+	params.key_len = key->key_len;
+	params.seq_len = key->seq_len;
+	params.seq = key->seq;
+	params.key = key->key;
+
+	callback(cookie, &params);
+
+	return key->key_len ? 0 : -ENOENT;
+}
+
+
+static int iwm_cfg80211_del_key(struct wiphy *wiphy, struct net_device *ndev,
+				u8 key_index, const u8 *mac_addr)
+{
+	struct iwm_priv *iwm = ndev_to_iwm(ndev);
+	struct iwm_key *key = &iwm->keys[key_index];
+
+	if (!iwm->keys[key_index].key_len) {
+		IWM_DBG_WEXT(iwm, DBG, "Key %d not used\n", key_index);
+		return 0;
+	}
+
+	if (key_index == iwm->default_key)
+		iwm->default_key = -1;
+
+	return iwm_set_key(iwm, 1, key);
+}
+
+static int iwm_cfg80211_set_default_key(struct wiphy *wiphy,
+					struct net_device *ndev,
+					u8 key_index)
+{
+	struct iwm_priv *iwm = ndev_to_iwm(ndev);
+	int ret;
+
+	IWM_DBG_WEXT(iwm, DBG, "Default key index is: %d\n", key_index);
+
+	if (!iwm->keys[key_index].key_len) {
+		IWM_ERR(iwm, "Key %d not used\n", key_index);
+		return -EINVAL;
+	}
+
+	ret = iwm_set_tx_key(iwm, key_index);
+	if (ret < 0)
+		return ret;
+
+	iwm->default_key = key_index;
+
+	return iwm_reset_profile(iwm);
+}
+
+
 int iwm_cfg80211_inform_bss(struct iwm_priv *iwm)
 {
 	struct wiphy *wiphy = iwm_to_wiphy(iwm);
@@ -326,6 +494,10 @@
 
 static struct cfg80211_ops iwm_cfg80211_ops = {
 	.change_virtual_intf = iwm_cfg80211_change_iface,
+	.add_key = iwm_cfg80211_add_key,
+	.get_key = iwm_cfg80211_get_key,
+	.del_key = iwm_cfg80211_del_key,
+	.set_default_key = iwm_cfg80211_set_default_key,
 	.scan = iwm_cfg80211_scan,
 	.set_wiphy_params = iwm_cfg80211_set_wiphy_params,
 	.join_ibss = iwm_cfg80211_join_ibss,