/* net/atm/resources.c - Staticly allocated resources */

/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */


#include <linux/config.h>
#include <linux/ctype.h>
#include <linux/string.h>
#include <linux/atmdev.h>
#include <linux/kernel.h> /* for barrier */
#include <linux/module.h>
#include <linux/bitops.h>
#include <net/sock.h>	 /* for struct sock */
#include <asm/segment.h> /* for get_fs_long and put_fs_long */

#include "common.h"
#include "resources.h"
#ifdef CONFIG_PACKET_MMAP
#include <linux/mm.h>
#endif


#ifndef NULL
#define NULL 0
#endif


struct atm_dev *atm_devs = NULL;
static struct atm_dev *last_dev = NULL;
struct atm_vcc *nodev_vccs = NULL;
extern spinlock_t atm_dev_lock;


static struct atm_dev *alloc_atm_dev(const char *type)
{
	struct atm_dev *dev;

	dev = kmalloc(sizeof(*dev),GFP_KERNEL);
	if (!dev) return NULL;
	memset(dev,0,sizeof(*dev));
	dev->type = type;
	dev->signal = ATM_PHY_SIG_UNKNOWN;
	dev->link_rate = ATM_OC3_PCR;
	dev->next = NULL;

	spin_lock(&atm_dev_lock);

	dev->prev = last_dev;

	if (atm_devs) last_dev->next = dev;
	else atm_devs = dev;
	last_dev = dev;
	spin_unlock(&atm_dev_lock);
	return dev;
}


static void free_atm_dev(struct atm_dev *dev)
{
	spin_lock (&atm_dev_lock);
	
	if (dev->prev) dev->prev->next = dev->next;
	else atm_devs = dev->next;
	if (dev->next) dev->next->prev = dev->prev;
	else last_dev = dev->prev;
	kfree(dev);
	
	spin_unlock (&atm_dev_lock);
}


struct atm_dev *atm_find_dev(int number)
{
	struct atm_dev *dev;

	for (dev = atm_devs; dev; dev = dev->next)
		if (dev->ops && dev->number == number) return dev;
	return NULL;
}


struct atm_dev *atm_dev_register(const char *type,const struct atmdev_ops *ops,
    int number,atm_dev_flags_t *flags)
{
	struct atm_dev *dev;

	dev = alloc_atm_dev(type);
	if (!dev) {
		printk(KERN_ERR "atm_dev_register: no space for dev %s\n",
		    type);
		return NULL;
	}
	if (number != -1) {
		if (atm_find_dev(number)) {
			free_atm_dev(dev);
			return NULL;
		}
		dev->number = number;
	}
	else {
		dev->number = 0;
		while (atm_find_dev(dev->number)) dev->number++;
	}
	dev->vccs = dev->last = NULL;
	dev->dev_data = NULL;
	barrier();
	dev->ops = ops;
	if (flags) dev->flags = *flags;
	else memset(&dev->flags,0,sizeof(dev->flags));
	memset((void *) &dev->stats,0,sizeof(dev->stats));
#ifdef CONFIG_PROC_FS
	if (ops->proc_read)
		if (atm_proc_dev_register(dev) < 0) {
			printk(KERN_ERR "atm_dev_register: "
			    "atm_proc_dev_register failed for dev %s\n",type);
			spin_unlock (&atm_dev_lock);		
			free_atm_dev(dev);
			return NULL;
		}
#endif
	spin_unlock (&atm_dev_lock);		
	return dev;
}


void atm_dev_deregister(struct atm_dev *dev)
{
#ifdef CONFIG_PROC_FS
	if (dev->ops->proc_read) atm_proc_dev_deregister(dev);
#endif
	free_atm_dev(dev);
}


void shutdown_atm_dev(struct atm_dev *dev)
{
	if (dev->vccs) {
		set_bit(ATM_DF_CLOSE,&dev->flags);
		return;
	}
	if (dev->ops->dev_close) dev->ops->dev_close(dev);
	atm_dev_deregister(dev);
}


/* Handler for sk->destruct, invoked by sk_free() */
static void atm_free_sock(struct sock *sk)
{
	kfree(sk->protinfo.af_atm);
}


struct sock *alloc_atm_vcc_sk(int family)
{
	struct sock *sk;
	struct atm_vcc *vcc;

	sk = sk_alloc(family, GFP_KERNEL, 1);
	if (!sk) return NULL;
	vcc = sk->protinfo.af_atm = kmalloc(sizeof(*vcc),GFP_KERNEL);
	if (!vcc) {
		sk_free(sk);
		return NULL;
	}
	sock_init_data(NULL,sk);
	sk->destruct = atm_free_sock;
	memset(vcc,0,sizeof(*vcc));
	vcc->sk = sk;
	if (nodev_vccs) nodev_vccs->prev = vcc;
	vcc->prev = NULL;
	vcc->next = nodev_vccs;
	nodev_vccs = vcc;
	return sk;
}


static void unlink_vcc(struct atm_vcc *vcc,struct atm_dev *hold_dev)
{
	if (vcc->prev) vcc->prev->next = vcc->next;
	else if (vcc->dev) vcc->dev->vccs = vcc->next;
	    else nodev_vccs = vcc->next;
	if (vcc->next) vcc->next->prev = vcc->prev;
	else if (vcc->dev) vcc->dev->last = vcc->prev;
	if (vcc->dev && vcc->dev != hold_dev && !vcc->dev->vccs &&
	    test_bit(ATM_DF_CLOSE,&vcc->dev->flags))
		shutdown_atm_dev(vcc->dev);
}


void free_atm_vcc_sk(struct sock *sk)
{
	struct atm_vcc *vcc = sk->protinfo.af_atm;
#ifdef CONFIG_PACKET_MMAP
	if (vcc->pg_vec) {
		struct tpacket_req req;
		memset(&req, 0, sizeof(req));
		atm_set_ring(sk, &req, 1);
	}
#endif
	unlink_vcc(vcc,NULL);
	sk_free(sk);
}


void bind_vcc(struct atm_vcc *vcc,struct atm_dev *dev)
{
	unlink_vcc(vcc,dev);
	vcc->dev = dev;
	if (dev) {
		vcc->next = NULL;
		vcc->prev = dev->last;
		if (dev->vccs) dev->last->next = vcc;
		else dev->vccs = vcc;
		dev->last = vcc;
	}
	else {
		if (nodev_vccs) nodev_vccs->prev = vcc;
		vcc->next = nodev_vccs;
		vcc->prev = NULL;
		nodev_vccs = vcc;
	}
}


EXPORT_SYMBOL(atm_dev_register);
EXPORT_SYMBOL(atm_dev_deregister);
EXPORT_SYMBOL(atm_find_dev);
EXPORT_SYMBOL(shutdown_atm_dev);
EXPORT_SYMBOL(bind_vcc);

#ifdef CONFIG_PACKET_MMAP

/* Dirty? Well, I still did not learn better way to account
 * for user mmaps.
 */

static void atm_mm_open(struct vm_area_struct *vma)
{
	struct file *file = vma->vm_file;
	struct inode *inode = file->f_dentry->d_inode;
	struct socket * sock = &inode->u.socket_i;
	struct sock *sk = sock->sk;
	
	if (sk)
		atomic_inc(&sk->protinfo.af_atm->mapped);
}

static void atm_mm_close(struct vm_area_struct *vma)
{
	struct file *file = vma->vm_file;
	struct inode *inode = file->f_dentry->d_inode;
	struct socket * sock = &inode->u.socket_i;
	struct sock *sk = sock->sk;
	
	if (sk)
		atomic_dec(&sk->protinfo.af_atm->mapped);
}

static struct vm_operations_struct atm_mmap_ops = {
	open:	atm_mm_open,
	close:	atm_mm_close,
};

static void free_pg_vec(unsigned long *pg_vec, unsigned order, unsigned len)
{
	int i;

	for (i=0; i<len; i++) {
		if (pg_vec[i]) {
			struct page *page, *pend;

			pend = virt_to_page(pg_vec[i] + (PAGE_SIZE << order) - 1);
			for (page = virt_to_page(pg_vec[i]); page <= pend; page++)
				ClearPageReserved(page);
			free_pages(pg_vec[i], order);
		}
	}
	kfree(pg_vec);
}

int atm_set_ring(struct sock *sk, struct tpacket_req *req, int closing)
{
	unsigned long *pg_vec = NULL;
	struct tpacket_hdr **io_vec = NULL;
	struct atm_vcc *vcc = sk->protinfo.af_atm;
	int order = 0;
	int err = 0;

	if (req->tp_block_nr) {
		int i, l;
		int frames_per_block;

		/* Sanity tests and some calculations */
		if ((int)req->tp_block_size <= 0)
			return -EINVAL;
		if (req->tp_block_size&(PAGE_SIZE-1))
			return -EINVAL;
		if (req->tp_frame_size < TPACKET_HDRLEN)
			return -EINVAL;
		if (req->tp_frame_size&(TPACKET_ALIGNMENT-1))
			return -EINVAL;
		frames_per_block = req->tp_block_size/req->tp_frame_size;
		if (frames_per_block <= 0)
			return -EINVAL;
		if (frames_per_block*req->tp_block_nr != req->tp_frame_nr)
			return -EINVAL;
		/* OK! */

		/* Allocate page vector */
		while ((PAGE_SIZE<<order) < req->tp_block_size)
			order++;

		err = -ENOMEM;

		pg_vec = kmalloc(req->tp_block_nr*sizeof(unsigned long*), GFP_KERNEL);
		if (pg_vec == NULL)
			goto out;
		memset(pg_vec, 0, req->tp_block_nr*sizeof(unsigned long*));

		for (i=0; i<req->tp_block_nr; i++) {
			struct page *page, *pend;
			pg_vec[i] = __get_free_pages(GFP_KERNEL, order);
			if (!pg_vec[i])
				goto out_free_pgvec;

			pend = virt_to_page(pg_vec[i] + (PAGE_SIZE << order) - 1);
			for (page = virt_to_page(pg_vec[i]); page <= pend; page++)
				SetPageReserved(page);
		}
		/* Page vector is allocated */

		/* Draw frames */
		io_vec = kmalloc(req->tp_frame_nr*sizeof(struct tpacket_hdr*), GFP_KERNEL);
		if (io_vec == NULL)
			goto out_free_pgvec;
		memset(io_vec, 0, req->tp_frame_nr*sizeof(struct tpacket_hdr*));

		l = 0;
		for (i=0; i<req->tp_block_nr; i++) {
			unsigned long ptr = pg_vec[i];
			int k;

			for (k=0; k<frames_per_block; k++, l++) {
				io_vec[l] = (struct tpacket_hdr*)ptr;
				io_vec[l]->tp_status = TP_STATUS_KERNEL;
				ptr += req->tp_frame_size;
			}
		}
		/* Done */
	} else {
		if (req->tp_frame_nr)
			return -EINVAL;
	}

	lock_sock(sk);

#if 0
	/* Detach socket from network */
	spin_lock(&po->bind_lock);
	if (po->running)
		dev_remove_pack(&po->prot_hook);
	spin_unlock(&po->bind_lock);
#endif

	err = -EBUSY;
	if (closing || atomic_read(&vcc->mapped) == 0) {
		struct sk_buff *skb;
		err = 0;
#define XC(a, b) ({ __typeof__ ((a)) __t; __t = (a); (a) = (b); __t; })

		spin_lock_bh(&vcc->recvq.lock);
		pg_vec = XC(vcc->pg_vec, pg_vec);
		io_vec = XC(vcc->iovec, io_vec);
		vcc->iovmax = req->tp_frame_nr-1;
		vcc->head = 0;
		vcc->frame_size = req->tp_frame_size;
		spin_unlock_bh(&vcc->recvq.lock);

		order = XC(vcc->pg_vec_order, order);
		req->tp_block_nr = XC(vcc->pg_vec_len, req->tp_block_nr);

		vcc->pg_vec_pages = req->tp_block_size/PAGE_SIZE;
	    while ((skb = skb_dequeue(&vcc->recvq)) != 0) {
	       atm_return(vcc,skb->truesize);
		   dev_kfree_skb_any(skb);
		}
#undef XC
		if (atomic_read(&vcc->mapped))
			printk(KERN_DEBUG "atm_mmap: vma is busy: %d\n", atomic_read(&vcc->mapped));
	}

#if 0
	spin_lock(&po->bind_lock);
	if (po->running)
		dev_add_pack(&po->prot_hook);
	spin_unlock(&po->bind_lock);
#endif

	release_sock(sk);

	if (io_vec)
		kfree(io_vec);

out_free_pgvec:
	if (pg_vec)
		free_pg_vec(pg_vec, order, req->tp_block_nr);
out:
	return err;
}

int atm_mmap(struct file *file, struct socket *sock, struct vm_area_struct *vma)
{
	struct sock *sk = sock->sk;
	struct atm_vcc *po = sk->protinfo.af_atm;
	unsigned long size;
	unsigned long start;
	int err = -EINVAL;
	int i;

	if (vma->vm_pgoff)
		return -EINVAL;

	size = vma->vm_end - vma->vm_start;

	lock_sock(sk);
	if (po->pg_vec == NULL)
		goto out;
	if (size != po->pg_vec_len*po->pg_vec_pages*PAGE_SIZE)
		goto out;

	atomic_inc(&po->mapped);
	start = vma->vm_start;
	err = -EAGAIN;
	for (i=0; i<po->pg_vec_len; i++) {
		if (remap_page_range(start, __pa(po->pg_vec[i]),
				     po->pg_vec_pages*PAGE_SIZE,
				     vma->vm_page_prot))
			goto out;
		start += po->pg_vec_pages*PAGE_SIZE;
	}
	vma->vm_ops = &atm_mmap_ops;
	err = 0;

out:
	release_sock(sk);
	return err;
}
#else
int atm_mmap(struct file *file, struct socket *sock, struct vm_area_struct *vma)
{
	return -EINVAL;
}
int atm_set_ring(struct sock *sk, struct tpacket_req *req, int closing)
{
	return -ESUPPORT;
}
#endif

EXPORT_SYMBOL(atm_mmap);
EXPORT_SYMBOL(atm_set_ring);
