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

 @(#) sdl_udp2.c,v 0.7 2000/11/30 08:12:03 brian Exp

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

     Copyright (C) 2000  Brian Bidulock.  All Rights Reserved.


                                  PUBLIC LICENSE

     This license is provided without fee, provided that the above copy-
     right notice and this public license must be retained on all copies,
     extracts, compilations and derivative works.  Use or distribution of
     this work in a manner that restricts its use except as provided here
     will render this license void.

     The author(s) hereby waive any and all other restrictions in respect
     of their copyright in this software and its associated documentation.
     The authors(s) of this software place in the public domain any novel
     methods or processes which are embodied in this software.

     The author(s) undertook to write it for the sake of the advancement
     of the Arts and Sciences, but it is provided as is, and the author(s)
     will not take any responsibility in it.

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

 Last Modified 2000/11/30 08:12:03 by brian

 sdl_udp2.c,v
 Revision 0.7  2000/11/30 08:12:03  brian
 Added driver implementations.

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

static char const ident[] = "sdl_udp2.c,v 0.7 2000/11/30 08:12:03 brian Exp";

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

#define SDL_DESCRIP   "SS7/SDL/UDP: (Signalling Data Link over UDP) STREAMS DRIVER."
#define SDL_COPYRIGHT "Copyright (c) 1997-2000 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_DEVICES(SDL_DEVICES);
#define MODULE_STATIC static
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#define MODULE_STATIC
#endif

#ifndef SDL_DEBUG
#define SDL_DEBUG 2
#endif
int sdl_debug = 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
#define SDL_C_MAJOR     0   /* FIXNE: pick something    */
#define SDL_N_MINOR     256 /* as many as possible      */

static int sdl_udp_major = SDL_C_MAJOR;

typedef struct {
    struct sdl_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  */
    spinlock_t          dev_lock;   /* per device lock  */
    mblk_t              *freehead;  /* spare buffers    */
    mblk_t              **freetail; /* buf tail ptr     */
    struct sdl_dcalls   *dcalls;    /* driver calls     */
} sdl_udpdev_t;

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

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

static int sdl_udp_ioctl(sdl_t *sdl, int cmd, void *arg, int size)
{
    sdl_device_t *dev = &sdl->device;
    sdl_udpdev_t *prv = dev->priv;
    sdl_config_t *ureq = NULL;
    sdl_ulong uarg = 0;

    switch (cmd) {
        case SDL_IOCTCONFIG:
        case SDL_IOCSCONFIG:
            if ( !arg || size < sizeof(sdl_config_t) ) return EINVAL;
            ureq = arg;
            break;
        case SDL_IOCSIFTYPE:
        case SDL_IOCSGRPTYPE:
        case SDL_IOCSIFMODE:
        case SDL_IOCSIFRATE:
        case SDL_IOCSIFCLOCK:
        case SDL_IOCSIFCODING:
        case SDL_IOCSIFLEADS:
        case SDL_IOCCIFLEADS:
            if ( !arg || size < sizeof(sdl_ulong) ) return EINVAL;
            uarg = *(sdl_ulong *)arg;
            break;
        case SDL_IOCCCONFIG:
        case SDL_IOCCMRESET:
        case SDL_IOCIFRESET:
        case SDL_IOCCDISCTX:
        case SDL_IOCCCONNTX:
            break;
    }
    switch (cmd) {
        case SDL_IOCTCONFIG:
        case SDL_IOCSCONFIG:
        case SDL_IOCCCONFIG:
        case SDL_IOCCMRESET:
        case SDL_IOCIFRESET:

        case SDL_IOCSIFTYPE:
            if ( uarg != SDL_TYPE_PACKET ) return EINVAL;
            return(0);
        case SDL_IOCSGRPTYPE:
            if ( uarg != SDL_GTYPE_UDP ) return EINVAL;
            return(0);
        case SDL_IOCSIFMODE:
            if ( uarg != SDL_MODE_PEER ) return EINVAL;
            return(0);
        case SDL_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 SDL_IOCSIFCLOCK:
            if ( uarg != SDL_CLOCK_SHAPER ) return EINVAL;
            return(0);
        case SDL_IOCSIFCODING:
            if ( uarg != SDL_CODING_NONE ) return EINVAL;
            return(0);
        case SDL_IOCSIFLEADS:
        case SDL_IOCCIFLEADS:
            if ( uarg ) return EINVAL;
            return(0);
        case SDL_IOCCDISCTX:
            dev->config.ifflags &= ~SDL_FLAGS_IFTX;
            prv->config.ifflags &= ~SDL_FLAGS_IFTX;
            return(0);
        case SDL_IOCCCONNTX:
            dev->config.ifflags |= SDL_FLAGS_IFTX;
            prv->config.ifflags |= SDL_FLAGS_IFTX;
            return(0);
    }
    return EOPNOTSUPP;
}


static sdl_device_t sdl_device_default
{
    SDL_TYPE_PACKET,    /* iftype   */
    SDL_GTYPE_UDP,      /* ifgtype  */
    SDL_MODE_PEER,      /* ifmode   */
    1544000,            /* ifrate   */
    SDL_CLOCK_SHAPER,   /* ifclock  */
    SDL_CODING_NONE,    /* ifcoding */
    0,                  /* ifleads  */
    0,                  /* ifindex  */
    0,                  /* irq      */
    0,                  /* iobase   */
    0,                  /* dma_rx   */
    0                   /* dma_tx   */
};

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

static void
sdl_udp_close(sdl_t *sdl)
{
    if (sdl->device.priv) kfree(sdl->device.priv);
    sdl->device.priv = NULL;
}

static void
sdl_udp_attach(sdl_t *sdl)
{
}

static void
sdl_udp_detach(sdl_t *sdl)
{
}

static void sdl_udp_data_ready(struct sock *, int);

static int
sdl_udp_enable(sdl_t *sdl)
{
    int err;
    struct socket *sock = NULL;
    sdl_device_t *dev = &sdl->device;
    sdl_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 = sdl; /* might be bad idea */
    sock->sk->data_ready = sdl_udp_data_ready;

    prv->udpsock = sock;
    sdl->state = SDL_ENABLED;
    return (0);
}


static int
sdl_udp_disable(sdl_t *sdl)
{
    sdl_udpdev_t *prv = sdl->device.priv;
    struct socket *sock = prv->udpsock;

    prv->config.ifflags &= ~SDL_FLAGS_IFTX;
    sdl->statem.deadt_state = SDL_STATE_IDLE;
    if ( sock ) {
        sock->protinfo.destruct_hook = NULL;
        sock_release(sock);
        prv->udpsock = NULL;
    }
    prv->config.ifflags &= ~SDL_FLAGS_IFRX;
    sdl->statem.deadr_state = SDL_STATE_IDLE;
    sdl->state = SDL_DISABLED;
    return(0);
}

/*
 *  Trick to link mblks to sk_bufs
 */

static void sdl_udp_freebuf(char *arg)
{
    struct sk_buff *skb = (struct sk_buff *)arg;
    struct sock *sk = skb->sk;

    skb_free_datagram(sk, skb);
}

static mblk_t *sdl_udp_esballoc(struct sk_buff *skb, unsigned char* base, int size)
{
    struct free_rtn f;

    f.free_func = sdl_udp_freebuf;
    f.free_arg  = (char *)skb;

    return esballoc(base, size, BRPI_MED, &f);
}

/*
 *  This is equivalent of RxISR.
 */
static void
sdl_udp_data_ready(struct sock *sk, int bytes)
{
    int err;
    struct sk_buff *skb;
    sdl_t *sdl = (sdl_t *)sock->protinfo.destruct_hook;
    sdl_udpdev_t *prv = sdl->device.priv;
    int rx_on = ( sdl && sdl->statem.daedr_state == SDL_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);

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

            skb->sk = sk;   /* just to be sure */

            if ( canputq(sdl->rq) && (mp = sdl_udp_allocb(skb, data, len)) ) {
                putq(sdl->rq, mp);
            } else {
                sdl->stats.rx_overruns++;
                sdl->stats.rx_buffer_overflows++;
                priv->dcalls.daedr_error_frame(sdl);
            }
        }
    }
    if ( err && rx_on && abs(err) != EAGAIN )
        /* FIXME: analyze this error */
        priv->dcalls.daedr_error_frame(sdl);
    return;
}

/*
 *  This is equivalent of TxISR.
 */
static int
sdl_udp_xmit(sdl_t *sdl, mblk_t *mp)
{
    int n, size, tdiff;
    mblk_t *db;
    sdl_device_t *dev = &sdl->device;
    sdl_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 */
                    sdl->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));
        }
        sdl->stats.tx_bytes += size;
        sdl->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 sdl_t *sdl_list = NULL;

static sdl_config_t sdl_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 sdl_devops_t sdl_device_operations =
{
    NULL,               /* probe    */
    sdl_udp_open,       /* open     */
    sdl_udp_close,      /* close    */
    sdl_udp_attach,     /* attach   */
    sdl_udp_detach,     /* detach   */
    sdl_udp_enable,     /* enable   */
    sdl_udp_disable,    /* disable  */
    sdl_udp_xmit,       /* xmit     */
    NULL,               /* rtnbuf   */
    sdl_udp_ioctl       /* ioctl    */
};

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

#if 0
static struct module_info sdl_minfo =
{
    SDL_MOD_ID,     /* Module ID number             */
    SDL_MOD_NAME,   /* Module name                  */
    SDL_MIN_SDU,    /* Min packet size accepted     */
    SDL_MAX_SDU,    /* Max packet size accepted     */
    SDL_HIWATER,    /* Hi water mark                */
    SDL_LOWATER     /* Lo water mark                */
};

static struct qinit sdl_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)               */
    &sdl_minfo, /* qi_minfo  */ /* Information                    */
    NULL        /* qi_mstat  */ /* Statistics                     */
}

static struct qinit sdl_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)               */
    &sdl_minfo, /* qi_minfo  */ /* Information                    */
    NULL        /* qi_mstat  */ /* Statistics                     */
};

MODULE_STATIC
struct streamtab sdl_udp_info =
{
    sdl_rinit,  /* Upper read  queue                */
    sdl_winit,  /* Upper write queue                */
    NULL,       /* Lower read  queue (not a mux)    */
    NULL        /* Lower write queue (not a mux)    */
};
#endif

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

static int sdl_udp_initialized = 0;

void sdl_udp_init(void)
{
    int err;
    if ( sdl_udp_initialized > 0 ) return;
    printk(KERN_INFO SDL_BANNER);   /* console splash */
    sdl_udp_initialized
        = sdl_register_driver(
                SDL_C_MAJOR, sdl_udp_info, SDL_N_MINOR, "sdl_udp",
                &sdl_device_operations);
    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;
    sdl_udp_initialized = sdl_unregister_driver(sdl_udp_major);
};

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

#ifdef MODULE

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

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


