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

 @(#) $Id: sdt_udp.c,v 0.7.2.8 2001/02/03 05:48:48 brian Test $

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

 Copyright (C) 1997-2001  Brian 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.

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

 Last Modified $Date: 2001/02/03 05:48:48 $ by $Author: brian $

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

static char const ident[] = "$Id: sdt_udp.c,v 0.7.2.8 2001/02/03 05:48:48 brian Test $";

/*
 *  This is a UDP emulation of the Siganlling Data Terminal 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 SDT provider; however, repetitions of FISUs and LSSUs are not
 *  transmitted continuously on the UDP link, only the first occurence of the
 *  repetion 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.
 */

#define SDT_DESCRIP   "SS7/SDT/UDP: (Signalling Data Terminal over UDP) STREAMS DRIVER."
#define SDT_COPYRIGHT "Copyright (c) 1997-2001 Brian Bidulock.  All Rights Reserved."
#define SDT_DEVICES   "Supports Linux Kernel UDP sockets."
#define SDT_CONTACT   "Brian Bidulock <bidulock@openss7.org>"
#define SDT_BANNER    SDT_DESCRIP   "\n" \
                      SDT_COPYRIGHT "\n" \
                      SDT_DEVICES   "\n" \
                      SDT_CONTACT   "\n"

#ifdef MODULE
MODULE_AUTHOR(SDT_CONTACT);
MODULE_DESCRIPTION(SDT_DESCRIP);
MODULE_SUPPORTED_DEVICES(SDT_DEVICES);
#define MODULE_STATIC static
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#define MODULE_STATIC
#endif

#ifndef SDT_DEBUG
#define SDT_DEBUG 2
#endif
int sdt_debug = SDT_DEBUG;

#define SDT_MOD_ID      0x1111
#define SDT_MOD_NAME    "sdt_udp"
#define SDT_MIN_SDU     3
#define SDT_MAX_SDU     277
#define SDT_HIWATER     1
#define SDT_LOWATER     0


#ifndef SDT_UDP_CMAJOR
#define SDT_UDP_CMAJOR  0   /* FIXNE: pick something    */
#endif
#define SDT_UDP_NMINOR  256 /* as many as possible      */

static int sdt_udp_major = SDT_UDP_CMAJOR;

typedef struct {
    struct sdt_config   config;     /* set configuration */
    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  */
    mblk_t              *freebufs;  /* spare buffers    */
} sdt_udpdev_t;

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

/*
 *  -----------------------------------------
 *
 *  IOCTL
 *
 *  -----------------------------------------
 */

static int sdt_dev_ioctl(sdt_t *sdt, int cmd, void *arg, int size)
{
    sdt_device_t *dev = &sdt->device;
    sdt_udpdev_t *prv = dev->priv;
    sdt_config_t *ureq = NULL;
    sdt_ulong uarg = 0;

    switch (cmd) {
        case SDT_IOCTCONFIG:
        case SDT_IOCSCONFIG:
            if ( !arg || size < sizeof(sdt_config_t) ) return EINVAL;
            ureq = arg;
            break;
        case SDT_IOCSIFTYPE:
        case SDT_IOCSGRPTYPE:
        case SDT_IOCSIFMODE:
        case SDT_IOCSIFRATE:
        case SDT_IOCSIFCLOCK:
        case SDT_IOCSIFCODING:
        case SDT_IOCSIFLEADS:
        case SDT_IOCCIFLEADS:
            if ( !arg || size < sizeof(sdt_ulong) ) return EINVAL;
            uarg = *(sdt_ulong *)arg;
            break;
        case SDT_IOCCCONFIG:
        case SDT_IOCCMRESET:
        case SDT_IOCIFRESET:
        case SDT_IOCCDISCTX:
        case SDT_IOCCCONNTX:
            break;
    }
    switch (cmd) {
        case SDT_IOCTCONFIG:
        case SDT_IOCSCONFIG:
        case SDT_IOCCCONFIG:
        case SDT_IOCCMRESET:
        case SDT_IOCIFRESET:

        case SDT_IOCSIFTYPE:
            if ( uarg != SDT_TYPE_PACKET ) return EINVAL;
            return(0);
        case SDT_IOCSGRPTYPE:
            if ( uarg != SDT_GTYPE_UDP ) return EINVAL;
            return(0);
        case SDT_IOCSIFMODE:
            if ( uarg != SDT_MODE_PEER ) return EINVAL;
            return(0);
        case SDT_IOCSIFRATE:
            {
                int tdiff;
                if ( uarg < 800L || uarg > 100000000L ) return EINVAL;
                dev->config.ifrate = uarg;
                prv->config.ifrate = uarg;
                if ( (tdiff = prv->timestamp - jiffies) > 0 )
                    prv->bytecount += prv->tickbytes * tdiff;
                else
                    prv->bytecount = 0;
                prv->timestamp = jiffies;
                prv->tickbytes = uarg/HZ/8;
                while ( prv->bytecount >= prv->tickbytes ) {
                    prv->bytecount -= prv->tickbytes;
                    prv->timestamp++;
                }
                return(0);
            }
        case SDT_IOCSIFCLOCK:
            if ( uarg != SDT_CLOCK_SHAPER ) return EINVAL;
            return(0);
        case SDT_IOCSIFCODING:
            if ( uarg != SDT_CODING_NONE ) return EINVAL;
            return(0);
        case SDT_IOCSIFLEADS:
        case SDT_IOCCIFLEADS:
            if ( uarg ) return EINVAL;
            return(0);
        case SDT_IOCCDISCTX:
            dev->config.ifflags &= ~SDT_FLAGS_IFTX;
            prv->config.ifflags &= ~SDT_FLAGS_IFTX;
            return(0);
        case SDT_IOCCCONNTX:
            dev->config.ifflags |= SDT_FLAGS_IFTX;
            prv->config.ifflags |= SDT_FLAGS_IFTX;
            return(0);
    }
    return EOPNOTSUPP;
}


static sdt_device_t sdt_device_default
{
    SDT_TYPE_PACKET,    /* iftype   */
    SDT_GTYPE_UDP,      /* ifgtype  */
    SDT_MODE_PEER,      /* ifmode   */
    1544000,            /* ifrate   */
    SDT_CLOCK_SHAPER,   /* ifclock  */
    SDT_CODING_NONE,    /* ifcoding */
    0,                  /* ifleads  */
    0,                  /* ifindex  */
    0,                  /* irq      */
    0,                  /* iobase   */
    0,                  /* dma_rx   */
    0                   /* dma_tx   */
};

static inline int
sdt_dev_open(sdt_device_t *dev)
{
    if ( (dev->priv = kmalloc(sizeof(sdt_udpdev_t), GFP_KERNEL)) )
    {
        sdt_udpdev_t *devp = dev->priv;
        bzero(devp, sizeof(*devp));
        bcopy(dev, &sdt_device_default, sizeof(*dev));
        devp->timestamp = jiffies;
        devp->tickbytes = 1544000/HZ/8;
        devp->bytecount = 0;
        return(0);
    }
    return ENOMEM;
}

static void
sdt_dev_close(sdt_t *sdt)
{
    if (sdt->device.priv) kfree(sdt->device.priv);
    sdt->device.priv = NULL;
}

static void
sdt_dev_attach(sdt_t *sdt)
{
}

static void
sdt_dev_detach(sdt_t *sdt)
{
}

static void sdt_dev_data_ready(struct sock *, int);

static int
sdt_dev_enable(sdt_t *sdt)
{
    int err;
    struct socket *sock = NULL;
    sdt_device_t *dev = &sdt->device;
    sdt_udpdev_t *prv = dev->priv;
    struct sockaddr *sa = (struct sockaddr *)prv->loc_addr;

    if ( (err = sock_create(PF_INET, SOCK_DGRAM, 0, &sock)) < 0 )
        return err;

    sock->sk->reuse = 1;    /* FIXME: check this */
    sock->sk->allocation = GFP_ATOMIC;

    if ( (err = sock->bind(sock, sa, sizeof(*sa))) < 0 ) {
        sock_release(sock);
        return err;
    }

    sock->protinfo.destruct_hook = sdt; /* might be bad idea */
    sock->sk->data_ready = sdt_dev_data_ready;

    prv->udpsock = sock;
    sdt->state = SDT_ENABLED;
    return (0);
}


static int
sdt_dev_disable(sdt_t *sdt)
{
    sdt_udpdev_t *prv = sdt->device.priv;
    struct socket *sock = prv->udpsock;

    prv->config.ifflags &= ~SDT_FLAGS_IFTX;
    sdt->statem.deadt_state = SDT_STATE_IDLE;
    if ( sock ) {
        sock->protinfo.destruct_hook = NULL;
        sock_release(sock);
        prv->udpsock = NULL;
    }
    prv->config.ifflags &= ~SDT_FLAGS_IFRX;
    sdt->statem.deadr_state = SDT_STATE_IDLE;
    sdt->state = SDT_DISABLED;
    return(0);
}

/*
 *  Buffer supply for receive.
 */
static int sdt_dev_rtnbuf(sdt_t *sdt, mblk_t *mp) {
    sdt_udpdev_t *prv = sdt->device.priv;
    do {
        mp->b_rptr = mp->b_wptr = mp->b_datap->db_base;
        mp->b_datap->db_type = M_DATA;
        mp->b_next = prv->freebufs;
        prv->freebufs = mp;
    } while ( (mp = unlinkb(mp)) );
    return(0);
}

static inline mblk_t *sdt_dev_allocb(sdt_t *sdt, int size, int prio)
{
    mblk_t *mp = NULL;
    sdt_udpdev_t *prv = sdt->device.priv;
    if ( prv->freebufs && prv->freebufs->b_datap->db_size >= size ) {
        mp = prv->freebufs;
        prv->freebufs = mp->next;
        mp->next = mp->prev = NULL;
    }
}

static inline void
sdt_dev_su_in_error(sdt_t *sdt)
{
    sdt->stats.rx_sus_in_error++;
    sdt_aerm_su_in_error(sdt);
    sdt_suerm_su_in_error(sdt);
    sdt_eim_su_in_error(sdt);
}

/*
 *  This is equivalent of RxISR.
 */
static void
sdt_dev_data_ready(struct sock *sk, int bytes)
{
    int err;
    struct sk_buff *skb;
    sdt_t *sdt = (sdt_t *)sock->protinfo.destruct_hook;
    int rx_on = ( sdt && sdt->statem.daedr_state == SDT_STATE_IN_SERVICE );

    (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);

            sdt->stats.rx_sus++;
            sdt->stats.rx_bytes += len;

            if ( canputq(sdt->rq) && (mp = sdt_dev_allocb(sdt, len, BPRI_HI)) ) {
                bcopy(mp->b_wptr, data, len);
                mp->b_wptr += len;
                (*(sdt->rq->q_qinfo->qi_putq))(sdt->rq, mp);
            } else {
                sdt->stats.rx_overruns++;
                sdt->stats.rx_buffer_overflows++;
                sdt_dev_su_in_error(sdt);
            }
        }
        skb_free_datagram(sk, skb);
    }
    if ( err && rx_on && abs(err) != EAGAIN )
        sdt_dev_su_in_error(sdt);
    return;
}

/*
 *  This is equivalent of TxISR.
 */
static int
sdt_dev_xmit(sdt_t *sdt, mblk_t *mp)
{
    int n, size, tdiff;
    mblk_t *db;
    sdt_device_t *dev = &sdt->device;
    sdt_udpdev_t *prv = dev->priv;

    if ( (tdiff = jiffies - prv->timestamp) < 0 )
        return EAGAIN; /* throttle */
    if ( tdiff > 0 ) {
        prv->bytecount = 0;
        prv->timestamp = jiffies;
    }
    for ( size=0, n=0, db=mp; db; db=db->b_cont )
        if ( db->b_data->db_type == M_DATA && db->b_wptr > db->b_rptr ) {
            size += db->b_wptr - db->b_rptr;
            n++;
        }
    if ( n ) {
        int err=0, size;
        struct msghdr udpmsg;
        struct iovec iov[n];

        for ( n=0, db=mp; db; db=db->b_cont ) {
            if ( db->b_data->db_type == M_DATA && db->b_wptr > db->b_rptr ) {
                ioc[n].iov_base = db->b_rptr;
                iov[n].iov_len  = db->b_wptr - db->b_rptr;
            }
        }
        udpmsg.msg_name = (void *)&prv->rem_addr;
        udpmsg.msg_namelen = sizeof(prv->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(prv->udpsock, &udpmsg, size);
            set_fs(fs);
        }
        if ( err ) {
            switch ( abs(err) ) {
                case ENOMEM:    /* ran out of memory */
                case ENOBUFS:   /* ran out of memory */
                case EAGAIN:    /* no space on non-blocking socket */
                case ERESTARTSYS:   /* signal */
                    return EAGAIN;  /* temporary, try again later */
                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 */
                    return abs(err);  /* bad error, link unusable */
                case EPERM:     /* blocked by firewall */
                case EMSGSIZE:  /* message bigger than MTU */
                    sdt->stats.tx_sus_in_error++;
                    freemsg(mp);
                    return(0);

                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 */
                    return EAGAIN;
            }
            freemsg(mp);
            return(abs(err));
        }
        sdt->stats.tx_bytes += size;
        sdt->stats.tx_sus++;
        prv->bytecount += size;
        while ( prv->bytecount >= prv->tickbytes ) {
            prv->bytecount -= prv->tickbytes;
            prv->timestamp++;
        }
    }
    return(0);
}

/*
 *  --------------------------------------
 *
 *  STREAMS OPEN and CLOSE routines
 *
 *  --------------------------------------
 */

static sdt_t *sdt_list = NULL;

static sdt_config_t sdt_config_default =
{
    SS7_PVAR_ITUT_96,               /* pvar     */
    SS7_POPT_HSL | SS7_POPT_XSN,    /* popt     */
    5,                              /* M        */
    4,                              /* Tin      */
    1,                              /* Tie      */
    32,                             /* T        */
    256,                            /* D        */
    16,                             /* N        */
    577169,                         /* Te       */
    144292,                         /* De       */
    9308,                           /* Ue       */
    10                              /* t8       */
};

static sdt_devops_t sdt_device_operations =
{
    NULL,               /* probe    */
    sdt_dev_open,       /* open     */
    sdt_dev_close,      /* close    */
    sdt_dev_attach,     /* attach   */
    sdt_dev_detach,     /* detach   */
    sdt_dev_enable,     /* enable   */
    sdt_dev_disable,    /* disable  */
    sdt_dev_xmit,       /* xmit     */
    sdt_dev_rtnbuf,     /* rtnbuf   */
    sdt_dev_ioctl       /* ioctl    */
};

/*
 *  ============================================
 *
 *  STREAMS Definitions
 *
 *  ============================================
 */

static struct module_info sdt_minfo =
{
    SDT_MOD_ID,     /* Module ID number             */
    SDT_MOD_NAME,   /* Module name                  */
    SDT_MIN_SDU,    /* Min packet size accepted     */
    SDT_MAX_SDU,    /* Max packet size accepted     */
    SDT_HIWATER,    /* Hi water mark                */
    SDT_LOWATER     /* Lo water mark                */
};

static struct qinit sdt_rinit =
{
    NULL,       /* qi_putp   */ /* Read put (message from below)  */
    NULL,       /* qi_srvp   */ /* Read queue service             */
    NULL,       /* qi_qopen  */ /* Each open                      */
    NULL,       /* qi_qclose */ /* Last close                     */
    NULL,       /* qi_qadmin */ /* Admin (not used)               */
    &sdt_minfo, /* qi_minfo  */ /* Information                    */
    NULL        /* qi_mstat  */ /* Statistics                     */
}

static struct qinit sdt_winit =
{
    NULL,       /* qi_putp   */ /* Write put (message from above) */
    NULL,       /* qi_srvp   */ /* Write queue service            */
    NULL,       /* qi_qopen  */ /* Each open                      */
    NULL,       /* qi_qclose */ /* Last close                     */
    NULL,       /* qi_qadmin */ /* Admin (not used)               */
    &sdt_minfo, /* qi_minfo  */ /* Information                    */
    NULL        /* qi_mstat  */ /* Statistics                     */
};

MODULE_STATIC
struct streamtab sdt_udp_info =
{
    sdt_rinit,  /* Upper read  queue                */
    sdt_winit,  /* Upper write queue                */
    NULL,       /* Lower read  queue (not a mux)    */
    NULL        /* Lower write queue (not a mux)    */
};

/*
 *  =======================================================================
 *
 *  LiS Module Initialization   (For registered driver.)
 *
 *  =======================================================================
 */

static int sdt_udp_initialized = 0;

void sdt_udp_init(void)
{
    int err;
    if ( sdt_udp_initialized > 0 ) return;
    printk(KERN_INFO SDT_BANNER);   /* console splash */
    sdt_udp_initialized
        = sdt_register_driver(
                SDT_UDP_CMAJOR, sdt_udp_info, SDT_UDP_NMINOR, "sdt_udp",
                &sdt_device_operations);
    if ( sdt_udp_initialized > 0 )
        sdt_udp_major = sdt_udp_initialized;
    if ( sdt_udp_initialized == 0 )
        sdt_udp_initialized = sdt_udp_major;
};

void sdt_udp_terminate(void)
{
    if ( sdt_udp_initialized <= 0 ) return;
    sdt_udp_initialized = sdt_unregister_driver(sdt_udp_major);
};

/*
 *  =======================================================================
 *
 *  Kernel Module Initialization   (For unregistered driver.)
 *
 *  =======================================================================
 */

#ifdef MODULE

int init_module(void)
{
    sdt_udp_init();
    if ( sdt_udp_initialized < 0 ) return sdt_udp_initialized;
    return(0);
}

void cleanup_module(void)
{
    sdt_udp_terminate;
};
#endif


