/******************************************************************************
  
  Copyright(c) 2004 Intel Corporation. All rights reserved.

  Portions of this file are based on the WEP enablement code provided by the
  Host AP project hostap-drivers v0.1.3
  Copyright (c) 2001-2002, SSH Communications Security Corp and Jouni Malinen
  <jkmaline@cc.hut.fi>
  Copyright (c) 2002-2003, Jouni Malinen <jkmaline@cc.hut.fi>
  Association logic, scanning WX and logics added by Andrea Merello 
  <andreamrl@tiscali.it>

  
  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., 59 
  Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  
  The full GNU General Public License is included in this distribution in the
  file called LICENSE.
  
  Contact Information:
  James P. Ketrenos <ipw2100-admin@linux.intel.com>
  Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497

******************************************************************************/
#include <linux/wireless.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
#include <linux/kmod.h> /* request_module */
#include <linux/kernel.h>
#include <linux/module.h>
#endif

#include "ieee80211.h"
#include <linux/if_arp.h>

#define RSSI2DBM(x) 98-(x)

static inline char *wlan_translate_scan(char *start, char *stop, 
					   struct ieee80211_beacon *beacon)
{
	struct iw_event iwe;
	int i;

	/* First entry *MUST* be the AP MAC address */
	iwe.cmd = SIOCGIWAP;
	iwe.u.ap_addr.sa_family = ARPHRD_ETHER; 
	memcpy(iwe.u.ap_addr.sa_data, beacon->bssid, ETH_ALEN);
	start = iwe_stream_add_event(start, stop, &iwe, IW_EV_ADDR_LEN);
	
	/* Remaining entries will be displayed in the order we provide them */

	/* Add the ESSID */
        iwe.u.data.length = beacon->ssid_len;
        if (iwe.u.data.length > 32)
		iwe.u.data.length = 32;
        iwe.cmd = SIOCGIWESSID;
        iwe.u.data.flags = 1;
        start = iwe_stream_add_point(start, stop, &iwe, beacon->ssid);

        /* Add mode */
        iwe.cmd = SIOCGIWMODE;
        if (beacon->capability & 
	    (WLAN_CAPABILITY_BSS | WLAN_CAPABILITY_IBSS)) {
		if (beacon->capability & WLAN_CAPABILITY_BSS)
			iwe.u.mode = IW_MODE_MASTER;
		else
			iwe.u.mode = IW_MODE_ADHOC;

		start = iwe_stream_add_event(start, stop, &iwe, 
					     IW_EV_UINT_LEN);
	}
	
        /* Add frequency */
	iwe.cmd = SIOCGIWFREQ;
	iwe.u.freq.m = wlan_frequencies[beacon->channel -1] * 100000;
	iwe.u.freq.e = 1;
	start = iwe_stream_add_event(start, stop, &iwe, IW_EV_FREQ_LEN);

	/* Add rates */
	iwe.cmd = SIOCGIWRATE;
	iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0;
	for (i = 0; i < beacon->rates_len; i++) {
		iwe.u.bitrate.value = (beacon->rates[i] & 0x7F) * 500000;
		start = iwe_stream_add_event(start, stop, &iwe, 
					     IW_EV_PARAM_LEN);
	}

	/* Add quality statistics */
	iwe.cmd = IWEVQUAL;
	iwe.u.qual.qual = beacon->quality ;
	iwe.u.qual.level = beacon->rssi ;
	iwe.u.qual.noise = 0;
	iwe.u.qual.updated = 7;	

	start = iwe_stream_add_event(start, stop, &iwe, IW_EV_QUAL_LEN);

	/* Add encryption capability */
	iwe.cmd = SIOCGIWENCODE;
	if (beacon->capability & WLAN_CAPABILITY_PRIVACY)
		iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
	else
		iwe.u.data.flags = IW_ENCODE_DISABLED;
	iwe.u.data.length = 0;
	start = iwe_stream_add_point(start, stop, &iwe, beacon->ssid);

	return start;
}


  int ieee80211_r8180_wx_get_scan(struct ieee80211_device *ieee,struct iw_request_info *a,union iwreq_data *wrqu,char *extra)
{

	
	struct ieee80211_beacon *beacon;
	struct list_head *b,*delprev,*prev,*next;
	unsigned long flags;
	char *ev = extra;
	char *stop = ev + IW_SCAN_MAX_DATA;
	
	delprev = NULL;
	
	spin_lock_irqsave(&ieee->irq_lock, flags);
		
	list_for_each(b,&ieee->beacons){
		if (delprev){
			kfree(list_entry(delprev,struct ieee80211_beacon,list));
			delprev=NULL;
		}
		beacon=list_entry(b,struct ieee80211_beacon,list);
			
		if(beacon->last_scanned + SCAN_JIFFI < jiffies){ 
		
			  prev = b->prev;
			  next = b->next;
			  next->prev = prev;
			  prev->next = next;
			  delprev=b;
		
		}else{
			ev = wlan_translate_scan(ev, stop, beacon);
		/*	printk("cell %s : %x %x %x %x %x %x\n",beacon->ssid,beacon->bssid[0],
				beacon->bssid[1],beacon->bssid[2],beacon->bssid[3],beacon->bssid[4],
				beacon->bssid[5]);*/
		}
	}
	

	spin_unlock_irqrestore(&ieee->irq_lock, flags);

	wrqu->data.length = ev -  extra;
	wrqu->data.flags = 0;
	
	return 0;
	
}

#ifndef CONFIG_IEEE80211_NOWEP
static void ieee80211_crypt_delayed_deinit(struct ieee80211_device *ieee,
					   struct ieee80211_crypt_data **crypt)
{
	struct ieee80211_crypt_data *tmp;
	unsigned long flags;

	if (*crypt == NULL)
		return;
	
	tmp = *crypt;
	*crypt = NULL;

	/* must not run ops->deinit() while there may be pending encrypt or
	 * decrypt operations. Use a list of delayed deinits to avoid needing
	 * locking. */

	spin_lock_irqsave(&ieee->lock, flags);
	list_add(&tmp->list, &ieee->crypt_deinit_list);
	if (!timer_pending(&ieee->crypt_deinit_timer)) {
		ieee->crypt_deinit_timer.expires = jiffies + HZ;
		add_timer(&ieee->crypt_deinit_timer);
	}
	spin_unlock_irqrestore(&ieee->lock, flags);
}
#endif
int ieee80211_r8180_wx_set_encode(struct ieee80211_device *ieee,
			    struct iw_request_info *info, 
			    union iwreq_data *wrqu, char *keybuf)
{
	struct iw_point *erq = &(wrqu->encoding);
#ifdef CONFIG_IEEE80211_NOWEP
	if (erq->flags & IW_ENCODE_DISABLED)
		return 0;
	return -EOPNOTSUPP;
#else
	struct net_device *dev = ieee->dev;
	struct ieee80211_security sec = {
		.flags = 0
	};
	int key;
	struct ieee80211_crypt_data **crypt;
	
	key = erq->flags & IW_ENCODE_INDEX;
	if (key) {
		if (key > 4)
			return -EINVAL;
		else
			key--;
		if (key >= WEP_KEYS)
			return -EINVAL;
	} else 
		key = ieee->tx_keyidx; 
	
	crypt = &ieee->crypt[key];
	
	if (erq->flags & IW_ENCODE_DISABLED) {
		if (*crypt) {
			sec.enabled = 0;
			sec.level = SEC_LEVEL_0;
			sec.flags |= SEC_ENABLED | SEC_LEVEL;
			ieee80211_crypt_delayed_deinit(ieee, crypt);
		}
		goto done;
	}
	
	sec.enabled = 1;
	sec.flags |= SEC_ENABLED;

	if (*crypt != NULL && (*crypt)->ops != NULL &&
	    strcmp((*crypt)->ops->name, "WEP") != 0) {
		/* changing to use WEP; deinit previously used algorithm */
		ieee80211_crypt_delayed_deinit(ieee, crypt);
	}
	
	if (*crypt == NULL) {
		struct ieee80211_crypt_data *new_crypt;
		
		/* take WEP into use */
		new_crypt = (struct ieee80211_crypt_data *)
			kmalloc(sizeof(struct ieee80211_crypt_data), GFP_KERNEL);
		if (new_crypt == NULL)
			return -ENOMEM;
		memset(new_crypt, 0, sizeof(struct ieee80211_crypt_data));
		new_crypt->ops = ieee80211_r8180_get_crypto_ops("WEP");
		if (!new_crypt->ops) {
			request_module("ieee80211_crypt_wep-r8180");
			new_crypt->ops = ieee80211_r8180_get_crypto_ops("WEP");
		}
		if (new_crypt->ops)
			new_crypt->priv = new_crypt->ops->init(key);
		if (!new_crypt->ops || !new_crypt->priv) {
			kfree(new_crypt);
			new_crypt = NULL;

			printk(KERN_WARNING "%s: could not initialize WEP: "
			       "load module ieee80211_crypt_wep_r8180.o\n",
			       dev->name);
			return -EOPNOTSUPP;
		}
		*crypt = new_crypt;
	}

	if (erq->length > 0) {
		int len = erq->length <= 5 ? 5 : 13;
		int first = 1, j;
		if (len > erq->length)
			memset(keybuf + erq->length, 0, len - erq->length);
		(*crypt)->ops->set_key(keybuf, len, NULL, (*crypt)->priv);
		memcpy(sec.keys[key], keybuf, len);
		sec.key_sizes[key] = len;
		sec.flags |= (1 << key);
		for (j = 0; j < WEP_KEYS; j++) {
			if (j != key && ieee->crypt[j]) {
				first = 0;
				break;
			}
		}
		if (first)
			ieee->tx_keyidx = key;
			sec.active_key = key;
			sec.flags |= SEC_ACTIVE_KEY;
	} else {
		/* No key data - just set the default TX key index */
		ieee->tx_keyidx = key;
		sec.active_key = key;
		sec.flags |= SEC_ACTIVE_KEY;
	}
	
 done:
	ieee->open_wep = erq->flags & IW_ENCODE_OPEN;
	sec.auth_mode = ieee->open_wep ? WLAN_AUTH_OPEN : WLAN_AUTH_SHARED_KEY;
	sec.flags |= SEC_AUTH_MODE;

	/* For now we just support WEP, so only set that security level... 
	 * TODO: When WPA is added this is one place that needs to change */
	sec.flags |= SEC_LEVEL;
	sec.level = SEC_LEVEL_1; /* 40 and 104 bit WEP */

	if (ieee->func && ieee->func->set_security)
		ieee->func->set_security(ieee, &sec);

	/* Do not reset port if card is in Managed mode since resetting will
	 * generate new IEEE 802.11 authentication which may end up in looping
	 * with IEEE 802.1X.  If your hardware requires a reset after WEP
	 * configuration (for example... Prism2), implement the reset_port in 
	 * the callbacks structures used to initialize the 802.11 stack. */
	if (ieee->reset_on_keychange &&
	    ieee->iw_mode != IW_MODE_INFRA && 
	    ieee->func->reset_port &&
	    ieee->func->reset_port(dev)) {
		printk(KERN_DEBUG "%s: reset_port failed\n", dev->name);
		return -EINVAL;
	}
	return 0;
#endif
}
#if 0
int ieee80211_wx_set_encode(struct ieee80211_device *ieee,
			   struct iw_request_info *info, 
			   union iwreq_data *wrqu, char *key)
{
	struct iw_point *erq = &(wrqu->encoding);
#ifdef CONFIG_IEEE80211_NOWEP
	if (erq->flags & IW_ENCODE_DISABLED)
		return 0;
	return -EOPNOTSUPP;
#else
	struct net_device *dev = ieee->dev;
	struct ieee80211_security sec = {
		.flags = 0
	};
	int first = 0;
	int i;

	if (erq->flags & IW_ENCODE_DISABLED) {
		sec.enabled = 0;
		sec.flags |= SEC_ENABLED;
		ieee80211_crypt_delayed_deinit(ieee, &ieee->crypt);
		goto done;
	}
	
	sec.enabled = 1;
	sec.flags |= SEC_ENABLED;

	if (ieee->crypt != NULL && ieee->crypt->ops != NULL &&
	    strcmp(ieee->crypt->ops->name, "WEP") != 0) {
		/* changing to use WEP; deinit previously used algorithm */
		ieee80211_crypt_delayed_deinit(ieee, &ieee->crypt);
	}
	
	if (ieee->crypt == NULL) {
		struct ieee80211_crypt_data *new_crypt;
		
		/* take WEP into use */
		new_crypt = (struct ieee80211_crypt_data *)
			kmalloc(sizeof(struct ieee80211_crypt_data), 
				GFP_KERNEL);
		if (new_crypt == NULL)
			return -ENOMEM;
		memset(new_crypt, 0, sizeof(struct ieee80211_crypt_data));
		new_crypt->ops = hostap_get_crypto_ops("WEP");
		if (!new_crypt->ops) {
			request_module("hostap_crypt_wep");
			new_crypt->ops = hostap_get_crypto_ops("WEP");
		}
		if (new_crypt->ops)
			new_crypt->priv = new_crypt->ops->init();
		if (!new_crypt->ops || !new_crypt->priv) {
			kfree(new_crypt);
			new_crypt = NULL;

			printk(KERN_WARNING "%s: could not initialize WEP: "
			       "load module hostap_crypt_wep.o\n",
			       dev->name);
			return -EOPNOTSUPP;
		}
		first = 1;
		ieee->crypt = new_crypt;
	}

	i = erq->flags & IW_ENCODE_INDEX;
	if (i < 1 || i > 4)
		i = ieee->crypt->ops->get_key_idx(ieee->crypt->priv);
	else
		i--;
	if (i < 0 || i >= WEP_KEYS)
		return -EINVAL;

	if (erq->length > 0) {
		int len = erq->length <= 5 ? 5 : 13;
		if (len > erq->length)
			memset(key + erq->length, 0, len - erq->length);

		ieee->crypt->ops->set_key(i, key, len, ieee->crypt->priv);
		memcpy(sec.keys[i], key, len);
		sec.key_sizes[i] = len;
		sec.flags |= (1 << i);

		if (first) {
			ieee->crypt->ops->set_key_idx(i, ieee->crypt->priv);
			sec.active_key = i;
			sec.flags |= SEC_ACTIVE_KEY;
		}

	} else if (ieee->crypt->ops->set_key_idx(i, ieee->crypt->priv) < 0)
		return -EINVAL; /* keyidx not valid */
	else {
		sec.active_key = i;
		sec.flags |= SEC_ACTIVE_KEY;
	}
	

 done:
	ieee->open_wep = erq->flags & IW_ENCODE_OPEN;
	sec.auth_mode = ieee->open_wep ? WLAN_AUTH_OPEN : WLAN_AUTH_SHARED_KEY;
	sec.flags |= SEC_AUTH_MODE;

	if (ieee->func && ieee->func->set_security)
		ieee->func->set_security(ieee, &sec);

	/* Do not reset port if card is in Managed mode since resetting will
	 * generate new IEEE 802.11 authentication which may end up in looping
	 * with IEEE 802.1X.  If your hardware requires a reset after WEP
	 * configuration (for example... Prism2), implement the reset_port in 
	 * the callbacks structures used to initialize the 802.11 stack. */
	if (ieee->reset_on_keychange &&
	    ieee->iw_mode != IW_MODE_INFRA && 
	    ieee->func->reset_port &&
	    ieee->func->reset_port(dev)) {
		printk(KERN_DEBUG "%s: reset_port failed\n", dev->name);
		return -EINVAL;
	}
	return 0;
#endif
}

#endif
 int ieee80211_r8180_wx_set_essid(struct ieee80211_device *ieee, struct iw_request_info *a,union iwreq_data *wrqu,char *extra)
{
	
	
	struct ieee80211_beacon *beacon;
	struct list_head *b,*delprev,*prev,*next;
	unsigned long flags;
	//u8* tmp;
	
	delprev = NULL;	
	
	if(ieee->iw_mode == IW_MODE_ADHOC || ieee->iw_mode == IW_MODE_MASTER){
		if(wrqu->essid.flags && wrqu->essid.length) 
			ieee80211_r8180_set_master_essid(ieee,extra,wrqu->essid.length); 
		else return -1;
		//return 0;
	}
	
	//if(wrqu->essid.flags && wrqu->essid.length){
		
		memcpy(ieee->ssid,extra,wrqu->essid.length);
		ieee->ssid[wrqu->essid.length] = '\0';
		//-----
		spin_lock_irqsave(&ieee->irq_lock, flags);
		
		list_for_each(b,&ieee->beacons){
			if (delprev){
				kfree(list_entry(delprev,struct ieee80211_beacon,list));
				delprev=NULL;
			}
			
			beacon=list_entry(b,struct ieee80211_beacon,list);
			
			if(beacon->last_scanned + SCAN_JIFFI < jiffies){ 
		
				prev = b->prev;
				next = b->next;
				next->prev = prev;
				prev->next = next;
				delprev=b;
		
			}else{
				ieee80211_new_net(ieee,beacon);
		
			}
		}
	
		if (delprev){
				kfree(list_entry(delprev,struct ieee80211_beacon,list));
				delprev=NULL;
		}
		
		spin_unlock_irqrestore(&ieee->irq_lock, flags);
		//------
		
		
	//}
	
	
	return 0;
	

}


int ieee80211_r8180_wx_set_wap(struct ieee80211_device *ieee,
			 struct iw_request_info *info,
			 union iwreq_data *awrq,
			 char *extra)
{
	struct sockaddr *temp = (struct sockaddr *)awrq;
	struct ieee80211_beacon *beacon;
	
	struct list_head *b,*delprev,*prev,*next;
	unsigned long flags;
	
	
	delprev = NULL;	
	if (temp->sa_family != ARPHRD_ETHER)
		return -EINVAL;
	memcpy(ieee->ap, temp->sa_data, ETH_ALEN); 
	if(ieee->iw_mode == IW_MODE_ADHOC || ieee->iw_mode == IW_MODE_MASTER){
		memcpy(ieee->beacon_cell_ssid, temp->sa_data, ETH_ALEN);
	}else{
		//IEEE80211DMESG("Setting AP to"MACSTR, MAC2STR(ieee->ap));
		spin_lock_irqsave(&ieee->irq_lock, flags);
			
		list_for_each(b,&ieee->beacons){
		
		if (delprev){
				kfree(list_entry(delprev,struct ieee80211_beacon,list));
				delprev=NULL;
			}
			
			beacon=list_entry(b,struct ieee80211_beacon,list);
			
		
			if(beacon->last_scanned + SCAN_JIFFI < jiffies){ 
		
				prev = b->prev;
				next = b->next;
				next->prev = prev;
				prev->next = next;
				delprev=b;
				
			}else{
				ieee80211_new_net(ieee,beacon);
				
			}
		}
	
		if (delprev){
				kfree(list_entry(delprev,struct ieee80211_beacon,list));
				delprev=NULL;
		}
		
		spin_unlock_irqrestore(&ieee->irq_lock, flags);
		//------
	}
	return 0;
}
	
 int ieee80211_r8180_wx_get_essid(struct ieee80211_device *ieee, struct iw_request_info *a,union iwreq_data *wrqu,char *b)
{
	int len;
	
	if(ieee->iw_mode == IW_MODE_ADHOC || ieee->iw_mode == IW_MODE_MASTER){
		if(ieee->master_essid == NULL) return -1;
		len = strlen(ieee->master_essid);
		wrqu->essid.length = len;
		memcpy(b,ieee->master_essid,len);
		wrqu->essid.flags = 1;
		return 0;
	}else{
		if(ieee->ssid[0] == '\0') return -1;
		len = strlen(ieee->ssid);
		wrqu->essid.length = len;
		memcpy(b,ieee->ssid,len);
		wrqu->essid.flags = 1;
		return 0;
	}
	

}





int ieee80211_r8180_wx_get_encode(struct ieee80211_device *ieee,
			   struct iw_request_info *info, 
			   union iwreq_data *wrqu, char *key)
{
	struct iw_point *erq = &(wrqu->encoding);
#ifdef CONFIG_IEEE80211_NOWEP
	erq->length = 0;
	erq->flags = IW_ENCODE_DISABLED;
	return 0;
#else
	int i, len;
	struct ieee80211_crypt_data *crypt;

	i = erq->flags & IW_ENCODE_INDEX;
	if (i < 1 || i > 4)
		i = ieee->tx_keyidx;
	else
		i--;
	if (i < 0 || i >= WEP_KEYS)
		return -EINVAL;
	crypt = ieee->crypt[i];
	erq->flags = i + 1;
								
	if (crypt == NULL || crypt->ops == NULL) {
		erq->length = 0;
		erq->flags |= IW_ENCODE_DISABLED;
		return 0;
	}

	if (strcmp(crypt->ops->name, "WEP") != 0) {
		/* only WEP is supported with wireless extensions, so just
		 * report that encryption is used */
		erq->length = 0;
		erq->flags |= IW_ENCODE_ENABLED;
		return 0;
	}

	len = crypt->ops->get_key(key, WEP_KEY_LEN, NULL, crypt->priv);
	erq->length = (len >= 0 ? len : 0);

	if (ieee->host_encrypt)
		erq->flags |= IW_ENCODE_ENABLED;
	else
		erq->flags |= IW_ENCODE_DISABLED;
	if (ieee->open_wep)
		erq->flags |= IW_ENCODE_OPEN;
	else
		erq->flags |= IW_ENCODE_RESTRICTED;

	return 0;
#endif
}


int ieee80211_r8180_wx_set_rate(struct ieee80211_device *ieee, 
			     struct iw_request_info *info,
			     union iwreq_data *wrqu, char *extra)
{
	//struct r8180_priv *priv = dev->priv;
	u32 target_rate = wrqu->bitrate.value;
	u32 rate = 0;

	if (target_rate == 1000000 || 
	    (!wrqu->bitrate.fixed && target_rate > 1000000))
		rate = 0;
	if (target_rate == 2000000 || 
	    (!wrqu->bitrate.fixed && target_rate > 2000000))
		rate = 1;
	if (target_rate == 5500000 || 
	    (!wrqu->bitrate.fixed && target_rate > 5500000))
		rate = 2;
	if (target_rate == 11000000 || 
	    (!wrqu->bitrate.fixed && target_rate > 11000000))
		rate = 3;
	
	ieee->rate = rate;
	return 0; 
}


int ieee80211_r8180_wx_get_rate(struct ieee80211_device *ieee, 
			     struct iw_request_info *info, 
			     union iwreq_data *wrqu, char *extra)
{
	

	switch(ieee->rate) {
	case 0:
		wrqu->bitrate.value = 1000000;
		break;
	case 1:
		wrqu->bitrate.value = 2000000;
		break;
	case 2:
		wrqu->bitrate.value = 5500000;
		break;
	case 3:
		wrqu->bitrate.value = 11000000;
		break;
	default:
		wrqu->bitrate.value = 11000000;
	}
	return 0;
}


EXPORT_SYMBOL(ieee80211_r8180_wx_set_encode);
EXPORT_SYMBOL(ieee80211_r8180_wx_get_encode);
EXPORT_SYMBOL(wlan_frequencies);
EXPORT_SYMBOL(ieee80211_r8180_wx_set_essid);
EXPORT_SYMBOL(ieee80211_r8180_wx_get_scan);
EXPORT_SYMBOL(ieee80211_r8180_wx_get_essid);
EXPORT_SYMBOL(ieee80211_r8180_wx_set_rate);
EXPORT_SYMBOL(ieee80211_r8180_wx_get_rate);
EXPORT_SYMBOL(ieee80211_r8180_wx_set_wap);
