/*****************************************************************************

 @(#) sdl_udp.c,v SS7AlphaRelease(0.7.8.1) 2001/12/11 13:15:47

 -----------------------------------------------------------------------------

 Copyright (c) 2001  OpenSS7 Corporation <http://www.openss7.com>
 Copyright (c) 1997-2000  Brian F. G. Bidulock <bidulock@dallas.net>

 All Rights Reserved.

 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., 675 Mass
 Ave, Cambridge, MA 02139, USA.

 -----------------------------------------------------------------------------

 U.S. GOVERNMENT RESTRICTED RIGHTS.  If you are licensing this Software on
 behalf of the U.S. Government ("Government"), the following provisions apply
 to you.  If the Software is supplied by the Department of Defense ("DoD"), it
 is classified as "Commercial Computer Software" under paragraph 252.227-7014
 of the DoD Supplement to the Federal Acquisition Regulations ("DFARS") (or any
 successor regulations) and the Government is acquiring only the license rights
 granted herein (the license rights customarily provided to non-Government
 users).  If the Software is supplied to any unit or agency of the Government
 other than DoD, it is classified as "Restricted Computer Software" and the
 Government's rights in the Software are defined in paragraph 52.227-19 of the
 Federal Acquisition Regulations ("FAR") (or any success regulations) or, in
 the cases of NASA, in paragraph 18.52.227-86 of the NASA Supplement to the FAR
 (or any successor regulations).

 -----------------------------------------------------------------------------

 Last Modified 2001/12/11 13:15:47 by brian

 *****************************************************************************/

#ident "@(#) sdl_udp.c,v SS7AlphaRelease(0.7.8.1) 2001/12/11 13:15:47"

static char const ident[] = "sdl_udp.c,v SS7AlphaRelease(0.7.8.1) 2001/12/11 13:15:47";

/*
 *  This is a UDP emulation of the Siganlling Data Link for use in testing and
 *  experimentation with the SS7 stack.  This driver obviates the need for a
 *  hardware card for testing the SS7 stack.  This driver is loosely based on
 *  the draft-bressler-sigtran-ipss7l2-00.txt SIGTRAN draft which specifies
 *  SS7 over TCP.
 *
 *  The UDP emulation behaves in the fashion that an actual driver would
 *  behave as an SDL provider; however, repetitions of FISUs and LSSUs are not
 *  transmitted continuously on the UDP link, only the first occurence of the
 *  repetition is transmitted and another occurence of the repetition is
 *  transmitted for every change in a received frame from UDP.
 *
 *  The UDP emulation can be run at full line (LAN) rate for transfering MSUs;
 *  however, for line rates below full network rate, traffic shaping is
 *  performed to throttle transmissions of MSUs to the average simulated rate
 *  in 10ms (1 tick) bursts.
 */

#include <linux/config.h>
#include <linux/version.h>
#include <linux/modversions.h>
#include <linux/socket.h>
#include <net/sock.h>
#include <linux/skbuff.h>

#include <linux/module.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/cmn_err.h>
#include <sys/dki.h>

#include <linux/errno.h>
#include <linux/types.h>
#include <linux/in.h>
#include <linux/sockios.h>
#include <linux/net.h>
#include <linux/inet.h>
#include <linux/udp.h>

#ifndef __inline__
#define __inline__ inline
#endif

#include "../debug.h"
#include "../bufq.h"

#include <ss7/lmi.h>
#include <ss7/lmi_ioctl.h>
#include <ss7/devi.h>
#include <ss7/devi_ioctl.h>
#include <ss7/sdli.h>
#include <ss7/sdli_ioctl.h>

#include "../lmi/lm.h"
#include "../devi/dev.h"
#include "../sdli/sdl.h"

#define SDL_DESCRIP	"SS7/SDL/UDP: (Signalling Data Link over UDP) STREAMS DRIVER."
#define SDL_COPYRIGHT	"Copyright (c) 1997-2001 Brian Bidulock.  All Rights Reserved."
#define SDL_DEVICES	"Supports Linux Kernel UDP sockets."
#define SDL_CONTACT	"Brian Bidulock <bidulock@openss7.org>"
#define SDL_BANNER	SDL_DESCRIP   "\n" \
			SDL_COPYRIGHT "\n" \
			SDL_DEVICES   "\n" \
			SDL_CONTACT   "\n"

#ifdef MODULE
MODULE_AUTHOR(SDL_CONTACT);
MODULE_DESCRIPTION(SDL_DESCRIP);
MODULE_SUPPORTED_DEVICE(SDL_DEVICES);
#define MODULE_STATIC static
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#define MODULE_STATIC
#endif

#ifdef SDL_DEBUG
static int sdl_debug = SDL_DEBUG;
#else
static int sdl_debug = 2;
#endif

#define DEBUG_LEVEL sdl_debug

#if 0
// #define SDL_MOD_ID		0x1111
// #define SDL_MOD_NAME		"sdl_udp"
// #define SDL_MIN_SDU		3
// #define SDL_MAX_SDU		277
// #define SDL_HIWATER		1
// #define SDL_LOWATER		0
#endif

#ifndef SDL_UDP_CMAJOR
#define SDL_UDP_CMAJOR	252 /* FIXNE: pick something    */
#endif
#define SDL_UDP_NMINOR	255 /* as many as possible      */

typedef struct {
	struct dev		dev;		/* device structure	*/
	struct sockaddr_in	loc_addr;	/* local  ip/port	*/
	struct sockaddr_in	rem_addr;	/* remote ip/port	*/
	struct socket		*udpsock;	/* UDP socket		*/
	signed long int		timestamp;	/* last tick		*/
	signed long int		tickbytes;	/* bytes per tick	*/
	signed long int		bytecount;	/* bytes this tick	*/
	struct timer_list	wakeup;		/* wakeup timer		*/
	bufq_t			txq;		/* transmit queue	*/
} sdl_udpdev_t;

/*
 *  =========================================
 *
 *  DEVICE SPECIFIC Procedures
 *
 *  =========================================
 */

/*
 *  -----------------------------------------
 *
 *  IOCTL
 *
 *  -----------------------------------------
 */
static int sdl_udp_ioctl(lmi_t *sdl, int cmd, void *arg)
{
	size_t size = _IOC_SIZE(cmd);
	sdl_udpdev_t *udp = (sdl_udpdev_t *)sdl->device;
	sdl_config_t *ureq = NULL;
	sdl_ulong uarg = 0;
	switch (cmd)
	{
		case SDL_IOCTCONFIG:
		case SDL_IOCSCONFIG:
			if ( !arg || size < sizeof(dev_device_t) )
				return EINVAL;
			ureq = arg;
			break;
		case DEV_IOCGIFFLAGS:
		case DEV_IOCGIFTYPE:
		case DEV_IOCGGRPTYPE:
		case DEV_IOCGIFMODE:
		case DEV_IOCGIFRATE:
		case DEV_IOCGIFCLOCK:
		case DEV_IOCGIFCODING:
		case DEV_IOCGIFLEADS:
		case DEV_IOCSIFFLAGS:
		case DEV_IOCSIFTYPE:
		case DEV_IOCSGRPTYPE:
		case DEV_IOCSIFMODE:
		case DEV_IOCSIFRATE:
		case DEV_IOCSIFCLOCK:
		case DEV_IOCSIFCODING:
		case DEV_IOCSIFLEADS:
		case DEV_IOCCIFLEADS:
			if ( !arg || size < sizeof(dev_ulong) )
				return EINVAL;
			uarg = *(dev_ulong *)arg;
			break;
		case SDL_IOCCCONFIG:
		case SDL_IOCCMRESET:
		case DEV_IOCCIFRESET:
		case DEV_IOCCDISCTX:
		case DEV_IOCCCONNTX:
			break;
	}
	switch (cmd)
	{
		case SDL_IOCTCONFIG:
		case SDL_IOCSCONFIG:
		case SDL_IOCCCONFIG:
		case SDL_IOCCMRESET:
		case DEV_IOCCIFRESET:
			break;
		case DEV_IOCGIFFLAGS:
			*(dev_ulong *)arg = udp->dev.iface.ifflags;
			return(0);
		case DEV_IOCGIFTYPE:
			*(dev_ulong *)arg = udp->dev.iface.iftype;
			return(0);
		case DEV_IOCGGRPTYPE:
			*(dev_ulong *)arg = udp->dev.iface.ifgtype;
			return(0);
		case DEV_IOCGIFMODE:
			*(dev_ulong *)arg = udp->dev.iface.ifmode;
			return(0);
		case DEV_IOCGIFRATE:
			*(dev_ulong *)arg = udp->dev.iface.ifrate;
			return(0);
		case DEV_IOCGIFCLOCK:
			*(dev_ulong *)arg = udp->dev.iface.ifclock;
			return(0);
		case DEV_IOCGIFCODING:
			*(dev_ulong *)arg = udp->dev.iface.ifcoding;
			return(0);
		case DEV_IOCGIFLEADS:
			*(dev_ulong *)arg = udp->dev.iface.ifleads;
			return(0);
		case DEV_IOCSIFFLAGS:
			udp->dev.iface.ifflags = uarg;
			return(0);
		case DEV_IOCSIFTYPE:
			if ( uarg != DEV_TYPE_PACKET )
				return EINVAL;
			return(0);
		case DEV_IOCSGRPTYPE:
			if ( uarg != DEV_GTYPE_UDP )
				return EINVAL;
			return(0);
		case DEV_IOCSIFMODE:
			if ( uarg != DEV_MODE_PEER )
				return EINVAL;
			return(0);
		case DEV_IOCSIFRATE:
		{
			int tdiff;
			if ( uarg < 800L || uarg > 100000000L )
				return EINVAL;
			udp->dev.iface.ifrate = uarg;
			if ( (tdiff = udp->timestamp - jiffies) > 0 )
				udp->bytecount += udp->tickbytes * tdiff;
			else
				udp->bytecount = 0;
			udp->timestamp = jiffies;
			udp->tickbytes = uarg/HZ/8;
			while ( udp->bytecount >= udp->tickbytes )
			{
				udp->bytecount -= udp->tickbytes;
				udp->timestamp++;
			}
			return(0);
		}
		case DEV_IOCSIFCLOCK:
			if ( uarg != DEV_CLOCK_SHAPER &&
					uarg != DEV_CLOCK_TICK)
				return EINVAL;
			udp->dev.iface.ifclock = uarg;
			return(0);
		case DEV_IOCSIFCODING:
			if ( uarg != DEV_CODING_NONE )
				return EINVAL;
			return(0);
		case DEV_IOCSIFLEADS:
		case DEV_IOCCIFLEADS:
			if ( uarg )
				return EINVAL;
			return(0);
		case DEV_IOCCDISCTX:
			udp->dev.iface.ifflags &= ~DEV_IF_TX_RUNNING;
			bufq_purge(&udp->txq);
			return(0);
		case DEV_IOCCCONNTX:
			udp->dev.iface.ifflags |= DEV_IF_TX_RUNNING;
			return(0);
	}
	return EOPNOTSUPP;
}

static dev_device_t dev_udpdev_default =
{
	{ },			/* iflock	*/
	0,			/* ifflags	*/
	DEV_TYPE_PACKET,	/* iftype	*/
	DEV_GTYPE_UDP,		/* ifgtype	*/
	DEV_MODE_PEER,		/* ifmode	*/
	64000,			/* ifrate	*/
	DEV_CLOCK_SHAPER,	/* ifclock	*/
	DEV_CODING_NONE,	/* ifcoding	*/
};

static void sdl_udp_send(struct dev *dev);

static lmi_t *
sdl_udp_devatt(dev_t dev)
{
	sdl_udpdev_t *udp;
	if ( (udp = kmalloc(sizeof(sdl_udpdev_t), GFP_KERNEL)) )
	{
		MOD_INC_USE_COUNT;
		bzero(udp, sizeof(*udp));
		bcopy(&dev_udpdev_default, &udp->dev.iface, sizeof(udp->dev.iface));
		udp->timestamp = jiffies;
		udp->tickbytes = 1544000/HZ/8;
		udp->bytecount = 0;
		bufq_init(&udp->txq);
		init_timer(&udp->wakeup);
		udp->wakeup.data = (unsigned long)udp;
		udp->wakeup.function = (void (*)(unsigned long))&sdl_udp_send;
		return((lmi_t *)udp);
	}
	return NULL;
}
static int
sdl_udp_open(lmi_t *lmi)
{
	return(0);  /* can't open yet, no address */
}
static int
sdl_udp_close(lmi_t *lmi)
{
	sdl_udpdev_t *udp = (sdl_udpdev_t *)lmi;
	bufq_purge(&udp->txq);
	del_timer(&udp->wakeup);
	kfree(udp);
	MOD_DEC_USE_COUNT;
	return(0);
}
static int
sdl_udp_infor(lmi_t *lmi, void **ppap, int *lenp)
{
	sdl_udpdev_t *udp = (sdl_udpdev_t *)lmi;
	size_t count = 2*sizeof(struct sockaddr_in);
	ensure( udp, return EINVAL );
	*ppap = &udp->loc_addr;
	*lenp = count;
#if _DEBUG
	{
		int i;
		char *c;
		printk("PPA =");
		for ( i=0, c=(char *)*ppap; i<count; i++, c++ )
			printk(" %02x", *c);
		printk("\n");
	}
#endif
	return(0);
}
static int
sdl_udp_attach(lmi_t *lmi, void *ppa, int len)
{
	sdl_udpdev_t *udp = (sdl_udpdev_t *)lmi;
	size_t count = 2*sizeof(struct sockaddr_in);
	if ( len < count )
		return EINVAL | LMI_BADPPA;
	bcopy(ppa, &udp->loc_addr, count);
#if _DEBUG
	{
		int i;
		char *c;
		printk("PPA =");
		for ( i=0, c=(char *)ppa; i<count; i++, c++ )
			printk(" %02x", *c);
		printk("\n");
	}
#endif
	return(0);
}
static int
sdl_udp_detach(lmi_t *lmi)
{
	sdl_udpdev_t *udp = (sdl_udpdev_t *)lmi;
	bzero(&udp->loc_addr, 2*sizeof(struct sockaddr_in));
	return(0);
}
static void sdl_udp_data_ready(struct sock *, int);
#ifndef abs
#define abs(x) ((x)<0 ? -(x):(x))
#endif
static int
sdl_udp_enable(lmi_t *lmi)
{
	int err;
	struct socket *sock = NULL;
	sdl_udpdev_t *udp = (sdl_udpdev_t *)lmi;
	struct sockaddr *sa = (struct sockaddr *)&udp->loc_addr;
	err = sock_create(PF_INET, SOCK_DGRAM, 0, &sock);
	if ( err < 0 )
		return (abs(err)|LMI_INITFAILED);
	sock->sk->reuse = 1;    /* FIXME: check this */
	sock->sk->allocation = GFP_ATOMIC;
	err = sock->ops->bind(sock, sa, sizeof(*sa));
	if ( err < 0 )
	{
		sock_release(sock);
		return (abs(err)|LMI_BADPPA);
	}
	sock->sk->protinfo.destruct_hook = udp; /* might be bad idea */
	sock->sk->data_ready = sdl_udp_data_ready;
	udp->udpsock = sock;
	return (0);
}
static int
sdl_udp_disable(lmi_t *lmi)
{
	sdl_udpdev_t *udp = (sdl_udpdev_t *)lmi;
	struct socket *sock;
	if ( udp )
	{
		udp->dev.iface.ifflags &= ~DEV_IF_TX_RUNNING;
		if ( (sock = udp->udpsock) )
		{
			sock->sk->protinfo.destruct_hook = NULL;
			sock_release(sock);
			udp->udpsock = NULL;
			bufq_purge(&udp->txq);
			del_timer(&udp->wakeup);
		}
		udp->dev.iface.ifflags &= ~DEV_IF_RX_RUNNING;
	}
	return(0);
}
static struct lmi_ops sdl_udp_lmi_ops =
{
	{
		sdl_udp_devatt,		/* dev.attach	*/
		sdl_udp_open,		/* dev.open	*/
		sdl_udp_close		/* dev.close	*/
	},
	{
		sdl_udp_infor,		/* lmi.info	*/
		sdl_udp_attach,		/* lmi.attach	*/
		sdl_udp_detach,		/* lmi.detach	*/
		sdl_udp_enable,		/* lmi.enable	*/
		sdl_udp_disable,	/* lmi.disable	*/
		sdl_udp_ioctl		/* lmi.ioctl	*/
	}
};

/*
 *  =========================================================================
 *
 *  SERVICE ROUTINES
 *
 *  =========================================================================
 */
/*
 *  This is equivalent of RxISR.
 */
static void
sdl_udp_data_ready(struct sock *sk, int bytes)
{
	int err = 0;
	struct sk_buff *skb;
	struct dev *dev = sk->protinfo.destruct_hook;
	int rx_on = ( dev && (dev->iface.ifflags & DEV_IF_RX_RUNNING) );
	(void)bytes;
	while ( (skb = skb_recv_datagram(sk, 0, 1, &err)) )
	{
		if ( rx_on )
		{
			mblk_t *mp;
			caddr_t data = skb->h.raw + sizeof(struct udphdr);
			size_t  len  = skb->len   - sizeof(struct udphdr);
			dev->module->stats.rx_sus++;
			dev->module->stats.rx_bytes += len;
			skb->sk = sk;   /* just to be sure */
//			if ( (mp = sdl_udp_esballoc(skb, data, len)) )
			{
				if ( (mp = allocb(len, BPRI_MED)) )
				{
					bcopy(data, mp->b_wptr, len);
					mp->b_wptr += len;
					mp->b_datap->db_type = M_DATA;
					dev->ucalls->daedr_recvd_frame(dev, mp);
				}
				else
				{
					dev->module->stats.rx_overruns++;
					dev->module->stats.rx_buffer_overflows++;
					dev->ucalls->daedr_error_frame(dev);
				}
			}
		}
		skb_free_datagram(sk, skb);
	}
//	FIXME: ignore errors for now...
//	if ( err && rx_on && abs(err) != EAGAIN )
//		/* FIXME: analyze this error */
//		dev->ucalls->daedr_error_frame(dev);
	return;
}
/*
 *  This is equivalent of TxISR.
 */
static void
sdl_udp_send(struct dev *dev)
{
	int n, size, tdiff;
	mblk_t *mp, *db;
	sdl_udpdev_t *udp = (sdl_udpdev_t *)dev;
	if ( !(dev->iface.ifflags & DEV_IF_TX_RUNNING) )
		return;
	while ( (mp = bufq_head(&udp->txq)) )
	{
		if ( (tdiff = jiffies - udp->timestamp) < 0 )
		{
			ptrace(("throttling!\n"));
			mod_timer(&udp->wakeup, jiffies+1); /* throttle back */
			return;
		}
		if ( tdiff > 0 )
		{
			udp->bytecount = 0;
			udp->timestamp = jiffies;
		}
		for ( size=0, n=0, db=mp; db; db=db->b_cont )
			if ( db->b_datap->db_type == M_DATA && db->b_wptr > db->b_rptr )
			{
				size += db->b_wptr - db->b_rptr;
				n++;
			}
//		ptrace(("number M_DATA blocks = %d\n",n));
		if ( n )
		{
			int i, err=0;
			struct msghdr udpmsg;
			struct iovec iov[n];
			for ( i=0, db=mp; db; db=db->b_cont )
			{
				if ( db->b_datap->db_type == M_DATA && db->b_wptr > db->b_rptr )
				{
					iov[i].iov_base = db->b_rptr;
					iov[i].iov_len  = db->b_wptr - db->b_rptr;
					i++;
				}
			}
			udpmsg.msg_name = (void *)&udp->rem_addr;
			udpmsg.msg_namelen = sizeof(udp->rem_addr);
			udpmsg.msg_iov = iov;
			udpmsg.msg_iovlen = n;
			udpmsg.msg_control = NULL;
			udpmsg.msg_controllen = 0;
			udpmsg.msg_flags = 0;
			{
				mm_segment_t fs = get_fs();
				set_fs(KERNEL_DS);
				err = sock_sendmsg(udp->udpsock, &udpmsg, size);
				set_fs(fs);
			}
			if ( err < 0 )
			{
				ptrace(("Error = %d\n",err));
				switch ( -err )
				{
					case ENOMEM:		/* ran out of memory */
					case ENOBUFS:		/* ran out of memory */
					case EAGAIN:		/* no space on non-blocking socket */
					case ERESTARTSYS:	/* signal */
						mod_timer(&udp->wakeup, jiffies+3);
						return;		/* temporary, try again later */
					default:
					case EINVAL:		/* bad address */
					case EACCES:		/* tried to send on broadcast address from nonbroadcast socket */
					case ENODEV:		/* bad index on iface directed output */
					case EFAULT:		/* bad user space address */
					case EPERM:		/* blocked by firewall */
					case EMSGSIZE:		/* message bigger than MTU */
					case EPIPE:		/* socket closed */
					case ENOTCONN:		/* sk->state != TCP_ESTABLISHED connected socket */
					case ECONNREFUSED:	/* no receiver */
					case ENETDOWN:		/* couldn't route to destination */
					case ENETUNREACH:	/* cant route to dest */
					case EHOSTUNREACH:	/* no routing entry to dest */
						dev->module->stats.tx_sus_in_error+=bufq_length(&udp->txq);
						bufq_purge(&udp->txq);
						return;		/* bad error, link unusable */
				}
			}
			dev->module->stats.tx_bytes += size;
			dev->module->stats.tx_sus++;
			udp->bytecount += size;
			while ( udp->bytecount >= udp->tickbytes )
			{
				udp->bytecount -= udp->tickbytes;
				udp->timestamp++;
			}
		}
		if ( dev->iface.ifclock == DEV_CLOCK_TICK )
		{
			if ( bufq_length(&udp->txq) == 1 )
			{
				int lssu = ((dev->module->option.popt&SS7_POPT_XSN)?6:3)+1;
				int lss2 = lssu+1;
				/* repeat FISUs or LSSUs other than SIB until told otherwise */
				if ( size < lss2+1
						&& ( size != lssu || mp->b_rptr[lssu-1] != 0x5 )
						&& ( size != lss2 || mp->b_rptr[lss2-1] != 0x5 )
						)
				{
					mod_timer(&udp->wakeup, jiffies+3);
					return;
				}
			}
		}
		bufq_freehead(&udp->txq);
	}
	dev->ucalls->daedt_tx_request(dev);
}
static void
sdl_udp_xmit(struct dev *dev, mblk_t *mp)
{
	sdl_udpdev_t *udp = (sdl_udpdev_t *)dev;
	del_timer(&udp->wakeup);
	if ( dev->iface.ifflags & DEV_IF_TX_RUNNING )
	{
		bufq_queue(&udp->txq, mp);
		sdl_udp_send(dev);
	}
	else
	{
		assert(mp->b_datap->db_ref);
		freemsg(mp);
	}
}
static void
sdl_udp_tx_start(struct dev *dev)
{
	dev->iface.ifflags |= DEV_IF_TX_RUNNING;
	return;
}
static void
sdl_udp_rx_start(struct dev *dev)
{
	dev->iface.ifflags |= DEV_IF_RX_RUNNING;
	return;
}
static dev_dcalls_t sdl_udp_dcalls = 
{
	sdl_udp_xmit,		/* daedt_xmit	*/
	sdl_udp_tx_start,	/* daedt_start	*/
	sdl_udp_rx_start	/* daedr_start	*/
};

/*
 *  =======================================================================
 *
 *  LiS Module Initialization   (For registered driver.)
 *
 *  =======================================================================
 */
static int sdl_udp_major = SDL_UDP_CMAJOR;
static int sdl_udp_initialized = 0;
void sdl_udp_init(void)
{
	if ( sdl_udp_initialized > 0 )
		return;
	cmn_err(CE_NOTE, SDL_BANNER);   /* console splash */
//	ptrace(("registering sdl_udp: cmajor %d, nminors %d\n", SDL_UDP_CMAJOR, SDL_UDP_NMINOR));
	sdl_udp_initialized
		= sdl_register_driver(SDL_UDP_CMAJOR, SDL_UDP_NMINOR, "sdl_udp",
				&sdl_udp_lmi_ops, &sdl_udp_dcalls);
//	ptrace(("return (device cmajor) = %d\n", sdl_udp_initialized));
	if ( sdl_udp_initialized > 0 )
		sdl_udp_major = sdl_udp_initialized;
	if ( sdl_udp_initialized == 0 )
	{
		sdl_udp_initialized = sdl_udp_major;
	}
}
void sdl_udp_terminate(void)
{
	if ( sdl_udp_initialized <= 0 )
		return;
//	ptrace(("unregistering sdl_udp cmajor %d\n", sdl_udp_major));
	sdl_udp_initialized = sdl_unregister_driver(sdl_udp_major);
//	ptrace(("return = %d\n", sdl_udp_initialized));
}

/*
 *  =======================================================================
 *
 *  Kernel Module Initialization   (For unregistered driver.)
 *
 *  =======================================================================
 */
#ifdef MODULE
int init_module(void)
{
	(void)sdl_debug;
	sdl_udp_init();
	if ( sdl_udp_initialized < 0 ) return sdl_udp_initialized;
	return(0);
}
void cleanup_module(void)
{
	sdl_udp_terminate();
}
#endif


