/******************************************************************************
 *  atmsar.c  --  General SAR library for ATM devices.
 *
 *  Copyright (C) 2000, Johan Verrept
 *
 *  This program 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.
 *
 *  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.
 *
 ******************************************************************************/

/*
 *  Written by Johan Verrept (Johan.Verrept@advalvas.be)
 *
 *  0.2.4A:	- Version for inclusion in 2.5 series kernel
 *		- Modifications by Richard Purdie (rpurdie@rpsys.net)
 *		- replaced "sarlib" with "atmsar"
 *		- adaptations for inclusion in kernel tree
 *
 *  0.2.4:	- Fixed wrong buffer overrun check in atmsar_decode_rawcell()
 *		reported by Stephen Robinson <stephen.robinson@zen.co.uk>
 *		- Fixed bug when input skb did not contain a multple of 52/53
 *		bytes (would happen when the speedtouch device resynced)
 *		also reported by Stephen Robinson <stephen.robinson@zen.co.uk>
 *
 *  0.2.3:	- Fixed wrong allocation size. caused memory corruption in some
 *		cases. Reported by Vladimir Dergachev <volodya@mindspring.com>
 *		- Added some comments
 *
 *  0.2.2:	- Fixed CRCASM
 *		patch from Linus Flannagan <linusf@netservices.eng.net>
 *		- Fixed problem when user did NOT use the
 *		ATMSAR_USE_53BYTE_CELL flag.
 *		reported by  Piers Scannell <email@lot105.com>
 *		- No more in-buffer rewriting for cloned buffers.
 *		- Removed the PII specific CFLAGS in the Makefile.
 *
 *  0.2.1:	- removed dependency on alloc_tx. tis presented problems when
 *		using this with the br2684 code.
 *
 *  0.2:	- added AAL0 reassembly
 *		- added alloc_tx support
 *		- replaced alloc_skb in decode functions to dev_alloc_skb to
 *		allow calling from interrupt
 *		- fixed embarassing AAL5 bug. I was setting the pti bit in the
 *		wrong byte...
 *		- fixed another emabrassing bug.. picked up the wrong crc type
 *		and forgot to invert the crc result...
 *		- fixed AAL5 length calculations.
 *		- removed automatic skb freeing from encode functions.
 *		This caused problems because i did kfree_skb it, while it
 *		needed to be popped. I cannot determine though whether it
 *		needs to be popped or not. Figu'e it out ye'self ;-)
 *		- added mru field. This is the buffersize. atmsar_decode_aal0
 *		will use when it allocates a receive buffer. A stop gap for
 *		real buffer management.
 *
 *  0.1:	- library created.
 *		- only contains AAL5, AAL0 can be easily added. (actually, only
 *		AAL0 reassembly is missing)
 *
 */

#include "atmsar.h"

/***********************
 **
 **  things to remember
 **
 ***********************/

/*
  1. the atmsar_vcc_data list pointer MUST be initialized to NULL
  2. atmsar_encode_rawcell will drop incomplete cells.
  3. ownership of the skb goes to the library !
*/

#define ATM_HDR_VPVC_MASK  (ATM_HDR_VPI_MASK | ATM_HDR_VCI_MASK)

/***********************
 **
 **  LOCAL STRUCTURES
 **
 ***********************/

/***********************
 **
 **  LOCAL MACROS
 **
 ***********************/
/*
#define DEBUG 1
*/
#ifdef DEBUG
#define PDEBUG(arg...)  printk(KERN_DEBUG "atmsar: " arg)
#else
#define PDEBUG(arg...)
#endif

#define ADD_HEADER(dest, header) \
  *dest++ = (unsigned char) (header >> 24); \
  *dest++ = (unsigned char) (header >> 16); \
  *dest++ = (unsigned char) (header >> 8); \
  *dest++ = (unsigned char) (header & 0xff);

/*
 * CRC Routines from  net/wan/sbni.c)
 * table generated by Rocksoft^tm Model CRC Algorithm Table Generation Program V1.0
 */
#define CRC32_REMAINDER CBF43926
#define CRC32_INITIAL 0xffffffff
#define CRC32(c,crc) (crc32tab[((size_t)(crc>>24) ^ (c)) & 0xff] ^ (((crc) << 8)))
unsigned long crc32tab[256] = {
	0x00000000L, 0x04C11DB7L, 0x09823B6EL, 0x0D4326D9L,
	0x130476DCL, 0x17C56B6BL, 0x1A864DB2L, 0x1E475005L,
	0x2608EDB8L, 0x22C9F00FL, 0x2F8AD6D6L, 0x2B4BCB61L,
	0x350C9B64L, 0x31CD86D3L, 0x3C8EA00AL, 0x384FBDBDL,
	0x4C11DB70L, 0x48D0C6C7L, 0x4593E01EL, 0x4152FDA9L,
	0x5F15ADACL, 0x5BD4B01BL, 0x569796C2L, 0x52568B75L,
	0x6A1936C8L, 0x6ED82B7FL, 0x639B0DA6L, 0x675A1011L,
	0x791D4014L, 0x7DDC5DA3L, 0x709F7B7AL, 0x745E66CDL,
	0x9823B6E0L, 0x9CE2AB57L, 0x91A18D8EL, 0x95609039L,
	0x8B27C03CL, 0x8FE6DD8BL, 0x82A5FB52L, 0x8664E6E5L,
	0xBE2B5B58L, 0xBAEA46EFL, 0xB7A96036L, 0xB3687D81L,
	0xAD2F2D84L, 0xA9EE3033L, 0xA4AD16EAL, 0xA06C0B5DL,
	0xD4326D90L, 0xD0F37027L, 0xDDB056FEL, 0xD9714B49L,
	0xC7361B4CL, 0xC3F706FBL, 0xCEB42022L, 0xCA753D95L,
	0xF23A8028L, 0xF6FB9D9FL, 0xFBB8BB46L, 0xFF79A6F1L,
	0xE13EF6F4L, 0xE5FFEB43L, 0xE8BCCD9AL, 0xEC7DD02DL,
	0x34867077L, 0x30476DC0L, 0x3D044B19L, 0x39C556AEL,
	0x278206ABL, 0x23431B1CL, 0x2E003DC5L, 0x2AC12072L,
	0x128E9DCFL, 0x164F8078L, 0x1B0CA6A1L, 0x1FCDBB16L,
	0x018AEB13L, 0x054BF6A4L, 0x0808D07DL, 0x0CC9CDCAL,
	0x7897AB07L, 0x7C56B6B0L, 0x71159069L, 0x75D48DDEL,
	0x6B93DDDBL, 0x6F52C06CL, 0x6211E6B5L, 0x66D0FB02L,
	0x5E9F46BFL, 0x5A5E5B08L, 0x571D7DD1L, 0x53DC6066L,
	0x4D9B3063L, 0x495A2DD4L, 0x44190B0DL, 0x40D816BAL,
	0xACA5C697L, 0xA864DB20L, 0xA527FDF9L, 0xA1E6E04EL,
	0xBFA1B04BL, 0xBB60ADFCL, 0xB6238B25L, 0xB2E29692L,
	0x8AAD2B2FL, 0x8E6C3698L, 0x832F1041L, 0x87EE0DF6L,
	0x99A95DF3L, 0x9D684044L, 0x902B669DL, 0x94EA7B2AL,
	0xE0B41DE7L, 0xE4750050L, 0xE9362689L, 0xEDF73B3EL,
	0xF3B06B3BL, 0xF771768CL, 0xFA325055L, 0xFEF34DE2L,
	0xC6BCF05FL, 0xC27DEDE8L, 0xCF3ECB31L, 0xCBFFD686L,
	0xD5B88683L, 0xD1799B34L, 0xDC3ABDEDL, 0xD8FBA05AL,
	0x690CE0EEL, 0x6DCDFD59L, 0x608EDB80L, 0x644FC637L,
	0x7A089632L, 0x7EC98B85L, 0x738AAD5CL, 0x774BB0EBL,
	0x4F040D56L, 0x4BC510E1L, 0x46863638L, 0x42472B8FL,
	0x5C007B8AL, 0x58C1663DL, 0x558240E4L, 0x51435D53L,
	0x251D3B9EL, 0x21DC2629L, 0x2C9F00F0L, 0x285E1D47L,
	0x36194D42L, 0x32D850F5L, 0x3F9B762CL, 0x3B5A6B9BL,
	0x0315D626L, 0x07D4CB91L, 0x0A97ED48L, 0x0E56F0FFL,
	0x1011A0FAL, 0x14D0BD4DL, 0x19939B94L, 0x1D528623L,
	0xF12F560EL, 0xF5EE4BB9L, 0xF8AD6D60L, 0xFC6C70D7L,
	0xE22B20D2L, 0xE6EA3D65L, 0xEBA91BBCL, 0xEF68060BL,
	0xD727BBB6L, 0xD3E6A601L, 0xDEA580D8L, 0xDA649D6FL,
	0xC423CD6AL, 0xC0E2D0DDL, 0xCDA1F604L, 0xC960EBB3L,
	0xBD3E8D7EL, 0xB9FF90C9L, 0xB4BCB610L, 0xB07DABA7L,
	0xAE3AFBA2L, 0xAAFBE615L, 0xA7B8C0CCL, 0xA379DD7BL,
	0x9B3660C6L, 0x9FF77D71L, 0x92B45BA8L, 0x9675461FL,
	0x8832161AL, 0x8CF30BADL, 0x81B02D74L, 0x857130C3L,
	0x5D8A9099L, 0x594B8D2EL, 0x5408ABF7L, 0x50C9B640L,
	0x4E8EE645L, 0x4A4FFBF2L, 0x470CDD2BL, 0x43CDC09CL,
	0x7B827D21L, 0x7F436096L, 0x7200464FL, 0x76C15BF8L,
	0x68860BFDL, 0x6C47164AL, 0x61043093L, 0x65C52D24L,
	0x119B4BE9L, 0x155A565EL, 0x18197087L, 0x1CD86D30L,
	0x029F3D35L, 0x065E2082L, 0x0B1D065BL, 0x0FDC1BECL,
	0x3793A651L, 0x3352BBE6L, 0x3E119D3FL, 0x3AD08088L,
	0x2497D08DL, 0x2056CD3AL, 0x2D15EBE3L, 0x29D4F654L,
	0xC5A92679L, 0xC1683BCEL, 0xCC2B1D17L, 0xC8EA00A0L,
	0xD6AD50A5L, 0xD26C4D12L, 0xDF2F6BCBL, 0xDBEE767CL,
	0xE3A1CBC1L, 0xE760D676L, 0xEA23F0AFL, 0xEEE2ED18L,
	0xF0A5BD1DL, 0xF464A0AAL, 0xF9278673L, 0xFDE69BC4L,
	0x89B8FD09L, 0x8D79E0BEL, 0x803AC667L, 0x84FBDBD0L,
	0x9ABC8BD5L, 0x9E7D9662L, 0x933EB0BBL, 0x97FFAD0CL,
	0xAFB010B1L, 0xAB710D06L, 0xA6322BDFL, 0xA2F33668L,
	0xBCB4666DL, 0xB8757BDAL, 0xB5365D03L, 0xB1F740B4L
};

#ifdef CRCASM

unsigned long calc_crc (char *mem, int len, unsigned initial)
{
	unsigned crc, dummy_len;
      __asm__ ("xorl %%eax,%%eax\n\t" "1:\n\t" "movl %%edx,%%eax\n\t" "shrl $16,%%eax\n\t" "lodsb\n\t" "xorb %%ah,%%al\n\t" "andl $255,%%eax\n\t" "shll $8,%%edx\n\t" "xorl (%%edi,%%eax,4),%%edx\n\t" "loop 1b":"=d" (crc),
		 "=c"
		 (dummy_len)
      :	 "S" (mem), "D" (&crc32tab[0]), "1" (len), "0" (initial)
      :	 "eax");
	return crc;
}

#else

unsigned long calc_crc (char *mem, int len, unsigned initial)
{
	unsigned crc;
	crc = initial;

	for (; len; mem++, len--) {
		crc = CRC32 (*mem, crc);
	}
	return (crc);
}
#endif

#define crc32( crc, mem, len) calc_crc(mem, len, crc);

/* initialization routines. not used at the moment
 * I will avoid these as long as possible !!
 */

int open_atmsar (void)
{
	return 0;
}

int remove_atmsar (void)
{
	return 0;
}

/* ATOMIC version of alloc_tx */
struct sk_buff *atmsar_alloc_skb_wrapper (struct atm_vcc *vcc, unsigned int size)
{
	struct sk_buff *skb;

	if (atomic_read (&vcc->tx_inuse) && !atm_may_send (vcc, size)) {
		PDEBUG ("Sorry: tx_inuse = %d, size = %d, sndbuf = %d\n",
			atomic_read (&vcc->tx_inuse), size, vcc->sk->sndbuf);
		return NULL;
	}
	skb = alloc_skb (size, GFP_ATOMIC);
	if (!skb)
		return NULL;
	atomic_add (skb->truesize + ATM_PDU_OVHD, &vcc->tx_inuse);
	return skb;
}

struct sk_buff *atmsar_alloc_tx (struct atmsar_vcc_data *vcc, unsigned int size)
{
	struct sk_buff *tmp = NULL;
	int bufsize = 0;

	switch (vcc->type) {
	case ATMSAR_TYPE_AAL0:
		/* reserving adequate headroom */
		bufsize =
		    size + (((size / 48) + 1) * ((vcc->flags & ATMSAR_USE_53BYTE_CELL) ? 5 : 4));
		break;
	case ATMSAR_TYPE_AAL1:
		/* reserving adequate headroom */
		bufsize =
		    size + (((size / 47) + 1) * ((vcc->flags & ATMSAR_USE_53BYTE_CELL) ? 5 : 4));
		break;
	case ATMSAR_TYPE_AAL2:
	case ATMSAR_TYPE_AAL34:
		/* not supported */
		break;
	case ATMSAR_TYPE_AAL5:
		/* reserving adequate tailroom */
		bufsize = size + (((size + 8 + 47) / 48) * 48);
		break;
	}

	PDEBUG ("Requested size %d, Allocating size %d\n", size, bufsize);
	tmp = vcc->alloc_tx (vcc->vcc, bufsize);
	skb_put (tmp, bufsize);

	return tmp;
}

struct atmsar_vcc_data *atmsar_open (struct atmsar_vcc_data **list, struct atm_vcc *vcc, uint type,
				     ushort vpi, ushort vci, unchar pti, unchar gfc, uint flags)
{
	struct atmsar_vcc_data *new;

	if (!vcc)
		return NULL;

	new = kmalloc (sizeof (struct atmsar_vcc_data), GFP_KERNEL);

	if (!new)
		return NULL;

	memset (new, 0, sizeof (struct atmsar_vcc_data));
	new->vcc = vcc;
/*
 * This gives problems with the ATM layer alloc_tx().
 * It is not usable from interrupt context and for
 * some reason this is used in interrupt context
 * with br2684.c
 *
  if (vcc->alloc_tx)
    new->alloc_tx  = vcc->alloc_tx;
  else
*/
	new->alloc_tx = atmsar_alloc_skb_wrapper;

	new->stats = vcc->stats;
	new->type = type;
	new->next = NULL;
	new->gfc = gfc;
	new->vp = vpi;
	new->vc = vci;
	new->pti = pti;

	switch (type) {
	case ATMSAR_TYPE_AAL0:
		new->mtu = ATMSAR_DEF_MTU_AAL0;
		break;
	case ATMSAR_TYPE_AAL1:
		new->mtu = ATMSAR_DEF_MTU_AAL1;
		break;
	case ATMSAR_TYPE_AAL2:
		new->mtu = ATMSAR_DEF_MTU_AAL2;
		break;
	case ATMSAR_TYPE_AAL34:
		/* not supported */
		new->mtu = ATMSAR_DEF_MTU_AAL34;
		break;
	case ATMSAR_TYPE_AAL5:
		new->mtu = ATMSAR_DEF_MTU_AAL5;
		break;
	}

	new->atmHeader = ((unsigned long) gfc << ATM_HDR_GFC_SHIFT)
	    | ((unsigned long) vpi << ATM_HDR_VPI_SHIFT)
	    | ((unsigned long) vci << ATM_HDR_VCI_SHIFT)
	    | ((unsigned long) pti << ATM_HDR_PTI_SHIFT);
	new->flags = flags;
	new->next = NULL;
	new->reasBuffer = NULL;

	new->next = *list;
	*list = new;

	PDEBUG ("Allocated new SARLib vcc 0x%p with vp %d vc %d\n", new, vpi, vci);

	return new;
}

void atmsar_close (struct atmsar_vcc_data **list, struct atmsar_vcc_data *vcc)
{
	struct atmsar_vcc_data *work;

	if (*list == vcc) {
		*list = (*list)->next;
	} else {
		for (work = *list; work && work->next && (work->next != vcc); work = work->next);

		/* return if not found */
		if (work->next != vcc)
			return;

		work->next = work->next->next;
	}

	if (vcc->reasBuffer) {
		dev_kfree_skb (vcc->reasBuffer);
	}

	PDEBUG ("Allocated SARLib vcc 0x%p with vp %d vc %d\n", vcc, vcc->vp, vcc->vc);

	kfree (vcc);
}

/***********************
 **
 **    ENCODE FUNCTIONS
 **
 ***********************/

/* encapsulate in an AAL5 frame, which is then split into ATM cells */
unsigned int atmsar_encode (struct atmsar_vcc_data *ctx, char *source, char *target, unsigned int pdu_length)
{
	unsigned int num_cells = (pdu_length + ATM_AAL5_TRAILER + ATM_CELL_PAYLOAD - 1) / ATM_CELL_PAYLOAD;
	unsigned int num_pdu_cells = pdu_length / ATM_CELL_PAYLOAD + 1;
	unsigned int aal5_length = num_cells * ATM_CELL_PAYLOAD;
	unsigned int zero_padding = aal5_length - pdu_length - ATM_AAL5_TRAILER;
	unsigned int final_length = num_cells * ATM_CELL_SIZE;
	unsigned char aal5_trailer [ATM_AAL5_TRAILER];
	unsigned char cell_header [ATM_CELL_HEADER];
	u32 crc;
	int i;

	PDEBUG ("atmsar_encode entered\n");

	PDEBUG ("pdu_length %d, num_cells %d, num_pdu_cells %d, aal5_length %d, zero_padding %d, final_length %d\n", pdu_length, num_cells, num_pdu_cells, aal5_length, zero_padding, final_length);

	PDEBUG ("source 0x=%p, target 0x%p\n", source, target);

	aal5_trailer [0] = 0; /* UU = 0 */
	aal5_trailer [1] = 0; /* CPI = 0 */
	aal5_trailer [2] = pdu_length >> 8;
	aal5_trailer [3] = pdu_length;

	crc = crc32 (~0, source, pdu_length);
	for (i = 0; i < zero_padding; i++)
		crc = CRC32 (0, crc);
	crc = crc32 (crc, aal5_trailer, 4);
	crc = ~crc;

	aal5_trailer [4] = crc >> 24;
	aal5_trailer [5] = crc >> 16;
	aal5_trailer [6] = crc >> 8;
	aal5_trailer [7] = crc;

	cell_header [0] = ctx->atmHeader >> 24;
	cell_header [1] = ctx->atmHeader >> 16;
	cell_header [2] = ctx->atmHeader >> 8;
	cell_header [3] = ctx->atmHeader;
	cell_header [4] = 0xec;

	for (i = 1; i < num_pdu_cells; i++) {
		memcpy (target, cell_header, ATM_CELL_HEADER);
		target += ATM_CELL_HEADER;
		memcpy (target, source, ATM_CELL_PAYLOAD);
		target += ATM_CELL_PAYLOAD;
		source += ATM_CELL_PAYLOAD;
		PDEBUG ("source 0x=%p, target 0x%p\n", source, target);
	}
	memcpy (target, cell_header, ATM_CELL_HEADER);
	target += ATM_CELL_HEADER;
	memcpy (target, source, pdu_length % ATM_CELL_PAYLOAD);
	target += pdu_length % ATM_CELL_PAYLOAD;
	if (num_pdu_cells < num_cells) {
		memset (target, 0, zero_padding + ATM_AAL5_TRAILER - ATM_CELL_PAYLOAD);
		target += zero_padding + ATM_AAL5_TRAILER - ATM_CELL_PAYLOAD;
		memcpy (target, cell_header, ATM_CELL_HEADER);
		target += ATM_CELL_HEADER;
		zero_padding = ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER;
	}
	memset (target, 0, zero_padding);
	target += zero_padding;
	memcpy (target, aal5_trailer, ATM_AAL5_TRAILER);

	/* set pti bit in last cell */
	*(target + ATM_AAL5_TRAILER + 3 - ATM_CELL_SIZE) |= 0x2;

	/* update stats */
	if (ctx->stats)
		atomic_inc (&ctx->stats->tx);

	if (ctx->stats && (ctx->type <= ATMSAR_TYPE_AAL1))
		atomic_add (num_cells, &(ctx->stats->tx));

	return final_length;
}

struct sk_buff *atmsar_encode_rawcell (struct atmsar_vcc_data *ctx, struct sk_buff *skb)
{
	int number_of_cells = (skb->len) / 48;
	int total_length = number_of_cells * (ctx->flags & ATMSAR_USE_53BYTE_CELL ? 53 : 52);
	unsigned char *source;
	unsigned char *target;
	struct sk_buff *out = NULL;
	int i;

	PDEBUG ("atmsar_encode_rawcell (0x%p, 0x%p) called\n", ctx, skb);

	if (skb_cloned (skb)
	    || (skb_headroom (skb) <
		(number_of_cells * (ctx->flags & ATMSAR_USE_53BYTE_CELL ? 5 : 4)))) {
		PDEBUG
		    ("atmsar_encode_rawcell allocating new skb. ctx->alloc_tx = 0x%p, ctx->vcc = 0x%p\n",
		     ctx->alloc_tx, ctx->vcc);
		/* get new skb */
		out = ctx->alloc_tx (ctx->vcc, total_length);
		if (!out)
			return NULL;

		skb_put (out, total_length);
		source = skb->data;
		target = out->data;
	} else {
		PDEBUG ("atmsar_encode_rawcell: sufficient headroom\n");
		source = skb->data;
		skb_push (skb, number_of_cells * ((ctx->flags & ATMSAR_USE_53BYTE_CELL) ? 5 : 4));
		target = skb->data;
		out = skb;
	}

	PDEBUG ("source 0x=%p, target 0x%p\n", source, target);

	if (ctx->flags & ATMSAR_USE_53BYTE_CELL) {
		for (i = 0; i < number_of_cells; i++) {
			ADD_HEADER (target, ctx->atmHeader);
			*target++ = (char) 0xEC;
			memcpy (target, source, 48);
			target += 48;
			source += 48;
			PDEBUG ("source 0x=%p, target 0x%p\n", source, target);
		}
	} else {
		for (i = 0; i < number_of_cells; i++) {
			ADD_HEADER (target, ctx->atmHeader);
			memcpy (target, source, 48);
			target += 48;
			source += 48;
			PDEBUG ("source 0x=%p, target 0x%p\n", source, target);
		};
	}

	if (ctx->flags & ATMSAR_SET_PTI) {
		/* setting pti bit in last cell */
		*(target - (ctx->flags & ATMSAR_USE_53BYTE_CELL ? 50 : 49)) |= 0x2;
	}

	/* update stats */
	if (ctx->stats && (ctx->type <= ATMSAR_TYPE_AAL1))
		atomic_add (number_of_cells, &(ctx->stats->tx));

	PDEBUG ("atmsar_encode_rawcell return 0x%p (length %d)\n", out, out->len);
	return out;
}

struct sk_buff *atmsar_encode_aal5 (struct atmsar_vcc_data *ctx, struct sk_buff *skb)
{
	int length, pdu_length;
	unsigned char *trailer;
	unsigned char *pad;
	uint crc = 0xffffffff;

	PDEBUG ("atmsar_encode_aal5 (0x%p, 0x%p) called\n", ctx, skb);

	/* determine aal5 length */
	pdu_length = skb->len;
	length = ((pdu_length + 8 + 47) / 48) * 48;

	if (skb_tailroom (skb) < (length - pdu_length)) {
		struct sk_buff *out;
		PDEBUG
		    ("atmsar_encode_aal5 allocating new skb. ctx->alloc_tx = 0x%p, ctx->vcc = 0x%p\n",
		     ctx->alloc_tx, ctx->vcc);
		/* get new skb */
		out = ctx->alloc_tx (ctx->vcc, length);
		if (!out)
			return NULL;

		PDEBUG ("out->data = 0x%p\n", out->data);
		PDEBUG ("atmsar_encode_aal5 pdu length %d, allocated length %d\n", skb->len,
			length);
		memcpy (out->data, skb->data, skb->len);
		skb_put (out, skb->len);

		skb = out;
	}

	PDEBUG ("skb->data = 0x%p\n", skb->data);
	/* note end of pdu and add length */
	pad = skb_put (skb, length - pdu_length);
	trailer = skb->tail - 8;

	PDEBUG ("trailer = 0x%p\n", trailer);

	/* zero padding space */
	memset (pad, 0, length - pdu_length - 8);

	/* add trailer */
	*trailer++ = (unsigned char) 0;	/* UU  = 0 */
	*trailer++ = (unsigned char) 0;	/* CPI = 0 */
	*trailer++ = (unsigned char) (pdu_length >> 8);
	*trailer++ = (unsigned char) (pdu_length & 0xff);
	crc = ~crc32 (crc, skb->data, length - 4);
	*trailer++ = (unsigned char) (crc >> 24);
	*trailer++ = (unsigned char) (crc >> 16);
	*trailer++ = (unsigned char) (crc >> 8);
	*trailer++ = (unsigned char) (crc & 0xff);

	/* update stats */
	if (ctx->stats)
		atomic_inc (&ctx->stats->tx);

	PDEBUG ("atmsar_encode_aal5 return 0x%p (length %d)\n", skb, skb->len);
	return skb;
}

/***********************
 **
 **  DECODE FUNCTIONS
 **
 ***********************/

struct sk_buff *atmsar_decode_rawcell (struct atmsar_vcc_data *list, struct sk_buff *skb,
				       struct atmsar_vcc_data **ctx)
{
	while (skb->len) {
		unsigned char *cell = skb->data;
		unsigned char *cell_payload;
		struct atmsar_vcc_data *vcc = list;
		unsigned long atmHeader =
		    ((unsigned long) (cell[0]) << 24) | ((unsigned long) (cell[1]) << 16) |
		    ((unsigned long) (cell[2]) << 8) | (cell[3] & 0xff);

		PDEBUG ("atmsar_decode_rawcell (0x%p, 0x%p, 0x%p) called\n", list, skb, ctx);
		PDEBUG ("atmsar_decode_rawcell skb->data %p, skb->tail %p\n", skb->data, skb->tail);

		if (!list || !skb || !ctx)
			return NULL;
		if (!skb->data || !skb->tail)
			return NULL;

		/* here should the header CRC check be... */

		/* look up correct vcc */
		for (;
		     vcc
		     && ((vcc->atmHeader & ATM_HDR_VPVC_MASK) != (atmHeader & ATM_HDR_VPVC_MASK));
		     vcc = vcc->next);

		PDEBUG ("atmsar_decode_rawcell found vcc %p for packet on vp %d, vc %d\n", vcc,
			(int) ((atmHeader & ATM_HDR_VPI_MASK) >> ATM_HDR_VPI_SHIFT),
			(int) ((atmHeader & ATM_HDR_VCI_MASK) >> ATM_HDR_VCI_SHIFT));

		if (vcc && (skb->len >= (vcc->flags & ATMSAR_USE_53BYTE_CELL ? 53 : 52))) {
			cell_payload = cell + (vcc->flags & ATMSAR_USE_53BYTE_CELL ? 5 : 4);

			switch (vcc->type) {
			case ATMSAR_TYPE_AAL0:
				/* case ATMSAR_TYPE_AAL1: when we have a decode AAL1 function... */
				{
					struct sk_buff *tmp = dev_alloc_skb (vcc->mtu);

					if (tmp) {
						memcpy (tmp->tail, cell_payload, 48);
						skb_put (tmp, 48);

						if (vcc->stats)
							atomic_inc (&vcc->stats->rx);

						skb_pull (skb,
							  (vcc->
							   flags & ATMSAR_USE_53BYTE_CELL ? 53 :
							   52));
						PDEBUG
						    ("atmsar_decode_rawcell returns ATMSAR_TYPE_AAL0 pdu 0x%p with length %d\n",
						     tmp, tmp->len);
						return tmp;
					};
				}
				break;
			case ATMSAR_TYPE_AAL1:
			case ATMSAR_TYPE_AAL2:
			case ATMSAR_TYPE_AAL34:
				/* not supported */
				break;
			case ATMSAR_TYPE_AAL5:
				if (!vcc->reasBuffer)
					vcc->reasBuffer = dev_alloc_skb (vcc->mtu);

				/* if alloc fails, we just drop the cell. it is possible that we can still
				 * receive cells on other vcc's
				 */
				if (vcc->reasBuffer) {
					/* if (buffer overrun) discard received cells until now */
					if ((vcc->reasBuffer->len) > (vcc->mtu - 48))
						skb_trim (vcc->reasBuffer, 0);

					/* copy data */
					memcpy (vcc->reasBuffer->tail, cell_payload, 48);
					skb_put (vcc->reasBuffer, 48);

					/* check for end of buffer */
					if (cell[3] & 0x2) {
						struct sk_buff *tmp;

						/* the aal5 buffer ends here, cut the buffer. */
						/* buffer will always have at least one whole cell, so */
						/* don't need to check return from skb_pull */
						skb_pull (skb,
							  (vcc->
							   flags & ATMSAR_USE_53BYTE_CELL ? 53 :
							   52));
						*ctx = vcc;
						tmp = vcc->reasBuffer;
						vcc->reasBuffer = NULL;

						PDEBUG
						    ("atmsar_decode_rawcell returns ATMSAR_TYPE_AAL5 pdu 0x%p with length %d\n",
						     tmp, tmp->len);
						return tmp;
					}
				}
				break;
			};
			/* flush the cell */
			/* buffer will always contain at least one whole cell, so don't */
			/* need to check return value from skb_pull */
			skb_pull (skb, (vcc->flags & ATMSAR_USE_53BYTE_CELL ? 53 : 52));
		} else {
			/* If data is corrupt and skb doesn't hold a whole cell, flush the lot */
			if (skb_pull (skb, (list->flags & ATMSAR_USE_53BYTE_CELL ? 53 : 52)) ==
			    NULL)
				return NULL;
		}
	}

	return NULL;
};

struct sk_buff *atmsar_decode_aal5 (struct atmsar_vcc_data *ctx, struct sk_buff *skb)
{
	uint crc = 0xffffffff;
	uint length, pdu_crc, pdu_length;

	PDEBUG ("atmsar_decode_aal5 (0x%p, 0x%p) called\n", ctx, skb);

	if (skb->len && (skb->len % 48))
		return NULL;

	length = (skb->tail[-6] << 8) + skb->tail[-5];
	pdu_crc =
	    (skb->tail[-4] << 24) + (skb->tail[-3] << 16) + (skb->tail[-2] << 8) + skb->tail[-1];
	pdu_length = ((length + 47 + 8) / 48) * 48;

	PDEBUG ("atmsar_decode_aal5: skb->len = %d, length = %d, pdu_crc = 0x%x, pdu_length = %d\n",
		skb->len, length, pdu_crc, pdu_length);

	/* is skb long enough ? */
	if (skb->len < pdu_length) {
		if (ctx->stats)
			atomic_inc (&ctx->stats->rx_err);
		return NULL;
	}

	/* is skb too long ? */
	if (skb->len > pdu_length) {
		PDEBUG ("atmsar_decode_aal5: Warning: readjusting illeagl size %d -> %d\n",
			skb->len, pdu_length);
		/* buffer is too long. we can try to recover
		 * if we discard the first part of the skb.
		 * the crc will decide whether this was ok
		 */
		skb_pull (skb, skb->len - pdu_length);
	}

	crc = ~crc32 (crc, skb->data, pdu_length - 4);

	/* check crc */
	if (pdu_crc != crc) {
		PDEBUG ("atmsar_decode_aal5: crc check failed!\n");
		if (ctx->stats)
			atomic_inc (&ctx->stats->rx_err);
		return NULL;
	}

	/* pdu is ok */
	skb_trim (skb, length);

	/* update stats */
	if (ctx->stats)
		atomic_inc (&ctx->stats->rx);

	PDEBUG ("atmsar_decode_aal5 returns pdu 0x%p with length %d\n", skb, skb->len);
	return skb;
};