/*
  Copyright 2004, 2005 Jean-Baptiste Note

  This file is part of islsm.

  islsm is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  islsm 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 islsm; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

*/

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/smp_lock.h>
#include <linux/completion.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/wireless.h>
#include <linux/if_arp.h>
#include <asm/uaccess.h>
#include <linux/list.h>

#include <net/iw_handler.h>
#include <net/ieee80211.h>

#include "isl_sm.h"
#include "islsm_protocol.h"
#include "sent_data_ok.h"
#include "islsm_log.h"

#define ISLSM_VERSION "0.0.1"

#define DRV_NAME         "islsm-ieee80211"
#define DRV_VERSION      ISLSM_VERSION
#define DRV_DESCRIPTION  "Prism54 softmac layer driver"
#define DRV_AUTHOR       "Jean-Baptiste Note <jean-baptiste.note@m4x.org>"

MODULE_DESCRIPTION(DRV_DESCRIPTION);
MODULE_VERSION(DRV_VERSION);
MODULE_AUTHOR(DRV_AUTHOR);
MODULE_LICENSE("GPL");

const char dummy_mac[ETH_ALEN] = { 0x00, 0x3d, 0xb4, 0x00, 0x00, 0x00 };

static unsigned minor = 0;

/******* ioctls definitions ********/

/* private ioctl */
static int
islsm_ioctl_sendannouncedpkt(struct net_device *netdev,
			     struct iw_request_info *info,
			     union iwreq_data *u, char *extra)
{
#define MAX_PKT_LEN 200
	char *data;
	size_t pktsize;
	int err = 0;

	FN_ENTER;

	if (u == 0) {
		err = -ENOTSUPP;
		goto exit;
	}

	pktsize = u->data.length;

	if (pktsize > MAX_PKT_LEN) {
		err = -ENOTSUPP;
		goto exit;
	}

	data = u->data.pointer;

	/* note : this does not work with second-generation devices */
	islsm_outofband_msg(netdev, data, pktsize);
      exit:
	FN_EXIT1(err);
	return err;
}

struct iw_statistics *
islsm_wireless_stats(struct net_device *ndev)
{
	struct islsm *priv = ieee80211_priv(ndev);
	/* completely bogus, as they are not (yet) update */
	return &priv->iwstatistics;
}

static int
islsm_commit(struct net_device *ndev,
	     struct iw_request_info *info,
	     union iwreq_data *wrqu, char *extra)
{
	return 0;
}

static int
islsm_get_name(struct net_device *ndev,
	       struct iw_request_info *info,
	       union iwreq_data *wrqu, char *extra)
{
	char *capabilities;

	capabilities = "IEEE 802.11b/g";	/* Default */

	strncpy(wrqu->name, capabilities, IFNAMSIZ);

	return 0;
}

static int
islsm_set_freq(struct net_device *ndev,
	       struct iw_request_info *info,
	       union iwreq_data *wrqu, char *extra)
{
	struct iw_freq *fwrq = &wrqu->freq;
	/* FIXME : the firmware provides a frequency table, use this */
	static int channels[ISLSM_NR_CHANNELS] =
		{ 0x96c, 0x971, 0x976, 0x97b, 0x980, 0x985, 0x98a, 0x98f,
		  0x994, 0x999, 0x99e, 0x9a3, 0x9a8, 0x9b4 };
	int c, err = 0;

	if (fwrq->e == 1) {
		if ((fwrq->m >= (int) 2.412e8 &&
		     fwrq->m <= (int) 2.487e8)) {
			int f = fwrq->m / 100000;
			int c = 0;

			while ((c < ISLSM_NR_CHANNELS) &&
			       (f != channels[c]))
				c++;

			/* hack to fall through */
			fwrq->e = 0;
			fwrq->m = c + 1;
		}
	}

	if (fwrq->m < 1000 && fwrq->m >= 1 && fwrq->m <= ISLSM_NR_CHANNELS)
		/* we have a valid channel number */
		c = fwrq->m;
	else
		/* we dont handle freq_to_channel yet */
		return -ENOTSUPP;

	err = islsm_freq_change(ndev, c, channels[c - 1],
				ISLSM_TX_CONTROL_CHANNEL_MAGIC1_SCAN,
				ISLSM_TX_CONTROL_CHANNEL_MAGIC2_SCAN);
	return err;
}

static int islsm_get_freq(struct net_device *dev,
			  struct iw_request_info *info,
			  union iwreq_data *wrqu, char *extra)
{
	wrqu->freq.e = 0;
	wrqu->freq.m = 0;
	return 0;
}

static int islsm_set_mode(struct net_device *ndev,
			  struct iw_request_info *info,
			  union iwreq_data *wrqu, char *extra)
{
	struct islsm *priv = ieee80211_priv(ndev);
	struct ieee80211_device *ieee = netdev_priv(ndev);
	int err = 0;

	if (wrqu->mode == ieee->iw_mode)
		return 0;

	switch (wrqu->mode) {
	case IW_MODE_MONITOR:
	case IW_MODE_ADHOC:
	case IW_MODE_INFRA:
		break;
	case IW_MODE_AUTO:
		wrqu->mode = IW_MODE_INFRA;
		break;
	default:
		return -EINVAL;
	}

	{
		uint16_t mode = ISLSM_TX_CONTROL_FILTER_NOTYPE;
		uint16_t magic2 = ISLSM_TX_CONTROL_FILTER_MAGIC2_FORM2;
		uint32_t magic3 = ISLSM_TX_CONTROL_FILTER_MAGIC3_FORM1;
		uint16_t magic8 = 0;
		uint16_t magic9 = 0;

		/* compute the elements needed for the rx/tx filter */
		/* mode  */
		switch (wrqu->mode) {
		case IW_MODE_INFRA:	/* infra station */
			magic2 = ISLSM_TX_CONTROL_FILTER_MAGIC2_STA;
			magic3 = ISLSM_TX_CONTROL_FILTER_MAGIC3_STA;
			magic8 = ISLSM_TX_CONTROL_FILTER_MAGIC8_STA;
			mode = ISLSM_TX_CONTROL_FILTER_STA;
			break;
		case IW_MODE_ADHOC:
			mode = ISLSM_TX_CONTROL_FILTER_ADHOC;
			break;
		case IW_MODE_MASTER:
			mode = ISLSM_TX_CONTROL_FILTER_HOSTAP;
			break;
		case IW_MODE_MONITOR:
			magic2 = ISLSM_TX_CONTROL_FILTER_MAGIC2_MONITOR;
			magic3 = ISLSM_TX_CONTROL_FILTER_MAGIC3_MONITOR;
			mode = ISLSM_TX_CONTROL_FILTER_MONITOR;
			break;
		}
		err = islsm_set_filter(ndev, mode, ieee->bssid,
				       magic2, magic3, magic8, magic9);
		if (err)
			goto failed;
	}

	/* once the hardware has been changed */
	if (wrqu->mode == IW_MODE_MONITOR)
		priv->netdev->type = ARPHRD_IEEE80211;
	else
		priv->netdev->type = ARPHRD_ETHER;

	ieee->iw_mode = wrqu->mode;

 failed:
	return err;
}

static int
islsm_get_mode(struct net_device *ndev,
	       struct iw_request_info *info,
	       union iwreq_data *wrqu, char *extra)
{
	struct ieee80211_device *ieee = netdev_priv(ndev);
	wrqu->mode = ieee->iw_mode;
	return 0;
}

static int __attribute__ ((unused))
prism54_set_scan(struct net_device *dev, struct iw_request_info *info,
		 struct iw_param *vwrq, char *extra)
{
	/* hehe the device does this automagicaly */
	return 0;
}


#define IW_IOCTL(x) [(x)-SIOCSIWCOMMIT]

static iw_handler islsm_handlers[] = {
	IW_IOCTL(SIOCSIWCOMMIT) = islsm_commit,
	IW_IOCTL(SIOCGIWNAME)   = islsm_get_name,
	IW_IOCTL(SIOCSIWFREQ)	= islsm_set_freq,
	IW_IOCTL(SIOCGIWFREQ)	= islsm_get_freq,
	IW_IOCTL(SIOCSIWMODE)	= islsm_set_mode,
	IW_IOCTL(SIOCGIWMODE)	= islsm_get_mode,
};

#undef IW_IOCTL

static iw_handler islsm_private_handlers[] = {
	[0] = islsm_ioctl_sendannouncedpkt,
};

static struct iw_priv_args islsm_private_args[] = {
	[0] = {
		.cmd = SIOCIWFIRSTPRIV,
		.set_args = ((IW_PRIV_TYPE_MASK & IW_PRIV_TYPE_BYTE) |	/* type of args */
			     (IW_PRIV_SIZE_MASK & MAX_PKT_LEN))	/* max number of args */
		&(~IW_PRIV_SIZE_FIXED),	/* size is not fixed */
		.get_args = 0,	/* Type and number of args */
		.name = "set_announcedpkt",	/* Name of the extension */
	},
};

static struct iw_handler_def islsm_iw_handler_def = {
	.standard               = islsm_handlers,
	.num_standard           = ARRAY_SIZE(islsm_handlers),
	.num_private            = ARRAY_SIZE(islsm_private_handlers),
	.num_private_args       = ARRAY_SIZE(islsm_private_args),
	.private                = islsm_private_handlers,
	.private_args           = islsm_private_args,
};

/****** net_device callbacks *******/

static void
islsm_tx_timeout(struct net_device *netdev)
{
	FN_ENTER;
	/* shedule a reboot of the hardware */
	FN_EXIT0;
	return;
}

static struct net_device_stats *
islsm_net_statistics(struct net_device *netdev)
{
	struct islsm *islsm = ieee80211_priv(netdev);
	/* trigger a statistics update
	   The data collected is not used for now */
	//(void) islsm_stats_readback(netdev);
	return &islsm->ieee->stats;
}

static int
islsm_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd)
{
	int error = -ENOTSUPP;
	FN_ENTER;
	switch (cmd) {
	case SIOCETHTOOL:
		islog(L_IOCTL, "%s: SIOCETHTOOL called, not supported\n",
		      DRV_NAME);
		break;
	default:
		break;
	}
	if (error)
		islog(L_IOCTL, "%s: problem with ioctl %02x, return value %i",
		      DRV_NAME, cmd, error);

	FN_EXIT1(error);
	return error;
}

/*
 * Transmit a frame. Does the tracking of everything that needs the
 * 802.11 layer.
 */

static int
islsm_tx_start(struct sk_buff *skb, struct net_device *netdev)
{
	struct islsm *islsm = ieee80211_priv(netdev);
	int err = 0;
	uint8_t rate = 0;
	uint8_t rateset[8] =
	    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
	struct sm_tx_p *txp;
	struct ieee80211_hdr *wh;
	int i;

	FN_ENTER;

	wh = (struct ieee80211_hdr *) skb->data;

	switch (wh->frame_ctl & IEEE80211_FCTL_FTYPE) {
	case IEEE80211_FTYPE_MGMT:
	case IEEE80211_FTYPE_CTL:
		/* default values for mgmt frames */
		switch (wh->frame_ctl & IEEE80211_FCTL_STYPE) {
		case IEEE80211_STYPE_PROBE_REQ:
			/* send frame to probe slot, freeing the previously allocated probe
			 * slot. The frame is emitted when a specific frequency change packet is
			 * sent */
			txp = &(islsm->smpar.probe);
			break;
		case IEEE80211_STYPE_BEACON:
			/* update the beacon reference ? */
			txp = &(islsm->smpar.beacon);
			break;
		default:
			txp = &(islsm->smpar.mgmt);
			break;
		}
		break;

	case IEEE80211_FTYPE_DATA:
		txp = &(islsm->smpar.data);
		break;
	default:
		printk(KERN_ERR "%s: unknown packet type\n", DRV_NAME);
		dev_kfree_skb(skb);
		err = 0;
		goto exit;
	}

/* 		if (ic->ic_fixed_rate != -1) { */
/* 			/\* contains if != -1 an index into the rates */
/* 			   table provided by setup_rates. This is exactly */
/* 			   what we want *\/ */
/* 			rate = ic->ic_fixed_rate; */
/* 		} */

/* 		if (skb->len > ic->ic_rtsthreshold) { */
/* //			rate |= ISLSM_TX_CTSONLY; */
/* 			rate |= ISLSM_TX_RTSCTS; */
/* 		} */

	for (i = 0; i < 8; i++)
		rateset[i] = rate;

	err = islsm_data_tx(netdev, txp, skb);

 exit:
	FN_EXIT1(err);
	return err;
}

static int
islsm_open(struct net_device *netdev)
{
	FN_ENTER;
	/* we should be verifying the device is ready to be opened */
	netif_start_queue(netdev);
	FN_EXIT0;
	return 0;
}

static int __attribute__ ((unused))
islsm_close(struct net_device *netdev)
{
	FN_ENTER;
	netif_stop_queue(netdev);
	FN_EXIT0;
	return 0;
}

static int
islsm_init(struct net_device *netdev)
{
	struct islsm *islsm = ieee80211_priv(netdev);
	int err = 0;

	uint8_t *bssid = netdev->broadcast;

	FN_ENTER;

	/* resize skbs in monitor mode -- good idea, leave enough
	 * headroom to fit the prism header */

	/* Hardware state machine reset */
	/* load firmware */
	if (islsm->isl_boot(netdev)) {
		printk(KERN_WARNING "unable to boot device\n");
		err = -EIO;
		goto done;
	}

	/* to be gotten from bra parsing, called in isl_boot */
	islsm->frame_mem_start = 0x00020200;
	islsm->frame_mem_end = 0x00024200;
	islsm->rxframe_mem_start = islsm->frame_mem_end - ISLSM_RXSPACE;

	/* initialize the memory allocator */
	err = islsm_alloc_create(&islsm->memory,
				 islsm->frame_mem_start,
				 islsm->frame_mem_end);
	if (err)
		goto done;

	err = islsm_eeprom_readback(netdev);
	if (err)
		goto failed_mgmt_readback;

	islsm_parse_eeprom(netdev);
	/* err = islsm_parse_eeprom(netdev);
	   if (err)
	   goto failed_parse_eeprom;
	 */

	/* initialize parameters
	   once the readback is finished -- do this inside the parsing
	   function ? */
	islsm_params_init(netdev);

	/* submit initial data on data pipe */
	/* this seems to allow frequency change successfully */
	islsm_outofband_msg(netdev,
			    magicpkt_table[islsm->device_version].data,
			    magicpkt_table[islsm->device_version].len);

	islsm_set_filter(netdev,
			 ISLSM_TX_CONTROL_FILTER_NOTYPE, bssid,
			 ISLSM_TX_CONTROL_FILTER_MAGIC2_FORM3, 1,
			 0, ISLSM_TX_CONTROL_FILTER_MAGIC9_FORM1);

	// This one may not be necessary any more.
	islsm_set_filter(netdev,
			 ISLSM_TX_CONTROL_FILTER_NOTYPE, bssid,
			 ISLSM_TX_CONTROL_FILTER_MAGIC2_FORM2,
			 ISLSM_TX_CONTROL_FILTER_MAGIC3_FORM1, 0, 0);

	// Start the statistics update cycle
	// mod_timer(&islsm->stats_update, jiffies + ((HZ * ISLSM_STATSTIME) / 1000));

	goto done;

 failed_mgmt_readback:
	islsm_alloc_destroy(&islsm->memory);
 done:
	FN_EXIT1(err);
	return err;
}

/******* ieee80211_device callbacks *******/

static int
islsm_tx(struct ieee80211_txb *txb, struct net_device *ndev) {
	int err = 0;
	/* i don't really know yet what a txb is */
	struct sk_buff *skb = txb->fragments[0];
	err = islsm_tx_start(skb,ndev);
	return err;
}

/******* function exported to other modules *******/

struct net_device *
alloc_islsm(int sizeof_priv)
{
	struct islsm *islsm;
	struct ieee80211_device *ieee;
	struct net_device *dev;
	int mode = 0;

	dev = alloc_ieee80211(sizeof(struct islsm)+sizeof_priv);
	if (!dev) {
		printk(KERN_ERR "%s: Unable allocate ieee80211 device\n", DRV_NAME);
		return NULL;
	}

	/* netdev-specific initialization */
	dev->open = &islsm_init;
	dev->stop = &islsm_open;
	/* The init function is called by register_netdev(). Maybe
	   should be set by hardware to its own private initialization
	   routine. Left to the callee. */
	/* dev->init = &islsm_init; */
	dev->do_ioctl = &islsm_ioctl;
	dev->get_stats = &islsm_net_statistics;
	/*  dev->ethtool_ops = ? */
	dev->tx_timeout = &islsm_tx_timeout;
	dev->wireless_handlers  = &islsm_iw_handler_def;
	dev->get_wireless_stats = &islsm_wireless_stats;
	/* dev->set_mac_address = &prism54_set_mac_address; */
	dev->watchdog_timeo = ISLSM_TX_TIMEOUT;
	/* dev->irq, mem_start, mem_end is left as an exercise to the
	 * caller */
	/* memcpy(dev->dev_addr, dummy_mac, ETH_ALEN); */

	/* ieee80211-specific initialization */
	ieee = netdev_priv(dev);
	ieee->hard_start_xmit = islsm_tx;
	ieee->tx_headroom += FULL_TX_CONTROL_ALLOCDATA + TX_MAX_PADDING;
#ifdef CONFIG_IEEE80211_WPA
	ieee->wpa_enabled = 0;
	ieee->tkip_countermeasures = 0;
	ieee->drop_unencrypted = 0;
	ieee->privacy_invoked = 0;
	ieee->ieee802_1x = 1;
#endif /* CONFIG_IEEE80211_WPA */

	switch (mode) {
	case 1:
		ieee->iw_mode = IW_MODE_ADHOC;
		break;
	case 2:
		ieee->iw_mode = IW_MODE_MONITOR;
		break;
	default:
	case 0:
		ieee->iw_mode = IW_MODE_INFRA;
		break;
	}

	/* islsm-specific initialization */
	islsm = ieee80211_priv(dev);
	islsm->netdev = dev;
	islsm->ieee = ieee;
	islsm->minor = minor++;
	/* do some sanity checks for ourselves */
	islsm_alloc_test(&islsm->memory);
	init_completion(&islsm->dev_init_comp);

	return dev;
}
EXPORT_SYMBOL(alloc_islsm);

void free_islsm(struct net_device *dev) {
	/* free the allocator, for instance */
	free_ieee80211(dev);
}
EXPORT_SYMBOL(free_islsm);

static int __init
islsm_modinit(void)
{
	return 0;
}

static void __exit
islsm_modexit(void)
{
	return;
}

module_init(islsm_modinit);
module_exit(islsm_modexit);
