[Bug 792] Fix WPA-PSK AES both for -Dipw and -Dwext.

Signed-off-by: Zhu Yi <yi.zhu@intel.com>
diff --git a/drivers/net/wireless/ipw2200.c b/drivers/net/wireless/ipw2200.c
index f8dac52..c6da5f5 100644
--- a/drivers/net/wireless/ipw2200.c
+++ b/drivers/net/wireless/ipw2200.c
@@ -5557,6 +5557,55 @@
 	}
 }
 
+static void ipw_set_hw_decrypt_unicast(struct ipw_priv *priv, int level)
+{
+	if (priv->ieee->host_encrypt)
+		return;
+
+	switch (level) {
+	case SEC_LEVEL_3:
+		priv->sys_config.disable_unicast_decryption = 0;
+		priv->ieee->host_decrypt = 0;
+		break;
+	case SEC_LEVEL_2:
+		priv->sys_config.disable_unicast_decryption = 1;
+		priv->ieee->host_decrypt = 1;
+		break;
+	case SEC_LEVEL_1:
+		priv->sys_config.disable_unicast_decryption = 0;
+		priv->ieee->host_decrypt = 0;
+		break;
+	case SEC_LEVEL_0:
+		priv->sys_config.disable_unicast_decryption = 1;
+		break;
+	default:
+		break;
+	}
+}
+
+static void ipw_set_hw_decrypt_multicast(struct ipw_priv *priv, int level)
+{
+	if (priv->ieee->host_encrypt)
+		return;
+
+	switch (level) {
+	case SEC_LEVEL_3:
+		priv->sys_config.disable_multicast_decryption = 0;
+		break;
+	case SEC_LEVEL_2:
+		priv->sys_config.disable_multicast_decryption = 1;
+		break;
+	case SEC_LEVEL_1:
+		priv->sys_config.disable_multicast_decryption = 0;
+		break;
+	case SEC_LEVEL_0:
+		priv->sys_config.disable_multicast_decryption = 1;
+		break;
+	default:
+		break;
+	}
+}
+
 static void ipw_set_hwcrypto_keys(struct ipw_priv *priv)
 {
 	switch (priv->ieee->sec.level) {
@@ -5567,33 +5616,23 @@
 					    priv->ieee->sec.active_key);
 
 		ipw_send_wep_keys(priv, DCW_WEP_KEY_SEC_TYPE_CCM);
-		priv->sys_config.disable_unicast_decryption = 0;
-		priv->sys_config.disable_multicast_decryption = 0;
-		priv->ieee->host_decrypt = 0;
 		break;
 	case SEC_LEVEL_2:
 		if (priv->ieee->sec.flags & SEC_ACTIVE_KEY)
 			ipw_send_tgi_tx_key(priv,
 					    DCT_FLAG_EXT_SECURITY_TKIP,
 					    priv->ieee->sec.active_key);
-
-		priv->sys_config.disable_unicast_decryption = 1;
-		priv->sys_config.disable_multicast_decryption = 1;
-		priv->ieee->host_decrypt = 1;
 		break;
 	case SEC_LEVEL_1:
 		ipw_send_wep_keys(priv, DCW_WEP_KEY_SEC_TYPE_WEP);
-		priv->sys_config.disable_unicast_decryption = 0;
-		priv->sys_config.disable_multicast_decryption = 0;
-		priv->ieee->host_decrypt = 0;
 		break;
 	case SEC_LEVEL_0:
-		priv->sys_config.disable_unicast_decryption = 1;
-		priv->sys_config.disable_multicast_decryption = 1;
-		break;
 	default:
 		break;
 	}
+
+	ipw_set_hw_decrypt_unicast(priv, priv->ieee->sec.level);
+	ipw_set_hw_decrypt_multicast(priv, priv->ieee->sec.level);
 }
 
 static void ipw_adhoc_check(void *data)
@@ -6185,12 +6224,31 @@
 	return ret;
 }
 
+static int ipw_wpa_ie_cipher2level(u8 cipher)
+{
+	switch (cipher) {
+	case 4:		/* CCMP */
+		return SEC_LEVEL_3;
+	case 2:		/* TKIP */
+		return SEC_LEVEL_2;
+	case 5:		/* WEP104 */
+	case 1:		/* WEP40 */
+		return SEC_LEVEL_1;
+	case 0:		/* NONE */
+		return SEC_LEVEL_0;
+	default:
+		return -1;
+	}
+}
+
 static int ipw_wpa_set_wpa_ie(struct net_device *dev,
 			      struct ipw_param *param, int plen)
 {
 	struct ipw_priv *priv = ieee80211_priv(dev);
 	struct ieee80211_device *ieee = priv->ieee;
 	u8 *buf;
+	u8 *ptk, *gtk;
+	int level;
 
 	if (param->u.wpa_ie.len > MAX_WPA_IE_LEN ||
 	    (param->u.wpa_ie.len && param->u.wpa_ie.data == NULL))
@@ -6209,8 +6267,35 @@
 		kfree(ieee->wpa_ie);
 		ieee->wpa_ie = NULL;
 		ieee->wpa_ie_len = 0;
+		goto done;
 	}
 
+	if (priv->ieee->host_encrypt)
+		goto done;
+
+	/* HACK: Parse wpa_ie here to get pairwise suite, otherwise
+	 * we need to change driver_ipw.c from wpa_supplicant. This
+	 * is OK since -Dipw is deprecated. The -Dwext driver has a
+	 * clean way to handle this. */
+	gtk = ptk = (u8 *) ieee->wpa_ie;
+	if (ieee->wpa_ie[0] == 0x30) {	/* RSN IE */
+		gtk += 4 + 3;
+		ptk += 4 + 4 + 2 + 3;
+	} else {		/* WPA IE */
+		gtk += 8 + 3;
+		ptk += 8 + 4 + 2 + 3;
+	}
+
+	if (ptk - (u8 *) ieee->wpa_ie > ieee->wpa_ie_len)
+		return -EINVAL;
+
+	level = ipw_wpa_ie_cipher2level(*gtk);
+	ipw_set_hw_decrypt_multicast(priv, level);
+
+	level = ipw_wpa_ie_cipher2level(*ptk);
+	ipw_set_hw_decrypt_unicast(priv, level);
+
+      done:
 	ipw_wpa_assoc_frame(priv, ieee->wpa_ie, ieee->wpa_ie_len);
 	return 0;
 }
@@ -6510,6 +6595,23 @@
 	return err;
 }
 
+static int wext_cipher2level(int cipher)
+{
+	switch (cipher) {
+	case IW_AUTH_CIPHER_NONE:
+		return SEC_LEVEL_0;
+	case IW_AUTH_CIPHER_WEP40:
+	case IW_AUTH_CIPHER_WEP104:
+		return SEC_LEVEL_1;
+	case IW_AUTH_CIPHER_TKIP:
+		return SEC_LEVEL_2;
+	case IW_AUTH_CIPHER_CCMP:
+		return SEC_LEVEL_3;
+	default:
+		return -1;
+	}
+}
+
 /* SIOCSIWAUTH */
 static int ipw_wx_set_auth(struct net_device *dev,
 			   struct iw_request_info *info,
@@ -6524,8 +6626,15 @@
 
 	switch (param->flags & IW_AUTH_INDEX) {
 	case IW_AUTH_WPA_VERSION:
+		break;
 	case IW_AUTH_CIPHER_PAIRWISE:
+		ipw_set_hw_decrypt_unicast(priv,
+					   wext_cipher2level(param->value));
+		break;
 	case IW_AUTH_CIPHER_GROUP:
+		ipw_set_hw_decrypt_multicast(priv,
+					     wext_cipher2level(param->value));
+		break;
 	case IW_AUTH_KEY_MGMT:
 		/*
 		 * ipw2200 does not use these parameters
@@ -10256,11 +10365,11 @@
 		priv->ieee->sec.level = sec->level;
 		priv->ieee->sec.flags |= SEC_LEVEL;
 		priv->status |= STATUS_SECURITY_UPDATED;
-
-		if (!priv->ieee->host_encrypt && (sec->flags & SEC_ENCRYPT))
-			ipw_set_hwcrypto_keys(priv);
 	}
 
+	if (!priv->ieee->host_encrypt && (sec->flags & SEC_ENCRYPT))
+		ipw_set_hwcrypto_keys(priv);
+
 	/* To match current functionality of ipw2100 (which works well w/
 	 * various supplicants, we don't force a disassociate if the
 	 * privacy capability changes ... */