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

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

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

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

#define __NO_VERSION__

#include <linux/config.h>
#include <linux/version.h>
#include <linux/modversions.h>
#include <linux/module.h>
#include <sys/stream.h>
#include <sys/stropts.h>

//#include <linux/version.h>
//#include <linux/modversions.h>
//#include <linux/kmod.h>

#include "../debug.h"

#include <ss7/lmi.h>
#include <ss7/lmi_ioctl.h>

#include "../lmi/lm.h"

#define DEBUG_LEVEL 2

/*
 *  -----------------------------------------------------------------------
 *
 *  LMI PRIMITIVES
 *
 *  -----------------------------------------------------------------------
 */

static inline mblk_t *
lmi_reuseb(mblk_t *mp, size_t room)
{
    mblk_t *m2;
    if ( mp && mp->b_datap->db_ref < 2 &&
            mp->b_datap->db_lim - mp->b_datap->db_base >= room ) {
        mp->b_rptr = mp->b_datap->db_base;
        mp->b_wptr = mp->b_rptr + room;
        if ( mp->b_cont && (m2 = unlinkb(mp)) ) freemsg(m2);
        return mp;
    }
    if ( (m2 = allocb(room, BPRI_HI)) ) {
        m2->b_wptr += room;
        if ( !mp ) return m2;
        m2->b_datap->db_type = mp->b_datap->db_type;
        freemsg(mp);
        return m2;
    }
    if ( mp ) freemsg(mp);
    return NULL;
}

/*
 *  =======================================================================
 *
 *  LMI PRIMITIVES
 *
 *  =======================================================================
 */

/* Upstream Primitives */

static inline int
lmi_error_ind(lmi_t *lmi, mblk_t **mpp, int err)
{
    if ( (*mpp = lmi_reuseb(*mpp, LMI_ERROR_IND_SIZE)) ) {
        lmi_error_ind_t *p      = (lmi_error_ind_t *)(*mpp)->b_rptr;
        (*mpp)->b_datap->db_type  = M_PCPROTO;
        p->lmi_primitive        = LMI_ERROR_IND;
        p->lmi_state            = lmi->state;
        p->lmi_errno            = err&0x0000ffff;
        p->lmi_reason           = err&0xffff0000;
        qreply(lmi->wq, *mpp);
        *mpp = NULL;
    }
    return(0);
}

static inline int
lmi_error_ack(lmi_t *lmi, mblk_t **mpp, int prim, int err)
{
    if ( (*mpp = lmi_reuseb(*mpp, LMI_ERROR_ACK_SIZE)) ) {
        lmi_error_ack_t *p      = (lmi_error_ack_t *)(*mpp)->b_rptr;
        p->lmi_primitive        = LMI_ERROR_ACK;
        p->lmi_state            = lmi->state;
        p->lmi_error_primitive  = prim;
        p->lmi_errno            = err&0x0000ffff;
        p->lmi_reason           = err&0xffff0000;
        qreply(lmi->wq, *mpp);
        *mpp = NULL;
    }
    return(0);
}

static inline int
lmi_ok_ack(lmi_t *lmi, mblk_t **mpp, int prim, int err)
{
    if ( err ) return lmi_error_ack(lmi, mpp, prim, err);
    if ( (*mpp = lmi_reuseb(*mpp, LMI_OK_ACK_SIZE)) ) {
        lmi_ok_ack_t *p         = (lmi_ok_ack_t *)(*mpp)->b_rptr;
        p->lmi_primitive        = LMI_OK_ACK;
        p->lmi_state            = lmi->state;
        p->lmi_correct_primitive= prim;
        qreply(lmi->wq, *mpp);
        *mpp = NULL;
    }
    return(0);
}

static inline int
lmi_info_ack(lmi_t *lmi, mblk_t **mpp, int prim, void *ppa, int len, int err)
{
    DTRACE;
    if ( err ) return lmi_error_ack(lmi, mpp, prim, err);
    DTRACE;
    if ( (*mpp = lmi_reuseb(*mpp, LMI_INFO_ACK_SIZE+len)) ) {
        lmi_info_ack_t *p       = (lmi_info_ack_t *)(*mpp)->b_rptr;
        p->lmi_primitive        = LMI_INFO_ACK;
        p->lmi_version          = 0x00070200;
        p->lmi_state            = lmi->state;
        p->lmi_max_sdu          = 0xffffffff;
        p->lmi_min_sdu          = 0;
        p->lmi_header_len       = 0;
        DTRACE;
        DPRINT(0,("%s: [%s %d] ppa len = %d, ppa addr = 0x%08x\n",
                    __FUNCTION__, __FILE__, __LINE__, len, (unsigned int)ppa));
        if ( len )  {
            DTRACE;
            p->lmi_ppa_style        = LMI_STYLE2;
            bcopy(ppa, p->lmi_ppa_addr, len);
        } else {
            DTRACE;
            p->lmi_ppa_style        = LMI_STYLE1;
        }
        qreply(lmi->wq, *mpp);
    }
    return(0);
}


static inline int
lmi_enable_con(lmi_t *lmi, mblk_t **mpp, int err)
{
    if ( err ) return lmi_error_ind(lmi, mpp, err);
    if ( (*mpp = lmi_reuseb(*mpp, LMI_ENABLE_CON_SIZE)) ) {
        lmi_enable_con_t *p     = (lmi_enable_con_t *)(*mpp)->b_rptr;
        p->lmi_primitive        = LMI_ENABLE_CON;
        p->lmi_state            = lmi->state;
        qreply(lmi->wq, *mpp);
    }
    return(0);
}

static inline int
lmi_enable_ack(lmi_t *lmi, mblk_t **mpp, int prim, int err)
{
    int ret;
    if ( err ) return lmi_error_ack(lmi, mpp, prim, err);
    if ( !(ret = lmi_ok_ack(lmi, mpp, prim, 0)) )
           ret = lmi_enable_con(lmi, mpp, 0);
    return(ret);
}

static inline int
lmi_disable_con(lmi_t *lmi, mblk_t **mpp, int err)
{
    if ( err ) return lmi_error_ind(lmi, mpp, err);
    if ( (*mpp = lmi_reuseb(*mpp, LMI_DISABLE_CON_SIZE)) ) {
        lmi_disable_con_t *p    = (lmi_disable_con_t *)(*mpp)->b_rptr;
        p->lmi_primitive        = LMI_DISABLE_CON;
        p->lmi_state            = lmi->state;
        qreply(lmi->wq, *mpp);
    }
    return(0);
}

static inline int
lmi_disable_ack(lmi_t *lmi, mblk_t **mpp, int prim, int err)
{
    int ret;
    if ( err ) return lmi_error_ack(lmi, mpp, prim, err);
    if ( !(ret = lmi_ok_ack(lmi, mpp, prim, 0)) )
           ret = lmi_disable_con(lmi, mpp, 0);
    return(ret);
}

/* Downstream Primitives */

int
lmi_info(lmi_t *lmi, void **ppap, int *lenp)
{
    int err;
    lmi_driver_t *drv = lmi->driver;
    if ( drv && (err = drv->ops.lmi.info(lmi->device, ppap, lenp)) ) return (err);
    return(0);
}

int
lmi_attach(lmi_t *lmi, void *ppa, int len)
{
    int err;
    lmi_driver_t *drv = lmi->driver;
    if ( lmi->state != LMI_UNATTACHED ) return (EINVAL|LMI_OUTSTATE);
    if ( drv && (err = drv->ops.lmi.attach(lmi->device, ppa, len)) ) return (err);
    lmi->state = LMI_DISABLED;
    return(0);
}

int
lmi_detach(lmi_t *lmi)
{
    int err;
    lmi_driver_t *drv = lmi->driver;
    if ( lmi->state != LMI_DISABLED ) return (EINVAL|LMI_OUTSTATE);
    if ( drv && (err = drv->ops.lmi.detach(lmi->device)) ) return (err);
    lmi->state = LMI_UNATTACHED;
    return(0);
}

int
lmi_enable(lmi_t *lmi)
{
    int err;
    lmi_driver_t *drv = lmi->driver;
    if ( lmi->state != LMI_DISABLED ) return (EINVAL|LMI_OUTSTATE);
    if ( drv && (err = drv->ops.lmi.enable(lmi->device)) ) return (err);
    lmi->state = LMI_ENABLED;
    return(0);
}

int
lmi_disable(lmi_t *lmi)
{
    int err;
    lmi_driver_t *drv = lmi->driver;
    if ( lmi->state != LMI_ENABLED ) return (EINVAL|LMI_OUTSTATE);
    if ( drv && (err = drv->ops.lmi.disable(lmi->device)) ) return (err);
    lmi->state = LMI_DISABLED;
    return(0);
}

static int
lmi_info_req(lmi_t *lmi, mblk_t *mp)
{
    static void *ppa = NULL;
    static int len = 0;
    int err = lmi_info(lmi, &ppa, &len);
    return lmi_info_ack(lmi, &mp, *((int *)mp->b_rptr), ppa, len, err);
}

static int
lmi_attach_req(lmi_t *lmi, mblk_t *mp)
{
    union LMI_primitives *p =
        (union LMI_primitives *)mp->b_rptr;
    return lmi_ok_ack(lmi, &mp, *((int *)mp->b_rptr),
            lmi_attach(lmi, p->attach_req.lmi_ppa,
                mp->b_wptr - mp->b_rptr - LMI_ATTACH_REQ_SIZE));
}

static int
lmi_detach_req(lmi_t *lmi, mblk_t *mp)
{
    return lmi_ok_ack(lmi, &mp, *((int *)mp->b_rptr), lmi_detach(lmi));
}

static int
lmi_enable_req(lmi_t *lmi, mblk_t *mp)
{
    return lmi_enable_ack(lmi, &mp, *((int *)mp->b_rptr), lmi_enable(lmi));
}

static int
lmi_disable_req(lmi_t *lmi, mblk_t *mp)
{
    return lmi_disable_ack(lmi, &mp, *((int *)mp->b_rptr), lmi_disable(lmi));
}

int (*lmi_lmi_ops[5])(lmi_t *, mblk_t *) =
{
    lmi_info_req,   /* LMI_INFO_REQ     */
    lmi_attach_req, /* LMI_ATTACH_REQ   */
    lmi_detach_req, /* LMI_DETACH_REQ   */
    lmi_enable_req, /* LMI_ENABLE_REQ   */
    lmi_disable_req /* LMI_DISABLE_REQ  */
};

/*
 *  =======================================================================
 *
 *  DRIVER
 *
 *  =======================================================================
 */

lmi_t *
lmi_drv_attach(dev_t dev, lmi_driver_t *list, size_t size)
{
    lmi_driver_t *drv;
    lmi_ops_t *ops;
    lmi_t **lmip, *lmi = NULL;
    int cminor = getminor(dev);
    int cmajor = getmajor(dev);

    for ( drv = list; drv; drv = drv->next )
        if ( cmajor == drv->cmajor )
            break;
    if ( !drv )
        return NULL;

    ops = &drv->ops;

    if ( cminor >= drv->nminor )
        return NULL;
    
    for ( lmip = &drv->list; *lmip; lmip = &(*lmip)->next ) {
        if ( cminor <  getminor((*lmip)->devnum) ) { lmi =  NULL; break; }
        if ( cminor == getminor((*lmip)->devnum) ) { lmi = *lmip; break; }
    }

    if ( !lmi ) {
        if ( !(lmi = kmalloc(size, GFP_KERNEL)) )
            return NULL;
        bzero(drv, size);
        if ( !(lmi->device = ops->dev.attach(dev)) ) {
            kfree(lmi);
            return NULL;
        }
        lmi->next = *lmip;
        *lmip = lmi;
        lmi->devnum = dev;
        lmi->driver = drv;
    }

    lmi->state  = LMI_UNATTACHED;

    return lmi;
}

int
lmi_drv_close(lmi_t *lmi,
        lmi_ulong *tids, int ntids, lmi_ulong *bids, int nbids)
{
    int i;
    lmi_t **lmip;
    lmi_driver_t *drv = lmi->driver;
    lmi_ops_t *ops = &drv->ops;

    for ( lmip = &drv->list; *lmip; lmip = &(*lmip)->next )
        if ( *lmip == lmi )
            break;
    if ( !*lmip )
        return ENXIO;

    if ( lmi->state == LMI_ENABLED ) {
        ops->lmi.disable(lmi);
        lmi->state = LMI_DISABLED;
    }
    if ( lmi->state == LMI_DISABLED ) {
        ops->lmi.detach(lmi);
        lmi->state = LMI_UNATTACHED;
    }

    for ( i=0; i<ntids; i++ )
        if ( tids[i] ) {
            untimeout(tids[i]);
            tids[i] = 0;
        }
    for ( i=0; i<nbids; i++ )
        if ( bids[i] ) {
            untimeout(bids[i]);
            bids[i] = 0;
        }

    ops->dev.close(lmi->device);

    *lmip = lmi->next;
    kfree(lmi);

    return (0);
}

int
lmi_drv_open(lmi_t *lmi)
{
    int err;
    if ( !lmi || !lmi->driver ) return ENXIO;
    if ( (err = lmi->driver->ops.dev.open(lmi->device)) ) {
        lmi->state = LMI_UNUSABLE;
        lmi_drv_close(lmi, NULL, 0, NULL, 0);
    }
    return (err);
}


/*
 *  =======================================================================
 *
 *  OPEN and CLOSE
 *
 *  =======================================================================
 */

int
lmi_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *crp,
        lmi_driver_t *list, size_t size)
{
    lmi_t *lmi = NULL;

    if ( q->q_ptr != NULL ) return(0);  /* do this in caller to avoid false success */

    if ( size < sizeof(lmi_t) ) return EFAULT;

    if ( sflag == DRVOPEN || WR(q)->q_next == NULL )
    {
        int err;
        lmi_t **lmip, *dev = NULL;
        lmi_driver_t *drv;
        int cminor = getminor(*devp);
        int cmajor = getmajor(*devp);

        DPRINT(0,("%s: [%s %d] Driver open for cmajor %d, cminor %d, size %d\n",
                    __FUNCTION__, __FILE__, __LINE__, cmajor, cminor, size));
        
        for ( drv = list; drv; drv = drv->next )
            if ( cmajor == drv->cmajor )
                break;
        if ( !drv )
            return ENXIO;

        if ( sflag == CLONEOPEN || cminor == 0 ) {
            for ( cminor = 1, lmip = &drv->list;
                  cminor <= drv->nminor && *lmip;
                  lmip = &(*lmip)->next ) {
                if ( cminor < getminor((*lmip)->devnum) ) {
                    DPRINT(0,("%s: [%s %d] Calling device attach\n", __FUNCTION__, __FILE__, __LINE__));
                    while ( !(dev = drv->ops.dev.attach(makedevice(cmajor, cminor))) )
                        if ( ++cminor < getminor((*lmip)->devnum) )
                            break;
                    if ( dev ) break;
                }
                if ( cminor == getminor((*lmip)->devnum) ) cminor++;
            }
            if ( !*lmip && cminor <= drv->nminor ) {
                DPRINT(0,("%s: [%s %d] Calling device attach\n", __FUNCTION__, __FILE__, __LINE__));
                dev = drv->ops.dev.attach(makedevice(cmajor, cminor));
            }
            if ( !dev || cminor > drv->nminor )
                return ENXIO;
            *devp = makedevice(getmajor(*devp), cminor);
        } else {
            if ( cminor > drv->nminor )
                return ENXIO;
            if ( !(dev = drv->ops.dev.attach(*devp)) )
                return ENXIO;
            for ( lmip = &drv->list; *lmip; lmip = &(*lmip)->next ) {
                if ( cminor <  getminor((*lmip)->devnum) ) break;
                if ( cminor == getminor((*lmip)->devnum) ) return EBUSY;
            }
        }
        if ( !(lmi = kmalloc(size, GFP_KERNEL)) )
            return ENOMEM;
        bzero(lmi, size);

        lmi->devnum = *devp;
        lmi->driver = drv;
        lmi->device = dev;
        DPRINT(0,("%s: [%s %d] Calling device open\n", __FUNCTION__, __FILE__, __LINE__));
        if ( (err = drv->ops.dev.open(dev)) ) {
            kfree(lmi);
            return(err);
        }
        dev->module = lmi;
        dev->ucalls = drv->ucalls;
        lmi->next = *lmip;
        *lmip = lmi;
    } else
    if ( sflag == MODOPEN || WR(q)->q_next != NULL ) {

        DPRINT(0,("%s: [%s %d] Module open for size %d\n",
                    __FUNCTION__, __FILE__, __LINE__, size));

        if ( !(lmi = kmalloc(size, GFP_KERNEL)) )
            return ENOMEM;
        bzero(lmi, size);
    } else
        return EIO;

    lmi->rq = RD(q);
    lmi->wq = WR(q);
    lmi->state = LMI_UNATTACHED;

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

    return(0);
}

int
lmi_close(queue_t *q, int flag, cred_t *crp,
        lmi_ulong *tids, int ntids, lmi_ulong *bids, int nbids)
{
    int i;
    lmi_t **lmip = NULL, *lmi = (lmi_t *)q->q_ptr;
    lmi_driver_t *drv = lmi->driver;


    if ( drv ) {
        DTRACE;
        for ( lmip = &drv->list; *lmip; lmip = &(*lmip)->next )
            if ( *lmip == lmi )
                break;
        if ( !*lmip )
            return EFAULT;
        DTRACE;
        if ( lmi->state == LMI_ENABLED ) {
            drv->ops.lmi.disable(lmi);
            lmi->state = LMI_DISABLED;
        }
        if ( lmi->state == LMI_DISABLED ) {
            drv->ops.lmi.detach(lmi);
            lmi->state = LMI_UNATTACHED;
        }
    }

    DTRACE;
    for ( i=0; i<ntids; i++ )
        if ( tids[i] ) {
            untimeout(tids[i]);
            tids[i] = 0;
        }
    DTRACE;
    for ( i=0; i<nbids; i++ )
        if ( bids[i] ) {
            untimeout(bids[i]);
            bids[i] = 0;
        }

    DTRACE;
    lmi->rq->q_ptr = lmi->wq->q_ptr = NULL;

    if ( drv ) {
        DTRACE;
        drv->ops.dev.close(lmi->device);
        *lmip = lmi->next;
    }

    DTRACE;
    kfree(lmi);
    return(0);
}

/*
 *  =======================================================================
 *
 *  DRIVER Registration
 *
 *  =======================================================================
 */

int
lmi_register_driver(list, cmajor, strtab, nminor, name, ops, dcalls, ucalls)
    lmi_driver_t     **list;
    major_t          cmajor;
    struct streamtab *strtab;
    int              nminor;
    char             *name;
    lmi_ops_t        *ops;
    void             *dcalls;
    void             *ucalls;
{
    int ret;
    lmi_driver_t *drv;

    if (    nminor < 1 || !list || !strtab || !dcalls || !ucalls || !ops ||
            !ops->dev.attach  || !ops->dev.open    || !ops->dev.close   ||
            !ops->lmi.info    || !ops->lmi.attach  || !ops->lmi.detach  ||
            !ops->lmi.enable  || !ops->lmi.disable || !ops->lmi.ioctl ) {
        DPRINT(3,("%s: [%s:%d] required pointer is NULL!\n", __FUNCTION__, __FILE__, __LINE__));
        return -EINVAL;
    }

    if ( !(drv = kmalloc(sizeof(*drv), GFP_KERNEL)) ) {
        DPRINT(3,("%s: [%s:%d] couldn't allocate memory!\n", __FUNCTION__, __FILE__, __LINE__));
        return -ENOMEM;
    }
    bzero(drv, sizeof(*drv));

    drv->next   = *list;  /* should spin lock this global */
    drv->cmajor  = cmajor;
    drv->nminor = nminor;
    drv->info   = strtab;
    drv->ops    = *ops;
    drv->ucalls = ucalls;
    drv->dcalls = dcalls;

    if ( (ret = lis_register_strdev(cmajor, strtab, nminor, name)) >= 0 ) {
        if ( ret && !cmajor )
            drv->cmajor = cmajor = ret;
        DTRACE;
        *list = drv;
    } else {
        kfree(drv);
        DPRINT(3,("%s: [%s:%d] couldn't register driver with LiS!\n", __FUNCTION__, __FILE__, __LINE__));
    }
    return(ret);
}

int
lmi_unregister_driver(lmi_driver_t **list, major_t cmajor)
{
    int ret;
    lmi_driver_t **drvp, *drv;

    for ( drvp = list; *drvp; drvp = &(*drvp)->next )
        if ( cmajor == (*drvp)->cmajor ) break;
    if ( !(drv = *drvp) ) {
        DPRINT(3,("%s: [%s:%d] couldn't find driver!\n", __FUNCTION__, __FILE__, __LINE__));
        return -ENXIO;
    }
    if ( drv->list ) {
        DPRINT(3,("%s: [%s:%d] drivers still running!\n", __FUNCTION__, __FILE__, __LINE__));
        return -EBUSY;
    }
    if ( (ret = lis_unregister_strdev(cmajor)) ) {
        DPRINT(3,("%s: [%s:%d] couldn't unregister driver from LiS!\n", __FUNCTION__, __FILE__, __LINE__));
        return(ret);
    }

    *drvp = drv->next;
    kfree(drv);
    return(0);
}

