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

 @(#) sl_udp.c,v 0.7 2000/11/25 07:18:52 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/25 07:18:52 by brian

 sl_udp.c,v
 Revision 0.7  2000/11/25 07:18:52  brian
 Stab at sl driver for UDP.

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

#ident @(#) sl_udp.c,v 0.7 2000/11/25 07:18:52 brian Exp

static char const ident[] = "sl_udp.c,v 0.7 2000/11/25 07:18:52 brian Exp";

/*
 *  This is an SLI driver which provides Signalling Link emulation over UDP.
 *  It is a Style 2 PPA driver which must be attached to a local and remote
 *  IP Address and Port.
 */

#include <sys/ss7/sli.h>
#include <sys/ss7/sli_ioctl.h>

#define SL_DESCRIP      "SS7/SL/UDP (Signalling Link over UDP) STREAMS MODULE"
#define SL_COPYRIGHT    "Copyright (c) 1997-2000 Brian Bidulock.  All Rights Reserved."
#define SL_DEVICES      "Supports Linux Kernel UDP sockets."
#define SL_CONTACT      "Brian Bidulock <bidulock@openss7.org>"
#define SL_BANNER       SL_DESCRIP      "\n" \
                        SL_COPYRIGHT    "\n" \
                        SL_DEVICES      "\n" \
                        SL_CONTACT      "\n"

#ifdef MODULE
MODULE_AUTHOR(SL_CONTACT);
MODULE_DESCRIPTION(SL_DESCRIP);
MODULE_SUPPORTED_DEVICES(SL_DEVICES);
#define MODULE_STATIC static
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#define MODULE_STATIC
#endif

#ifndef SL_DEBUG
#define SL_DEBUG 2
#endif
static int sl_debug = SL_DEBUG;

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

#define SL_DEVICE       "sl_udp"
#define SL_MOD_ID       0x2221
#define SL_MIN_PDU      3
#define SL_MAX_PDU      277
#define SL_HIWATER      1
#define SL_LOWATER      0

static struct module_info sl_minfo =
{
    SL_MOD_NUMBER,
    SL_DEVICE,
    SL_MIN_PDU,
    SL_MAX_PDU,
    SL_HIWATER,
    SL_LOWATER
};

static int  sl_open (queue_t *, dev_t *, int, int, cred_t *);
static int  sl_close(queue_t *, int, cred_t *);
static void sl_rput (queue_t *, mblk_t *);
static void sl_rsrv (queue_t *);
static void sl_wput (queue_t *, mblk_t *);
static void sl_wsrv (queue_t *);

static struct qinit sl_rinit =
{
    sl_rput,
    sl_rsrv,
    sl_open,
    sl_close,
    NULL,
    &sl_minfo,
    NULL
};

static struct qinit sl_winit =
{
    sl_wput,
    sl_wsrv,
    NULL,
    NULL,
    &sl_winfo,
    NULL
};

MODULE_STATIC
struct streamtab sl_info =
{
    sl_rinit,
    sl_winit,
    NULL,
    NULL
};

typedef struct {
    sl_device_t     device;
    struct sockaddr_in  loc_addr;
    struct sockaddr_in  rem_addr;
    struct socket       *udpsock;
} sdl_udpdev_t;

typedef struct {
    sl_t        *next;
    sl_ushort   minor;
    queue_t     *q;
    sl_ulong    state;
    sl_ulong    flags;
    sl_config_t config;
    sl_statem_t statem;
    sl_stats_t  stats;
    sl_notify_t notify;
    sl_udpdev_t device;
} sl_t;

#define SL_N_MINOR 256 /* for now */

MODULE_STATIC
sl_t *sl_udp_list = NULL;

static int sl_initialized = 0;

void sl_udp_init(void)
{
    if ( sl_initialized ) return;
    sl_initialized = 1;
    printk(KERN_INFO, SL_BANNER); /* console splash */
};

void sl_udp_terminate(void)
{
    if ( !sl_initialized ) return;
    sl_initialized = 0;
};

static sl_config_t sl_config_default = {
    0,
    /* FIXME: fill out defaults */
};

static sl_device_t sl_device_default = {
    0,
    /* FIXME: fill out defaults */
};

#define SL_UDP_PPA_SIZE 2*sizeof(struct sockaddr)

static int  sl_dev_open (sl_udpdev_t *); /* opens device */
static void sl_dev_close(sl_udpdev_t *); /* close device */

static int
sl_open(q, devp, flag, sflag, crp)
    queue_t *q;
    dev_t   *devp;
    int     flag;
    int     sflag;
    cred_t  *crp;
{
    int minor, err;
    sl_t **slp, *sl;

    if ( q->q_ptr != NULL)
        return(0);
    if ( sflag == MODOPEN || WR(q)->q_next != NULL )
        return ENXIO;
    if ( sflag == CLONEOPEN ) {
        for (   minor = 0, slp = &sl_udp_list;
                minor < SL_N_MINOR, *sdp;
                slp = &(*slp)->next
                ) {
            if ( minor <  (*slp)->minor ) break;
            if ( minor == (*slp)->minor ) minor++;
        }
        if ( minor >= SL_N_MINOR )
            return ENXIO;
        *devp = makedevice(getmajor(*devp), minor);
    } else {
        if ( (minor = getminor(*devp)) >= SL_N_MINOR )
            return ENXIO;
        for ( slp = &sl_udp_list; *slp; slp=&(*slp)->next ) {
            if ( minor <  (*slp)->minor ) break;
            if ( minor == (*slp)->minor ) return EBUSY;
        }
    }
    MOD_INC_USE_COUNT;
    if ( !(sl = kmalloc(sizeof(sl_t), GFP_KERNEL)) ) {
        MOD_DEC_USE_COUNT;
        return ENOMEM;
    }
    bzero(sl, sizeof(*sl));

    sl->next = *slp;
    *slp = sl;
    sl->minor = minor;

    sl->q = q;
    sl->state = SL_UNATTACHED;  /* Style 2 */
    sl->flags = 0;
    bcopy(&sl->config, &sl_config_default, sizeof(sl_config_default));
    bcopy(&sl->device, &sl_device_default, sizeof(sl_device_default));

    if ( (err = sl_dev_open(&sl->device)) ) {
        *slp = sl->next;
        kfree(sl);
        MOD_DEC_USE_COUNT;
        return err;
    }

    q->q_ptr = WR(q)->q_ptr = sl;

    return (0);
}

static int  sl_dev_enable (sl_t *);
static void sl_dev_disable(sl_t *);

static int
sl_close(q, flag, crp)
    queue_t *q;
    int     flag;
    cred_t  *crp;
{
    sl_t **slp, *sl = (sl_t *)q->q_ptr;

    for ( slp = &sl_udp_list; *slp; slp = &(*slp)->next )
        if ( *slp == sl ) break;
    if ( !*slp )
        return ENXIO;   /* where is it? */

    if ( sl->state == SL_ENABLED )
        sl_dev_disable(sl);

    q->q_ptr = WR(q)->q_ptr = NULL;

    sl_dev_close(&sl->device);

    *slp = sl->next;
    kfree(sl);

    MOD_DEC_USE_COUNT;
    return(0);
}

static void
sl_wput (queue_t *q, mblk_t *mp)
{
    int err=0;

    switch ( mp->b_data->db_type ) {
        case M_DATA:
            if ( (err = sl_m_data(q, mp)) ) break;
            return;
        case M_PROTO:
        case M_PCPROTO:
            if ( (err = sl_m_proto(q, mp)) ) break;
            return;
        case M_FLUSH:
            if ( *mp->b_rptr & FLUSHW ) {
                flushq(q, FLUSHALL);
                *mp->b_rptr &= ~FLUSHW;
            }
            if ( *mp->b_rptr & FLUSHR ) {
                flushq(RD(q), FLUSHALL);
                qreply(q, mp);
                return;
            }
            break;
        case M_CTL:
            if ( (err = sl_m_ctl(q, mp)) ) break;
            return;
        case M_IOCTL:
            if ( (err = sl_m_ioctl(q, mp)) ) break;
            return;
    }
    if ( err == EAGAIN ) {
        putq(q, mp);
        return;
    }
    freemsg(mp);
    return;
}

static void
sl_wsrv (queue_t *q)
{
    int err=0;

    while ( (mp = getq(q)) ) {
        switch ( mp->b_data->db_type ) {
            case M_DATA:
                if ( (err = sl_m_data(q, mp)) ) break;
                continue;
            case M_PROTO:
            case M_PCPROTO:
                if ( (err = sl_m_proto(q, mp)) ) break;
                continue;
            case M_CTL:
                if ( (err = sl_m_ctl(q, mp)) ) break;
                continue;
            case M_IOCTL:
                if ( (err = sl_m_ioctl(q, mp)) ) break;
                continue;
        }
        if ( err == EAGAIN ) {
            putbq(q, mp);
            return;
        }
        freemsg(mp);
    }
}

static void
sl_rput (queue_t *q, mblk_t *mp)
{
    sl_t *sl = q->q_ptr;
    if ( mp->b_data->db_type >= QPCTL ) {
        putnext(q, mp);
        return;
    }
    if ( !q->q_count && canputnext(q) ) {
        /* process received frame */
        putnext(q);
        return;
    }
    putq(q, mp);
    sl->statem.Cr++;
    return;
}

static void
sl_rsrv (queue_t *q)
{
    mblk_t *mp;
    sl_t *sl = (sl_t *)q->q_ptr;
    int backlog = sl->statem.Cr;

    while ( (mp = getq(q)) ) {
        sl->statem.Cr--;
        if ( mp->b_datap->db_type >= QPCTL ) {
            /* process received frame */
            putnext(q, mp);
            continue;
        }
        if ( canputnext(q) ) {
            /* process received frame */
            putnext(q);
            continue;
        }
        sl->statem.Cr++;
        putbq(q, mp);
        break;
    }
    if ( !q->q_count )
        sl->statem.Cr = 0;
    if ( sl->statem.Cr != backlog )
        sl_rb_congestion_function(sl);
    return;
}


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

static int
sl_dev_open(sl_udpdev_t *dev)
{
    return(0);
}

static void
sl_dev_close(sl_udpdev_t *dev)
{
    return;
}

static int
sl_dev_enable(sl_t *sl)
{
    int err;
    struct socket *sock = NULL;
    struct sockaddr *sa = (struct sockaddr *)&sl->device.loc_addr;

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

    sock->sk->reuse = 1;
    sock->sk->allocation = GFP_ATOMIC;

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

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

    sl->device.udpsock = sock;

    sl->state = SL_ENABLED;
    return(0);
}

static void
sl_dev_disable(sl_t *sl)
{
    struct socket *sock = sl->device.udpsock;

    sl->statem.daedt_state = SL_STATE_IDLE;
    if ( sock ) {
        sock->protinfo.destruct_hook = NULL;
        sock_release(sock);
        sl->device.udpsock = NULL;
    }
    sl->statem.daedr_state = SL_STATE_IDLE;

    sl->state = SL_DISABLED;
}



