/* mgmt.c: 802.11 management layer.
 *
 * Copyright (C) 2003 David S. Miller (davem@redhat.com)
 */

#ifndef EXPORT_SYMTAB
#define	EXPORT_SYMTAB
#endif

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/types.h>
#include <linux/init.h>

#include "if_80211.h"
#include "p80211_impl.h"

static int resolution_queue_maxlen_default = 32;

static LIST_HEAD(wireless_all);
static spinlock_t wireless_lock = SPIN_LOCK_UNLOCKED;

static struct wireless_info *__wireless_find_by_dev(struct net_device *dev)
{
	struct list_head *entry;

	list_for_each(entry, &wireless_all) {
		struct wireless_info *p = wireless_entry(entry);

		if (p->dev == dev)
			return p;
	}
	return NULL;
}

struct wireless_info *p80211_find_by_dev(struct net_device *dev)
{
	struct wireless_info *wp;

	spin_lock_irq(&wireless_lock);
	wp = __wireless_find_by_dev(dev);
	if (wp)
		wireless_get(wp);
	spin_unlock_irq(&wireless_lock);

	return wp;
}

struct wireless_info *p80211_open(struct net_device *dev,
				  struct wireless_ops *ops)
{
	struct wireless_info *p = kmalloc(sizeof(*p), GFP_KERNEL);

	if (!p)
		return NULL;

	INIT_LIST_HEAD(&p->all_info);
	INIT_LIST_HEAD(&p->nodes);
	spin_lock_init(&node_lock);
	atomic_set(&p->refcnt, 1);
	spin_lock_init(&state_lock);
	p->cur_seq = 0;
	p->mode = WIRELESS_MODE_UNKNOWN;
	p->state = WIRELESS_STATE_DOWN;
	p->phy_type = WIRELESS_PHY_TYPE_INV;
	p->phy_cur = WIRELESS_PHY_CUR_INV;
	p->__pad = 0;
	memset(p->rate_caps, 0, sizeof(p->rate_caps));
	INIT_LIST_HEAD(&p->self.list);
	memset(p->self.mac, 0, sizeof(p->self.mac));
	memset(p->self.bssid, 0, sizeof(p->self.bssid));
	memset(p->ssid, 0, sizeof(p->ssid));
	p->ssid_len = 0;
	skb_queue_head_init(&p->resolution_queue);
	p->resolution_queue_maxlen = resolution_queue_maxlen_default;

	p->dev = dev;
	p->ops = ops;

	spin_lock_irq(&wireless_lock);
	list_add(&p->all_info, &wireless_all);
	spin_unlock_irq(&wireless_lock);

	return p;
}
EXPORT_SYMBOL(p80211_open);

static void p80211_flush_nodes(struct wireless_info *p)
{
	struct list_head *entry, *tmp;
	unsigned long flags;

	spin_lock_irqsave(&p->node_lock, flags);
	list_for_each_safe(entry, tmp, &p->nodes) {
		struct wireless_node *n = wireless_node_entry(entry);

		list_del_init(&n->list);

		kfree(n);
	}
	spin_unlock_irqrestore(&p->node_lock, flags);
}

void __p80211_destroy(struct wireless_info *p)
{
	if (atomic_read(&p->refcnt))
		BUG();

	p80211_flush_nodes(p);

	skb_queue_purge(&p->resolution_queue);

	p->mode = WIRELESS_MODE_UNKNOWN;
	p->state = WIRELESS_STATE_DOWN;
	p->phy_type = WIRELESS_PHY_TYPE_INV;
	p->phy_cur = WIRELESS_PHY_CUR_INV;
	memset(p->ssid, 0, sizeof(p->ssid));
	p->ssid_len = 0;
	p->dev = NULL;
	p->ops = NULL;

	kfree(p);
}

void p80211_close(struct net_device *dev, struct wireless_info *p)
{
	spin_lock_irq(&wireless_lock);
	list_del_init(&p->all_info);
	spin_unlock_irq(&wireless_lock);

	wireless_put(p);
}
EXPORT_SYMBOL(p80211_close);

int p80211_set_mode(struct wireless_info *wp, u8 mode)
{
	switch (mode) {
	case WIRELESS_MODE_UNKNOWN:
	case WIRELESS_MODE_STATION:
	case WIRELESS_MODE_AP:
	case WIRELESS_MODE_IBSS:
		break;
	default:
		return -EINVAL;
	};

	/* XXX Flush everything here... */

	wp->mode = mode;
	return 0;
}
EXPORT_SYMBOL(p80211_set_mode);

int p80211_start_scan(struct wireless_info *wp)
{
	return 0;
}
EXPORT_SYMBOL(p80211_start_scan);
