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

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

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

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

#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 <sys/cred.h>
#include <sys/cdi.h>
#include <sys/types.h>
#include <linux/param.h>
#include <linux/errno.h>
#include <sys/debug.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>
#include "../include/ss7dev.h"

#define ACB56_DESCRIP   "SS7ACB56: SS7 LEVEL 2 DRIVER FOR THE SEALEVEL ACB56(TM) BOARD."
#define ACB56_COPYRIGHT "Copyright (C) 1997-2001  Brian Bidulock  All Rights Reserved."
#define ACB56_DEVICES   "Support the Sealevel ACB56(tm) V.35 boards."
#define ACB56_CONTACT   "Brian F. G. Bidulock <bidulock@openswitch.org>"
#define ACB56_BANNER    ACB56_DESCRIP "\n" \
                        ACB56_COPYRIGHT "\n" \
                        ACB56_DEVICES "\n" \
                        ACB56_CONTACT "\n"

#ifdef MODULE
MODULE_AUTHOR(ACB56_CONTACT);
MODULE_DESCRIPTION(ACB56_DESCRIP);
MODULE_SUPPORTED_DEVICE(ACB56_DEVICES);
#endif

#ifdef ACB56_DEBUG
int acb_debug = ACB56_DEBUG;
#else
int acb_debug = 2;
#endif

enum {
    ACB56_MODE_DTE,
    ACB56_MODE_DCE,
    ACB56_MODE_LOOP,
    ACB56_MODE_JACK,
    ACB56_MODE_ECHO,
    ACB56_MODE_BOTH
};

enum {
    ACB56_CLOCK_EXT,
    ACB56_CLOCK_INT,
    ACB56_CLOCK_DPLL
};

/*
 *  This should be adjusted to be more uniform for SS7
 *  link device drivers...
 */
typedef struct acb_stats {
    unsigned long transmitted_sus;    /* count of SUs   setn */
    unsigned long sent_fisus;         /* count of FISUs sent */
    unsigned long sent_lssus;         /* count of LSSUs sent */
    unsigned long sent_msus;          /* count of MSUs  sent */
    unsigned long repeated_sus;       /* count of sus repeated for lack of data */
    unsigned long tx_underruns;       /* count of destructive transmitter underruns */
    unsigned long breaks_or_aborts;   /* count of breaks or aborts received */
    unsigned long dcd_transitions;    /* count of transitions on the DCD lead */
    unsigned long cts_transitions;    /* count of transitions on the CTS lead */
    unsigned long received_sus;       /* count of SUs received */
    unsigned long recv_fisus;         /* count of received fisus */
    unsigned long recv_lssus;         /* count of received lssus */
    unsigned long recv_msus;          /* count of received msus */
    unsigned long sus_in_error;       /* count of SUs received in error */
    unsigned long bits_octet_counted; /* count of bits counted in octet mode */
    unsigned long sync_transitions;   /* count of flag detection transitions */
    unsigned long su_length_errors;   /* count of SUs with erroneous lengths */
    unsigned long parity_errors;      /* count of parity errors */
    unsigned long rx_overruns;        /* count of reciver overruns */
    unsigned long crc_errors;         /* count of reciver CRC errors */
    unsigned long frame_errors;       /* count of reciver frame errors */
    unsigned long residue_errors;     /* count of reciver residue errors */
    unsigned long rx_buffer_overflows;/* count of RB overflows */
    unsigned long rx_frame_overflows; /* count of bytes overflowing frame */
    unsigned long compressed_sus;     /* count of sus compressed */
    unsigned long cha_rx_sp_cond;     /* count of this vectored interrupt */
    unsigned long cha_rx_char_avail;  /* count of this vectored interrupt */
    unsigned long cha_ext_status;     /* count of this vectored interrupt */
    unsigned long cha_tx_buf_empty;   /* count of this vectored interrupt */
    unsigned long chb_rx_sp_cond;     /* count of this vectored interrupt */
    unsigned long chb_rx_char_avail;  /* count of this vectored interrupt */
    unsigned long chb_ext_status;     /* count of this vectored interrupt */
    unsigned long chb_tx_buf_empty;   /* count of this vectored interrupt */
    unsigned long interrupts;         /* count of interrupts */
    unsigned long rx_bytes;           /* count of bytes received */
    unsigned long tx_bytes;           /* count of bytes transmitted */
    unsigned long tx_eoms;            /* count of normally completed frames */
    unsigned long tx_aborts;          /* count of aborted frames */
    unsigned long buffer_errors;      /* count of times could get no buffer */
} acb_stats_t;

typedef struct acb_dev {
    struct acb_dev *next;
    unsigned long base_addr;    /* base i/o address */
    unsigned int irq;           /* IRQ */
    int if_index;               /* which card? */
    int if_mode;                /* interface mode for this card */
    int if_clock;               /* interface clocking for this card */
    int dma_rx;                 /* Rx DMA Channel Number */
    int dma_tx;                 /* Tx DMA Channel Number */
    int rx_octet_mode;          /* is octet counting on? */
    int rx_octet_count;         /* bits counted */
    unsigned char regs[16];     /* register images */
    unsigned char rr0;          /* Image of Read Register 0 (RR0) */
    mblk_t *tx_msg;             /* transmit buffer */
    mblk_t *rx_msg;             /* receive buffer */
    mblk_t *comp_msg;           /* compressed su buffer */
    acb_stats_t stats;        /* statistics for this card */
    unsigned char *tx_buf;      /* current transmit pointer */
    unsigned char *tx_max;      /* end of current transmit buffer */
    int tx_error;               /* transmit error flags */
    unsigned char *rx_buf;      /* current receive pointer */
    unsigned char *rx_max;      /* end of current receive buffer */
    int rx_error;               /* receive error flags */
} acb_dev_t;


#define MAXACB56 5

struct acb
{
    struct acb  *next;
    queue_t     *rq;
    sl_ulong    slstate;
    sl_ulong    flags;
    sl_ushort   idtype;
    sl_ulong    id;
    sl_ushort   cminor;
    acb_dev_t   dev;
};

/*
 * ============================================
 *
 *  Device specific initializations and probes
 *
 * ============================================
 */

/*
 *  Description:
 *      ACB56 boards are Style 1 devices: there is a V.35 interface on the back panel of
 *      the board to which the Signalling Data Link is attached.  Internally on the card
 *      there is only one Signalling Data Terminal (HDLC Chip) which can be assigned to the
 *      port on the back.  By opening a specific device, the user knows which port is
 *      requested and an SL_ATTACH_REQ is not necessary.
 *
 *      The ACB56 is an ISA bus device.  It has DIP-switch settable ioport assignments.  It
 *      is very difficult on PC architectures to support more than about 4 of these boards.
 *      Port assignments which do not conflict with normal PC ioport assignments are as
 *      provided in the ports[] array.  This is the order and position of a probe for
 *      installed ACB56 devices.
 */

struct acb_dev;
struct acb_dev* acb_devices = NULL;

static int debug = -1;
static int io    [] = { -1, -1, -1, -1, -1 };
static int irq   [] = { -1, -1, -1, -1, -1 };
static int dma_rx[] = { -1, -1, -1, -1, -1 };
static int dma_tx[] = { -1, -1, -1, -1, -1 };
static int mode  [] = { -1, -1, -1, -1, -1 };
static int clock [] = { -1, -1, -1, -1, -1 };

#ifdef MODULE
MODULE_PARM(debug, "i");    /* debug flag */
MODULE_PARM(io ,   "1-5i"); /* i/o port for the i'th card */
MODULE_PARM(irq,   "1-5i"); /* irq for the i'th card */
MODULE_PARM(dma_rx,"1-5i"); /* dma for the i'th card */
MODULE_PARM(dma_tx,"1-5i"); /* dma for the i'th card */
MODULE_PARM(mode,  "i-5i"); /* interface mode */
MODULE_PARM(clock, "i-5i"); /* clock source */
#endif

static unsigned char irqprobe[] = {
    0x01, 0x19, 0x0F, 0xFA, 0x00, 0x10, 0x00, 0x10, 0x09, 0x08, 0x0E, 0x03
};

static void dummy_isr(int irq, void *dev_id, struct pt_regs *regs) {
    volatile int *p; (void)irq; (void)dev_id; (void)regs; p = NULL; p++;
}

static int board = 0;
static int ports[] = { 0x238, 0x280, 0x2A0, 0x300, 0x328, 0 };

int acb56_probe(struct acb_dev* dev)
{
    int *test, iobase, _irq, actrl, _dma_rx, _dma_tx, i, err;
    unsigned long time, cookie;
    
    if ((iobase=io[board])!=-1 && !(err=check_region(iobase,8))) goto probe_irq;
    if (iobase!=-1) return err;
    /* probe for iobase */
    for (test = &ports[0]; *test; test++) {
        iobase = *test; actrl = iobase+1;
        if (check_region(iobase,8)) continue;   /* device conflict */
        outb(0x02,actrl); outb(0x55,actrl); /* write to unallocated 8530 bit in WR2 */
        outb(0x02,actrl); if (inb(actrl)!=0x55) continue; /* probably an 8530 */
        outb(0x09,actrl); outb(0xc0,actrl); /* force hardware reset */
        outb(0x0f,actrl); outb(0x01,actrl); /* Access W7P register */
        outb(0x0f,actrl); if (!inb(actrl)&0x01) continue; /* probably an 8530 */
        outb(0x0f,actrl); outb(0x00,actrl); /* Remove accss to W7P register */
        outb(0x0f,actrl); if (inb(actrl)&0x01) continue; /* probably an 8530 */
        goto probe_irq;
    }
    return -ENODEV;
probe_irq:
    if ((_irq=irq[board])!=-1 &&
            !(err = request_irq(_irq,dummy_isr,SA_SHIRQ,"acb56_dummy",NULL)))
        goto probe_dma;
    if (_irq!=-1) return err;
    /* probe for irq */
    actrl = iobase+1;
    for (i=0;i<sizeof(preamble);) { /* setup chip */
        outb(preamble[i],actrl); i++; outb(preamble[i],actrl); i++; }
    cookie = probe_irq_on();
    for (i=0;i<sizeof(irqprobe);) { /* setup for guaranteed interrupt */
        outb(irqprobe[i],actrl); i++; outb(irqprobe[i],actrl); i++; }
    /* fill tx fifo to get an interrupt */
    outb(0x55,iobase); outb(0x55,iobase); outb(0x55,iobase);
    outb(0x55,iobase); outb(0x55,iobase); outb(0x55,iobase);
    outb(0x55,iobase); outb(0x55,iobase); outb(0x55,iobase);
    for (time=jiffies;jiffies-time<100;i++);
    if (!(_irq = probe_irq_off(cookie))) return -ENODEV;   /* no irq! */
    outb(0x03,actrl); if (!inb(actrl)) return -ENODEV;    /* it wasn't us */
    outb(0x09,actrl); outb(0x00,actrl);
    outb(0x09,actrl); outb(0xc0,actrl); /* force hardware reset */
    if ((err=request_irq(_irq,dummy_isr,SA_SHIRQ,"acb56_dummy",NULL))) return err;
probe_dma:
    free_irq(_irq,NULL);
    /* check for dma */
    if ((_dma_rx=dma_rx[board])&&_dma_rx!=-1&&!(request_dma(_dma_rx,"acb56_probe")))
        free_dma(_dma_rx); else _dma_rx=0;
    if ((_dma_tx=dma_tx[board])&&_dma_tx!=-1&&!(request_dma(_dma_tx,"acb56_probe")))
        free_dma(_dma_tx); else _dma_tx=0;

    if (!dev) {
        if (!(dev = kmalloc(sizeof(struct acb56_dev),GFP_KERNEL))) return -ENOBUFS;
        memset(dev, 0x00, sizeof(struct acb56_dev));
    }
    /* setup major operating modes */
    if ((dev->if_mode =mode [board])==-1) dev->if_mode  = ACB56_MODE_LOOP;
    if ((dev->if_clock=clock[board])==-1) dev->if_clock = ACB56_CLOCK_EXT;

    dev->if_index  = board;
    dev->dma_tx    = _dma_tx;
    dev->base_addr = iobase;
    dev->irq       = _irq;
    dev->dma_rx    = _dma_rx;

    {
        static const char *if_modes [] = { "DTE", "DCE", "LOOP", "JACK", "ECHO", "LOOP&/ECHO" };
        static const char *if_clocks[] = { "EXT", "INT", "DPLL" };
        printk("acb: escc%d at %04xH, irq %d, dma %d rx %d tx, %s clk %s\n",
            dev->if_index, dev->base_addr, dev->irq, dev->dma_rx, dev->dma_tx,
            if_modes[dev->if_mode], if_clocks[dev->if_clock]);
    }

    request_region(iobase,8,"acb56");

    dev->next = acb_devices;
    acb_devices = dev;
    return(0);
}

/*
 * ============================================
 *
 *  STREAMS stuff.
 *
 * ============================================
 */

typedef enum retval
{
    DONE,
    RETRY,
    ERR
} retval_t;

static struct module_info acb_minfo =
{
    0xdddd, /* FIXME */ /* Module ID number                     */
    "acb",              /* Module name                          */
    MINDATA,            /* Min packet size accepted             */
    MAXDATA,            /* Max packet size accepted             */
    10*MAXDATA,         /* Hi water mark                        */
    MAXDATA             /* Lo water mark                        */
};

static int acb_rsrv(queue_t *);
static int acb_open(queue_t *, dev_t *, int, int, cred_t *);
static int acb_close(queue_t *, int, cred_t *);

static int acb_wput(queue_t *, mblk_t *);
static int acb_wsrv(queue_t *);

static struct qinit acb_rinit =
{
    NULL,       /* qi_putp  */  /* Read put (message from below) */
    acb_rsrv,   /* qi_srvp  */  /* Read queue service   */
    acb_open,   /* qi_open  */  /* Each open            */
    acb_close,  /* qi_close */  /* Last close           */
    NULL,       /* qi_admin */  /* Admin                */
    &acb_minfo, /* qi_info  */  /* Information          */
    NULL        /* qi_mstat */  /* Statistics           */
};

static struct qinit acb_winit =
{
    acb_wput,   /* qi_putp  */  /* Write put (message from above) */
    acb_wsrv,   /* qi_srvp  */  /* Write queue service  */
    NULL,       /* qi_open  */  /* Each open            */
    NULL,       /* qi_close */  /* Last close           */
    NULL,       /* qi_admin */  /* Admin                */
    &acb_minfo, /* qi_info  */  /* Information          */
    NULL        /* qi_mstat */  /* Statistics           */
};

#ifdef MODULE
static
#endif
struct streamtab acb_info =
{
    &acb_rinit,         /* Read queue           */
    &acb_winit,         /* Write queue          */
    NULL,               /* Not a multiplexer    */
    NULL                /* Not a multiplexer    */
};

static int acb_initialized = 0;

void acb_init(void)
{
    if (acb_initialized) return;
    /*
     *  Note: one of the things that I could do here is a
     *  Linux style probe for devices so that a later clone
     *  operation doesn't have to do any probing around.
     *  Use the code from the Linux Kernel driver.
     */

    printk(KERN_INFO ACB56_BANNER);

    /*
     *  Do some intializations of structures and stuff.
     */

    acb_initialized = 1;
}

void acb_terminate(void)
{
    if (!acb_initialized) return;

    /*
     *  Do some cleanup.
     */

    acb_initialized = 0;
}

static int acb_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *crp)
{
    int i;
    if (sflag == MODOPEN)
        return EINVAL;
    if (q->q_ptr != NULL)
        return 0;
    if (sflag == CLONEOPEN)
    {
        /*
         *  Note: if we have already done a probe here, we
         *  can just look through the list of probed devices
         *  for the next available and free device.
         */
        for (i=0; i<MAXACB56; i++)
            if (acb[i].rq == NULL)
                break;
        if (i>MAXACB56)
            return ENXIO;
        *devp = makedevice(getmajor(*devp),i);
    }
    else
    {
        if ((i = getminor(*devp)) >= MAXACB56)
            return ENXIO;
    }
    /*
     *  Note: whether we have a specific device or have
     *  selected one with a clone, eweh have to go about
     *  acquiring the io region, IRQ and stuff like that.
     *  This should be the same as the Linux Kernel driver's
     *  open routines.
     */
    acb[i].rq = q;
    acb[i].idtype = NO_ID;
    acb[i].state = CD_UNATTACHED;
    q->q_ptr = (caddr_t)&acb[i];
    WR(q)->q_ptr = (caddr_t)&acb[i];
    return 0;
}

static int acb_close(queue_t *q, int flag, cred_t *crp)
{
    struct acb *acb = (struct acb *)q->q_ptr;
    if (acb->idtype == TIME_ID)
        untimeout(acb->id);
    else
    if (acb->idtype == BUF_ID)
        unbuffcall(acb->id);
    acb->state = CD_UNATTACHED;
    acb->rq = NULL;
    q->q_ptr = NULL;
    WR(q)->q_ptr = NULL;
    return 0;
}

static retval_t acb_p_info (struct acb *acb, mblk_t *mp);
{
    cd_info_ack_t *ackp;
    mblk_t *rp;
    (void)mp;
    int i = CD_INFO_ACK_SIZE+TOTADDRSZ+MACSZ; /* FIXME */
    if ((rp = allocb(i, BPRI_HI)) == NULL)
        return ERR;
    rp->b_datap->db_type = M_PCPROTO;
    ackp = (cd_info_ack_t *)rp->b_wptr;
    ackp->cd_primitive = CD_INFO_ACK;
    acp->cd_state = acb->state;
    acp->cd_max_sdu = MAXDATA;
    acp->cd_min_sdu = MINDATA;
    acp->cd_class = CD_HDLC;
    acp->cd_duplex = CD_HALFDUPLEX;
    acp->cd_output_style = (CD_ACKEDOUTPUT|CD_PACEDOUTPUT);
    acp->cd_features = (CD_CANREAD|CD_AUTOALLOW);
    acp->cd_addr_length = 0;    /* FIXME */
    acp->cd_ppa_style = CD_STYLE2;
    rp->b_wptr += CD_INFO_ACK_SIZE;
    if (acb->state == CD_UNATTACHED)
    {
        /* FIXME */
    }
    else
    {
        /* FIXME */
    }
    /* FIXME */
    putnext(acb->rq, rp);
    return DONE;
}

static retval_t acb_p_attach (struct acb *, mblk_t *);
static retval_t acb_p_enable (struct acb *, mblk_t *);
static retval_t acb_p_disable (struct acb *, mblk_t *);
static retval_t acb_p_pdu (struct acb *, mblk_t *);
static retval_t acb_p_emerg (struct acb *, mblk_t *);
static retval_t acb_p_emerg_cease (struct acb *, mblk_t *);
static retval_t acb_p_start (struct acb *, mblk_t *);
static retval_t acb_p_stop (struct acb *, mblk_t *);
static retval_t acb_p_retr_bsnt (struct acb *, mblk_t *);
static retval_t acb_p_retr_and_fsnc (struct acb *, mblk_t *);
static retval_t acb_p_resume (struct acb *, mblk_t *);
static retval_t acb_p_clr_bufs (struct acb *, mblk_t *);
static retval_t acb_p_local_proc_out (struct acb *, mblk_t *);
static retval_t acb_p_cong_discard (struct acb *, mblk_t *);
static retval_t acb_p_cong_accept (struct acb *, mblk_t *);
static retval_t acb_p_no_cong (struct acb *, mblk_t *);


#define PLAST SL_DS_LAST+1
typedef retval_t (*acb_prim_ops_t[PLAST])(struct acb *, mblk_t *);
static acb_prim_ops_t acb_prim_ops = {
    acb_p_info,           /* SL_INFO_REQ                       */
    acb_p_attach,         /* SL_ATTACH_REQ                     */
    acb_p_enable,         /* SL_ENABLE_REQ                     */
    acb_p_disable,        /* SL_DISABLE_REQ                    */
    acb_p_pdu,            /* SL_PDU_REQ                        */
    acb_p_emerg,          /* SL_EMERGENCY_REQ                  */
    acb_p_emerg_cease,    /* SL_EMERGENCY_CEASES_REQ           */
    acb_p_start,          /* SL_START_REQ                      */
    acb_p_stop,           /* SL_STOP_REQ                       */
    acb_p_retr_bsnt,      /* SL_RETRIEVE_BSNT_REQ              */
    acb_p_retr_and_fsnc,  /* SL_RETRIEVAL_REQUEST_AND_FSNC_REQ */
    acb_p_resume,         /* SL_RESUME_REQ                     */
    acb_p_clr_bufs,       /* SL_CLEAR_BUFFERS_REQ              */
    acb_p_local_proc_out, /* SL_LOCAL_PROCESSOR_OUTAGE_REQ     */
    acb_p_cong_discard,   /* SL_CONGESTION_DISCARD_REQ         */
    acb_p_cong_accept,    /* SL_CONGESTION_ACCEPT_REQ          */
    acb_p_no_cong         /* SL_NO_CONGESTION_REQ              */
};

#if 0 /* not for drivers */
/*
 *  We have a message being put from below.
 */
static int acb_rput(queue_t *q, mblk_t *mp)
{
    if (mp->b_datap->db_type >= QPCTL)
    {
        /* we have a priority message, process it now... */

        /*
         * FIXME: Process priority message...
         */

        putnext(q, mp);
    } else {
        if (canput(q->q_next))
        {
            /*
             * FIXME: Process normal message...
             */

            putnext(q, mp);
        } else {
            /*
             * put message back on our own queue,
             * wait for sevice...
             */
            putq(q, mp);
        }
    }
    return 0;
}
#endif

typedef retval_t (*acb_ioc_ops_t[ILAST])(struct acb*, struct ioblk *, mblk_t *);

static acb_ioc_ops_t acb_ioc_ops = {
    acb_i_control_0,        /* 'A' << 8 +  0 */
    acb_i_control_1,        /* 'A' << 8 +  1 */
    acb_i_control_2,        /* 'A' << 8 +  2 */
    acb_i_control_3,        /* 'A' << 8 +  3 */
    acb_i_control_4,        /* 'A' << 8 +  4 */
    acb_i_control_5,        /* 'A' << 8 +  5 */
    acb_i_control_6,        /* 'A' << 8 +  6 */
    acb_i_control_7,        /* 'A' << 8 +  7 */
    acb_i_control_8,        /* 'A' << 8 +  8 */
    acb_i_control_9,        /* 'A' << 8 +  9 */
    acb_i_control_10,       /* 'A' << 8 + 10 */
    acb_i_control_11,       /* 'A' << 8 + 11 */
    acb_i_control_12,       /* 'A' << 8 + 12 */
    acb_i_control_13,       /* 'A' << 8 + 13 */
    acb_i_control_14,       /* 'A' << 8 + 14 */
    acb_i_control_15        /* 'A' << 8 + 15 */
};

/*
 *  IOCTLs to perform implemnetation-specific functions on
 *  device.  We proprpbly want to define a few generic
 *  IOCTLs here and then pass the rest transparently on to
 *  the actual device code.
 */
static retval_t acb_m_ioctl(queue_t *q, mblk_t *mp)
{
    struct acb *acb = q->q_ptr;
    assert(acb != NULL);
    assert(mp != NULL);
    assert(mp->b_datap->db_type == M_IOCTL);

    iocp = (struct iocblk *)mp->b_rptr;

    /* FIXME */ iocmd = iocp->ioc_cmd;

    if ( iocmd >= ACB_IOC_FIRST && iocmd <= ACB_IOC_LAST )
        return acb_ioc_ops[iocmd](acb, iocp, mp);
    /*
     *  Here we would forward on transparently if we were a
     *  module, but we're a driver, so we do the final
     *  upstream M_IOCNAK on all unrecognized ioctls.
     */
    mp->b_datap->db_type = M_IOCNAK;
    qreply(WR(acb->rq), mp);
    return DONE;
}

/*
 *  We have a message being put from above.
 */
static int acb_wput(queue_t *q, mblk_t *mp)
{
    switch (mp->b_datap->db_type)
    {
    /*
     *  Typical STREAMS flush code.
     */
        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);
            }
            else
                freemsg(mp);
            break;
    /*
     *  Note: we only process I_STR ioctls.  Transparent ioclts come form direct ioctl()
     *  control of the character device.  It is normal to pass ioctls that we do not
     *  recognize downwards; however, we are a driver (so there is no downwards.  If we
     *  don't recognize the ioctl, we just NACK it.
     */
        case M_IOCTL:
            acb_m_ioctl(q, mp);
            break;
    /*
     *  M_DATA (raw write), M_PROTO or M_PCPROTO (SL_PDU_REQ) should be requeued only if
     *  the q->q_count != 0 i.e., there is already messages queued and we must keep them
     *  FIFO order.  Otherwise, we should process them immediately.  M_PCPROTO (SL_PDU_REQ)
     *  should always be processed immediately regardless of the queue (priority messages
     *  are not queued).
     */
        case M_DATA:    /* FIXME */
        case M_PROTO:   /* FIXME */
        case M_PCPROTO: /* FIXME */
            putq(q, mp);
            break;

     /* case M_READ:       Pointless, you have all our data   */
     /* case M_STOP:    <-                                    */
     /* case M_START:   <- These don't appear to ever be      */
     /* case M_STARTI:  <- used anywhere.                     */
     /* case M_STOPI:   <-                                    */
     /* case M_IOCDATA:    We don't need to process this      */
     /*                    because we don't do transparent    */
     /*                    ioctl.                             */
        default:
            freemsg(mp);
            break;
    }
    return 0;
}

/*
 *  We are servicing our write queue.
 */
static int acb_wsrv(queue_t *q)
{
    mblk_t *mp;
    ulong_t err, prim;
    union CD_primitives *cdp;
    struct acb *acb = (struct acb *)q->q_ptr;
    if (acb == NULL)
        return 0;
    while ((mp = getq(q)) != NULL)
    {
        assert(mp->b_datap->db_type == M_PROTO ||
                mp->b_datap->db_type == M_PCPROTO);
        err = 0;
        cdp = (union CD_primitives *)mp->b_rptr;
        prim = cdp->cd_primitive;
        switch (prim)
        {
            case SL_PDU_REQ:
                {
                    int s = splstr();
                    if (acb_sndbusy)
                        putbq(q, mp);
                    else
                        acb_send(acb, mp);
                    splx(s);
                    return 0;
                }
            case SL_INFO_REQ: /* info request */
                if (acb_info(acb) != DONE)
                    cmn_err(CE_WARN, "acb: can't respond to SL_INFO_REQ");
                freemsg(mp);
                break;
            case SL_ATTACH_REQ:  /* attach a PPA */
                if (acb_attach(acb, mp) == RETRY) putbq(q, mp);
                break;
            case SL_DETACH_REQ:  /* detach a PPA */
                if (acb_detach(acb, mp) == RETRY) putbq(q, mp);
                break;
            case SL_ENABLE_REQ:  /* prepare device */
                if (acb_enable(acb, mp) == RETRY) putbq(q, mp);
                break;
            case SL_DISABLE_REQ: /* disable device */
                if (acb_disable(acb, mp) == RETRY) putbq(q, mp);
                break;
            case SL_EMERGENCY_REQ: /* set emergency */
            case SL_EMERGENCY_CEASES_REQ: /* set emergency ceases */
            case SL_START_REQ:  /* start link */
            case SL_STOP_REQ: /* stop link */
            case SL_RETRIEVE_BSNT_REQ: /* request for bsnt value */
            case SL_RETRIEVAL_REQUEST_AND_FSNC_REQ: /* request for tb+rtb for fsnc */
            case SL_RESUME_REQ: /* resume request */
            case SL_CLEAR_BUFFERS_REQ: /* request to clear buffers (also M_FLUSH?) */
            case SL_LOCAL_PROCESSOR_OUTAGE_REQ: /* local outage (premautre unload too?) */
            case SL_CONGESTION_DISCARD_REQ: /* set congestion discard */
            case SL_CONGESTION_ACCEPT_REQ: /* set congestion accept */
            case SL_NO_CONGESTION_REQ: /* clear congestion */
                err = SL_NOTSUPP;
            default:
                if (err == 0) err = CD_BADPRIM;
                if (acb_errorack(acb, prim, err, 0) == RETRY)
                {
                    if (mp->b_datap->db_type < QPCTL)
                    {
                        putbq(q, mp);
                        return 0;
                    }
                    cmn_err(CE_WARN, "acb: can't generate CD_ERROR_ACK (%d)", prim);
                }
                freemsg(mp);
                break;
        }
    }
    return 0;
}

static int acb_rsrv(queue_t *q)
{
    mblk_t *mp;
    while ((mp = getq(q)) != NULL)
    {
        assert(mp->b_datap->db_type == M_PROTO);
        if (canput(q->q_next))
        {
            putnext(q, mp);
        }
        else
        {
            putbq(q, mp);
            break;
        }
    }
    return 0;
}


}

static retval_t acb_p_attach (struct acb *, mblk_t *)
{
}

static retval_t acb_p_enable (struct acb *, mblk_t *)
{
}

static retval_t acb_p_disable (struct acb *, mblk_t *)
{
}

static retval_t acb_p_pdu (struct acb *, mblk_t *)
{
}

static retval_t acb_p_emerg (struct acb *, mblk_t *)
{
}

static retval_t acb_p_emerg_cease (struct acb *, mblk_t *)
{
}

static retval_t acb_p_start (struct acb *, mblk_t *)
{
}

static retval_t acb_p_stop (struct acb *, mblk_t *)
{
}

static retval_t acb_p_retr_bsnt (struct acb *, mblk_t *)
{
}

static retval_t acb_p_retr_and_fsnc (struct acb *, mblk_t *)
{
}

static retval_t acb_p_resume (struct acb *, mblk_t *)
{
}

static retval_t acb_p_clr_bufs (struct acb *, mblk_t *)
{
}

static retval_t acb_p_local_proc_out (struct acb *, mblk_t *)
{
}

static retval_t acb_p_cong_discard (struct acb *, mblk_t *)
{
}

static retval_t acb_p_cong_accept (struct acb *, mblk_t *)
{
}

static retval_t acb_p_no_cong (struct acb *, mblk_t *)
{
}


#ifdef MODULE
int init_module(void)
{
#ifdef CONFIG_KMOD
    /* might want to request other modules here with request_module() */
#endif
    int ret = lis_register_strdev(ACB_CMAJOR_0, &acb_info, ACB_N_MINOR, "acb");
    if (ret < 0) {
        printk(KERN_ERROR "acb: Unable to register driver.\n");
        return ret;
    }
    acb_init();
    /*
     *  Note: if acb_init() does a probe here, we could
     *  return -ENODEV if we found no devices and cause the
     *  module to be automatically unloaded.  There is no
     *  need for the module if there are no devices of this
     *  type.  Check to see what lis_register_strdev does
     *  here though.
     */
    return (acb_devices == NULL) ? -ENODEV : 0;
}
void cleanup_module(void)
{
    if (lis_unregister_strdev(ACB_CMAJOR_0) < 0)
        printk("acb: Unable to unregister driver.\n");
    else
        printk("acb: Unregistered, ready to be unloaded.\n");
    return;
    acb_terminate();
}
#endif

/*
 *  Note: I have a real question as to when the transmitters
 *  should be set to start sending SIOS.  The Q.703 spec
 *  indicates on power-on, but these cards aren't that
 *  smart.  Should we start the transmitters when we find
 *  the device (i.e., after a probe when the module is
 *  initialized) or when the device is first opened but not
 *  yet enabled.  Or should we start transmitters when
 *  enabled, but not start receivers till we get an SL_START
 *  protocol command?
 */
