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

                      SS7 LINK LEVEL (L2) INTERFACE FOR NET4

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

 Copyright (C) 1997, 1998, 1999, 2000  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: 2000/10/09 02:40:40 $ by $Author: brian $

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

 $Log: ss7if.c,v $
 Revision 0.7.2.4  2000/10/09 02:40:40  brian
 Pre-release 1.

 Revision 0.7.2.3  2000/09/11 23:35:27  brian
 Changed to work with new header files.

 Revision 0.7.2.2  2000/09/07 13:29:01  brian
 ss7link.o compiles as a module and loads fine.

 Revision 0.7.2.1  2000/09/07 12:49:11  brian
 Got ss7link interface support for drivers compiling.

 Revision 0.7  2000/09/07 11:20:33  brian
 Initial import of OpenSS7 stack.

 Revision 0.7.2.2  2000/09/07 11:07:36  brian
 Fixed minor error.

 Revision 0.7.2.1  2000/09/07 10:51:42  brian
 Got these files going.

 Revision 0.7  2000/09/06 19:39:10  brian
 Added files back in at revision 0.7.

 Revision 1.1.4.5  2000/09/06 18:39:08  brian
 Added a lot of stuff.

 Revision 1.1.4.4  2000/09/06 07:30:22  brian
 Even more playing with headers.

 Revision 1.1.4.3  2000/09/06 07:28:26  brian
 More playing with headers.

 Revision 1.1.4.2  2000/09/06 07:23:15  brian
 More playing with headers.

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

static char const ident[] = "$Id: ss7if.c,v 0.7.2.4 2000/10/09 02:40:40 brian Exp $";

#include <linux/version.h>
#include <linux/module.h>
#include <linux/modversions.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/in.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/string.h>
#include <linux/sockios.h>
#include <linux/net.h>
#include <linux/inet.h>
#include <linux/if_arp.h>
#include <linux/skbuff.h>
#include <linux/notifier.h>
#include <linux/netdevice.h>
#include <net/sock.h>

#include "../../net/ss7/af_ss7.h"
#include "../../net/ss7/mtp.h"
#include "../../net/ss7/mtp_route.h"
#include "../../net/ss7/mtp_sm.h"
#include "../../include/linux/ss7link.h"
#include "../../include/linux/ss7_timers.h"

#include "ss7if.h"
#include <net/pkt_sched.h>

#define SS7LINK_DESCRIPTION "SS7/LINK($Revision: 0.7.2.4 $): SS7 LINK LEVEL (L2) INTERFACE FOR NET4"
#define SS7LINK_COPYRIGHT   "Copyright (C) 1997-2000  Brian Bidulock.  All Rights Reserved."
#define SS7LINK_DEVICES     "Supporting various serial and T1 cards."
#define SS7LINK_AUTHOR      "Brian F. G. Bidulock <bidulock@dallas.net>"
#define SS7LINK_BANNER      SS7LINK_DESCRIPTION "\n" \
                            SS7LINK_COPYRIGHT "\n" \
                            SS7LINK_DEVICES "\n" \
                            SS7LINK_AUTHOR "\n"

MODULE_AUTHOR(SS7LINK_AUTHOR);
MODULE_DESCRIPTION(SS7LINK_DESCRIPTION);
MODULE_SUPPORTED_DEVICE(SS7LINK_DEVICES);

static void ss7_timer_init(struct ss7_iface *l, int n, int d)
{
    struct ss7if_timer *t = &l->timers[n];
    struct timer_list *k = &t->kernel_timer;

    init_timer(k);
    t->taskq.sync = 0;
    t->taskq.next = 0;  /* Its a floor wax AND a dessert topping! */
    t->taskq.data = l;
    t->duration = d;
    k->data = (unsigned long)t;
    k->function = (void(*)(unsigned long))ss7_timer_timeout;
}

/*
 *  This is a wrapper for the dev->open() interface service routine. It
 *  performs initializations on SS7 LINK interface and then calls the
 *  device driver's initialization routine.
 */

static int ss7if_open(struct device *dev)
{
    struct ss7_iface *iface;

    if ((iface = dev->ss7_ptr) == NULL)
        return -ENODEV;
    if (iface->LSC_state && iface->LSC_state != LSC_STATE_Out_of_Service)
        return -EINVAL;

    /*
     *  Initialize interface structures:
     */

    ss7_timer_init(iface,TIMER_T1, TIMER_T1V);
    ss7_timer_init(iface,TIMER_T2, TIMER_T2V);
    ss7_timer_init(iface,TIMER_T3, TIMER_T3V);
    ss7_timer_init(iface,TIMER_T4, TIMER_T4NV);
    ss7_timer_init(iface,TIMER_T5, TIMER_T5V);
    ss7_timer_init(iface,TIMER_T6, TIMER_T6V);
    ss7_timer_init(iface,TIMER_T7, TIMER_T7V);

    skb_queue_head_init(&iface->xmit_q);  /* trasmit buffer      */
    skb_queue_head_init(&iface->rexm_q);  /* retransmit buffer   */
    skb_queue_head_init(&iface->writ_q);  /* output queue        */
    skb_queue_head_init(&iface->read_q);  /* input  queue        */

    iface->tx_taskq.sync = 0;
    iface->tx_taskq.next = 0;
    iface->tx_taskq.data = iface;

    iface->rx_taskq.sync = 0;
    iface->rx_taskq.next = 0;
    iface->rx_taskq.data = iface;

    if (!iface->fisu_skb)
        if ((iface->fisu_skb = alloc_skb(4,GFP_ATOMIC)) == NULL)
            return -ENOBUFS;
    iface->fisu_skb->data[0] = 0x00;
    iface->fisu_skb->data[1] = 0x00;
    iface->fisu_skb->data[2] = 0x00;
    iface->fisu_skb->data[3] = 0x00;

    if (!iface->lssu_skb)
        if ((iface->lssu_skb = alloc_skb(5,GFP_ATOMIC)) == NULL)
            return -ENOBUFS;
    iface->lssu_skb->data[0] = 0x00;
    iface->lssu_skb->data[1] = 0x00;
    iface->lssu_skb->data[2] = 0x01;
    iface->lssu_skb->data[3] = 0x00;

    if (iface->if_open)
        return iface->if_open(dev);

    /*
     *  The L3 protocol entity will prepare us for service by sending us an
     *  L2 Start command once it sees our NETDEV_UP notification.
     */

    return(0);
}

/*
 *  This is a wrapper for the dev->stop() interface service routine.  It
 *  performs cleanup on the SS7 LINK interface and then calls the device
 *  driver's close routine.  If the SS7 link is running (haven't received
 *  L3 Stop command), then we are in trouble 'cause our backlog queue is
 *  about to be flushed.  Scream loudly and hope that L3 can recover by
 *  finding out through the notifier chain.
 *
 *  Should not be called at interrupt level.
 */

static int ss7if_stop(struct device *dev)
{
    struct ss7_iface *iface;

    if ((iface = dev->ss7_ptr) == NULL)
        return -ENODEV;
    if (iface->LSC_state && iface->LSC_state != LSC_STATE_Out_of_Service)
        printk(KERN_ERR "SS7/LINK: Running SS7 Link closed!\n");

    {
        /*
         *  FIXME: Is this OK?  No interrupts disabled or anything?
         */
        skb_queue_purge(&iface->xmit_q);
        skb_queue_purge(&iface->rexm_q);
        skb_queue_purge(&iface->writ_q);
        skb_queue_purge(&iface->read_q);

        if (iface->fisu_skb) {
            kfree_skb(iface->fisu_skb);
            iface->fisu_skb = NULL;
        }

        if (iface->lssu_skb) {
            kfree_skb(iface->lssu_skb);
            iface->lssu_skb = NULL;
        }
    }

    /*  FIXME: cleanup interface structures but don't deallocate: we wait
     *  for the destructor to deallocate.  */

    if (iface->if_stop)
        return iface->if_stop(dev);
    return(0);
}

/*
 *  This is a wrapper for the dev->destructor() interface service routine.
 *  It performs the same cleanup as an explicit call to ss7if_unregister()
 *  or ss7if_detach().  The interface is detached from the device and all
 *  associated SS7 LINK interface structure are deallocated.
 */

static void ss7if_kill(struct device *dev)
{
    return ss7if_unregister(dev);
}

/*
 *  This is the wrappter for the dev->hard_start_xmit() interface service
 *  routine.  It queues frames coming downwards from the L3 protocol entity
 *  and defers processing on them until a bottom-half runs.  For proper
 *  operation, the `real' device driver should never set the dev->tbusy
 *  flag.
 */

static int ss7if_xmit(struct sk_buff *skb, struct device *dev)
{
    struct ss7_iface *iface;

    if (dev->type != ARPHRD_SS7) goto ss7if_not_for_us;
    if (!(iface = dev->ss7_ptr)) goto ss7if_not_for_us;

    return ss7if_output(skb,dev);

ss7if_not_for_us:

    if (net_ratelimit())
        printk(KERN_ERR "SS7/LINK: link for %s received unexpected packet!\n", dev->name);

    kfree_skb(skb);

    return(0);
}

/*
 *  This is the wrapper for the dev->do_ioctl() interface service routine.
 *  It is expected that this has been called via dev_ioctl(), so we only
 *  strip off our private ioctl's and pass the rest on to the `real'
 *  device.
 */

static int ss7if_ioctl(struct device *dev, struct ifreq *ifr, int cmd)
{
    struct ss7_iface *iface;

    if (!(iface = dev->ss7_ptr))
        return -ENODEV;

    switch (cmd)
    {
        /*
         *  We only intercept the SS7 specific private ioctls here.  We
         *  allocate from SIOCDEVPRIVATE + 15 downwards to keep away from
         *  the device's SIOCDEVPRIVATE + 0 upwards allocation.
         *
         *  FIXME: add some ioctls.
         */

        case SIOCSPRIV_f:
        case SIOCSPRIV_e:
        case SIOCSPRIV_d:
        case SIOCSPRIV_c:
        case SIOCSPRIV_b:
        default:
            if (iface->if_ioctl)
                return iface->if_ioctl(dev, ifr, cmd);
    }
    return -EINVAL;
}

static int __ss7if_remove(struct device *dev, struct ss7if_reg  *regs)
{
    struct ss7_iface *iface;

    if ((iface = dev->ss7_ptr) == NULL)
        return -ENODEV;
    if (iface->LSC_state && iface->LSC_state != LSC_STATE_Out_of_Service)
        printk(KERN_ERR "SS7/LINK: Running SS7 Link closed!\n");

    /* FIXME: do some more cleanup here. */

    dev->ss7_ptr = NULL;
    if (regs) (*regs) = iface->calls;

#if 0
    /* FIXME: get this working. */

    if (iface->linkset) { /* oops, my address is linked in... */
        unsigned long flags;
        save_flags(flags); cli();

            iface->next->prev = iface->prev;
            iface->prev->next = iface->next;
            if (iface->next == iface->prev)
                iface->next = iface->prev = NULL;
            if (iface->linkset->link == iface)
                iface->linkset->link= iface->next;
            /*
             *  Let L3 find the empty linkset during routing.
             */

        restore_flags(flags);
    }
#endif

    kfree(iface);
    MOD_DEC_USE_COUNT;
    return(0);
}

void ss7if_unregister(struct device *dev)
{
    __ss7if_remove(dev,NULL);
}

void ss7if_detach(struct device *dev)
{
    __ss7if_remove(dev,NULL);
}

int ss7if_register(struct device *dev, struct ss7if_reg  *regs)
{
    struct ss7_iface *iface;

    if (!regs || !regs->hard_start_xmit)
        return -EINVAL;
    if ((iface = kmalloc(sizeof(*iface),GFP_ATOMIC)) == 0)
        return -ENOMEM;
    memset(iface,0x00,sizeof(*iface));

    iface->calls = (*regs);

    dev->mtu             = SS7_MTU;
    dev->type            = ARPHRD_SS7;
    dev->hard_header_len = 0;
    dev->addr_len        = 0;
    dev->tx_queue_len    = TB_Q_MAX_SIZE;

    dev->destructor      = ss7if_kill;
    dev->open            = ss7if_open;
    dev->stop            = ss7if_stop;
    dev->hard_start_xmit = ss7if_xmit;
    dev->do_ioctl        = ss7if_ioctl;

    dev->ss7_ptr = iface;
    iface->dev   = dev;

    /* User will have to do this: symbol not exported */
    /* dev_activate(dev);  * reinitialize buffer queues */

    /*
     *  The L3 protocol entity will initialize our address and assign us to
     *  a linkset when it sees out NETDEV_REGISTER notification.
     */

    MOD_INC_USE_COUNT;
    return(0);
}

int ss7if_attach(struct device *dev)
{
    struct ss7if_reg  regs;

    regs.destructor      = dev->destructor      ;
    regs.open            = dev->open            ;
    regs.stop            = dev->stop            ;
    regs.hard_start_xmit = dev->hard_start_xmit ;
    regs.do_ioctl        = dev->do_ioctl        ;

    return ss7if_register(dev,&regs);
}

/*
 *  A device registered with our hardware type: create a new interface and
 *  attach it to the device.
 */
static int ss7if_device_event(struct notifier_block *self, unsigned long event, void *devptr)
{
    struct device* dev = devptr;

    if (    dev->type == ARPHRD_SS7 &&
            event == NETDEV_REGISTER &&
            ss7if_attach(dev)
       )
        return NOTIFY_BAD;

    return NOTIFY_DONE;
}

static struct notifier_block ss7if_nb =
{
    ss7if_device_event,
    NULL,
    10
};

int init_module(void)
{
    printk(KERN_INFO SS7LINK_BANNER);
    register_netdevice_notifier(&ss7if_nb);
    printk(KERN_INFO "Ready to accept SS7 Link devices.\n");
    return(0);
}

void cleanup_module(void)
{
    unregister_netdevice_notifier(&ss7if_nb);
    printk(KERN_DEBUG "SS7/LINK: Say \"goodnight,\" Gracy!\n");
}
