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

 @(#) $Id: sdl_udp.c,v 0.7.2.19 2001/02/03 05:48:45 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:45 $ by $Author: brian $

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

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

/*
 *  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 <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

// #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

#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  */
} 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;

    DTRACE;
    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 ) return EINVAL;
            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;
            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   */
    1544000,            /* ifrate   */
    DEV_CLOCK_SHAPER,   /* ifclock  */
    DEV_CODING_NONE,    /* ifcoding */
};

static lmi_t *
sdl_udp_devatt(dev_t dev)
{
    sdl_udpdev_t *udp;
    DTRACE;
    if ( (udp = kmalloc(sizeof(sdl_udpdev_t), GFP_KERNEL)) ) {
        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;
        return((lmi_t *)udp);
    }
    return NULL;
}

static int
sdl_udp_open(lmi_t *lmi)
{
    DTRACE;
    return(0);  /* can't open yet, no address */
}

static int
sdl_udp_close(lmi_t *lmi)
{
    sdl_udpdev_t *dev = (sdl_udpdev_t *)lmi;
    DTRACE;
    kfree(dev);
    return(0);
}

static int
sdl_udp_infor(lmi_t *lmi, void **ppap, int *lenp)
{
    sdl_udpdev_t *dev = (sdl_udpdev_t *)lmi;
    size_t count = 2*sizeof(struct sockaddr_in);
    if ( !dev ) {
        DPRINT(0,("%s: [%s %d] Null device pointer!\n", __FUNCTION__, __FILE__, __LINE__));
        return EINVAL;
    }
    DTRACE;
    *ppap = &dev->loc_addr;
    DTRACE;
    *lenp = count;
    DTRACE;
#if _DEBUG
    {
        int i;
        char *c;
        DTRACE;
        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 *dev = (sdl_udpdev_t *)lmi;
    size_t count = 2*sizeof(struct sockaddr_in);
    DTRACE;
    if ( len < count )
        return EINVAL | LMI_BADPPA;
    bcopy(ppa, &dev->loc_addr, count);
#if _DEBUG
    {
        int i;
        char *c;
        DTRACE;
        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 *dev = (sdl_udpdev_t *)lmi;
    DTRACE;
    bzero(&dev->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 *dev = (sdl_udpdev_t *)lmi;
    struct sockaddr *sa = (struct sockaddr *)&dev->loc_addr;

    DTRACE;
    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 = dev; /* might be bad idea */
    sock->sk->data_ready = sdl_udp_data_ready;

    dev->udpsock = sock;
    return (0);
}


static int
sdl_udp_disable(lmi_t *lmi)
{
    sdl_udpdev_t *dev = (sdl_udpdev_t *)lmi;
    struct socket *sock = dev->udpsock;

    DTRACE;
    if ( dev ) {
        dev->dev.iface.ifflags &= ~DEV_IF_TX_RUNNING;
        if ( sock ) {
            sock->sk->protinfo.destruct_hook = NULL;
            sock_release(sock);
            dev->udpsock = NULL;
        }
        dev->dev.iface.ifflags &= ~DEV_IF_RX_RUNNING;
    }
    return(0);
}

static lmi_ops_t 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;
    DTRACE;
    while ( (skb = skb_recv_datagram(sk, 0, 1, &err)) ) {
        DTRACE;
        if ( rx_on ) {
            mblk_t *mp;
            caddr_t data = skb->h.raw + sizeof(struct udphdr);
            size_t  len  = skb->len   - sizeof(struct udphdr);

            DTRACE;
            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;
                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 int
sdl_udp_send(struct dev *dev, mblk_t *mp)
{
    int n, size, tdiff;
    mblk_t *db;
    sdl_udpdev_t *udp = (sdl_udpdev_t *)dev;

    DTRACE;
    if ( (tdiff = jiffies - udp->timestamp) < 0 )
        return EAGAIN; /* throttle */
    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++;
        }
    if ( n ) {
        int err=0;
        struct msghdr udpmsg;
        struct iovec iov[n];

        for ( n=0, db=mp; db; db=db->b_cont ) {
            if ( db->b_datap->db_type == M_DATA && db->b_wptr > db->b_rptr ) {
                iov[n].iov_base = db->b_rptr;
                iov[n].iov_len  = db->b_wptr - db->b_rptr;
            }
        }
        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 ) {
            DTRACE;
            DPRINT(0,("%s [%s %d] Error = %d\n",
                        __FUNCTION__,__FILE__,__LINE__,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 */
                    dev->module->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));
        }
        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++;
        }
    }
    DTRACE;
    return(0);
}

static void
sdl_udp_xmit(struct dev *dev, mblk_t *mp)
{
    DTRACE;
    if ( !(dev->iface.ifflags & DEV_IF_TX_RUNNING) )
        freemsg(mp);
    else
        sdl_udp_send(dev, mp);
}

static void
sdl_udp_tx_start(struct dev *dev)
{
    DTRACE;
    dev->iface.ifflags |= DEV_IF_TX_RUNNING;
    return;
}

static void
sdl_udp_rx_start(struct dev *dev)
{
    DTRACE;
    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)
{
    DTRACE;
    if ( sdl_udp_initialized > 0 ) return;
    printk(KERN_INFO SDL_BANNER);   /* console splash */
    DPRINT(0,("%s: registering sdl_udp: cmajor %d, nminors %d\n", __FUNCTION__, 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);
    DPRINT(0,("%s: return (device cmajor) = %d\n", __FUNCTION__, 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)
{
    DTRACE;
    if ( sdl_udp_initialized <= 0 ) return;
    DPRINT(0,("%s: unregistering sdl_udp cmajor %d\n", __FUNCTION__, sdl_udp_major));
    sdl_udp_initialized = sdl_unregister_driver(sdl_udp_major);
    DPRINT(0,("%s: return = %d\n", __FUNCTION__, sdl_udp_initialized));
};

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

#ifdef MODULE

int init_module(void)
{
    (void)sdl_debug;
    DTRACE;
    sdl_udp_init();
    if ( sdl_udp_initialized < 0 ) return sdl_udp_initialized;
    return(0);
}

void cleanup_module(void)
{
    DTRACE;
    sdl_udp_terminate();
};
#endif


