/* net/atm/raw.c - Raw AAL0 and AAL5 transports */

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


#include <linux/module.h>
#include <linux/sched.h>
#include <linux/atmdev.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/mm.h>

#include "common.h"
#include "protocols.h"


#if 0
#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
#else
#define DPRINTK(format,args...)
#endif


/*
 * SKB == NULL indicates that the link is being closed
 */

void atm_push_raw(struct atm_vcc *vcc,struct sk_buff *skb)
{
	struct tpacket_hdr *h;
	if (skb) {
#ifndef CONFIG_PACKET_MMAP
		skb_queue_tail(&vcc->recvq,skb);
		wake_up(&vcc->sleep);
#else
	   if (vcc->iovec == 0) {
		  skb_queue_tail(&vcc->recvq,skb);
		  wake_up(&vcc->sleep);
	   } else {
	      unsigned long status = TP_STATUS_LOSING|TP_STATUS_USER;
	      unsigned short off = TPACKET_ALIGN(TPACKET_HDRLEN) + 16;
	      unsigned snaplen = skb->len;
		  
	      spin_lock(&vcc->recvq.lock);
	      h = vcc->iovec[vcc->head];
	      if (h->tp_status) {
	         spin_unlock(&vcc->recvq.lock);
	         atomic_inc(&vcc->stats->rx_drop);
		     goto ring_is_full;
		  }
	      vcc->head = vcc->head != vcc->iovmax ? vcc->head+1 : 0;
	      spin_unlock(&vcc->recvq.lock);

	      if (off + snaplen > vcc->frame_size) {
		     snaplen = vcc->frame_size - off;
		     if ((int)snaplen < 0)
			    snaplen = 0;
	      }

	      memcpy((u8*)h + off, skb->data, snaplen);

	      h->tp_len = skb->len;
	      h->tp_snaplen = snaplen;
	      h->tp_mac = off;
	      h->tp_net = off;
	      h->tp_sec = skb->stamp.tv_sec;
	      h->tp_usec = skb->stamp.tv_usec;

		  h->tp_status = status;
		  mb();

		  {
			  struct page *p_start, *p_end;
			  u8 *h_end = (u8 *)h + off + snaplen - 1;

			  p_start = virt_to_page(h);
			  p_end = virt_to_page(h_end);
			  while (p_start <= p_end) {
				  flush_dcache_page(p_start);
				  p_start++;
			  }
		  }
		  wake_up(&vcc->sleep);

ring_is_full:
	      atm_return(vcc,skb->truesize);
	      dev_kfree_skb_any(skb);
	   }
#endif
	}
}


static void atm_pop_raw(struct atm_vcc *vcc,struct sk_buff *skb)
{
	DPRINTK("APopR (%d) %d -= %d\n",vcc->vci,vcc->tx_inuse,skb->truesize);
	atomic_sub(skb->truesize+ATM_PDU_OVHD,&vcc->tx_inuse);
	dev_kfree_skb_any(skb);
	wake_up(&vcc->sleep);
}


static int atm_send_aal0(struct atm_vcc *vcc,struct sk_buff *skb)
{
	/*
	 * Note that if vpi/vci are _ANY or _UNSPEC the below will
	 * still work
	 */
	if (!capable(CAP_NET_ADMIN) &&
            (((u32 *) skb->data)[0] & (ATM_HDR_VPI_MASK | ATM_HDR_VCI_MASK)) !=
            ((vcc->vpi << ATM_HDR_VPI_SHIFT) | (vcc->vci << ATM_HDR_VCI_SHIFT)))
	    {
		kfree_skb(skb);
		return -EADDRNOTAVAIL;
        }
	return vcc->dev->ops->send(vcc,skb);
}


int atm_init_aal0(struct atm_vcc *vcc)
{
	vcc->push = atm_push_raw;
	vcc->pop = atm_pop_raw;
	vcc->push_oam = NULL;
	vcc->send = atm_send_aal0;
	return 0;
}


int atm_init_aal34(struct atm_vcc *vcc)
{
	vcc->push = atm_push_raw;
	vcc->pop = atm_pop_raw;
	vcc->push_oam = NULL;
	vcc->send = vcc->dev->ops->send;
	return 0;
}


int atm_init_aal5(struct atm_vcc *vcc)
{
	vcc->push = atm_push_raw;
	vcc->pop = atm_pop_raw;
	vcc->push_oam = NULL;
	vcc->send = vcc->dev->ops->send;
	return 0;
}


EXPORT_SYMBOL(atm_init_aal5);
