/* Generic driver for Z8530 boards, modified from the PE1CHL
 * driver for use with NOS. This version also supports the NRS
 * mode when used as an asynch port. Device setup is similar to
 * that of the PE1CHL version, with the addition of user specification
 * of buffer size (bufsize). See the file "scc.txt" for general
 * information on the use of this driver and setup procedures.
 *
 * General differences between this driver and the original version:
 *
 * 1) Slip encoding and decoding is not done in the driver, but
 *    using the routines in slip.c, and these routines are supported
 *    in a manner similar to the asynch routines for the 8250. The
 *    input is handled via fifo buffer, while output is direct. The
 *    routines scc_send and get_scc are called via pointers in the
 *    Slip and Nrs structs for the parcticular channel.
 *
 * 2) The timer routine, scctim, is not installed directly in the
 *    timer interrupt chain, but is called through the systick routine
 *    in pc.c.
 *
 * 3) Facilities of nos are used whenever possible in place of direct
 *    structure or variable manipulation. Mbuf management is handled
 *    this way, along with interface initialization.
 *
 * 4) Nrs mode support is added in a manner similar to that of the
 *    Slip support. I have not had an opportunity to test this, but
 *    it is essentially identical to the way the 8250 version works.
 *
 * 5) Callsign specification on radio modes (kiss,nrs,ax25) is an
 *    option. If not supplied, the value of Mycall will be used.
 *
 * 6) Bufsize specification is now a parameter on setup of each channel.
 *    This is the size of the fifo on asynch input, and the size of
 *    mbuf buffers for sdlc mode. Since the fifo buffer can fill up,
 *    this value should be reasonably large for asynch mode. Mbufs
 *    are chained when they fill up, so having a small bufsize with
 *    sdlc modes (ax25) does not result in loss of characters.
 *
 * 7) Because slip and nrs decoding is handled outside the driver,
 *    sccstat cannot be used to report sent and receive packet counts
 *    in asynch mode, and these fields are blanked on display in asynch
 *    modes.
 *
 *
 * I am interested in setting up some default initializations for
 * the popular Z8530 boards, to minimize user problems in constructing
 * the proper attach init entries. These would allow for shortened
 * entries to use the defaults, such as "attach scc 1 init drsi" to
 * attach a DRSI board in standard configuration at its default address.
 * Since I do not have complete technical information on all such boards,
 * I would very much appreciate any information that users can provide
 * me regarding particular boards.
 *
 * 1/25/90
 *
 * Modifications:
 *
 * 2/17/90:
 *
 * 1) Added mods from PE1CHL which reflect relevent changes to the
 *    scc driver in his version of net between 10/89 and 1/90. Changes
 *    incorporated include additional delays in sccvec.asm, addition
 *    of external clock mode, and initialization for the 8536 as a
 *    clock divider on the DRSI board. "INLINE" is a slight delay
 *    for register access incorporated for use with the inline i/o
 *    code in MSC. This may not be useful or necessary with TURBO.
 *    Changes making "TPS" a variable were not added, since the
 *    scc timer does not install itself on the hardware interrupt
 *    in this version.
 *
 *
 * Ken Mitchum, KY3B       km@cs.pitt.edu  km@dsl.pitt.edu
 *                             or mail to the tcpip group
 * 5/07/92:
 *
 * 1) Changes needed for BayCom-USCC card were made. CTS-Interrupt
 *    was disabled, because it keeps the system hanging. Clock sources
 *    will be changed when transmitter is switched on/off, if internal
 *    clock is used. Now DF9ICs G3RUH compatible modem is supported,
 *    which needs NRZ-signals and provides external clocks.
 *
 * Rene Stange, DG0FT @DB0KG.DEU.EU
 *
 */
  
/* Added ANSI-style prototypes, reformatted source, minor delinting.
 * Integrated into standard 900201 NOS by KA9Q.
 */
  
/*
 * Generic driver for Z8530 SCC chip in SLIP, KISS or AX.25 mode.
 *
 * Written by R.E. Janssen (PE1CHL) using material from earlier
 * EAGLE and PC100 drivers in this package.
 *
 * The driver has initially been written for my own Atari SCC interface
 * board, but it could eventually replace the other SCC drivers.
 *
 * Unfortunately, there is little consistency between the different interface
 * boards, as to the use of a clock source, the solution for the fullduplex
 * clocking problem, and most important of all: the generation of the INTACK
 * signal.  Most designs do not even support the generation of an INTACK and
 * the read of the interrupt vector provided by the chip.
 * This results in lots of configuration parameters, and a fuzzy
 * polltable to be able to support multiple chips connected at one interrupt
 * line...
 *
 */
  
#include <ctype.h>
#include <time.h>
#include <dos.h>
#include "global.h"
#ifdef SCC
#include "mbuf.h"
#include "config.h"
#include "netuser.h"
#include "proc.h"
#include "iface.h"
#include "pktdrvr.h"
#include "slip.h"
#include "nrs.h"
#include "i8250.h"
#include "scc.h"
#include "z8530.h"
#include "z8536.h"
#include "ax25.h"
#include "trace.h"
#include "pc.h"
#include "kiss.h"
#include "devparam.h"
  
/* interrupt handlers */
extern INTERRUPT sccvec();
extern INTERRUPT sccnovec();
  
/* variables used by the SCC interrupt handler in sccvec.asm */
static INTERRUPT (*Orgivec)();  /* original interrupt vector */
  
struct sccinfo Sccinfo = {0};       /* global info about SCCs */
struct sccchan *Sccchan[2 * MAXSCC] = {0}; /* information per channel */
ioaddr Sccvecloc = {0};         /* location to access for SCC vector */
unsigned char Sccmaxvec = {0};      /* maximum legal vector from SCC */
ioaddr Sccpolltab[MAXSCC+1][2] = {0};   /* polling table when no vectoring */
  
#if defined(INLINE)
static unsigned scc_delay __ARGS((unsigned v));
  
static unsigned scc_delay (v)       /* delay for about 5 PCLK cycles */
unsigned v;              /* pass-through used for input */
  
{
    register int i,j;            /* it takes time to save them */
  
    return v;                /* return the passed parameter */
}
#endif
  
unsigned char Random = 0;       /* random number for p-persist */
  
static int scc_call __ARGS((struct iface *ifp,char *call));
static int scc_init __ARGS((int nchips,ioaddr iobase,int space,int aoff,
int boff,int doff,ioaddr intack,int ivec,long clk,int pclk,int hwtype,
int hwparam));
static int scc_raw __ARGS((struct iface *ifp,struct mbuf *bp));
static int scc_stop __ARGS((struct iface *ifp));
static int get_scc __ARGS((int dev));
static int scc_send __ARGS((int dev,struct mbuf *bp));
static int scc_async __ARGS((struct sccchan *scc));
  
static void scc_sdlc __ARGS((struct sccchan *scc));
static void scc_tossb __ARGS((struct sccchan *scc));
static void scc_txon __ARGS((struct sccchan *scc));
static void scc_txoff __ARGS((struct sccchan *scc));
static int32 scc_aioctl __ARGS((struct iface *ifp,int cmd,int set,int32 val));
static int32 scc_sioctl __ARGS((struct iface *ifp,int cmd,int set,int32 val));
static void scc_sstart __ARGS((struct sccchan *scc));
static unsigned int scc_speed __ARGS((struct sccchan *scc,
unsigned int clkmode,long speed));
static void scc_asytx __ARGS((struct sccchan *scc));
static void scc_asyex __ARGS((struct sccchan *scc));
static void scc_asyrx __ARGS((struct sccchan *scc));
static void scc_asysp __ARGS((struct sccchan *scc));
static void scc_sdlctx __ARGS((struct sccchan *scc));
static void scc_sdlcex __ARGS((struct sccchan *scc));
static void scc_sdlcrx __ARGS((struct sccchan *scc));
static void scc_sdlcsp __ARGS((struct sccchan *scc));
  
/* Attach an SCC channel to the system, or initialize SCC driver.
 * operation depends on argv[2]:
 * when "init", the SCC driver is initialized, and global information about
 * the hardware is set up.
 * argv[0]: hardware type, must be "scc"
 * argv[1]: number of SCC chips we will support
 * argv[2]: mode, must be: "init" in this case
 * argv[3]: base address of SCC chip #0 (hex)
 * argv[4]: spacing between SCC chip base addresses
 * argv[5]: offset from chip base address to channel A control register
 * argv[6]: offset from chip base address to channel B control register
 * argv[7]: offset from each channel's control register to data register
 * argv[8]: address of INTACK/Read Vector port. 0 to read from RR3A/RR2B
 * argv[9]: CPU interrupt vector number for all connected SCCs
 * argv[10]: clock frequency (PCLK/RTxC) of all SCCs in cycles per second
 *       prefix with "p" for PCLK, "r" for RTxC clock (for baudrate gen)
 * argv[11]: optional hardware type (for special features)
 * argv[12]: optional extra parameter for special hardware
 *
 * otherwise, a single channel is attached using the specified parameters:
 * argv[0]: hardware type, must be "scc"
 * argv[1]: SCC channel number to attach, 0/1 for first chip A/B, 2/3 for 2nd...
 * argv[2]: mode, can be:
 *      "slip", "kiss", "ax25"
 * argv[3]: interface label, e.g., "sl0"
 * argv[4]: maximum transmission unit, bytes
 * argv[5]: interface speed, e.g, "1200". prefix with "d" when an external
 *      divider is available to generate the TX clock.  When the clock
 *      source is PCLK, this can be a /32 divider between TRxC and RTxC.
 *      When the clock is at RTxC, the TX rate must be supplied at TRxC.
 *      This is needed only for AX.25 fullduplex.
 *      When this arg is given as "ext", the transmit and receive clock
 *      are external, and the BRG and DPLL are not used.
 *      On the BayCom-USCC card "ext" enables NRZ-mode for DF9IC-modem.
 * argv[6]: buffer size
 * argv[7]: callsign used on the radio channels (optional)
 */
  
int
scc_attach(argc,argv)
int argc;
char *argv[];
{
    register struct iface *ifp;
    struct sccchan *scc;
    unsigned int chan,brgrate;
    int pclk = 0,hwtype = 0,hwparam = 0;
    int xdev;
  
    /* first handle the special "init" mode, to initialize global stuff */
  
    if(!strcmp(argv[2],"init")){
        if(argc < 11)           /* need at least argv[1]..argv[10] */
            return -1;
  
        if(isupper(argv[10][0]))
            argv[10][0] = tolower(argv[10][0]);
  
        if(argv[10][0] == 'p'){ /* wants to use PCLK as clock? */
            pclk = 1;
            argv[10]++;
        } else {
            if(argv[10][0] == 'r')  /* wants to use RTxC? */
                argv[10]++;     /* that's the default */
        }
        if(argc > 11)           /* optional hardware type */
            hwtype = htoi(argv[11]);    /* it is given in hex */
  
        if(argc > 12)           /* optional hardware param */
            hwparam = htoi(argv[12]);   /* also in hex */
  
        return scc_init(atoi(argv[1]),(ioaddr) htol(argv[3]),atoi(argv[4]),
        atoi(argv[5]),atoi(argv[6]),atoi(argv[7]),
        (ioaddr) htol(argv[8]),atoi(argv[9]),
        atol(argv[10]),pclk,hwtype,hwparam);
    }
  
    /* Check for existing interface name ! - WG7J */
    if(if_lookup(argv[4]) != NULLIF){
        tprintf(Existingiface,argv[3]);
        return -1;
    }
  
    /* not "init", so it must be a valid mode to attach a channel */
    if(strcmp(argv[2],"ax25") && strcmp(argv[2],"kiss") &&
    strcmp(argv[2],"slip") && strcmp(argv[2],"nrs")){
        tprintf("Mode %s unknown for SCC\n",argv[2]);
        return -1;
    }
    if(strcmp(argv[2],"slip") == 0 || strcmp(argv[2],"kiss") == 0){
        for(xdev = 0;xdev < SLIP_MAX;xdev++)
            if(Slip[xdev].iface == NULLIF)
                break;
        if(xdev >= SLIP_MAX){
            tprintf("Too many slip devices\n");
            return -1;
        }
    }
#ifdef NRS
    if(strcmp(argv[2],"nrs") == 0){
        for(xdev = 0;xdev < NRS_MAX;xdev++)
            if(Nrs[xdev].iface == NULLIF)
                break;
  
        if(xdev >= NRS_MAX){
            tprintf("Too many nrs devices\n");
            return -1;
        }
    }
#endif
    if(!Sccinfo.init){
        tprintf("First init SCC driver\n");
        return -1;
    }
    if((chan = atoi(argv[1])) > Sccinfo.maxchan){
        tprintf("SCC channel %d out of range\n",chan);
        return -1;
    }
    if(Sccchan[chan] != NULLCHAN){
        tprintf("SCC channel %d already attached\n",chan);
        return -1;
    }
    /* create interface structure and fill in details */
    ifp = (struct iface *) callocw(1,sizeof(struct iface));
    ifp->name = mallocw(strlen(argv[3]) + 1);
    strcpy(ifp->name,argv[3]);
  
    ifp->mtu = atoi(argv[4]);
    ifp->dev = chan;
    ifp->stop = scc_stop;
  
    scc = (struct sccchan *) callocw(1,sizeof(struct sccchan));
    scc->ctrl = Sccinfo.iobase + (chan / 2) * Sccinfo.space + Sccinfo.off[chan % 2];
    scc->data = scc->ctrl + Sccinfo.doff;
    scc->iface = ifp;
  
    if(isupper(argv[5][0]))
        argv[5][0] = tolower(argv[5][0]);
  
    switch (argv[5][0]) {
        case 'd':               /* fulldup divider installed? */
            scc->fulldup = 1;       /* set appropriate flag */
            argv[5]++;          /* skip the 'd' */
            break;
  
        case 'e':               /* external clocking? */
            scc->extclock = 1;      /* set the flag */
            break;
    }
  
    scc->bufsiz = atoi(argv[6]);
    ifp->addr = Ip_addr;
    Sccchan[chan] = scc;        /* put addr in table for interrupts */
  
    switch(argv[2][0]){             /* mode already checked above */
#ifdef AX25
        case 'a':   /* AX.25 */
            scc_sdlc(scc);              /* init SCC in SDLC mode */
  
            if (!scc->extclock) {
                brgrate = scc_speed(scc,32,atol(argv[5]));/* init SCC speed */
                scc->speed = Sccinfo.clk / (64L * (brgrate + 2));/* calc real speed */
            }
  
            brgrate = scc_speed(scc,32,atol(argv[5]));/* init SCC speed */
            scc->speed = Sccinfo.clk / (64L * (brgrate + 2));/* calc real speed */
            setencap(ifp,"AX25");
            scc_call(ifp,argc > 7 ? argv[7] : (char *) 0);  /* set the callsign */
            ifp->ioctl = scc_sioctl;
            ifp->raw = scc_raw;
  
        /* default KISS Params */
            scc->a.txdelay = 36*TPS/100;    /* 360 ms */
            scc->a.persist = 25;        /* 10% persistence */
            scc->a.slottime = 16*TPS/100; /* 160 ms */
#if TPS > 67
            scc->a.tailtime = 3*TPS/100;    /* 30 ms */
#else
            scc->a.tailtime = 2;        /* minimal reasonable value */
#endif
            scc->a.fulldup = 0;     /* CSMA */
            scc->a.waittime = 50*TPS/100; /* 500 ms */
            scc->a.maxkeyup = 7;        /* 7 s */
            scc->a.mintime = 3;     /* 3 s */
            scc->a.idletime = 120;  /* 120 s */
            break;
        case 'k':   /* kiss */
            scc_async(scc);             /* init SCC in async mode */
            brgrate = scc_speed(scc,16,atol(argv[5]));
            scc->speed = Sccinfo.clk / (32L * (brgrate + 2));
  
            setencap(ifp,"AX25");
            scc_call(ifp,argc > 7 ? argv[7] : (char *) 0);  /* set the callsign */
  
            ifp->ioctl = kiss_ioctl;
            ifp->raw = kiss_raw;
  
            for(xdev = 0;xdev < SLIP_MAX;xdev++){
                if(Slip[xdev].iface == NULLIF)
                    break;
            }
            ifp->xdev = xdev;
            Slip[xdev].iface = ifp;
            Slip[xdev].type = CL_KISS;
            Slip[xdev].send = scc_send;
            Slip[xdev].get = get_scc;
            ifp->rxproc = newproc("ascc rx",256,asy_rx,xdev,NULL,NULL,0);
            break;
#endif
#ifdef SLIP
        case 's':   /* slip */
            scc_async(scc);             /* init SCC in async mode */
            brgrate = scc_speed(scc,16,atol(argv[5]));
            scc->speed = Sccinfo.clk / (32L * (brgrate + 2));
            setencap(ifp,"SLIP");
            ifp->ioctl = scc_aioctl;
            ifp->raw = slip_raw;
            for(xdev = 0;xdev < SLIP_MAX;xdev++){
                if(Slip[xdev].iface == NULLIF)
                    break;
            }
            ifp->xdev = xdev;
            Slip[xdev].iface = ifp;
            Slip[xdev].type = CL_SERIAL_LINE;
            Slip[xdev].send = scc_send;
            Slip[xdev].get = get_scc;
            ifp->rxproc = newproc("ascc rx",256,asy_rx,xdev,NULL,NULL,0);
            break;
#endif
#ifdef NRS
        case 'n':   /* nrs */
            scc_async(scc);             /* init SCC in async mode */
            brgrate = scc_speed(scc,16,atol(argv[5]));
            scc->speed = Sccinfo.clk / (32L * (brgrate + 2));
            setencap(ifp,"AX25");
            scc_call(ifp,argc > 7 ? argv[7] : (char *) 0);  /* set the callsign */
            ifp->ioctl = scc_aioctl;
            ifp->raw = nrs_raw;
  
            for(xdev = 0;xdev < NRS_MAX;xdev++)
                if(Nrs[xdev].iface == NULLIF)
                    break;
  
            ifp->xdev = xdev;
            Nrs[xdev].iface = ifp;
            Nrs[xdev].send = scc_send;
            Nrs[xdev].get = get_scc;
            ifp->rxproc = newproc("nscc rx",256,nrs_recv,xdev,NULL,NULL,0);
            break;
#endif
    }
    ifp->next = Ifaces;         /* link interface in list */
    Ifaces = ifp;
    return 0;
}
  
/* SCC driver initialisation. called on "attach scc <num> init ..." */
static int
scc_init(nchips,iobase,space,aoff,boff,doff,intack,ivec,clk,pclk,hwtype,hwparam)
int nchips;         /* number of chips */
ioaddr iobase;          /* base of first chip */
int space,aoff,boff,doff;
ioaddr intack;          /* INTACK ioport or 0 for no INTACK */
int ivec;           /* interrupt vector number */
long clk;           /* clock frequency */
int pclk;           /* PCLK or RTxC for clock */
int hwtype;         /* selection of special hardware types */
int hwparam;            /* extra parameter for special hardware */
{
    int chip,chan;
    ioaddr chipbase;
    register ioaddr ctrl;
    int i_state,d;
    int dum = 1;
  
#define z 0
  
    if(Sccinfo.init){
        tprintf("SCC driver already initialized - nothing done\n");
        return 1;
    }
    Sccinfo.init = 1;
    Sccinfo.nchips = nchips;
    Sccinfo.maxchan = (2 * nchips) - 1;
    Sccinfo.iobase = iobase;
    Sccinfo.space = space;
    Sccinfo.off[0] = aoff;
    Sccinfo.off[1] = boff;
    Sccinfo.doff = doff;
    Sccinfo.ivec = ivec;
    Sccinfo.clk = clk;
    Sccinfo.pclk = pclk;
    Sccinfo.hwtype = hwtype;
    Sccinfo.hwparam = hwparam;
  
    /* reset and pre-init all chips in the system */
    for(chip = 0; chip < nchips; chip++){
        chipbase = iobase + chip * space;
        ctrl = chipbase + Sccinfo.off[0];
        i_state = dirps();      /* because of 2-step accesses */
        VOID(RDREG(ctrl));      /* make sure pointer is written */
        WRSCC(ctrl,R9,FHWRES);      /* force hardware reset */
        for (d = 0; d < 1000; d++)  /* wait a while to be sure */
            dum *= 10;
        for(chan = 0; chan < 2; chan++){
            ctrl = chipbase + Sccinfo.off[chan];
  
            /* initialize a single channel to no-op */
            VOID(RDREG(ctrl));      /* make sure pointer is written */
            WRSCC(ctrl,R4,z);       /* no mode selected yet */
            WRSCC(ctrl,R1,z);       /* no W/REQ operation */
            WRSCC(ctrl,R2,16 * chip);   /* chip# in upper 4 bits of vector */
            WRSCC(ctrl,R3,z);       /* disable rx */
            WRSCC(ctrl,R5,z);       /* disable tx */
            WRSCC(ctrl,R9,VIS);     /* vector includes status, MIE off */
            Sccpolltab[chip][chan] = ctrl; /* store ctrl addr for polling */
        }
        if(hwtype & HWEAGLE)        /* this is an EAGLE card */
            WRREG(chipbase + 4,0x08);   /* enable interrupt on the board */
  
        if(hwtype & HWPC100)        /* this is a PC100 card */
            WRREG(chipbase,hwparam);    /* set the MODEM mode (22H normally) */
  
        if(hwtype & HWPRIMUS)       /* this is a PRIMUS-PC */
            WRREG(chipbase + 4,hwparam); /* set the MODEM mode (02H normally) */
  
        if (hwtype & HWDRSI) {      /* this is a DRSI PC*Packet card */
            ioaddr z8536 = chipbase + 7; /* point to 8536 master ctrl reg */
  
            /* Initialize 8536 to perform its divide-by-32 function */
            /* This part copied from N6TTO DRSI-driver */
  
            /* Start by forcing chip into known state */
  
            VOID(RDREG(z8536));     /* make sure pointer is written */
            WRSCC(z8536,CIO_MICR,0x01); /* force hardware reset */
  
            for (d = 0; d < 1000; d++)  /* wait a while to be sure */
                dum *= 10;
  
            WRSCC(z8536,CIO_MICR,0x00); /* Clear reset and start */
  
            /* Wait for chip to come ready */
  
            while (RDSCC(z8536,CIO_MICR) != 0x02)
                dum *= 10;
  
            WRSCC(z8536,CIO_MICR,0x26); /* NV|CT_VIS|RJA */
            WRSCC(z8536,CIO_MCCR,0xf4); /* PBE|CT1E|CT2E|CT3E|PAE */
  
            WRSCC(z8536,CIO_CTMS1,0xe2);/* Continuous, EOE, ECE, Pulse output */
            WRSCC(z8536,CIO_CTMS2,0xe2);/* Continuous, EOE, ECE, Pulse output */
  
            WRSCC(z8536,CIO_CT1MSB,0x00); /* Load time constant CTC #1 */
            WRSCC(z8536,CIO_CT1LSB,0x10);
            WRSCC(z8536,CIO_CT2MSB,0x00); /* Load time constant CTC #2 */
            WRSCC(z8536,CIO_CT2LSB,0x10);
  
            WRSCC(z8536,CIO_IVR,0x06);
  
            /* Set port direction bits in port A and B           */
            /* Data is input on bits d1 and d5, output on d0 and d4. */
            /* The direction is set by 1 for input and 0 for output  */
  
            WRSCC(z8536,CIO_PDCA,0x22);
            WRSCC(z8536,CIO_PDCB,0x22);
  
            WRSCC(z8536,CIO_CSR1,CIO_GCB|CIO_TCB); /* Start CTC #1 running */
            WRSCC(z8536,CIO_CSR2,CIO_GCB|CIO_TCB); /* Start CTC #2 running */
        }
  
        restore(i_state);
    }
    Sccpolltab[chip][0] = 0;    /* terminate the polling table */
    Sccvecloc = intack;     /* location of INTACK/vector read */
    Sccmaxvec = 16 * nchips;    /* upper limit on valid vector */
    /* save original interrupt vector */
    Orgivec = getirq(ivec);
  
    if(intack){ /* INTACK method selected? */
        /* set interrupt vector to INTACK-generating routine  */
        setirq(ivec,sccvec);
    } else {
        /* set interrupt vector to polling routine */
        setirq(ivec,sccnovec);
    }
    /* enable the interrupt  */
    maskon(ivec);
    return 0;
}
  
/* initialize an SCC channel in asynchronous mode */
static int
scc_async(scc)
register struct sccchan *scc;
{
    int i_state;
    register struct fifo *fp = &(scc->fifo);
  
    if((fp->buf = malloc(scc->bufsiz)) == NULLCHAR){
        tprintf("scc%d: No space for rx buffer\n",scc->iface->dev);
        return -1;
    }
    fp->bufsize = scc->bufsiz;
    fp->wp = fp->rp = fp->buf;
    fp->cnt = 0;
  
    scc->int_transmit = scc_asytx;  /* set interrupt handlers */
    scc->int_extstat = scc_asyex;
    scc->int_receive = scc_asyrx;
    scc->int_special = scc_asysp;
  
    i_state = dirps();
  
    wr(scc,R4,X16CLK|SB1);      /* *16 clock, 1 stopbit, no parity */
    wr(scc,R1,z);           /* no W/REQ operation */
    wr(scc,R3,Rx8);         /* RX 8 bits/char, disabled */
    wr(scc,R5,Tx8|DTR|RTS);     /* TX 8 bits/char, disabled, DTR RTS */
    wr(scc,R9,VIS);         /* vector includes status */
    wr(scc,R10,NRZ|z);          /* select NRZ */
    wr(scc,R11,RCBR|TCBR);      /* clocks are BR generator */
    wr(scc,R14,Sccinfo.pclk? BRSRC:z);  /* brg source = PCLK/RTxC */
    wr(scc,R15,BRKIE);          /* enable BREAK ext/status int */
  
    or(scc,R3,RxENABLE);        /* enable receiver */
    or(scc,R5,TxENAB);          /* enable transmitter */
  
    WRREG(scc->ctrl,RES_EXT_INT);   /* reset ext/status interrupts */
    WRREG(scc->ctrl,RES_EXT_INT);   /* must be done twice */
    scc->status = RDREG(scc->ctrl); /* read initial status */
  
    or(scc,R1,INT_ALL_Rx|TxINT_ENAB|EXT_INT_ENAB); /* enable interrupts */
    or(scc,R9,MIE);         /* master interrupt enable */
  
    restore(i_state);
    return 0;
}
  
/* initialize an SCC channel in SDLC mode ; new BayCom version G8FSL */
static void
scc_sdlc(scc)
register struct sccchan *scc;
{
    int i_state;
  
    scc->int_transmit = scc_sdlctx; /* set interrupt handlers */
    scc->int_extstat = scc_sdlcex;
    scc->int_receive = scc_sdlcrx;
    scc->int_special = scc_sdlcsp;
  
    i_state = dirps();
  
    wr(scc,R4,X1CLK|SDLC);          /* *1 clock, SDLC mode */
    wr(scc,R1,z);                   /* no W/REQ operation */
    wr(scc,R3,Rx8|RxCRC_ENAB);      /* RX 8 bits/char, CRC, disabled */
    wr(scc,R5,Tx8|DTR|TxCRC_ENAB);  /* TX 8 bits/char, disabled, DTR */
    wr(scc,R6,z);                   /* SDLC address zero (not used) */
    wr(scc,R7,FLAG);                /* SDLC flag value */
    wr(scc,R9,VIS);                 /* vector includes status */
  
    if (scc->extclock){             /* when using external clocks */
        if (Sccinfo.hwtype & HWBAYCOM){         /* DF9IC-modem? */
            wr(scc,R10,CRCPS|NRZ|ABUNDER);  /* CRC preset 1, select NRZ, ABORT on underrun */
            wr(scc,R11,RCTRxCP|TCRTxCP);    /* RXclk TRxC, TXclk RTxC. */
        } else {
            wr(scc,R10,CRCPS|NRZI|ABUNDER); /* CRC preset 1, select NRZI, ABORT on underrun */
            wr(scc,R11,RCRTxCP|TCTRxCP);    /* RXclk RTxC, TXclk TRxC. */
        }
        wr(scc,R14,z);                          /* No BRG options */
        WRSCC(scc->ctrl,R14,DISDPLL|scc->wreg[R14]); /* No DPLL operation */
    } else {
        wr(scc,R10,CRCPS|NRZI|ABUNDER); /* CRC preset 1, select NRZI, ABORT on underrun */
        if(scc->fulldup){               /* when external clock divider */
            if(Sccinfo.pclk){       /* when using PCLK as clock source */
                if (Sccinfo.hwtype & HWBAYCOM){
                    /* RXclk DPLL, TXclk RTxC, out=DPLL (Echo duplex) */
                    wr(scc,R11,RCDPLL|TCRTxCP|TRxCOI|TRxCDP);
                } else {
                    /* RXclk DPLL, TXclk RTxC, out=BRG. external /32 TRxC->RTxC */
                    wr(scc,R11,RCDPLL|TCRTxCP|TRxCOI|TRxCBR);
                }
            } else {
                /* RXclk DPLL, TXclk TRxC.      external TX clock to TRxC */
                wr(scc,R11,RCDPLL|TCTRxCP);
            }
        } else {        /* only half-duplex operation */
            /* RXclk DPLL, TXclk BRG. BRG reprogrammed at every TX/RX switch */
#ifdef notdef   /* KA9Q - for PSK modem */
            wr(scc,R11,RCDPLL|TCBR);
#else
            /* DPLL -> Rx clk, (DPLL -> Tx CLK), DPLL -> TRxC pin */
            wr(scc,R11,RCDPLL|TCDPLL|TRxCOI|TRxCDP);
#endif
        }
        wr(scc,R14,Sccinfo.pclk? BRSRC:z);         /* BRG source = PCLK/RTxC */
        WRSCC(scc->ctrl,R14,SSBR|scc->wreg[R14]);  /* DPLL source = BRG */
        WRSCC(scc->ctrl,R14,SNRZI|scc->wreg[R14]); /* DPLL NRZI mode */
    }
  
    if (Sccinfo.hwtype & HWBAYCOM){         /* BayCom-USCC card? */
        wr(scc,R15,BRKIE|DCDIE);        /* enable ABORT, DCD interrupts */
    } else {
        wr(scc,R15,BRKIE|CTSIE|DCDIE);  /* enable ABORT, CTS & DCD interrupts */
    }
  
    if(RDREG(scc->ctrl) & DCD){             /* DCD is now ON */
        if (!scc->extclock)
            WRSCC(scc->ctrl,R14,SEARCH|scc->wreg[R14]); /* DPLL: enter search mode */
        or(scc,R3,ENT_HM|RxENABLE);     /* enable the receiver, hunt mode */
    }
    WRREG(scc->ctrl,RES_EXT_INT);   /* reset ext/status interrupts */
    WRREG(scc->ctrl,RES_EXT_INT);   /* must be done twice */
    scc->status = RDREG(scc->ctrl); /* read initial status */
  
    or(scc,R1,INT_ALL_Rx|TxINT_ENAB|EXT_INT_ENAB); /* enable interrupts */
    or(scc,R9,MIE);                 /* master interrupt enable */
  
    restore(i_state);
}
  
/* set SCC channel speed
 * clkmode specifies the division rate (1,16,32) inside the SCC
 * returns the selected brgrate for "real speed" calculation
 */
static unsigned int
scc_speed(scc,clkmode,speed)
register struct sccchan *scc;
unsigned int clkmode;
long speed;             /* the desired baudrate */
{
    unsigned int brgrate;
    long spdclkm;
    int i_state;
  
    /* calculate baudrate generator value */
  
    if ((spdclkm = speed * clkmode) == 0)
        return 65000U;          /* avoid divide-by-zero */
  
    brgrate = (unsigned) ((Sccinfo.clk + spdclkm) / (spdclkm * 2)) - 2;
  
    i_state = dirps();      /* 2-step register accesses... */
  
    cl(scc,R14,BRENABL);        /* disable baudrate generator */
    wr(scc,R12,brgrate);        /* brg rate LOW */
    wr(scc,R13,brgrate >> 8);       /* brg rate HIGH */
    or(scc,R14,BRENABL);        /* enable baudrate generator */
  
    restore(i_state);
    return brgrate;
}
  
/* de-activate SCC channel */
static int
scc_stop(ifp)
struct iface *ifp;
{
    struct sccchan *scc = Sccchan[ifp->dev];
    int i_state;
  
    i_state = dirps();
  
    VOID(RDREG(scc->ctrl));     /* make sure pointer is written */
    wr(scc,R9,(ifp->dev % 2)? CHRB : CHRA); /* reset the channel */
  
    switch(ifp->type){
        case CL_SERIAL_LINE:
        case CL_KISS:
            free(scc->fifo.buf);
        default:
            break;
    }
    free(scc);
    Sccchan[ifp->dev] = NULLCHAN;
    restore(i_state);
    return 0;
}
  
/* de-activate SCC driver on program exit */
void
sccstop()
{
    if(Sccinfo.init){           /* was it initialized? */
        maskoff(Sccinfo.ivec);      /* disable the interrupt */
        setirq(Sccinfo.ivec,Orgivec);   /* restore original interrupt vector */
    }
}
  
/* perform ioctl on SCC (async) channel
 * this is used for SLIP mode only, and will read/set the line speed
 */
static int32
scc_aioctl(ifp,cmd,set,val)
struct iface *ifp;
int cmd;
int set;
int32 val;
{
    struct sccchan *scc;
    unsigned int brgrate;
  
    scc = Sccchan[ifp->dev];
  
    switch(cmd){
        case PARAM_SPEED:
            if(set){
                brgrate = scc_speed(scc,16,val);
                scc->speed = Sccinfo.clk / (32L * (brgrate + 2));
            }
            return scc->speed;
    }
    return 0;
}
  
/* perform ioctl on SCC (sdlc) channel
 * this is used for AX.25 mode only, and will set the "kiss" parameters
 */
static int32
scc_sioctl(ifp,cmd,set,val)
struct iface *ifp;
int cmd;
int set;
int32 val;
{
    struct sccchan *scc;
    int i_state;
    unsigned int brgrate;
  
    scc = Sccchan[ifp->dev];
  
    switch(cmd){
        case PARAM_SPEED:
            if(set){
                if(val == 0)
                    scc->extclock = 1;
                else {
                    brgrate = scc_speed(scc,32,val);/* init SCC speed */
                    scc->speed = Sccinfo.clk / (64L * (brgrate + 2));/* calc real speed */
                }
            }
            return scc->speed;
        case PARAM_TXDELAY:
            if(set)
                scc->a.txdelay = val;
            return scc->a.txdelay;
        case PARAM_PERSIST:
            if(set)
                scc->a.persist = val;
            return scc->a.persist;
        case PARAM_SLOTTIME:
            if(set)
                scc->a.slottime = val;
            return scc->a.slottime;
        case PARAM_TXTAIL:
            if(set)
                scc->a.tailtime = val;
            return scc->a.tailtime;
        case PARAM_FULLDUP:
            if(set)
                scc->a.fulldup = val;
            return scc->a.fulldup;
        case PARAM_WAIT:
            if(set)
                scc->a.waittime = val;
            return scc->a.waittime;
        case PARAM_MAXKEY:
            if(set)
                scc->a.maxkeyup = val;
            return scc->a.maxkeyup;
        case PARAM_MIN:
            if(set)
                scc->a.mintime = val;
            return scc->a.mintime;
        case PARAM_IDLE:
            if(set)
                scc->a.idletime = val;
            return scc->a.idletime;
        case PARAM_DTR:
            if(set){
                if(val)
                    scc->wreg[R5] |= DTR;
                else
                    scc->wreg[R5] &= ~DTR;
  
                i_state = dirps();
                if(scc->a.tstate == IDLE && scc->timercount == 0)
                    scc->timercount = 1;    /* force an update */
                restore(i_state);
            }
            return (scc->wreg[R5] & DTR) ? 1 : 0;
        case PARAM_GROUP:
            if(set)
                scc->group = (int) val;
            return scc->group;
    }
    return -1;
}
  
/* start SCC transmitter when it is idle (SLIP/KISS mode only) */
static void
scc_sstart(scc)
register struct sccchan *scc;
{
    if(scc->tbp != NULLBUF ||   /* busy */
        scc->sndq == NULLBUF)  /* no work */
        return;
  
    scc->tbp = dequeue(&scc->sndq);
    WRREG(scc->data,FR_END);
}
  
/* show SCC status */
int
dosccstat()
{
    register struct sccchan *scc;
    int i;
  
    if(!Sccinfo.init){
        tprintf("SCC driver not initialized\n");
        return 0;
    }
    tprintf("Ch Iface    Sent   Rcvd   Error Space Overr   Rxints   Txints   Exints   Spints\n");
  
    for(i = 0; i <= Sccinfo.maxchan; i++){
        if((scc = Sccchan[i]) == NULLCHAN)
            continue;
  
        if(scc->int_receive == scc_asyrx)
            tprintf("%2d %-6s  ** asynch ** %7lu %5u %5u %8lu %8lu %8lu %8lu\n",i,scc->iface->name,
            scc->rxerrs,scc->nospace,scc->rovers,
            scc->rxints,scc->txints,scc->exints,scc->spints);
        else
            tprintf("%2d %-6s %6lu %6lu %7lu %5u %5u %8lu %8lu %8lu %8lu\n",i,scc->iface->name,
            scc->enqueued,scc->rxframes,scc->rxerrs,scc->nospace,scc->rovers,
            scc->rxints,scc->txints,scc->exints,scc->spints);
    }
    return 0;
}
  
/* send raw frame to SCC. used for AX.25 */
static int
scc_raw(ifp,bp)
struct iface *ifp;
struct mbuf *bp;
{
    struct sccchan *scc;
    int i_state;
  
    dump(ifp,IF_TRACE_OUT,CL_AX25,bp);
    ifp->rawsndcnt++;
    ifp->lastsent = secclock();
  
    scc = Sccchan[ifp->dev];
  
    if (scc->tx_inhibit){       /* transmitter inhibit */
        free_p(bp);
        return -1;
    }
  
    enqueue(&scc->sndq,bp);     /* enqueue packet */
    scc->enqueued++;
  
    i_state = dirps();
  
    if(scc->a.tstate == IDLE){  /* when transmitter is idle */
        scc->a.tstate = DEFER;  /* start the key-up sequence */
        scc->a.maxdefer = TPS * scc->a.idletime /
        scc->a.slottime;
        scc->timercount = scc->a.waittime;
    }
    restore(i_state);
    return 0;
}
  
static int
scc_send(dev,bp)
int dev;
struct mbuf *bp;
{
    struct sccchan *scc;
  
    scc = Sccchan[dev];
    enqueue(&scc->sndq,bp);
  
    if(scc->tbp == NULLBUF)
        scc_sstart(scc);
    return(0);
}
  
/* initialize interface for AX.25 use */
static int
scc_call(ifp,call)
register struct iface *ifp;
char *call;
{
    char out[AXALEN];
  
    ifp->hwaddr = mallocw(AXALEN);
    if(setcall(out,call) == 0)
        memcpy(ifp->hwaddr,out,AXALEN);
    else
        memcpy(ifp->hwaddr,Mycall,AXALEN);
    return 0;
}
  
/* Interrupt handlers for asynchronous modes (kiss, slip) */
  
/* Transmitter interrupt handler */
/* This routine sends data from mbufs in SLIP format */
static void
scc_asytx(scc)
register struct sccchan *scc;
{
    register struct mbuf *bp;
  
    scc->txints++;
  
    if(scc->txchar != 0){       /* a character pending for transmit? */
        WRREG(scc->data,scc->txchar);   /* send it now */
        scc->txchar = 0;        /* next time, ignore it */
        return;
    }
  
    if(scc->tbp == NULLBUF){    /* nothing to send? */
        if((scc->tbp = scc->sndq) != NULLBUF){ /* dequeue next frame */
            scc->sndq = scc->sndq->anext;
            WRREG(scc->data,FR_END);    /* send FR_END to flush line garbage */
        } else {
            WRREG(scc->ctrl,RES_Tx_P);  /* else only reset pending int */
        }
        return;
    }
    while ((bp = scc->tbp)->cnt == 0){ /* nothing left in this mbuf? */
        bp = bp->next;          /* save link to next */
  
        free_mbuf(scc->tbp);
  
        if((scc->tbp = bp) == NULLBUF){ /* see if more mbufs follow */
            WRREG(scc->data,FR_END);    /* frame complete, send FR_END */
            return;
        }
    }
    /* now bp = scc->tbp (either from while or from if stmt above) */
  
    WRREG(scc->data,*(bp->data));   /* just send the character */
    bp->cnt--;              /* decrease mbuf byte count */
    bp->data++;             /* and increment the data pointer */
}
  
/* External/Status interrupt handler */
static void
scc_asyex(scc)
register struct sccchan *scc;
{
    register unsigned char status,changes;
  
    scc->exints++;
    status = RDREG(scc->ctrl);
    changes = status ^ scc->status;
  
    if(changes & BRK_ABRT){     /* BREAK? */
        if((status & BRK_ABRT) == 0)    /* BREAK now over? */
            VOID(RDREG(scc->data)); /* read the NUL character */
    }
    scc->status = status;
    WRREG(scc->ctrl,RES_EXT_INT);
}
  
/* Receiver interrupt handler under NOS.
 * Since the higher serial protocol routines are all written to work
 * well with the routines in 8250.c, it makes sense to handle
 * asynch i/o with the 8530 in a similar manner. Therefore, these
 * routines are as close to their counterparts in 8250.c as possible.
 */
  
static void
scc_asyrx(scc)
register struct sccchan *scc;
{
    register struct fifo *fp;
    char c;
  
    scc->rxints++;
  
    fp = &(scc->fifo);
    do {
        c = RDREG(scc->data);
        if(fp->cnt != fp->bufsize){
            *fp->wp++ = c;
            if(fp->wp >= &fp->buf[fp->bufsize])
                fp->wp = fp->buf;
            fp->cnt++;
        } else
            scc->nospace++;
    } while(RDREG(scc->ctrl) & Rx_CH_AV);
    psignal(fp,1);  /* eventually move this to timer routine */
}
  
/* Blocking read from asynch input.
 * Essentially the same as get_asy() in 8250.c
 * See comments in asy_rxint().
 */
static int
get_scc(dev)
int dev;
{
    int i_state;
    register struct fifo *fp;
    char c;
  
    fp = &(Sccchan[dev]->fifo);
  
    i_state = dirps();
    while(fp->cnt == 0)
        pwait(fp);
    fp->cnt--;
    restore(i_state);
  
    c = *fp->rp++;
    if(fp->rp >= &fp->buf[fp->bufsize])
        fp->rp = fp->buf;
  
    return uchar(c);
}
  
int
scc_frameup(dev)
int dev;
{
    Sccchan[dev]->rxframes++;
    return 0;
}
  
/* Receive Special Condition interrupt handler */
static void
scc_asysp(scc)
register struct sccchan *scc;
{
    register unsigned char status;
  
    scc->spints++;
  
    status = rd(scc,R1);        /* read receiver status */
    VOID(RDREG(scc->data));     /* flush offending character */
  
    if(status & (CRC_ERR | Rx_OVR))     /* did a framing error or overrun occur ? */
        scc->rovers++;          /* report as overrun */
  
    WRREG(scc->ctrl,ERR_RES);
}
  
/* Interrupt handlers for sdlc mode (AX.25) */
  
/* Transmitter interrupt handler */
static void
scc_sdlctx(scc)
register struct sccchan *scc;
{
    register struct mbuf *bp;
  
    scc->txints++;
  
    switch(scc->a.tstate){      /* look at transmitter state */
        case ACTIVE:            /* busy sending data bytes */
            while ((bp = scc->tbp)->cnt == 0){  /* nothing left in this mbuf? */
                bp = bp->next;          /* save link to next */
                free_mbuf(scc->tbp);    /*KM*/
                if((scc->tbp = bp) == NULLBUF){/* see if more mbufs follow */
                    if(RDREG(scc->ctrl) & TxEOM){   /* check tx underrun status */
                        scc->rovers++;      /* oops, an underrun! count them */
                        WRREG(scc->ctrl,SEND_ABORT);/* send an abort to be sure */
                        scc->a.tstate = TAIL;   /* key down tx after TAILTIME */
                        scc->timercount = scc->a.tailtime;
                        return;
                    }
                    cl(scc,R10,ABUNDER);        /* frame complete, allow CRC transmit */
                    scc->a.tstate = FLUSH;
                    WRREG(scc->ctrl,RES_Tx_P);  /* reset pending int */
                    return;
                }
            }
        /* now bp = scc->tbp (either from while or from if stmt above) */
            WRREG(scc->data,*(bp->data++)); /* send the character */
            bp->cnt--;          /* decrease mbuf byte count */
            return;
        case FLUSH: /* CRC just went out, more to send? */
            or(scc,R10,ABUNDER);        /* re-install underrun protection */
        /* verify that we are not exeeding max tx time (if defined) */
            if((scc->timercount != 0 || scc->a.maxkeyup == 0) &&
            (scc->tbp = scc->sndq) != NULLBUF){ /* dequeue a frame */
                scc->sndq = scc->sndq->anext;
                WRREG(scc->ctrl,RES_Tx_CRC); /* reset the TX CRC generator */
                scc->a.tstate = ACTIVE;
                scc_sdlctx(scc);        /* write 1st byte */
                WRREG(scc->ctrl,RES_EOM_L); /* reset the EOM latch */
                return;
            }
            scc->a.tstate = TAIL;       /* no more, key down tx after TAILTIME */
            scc->timercount = scc->a.tailtime;
            WRREG(scc->ctrl,RES_Tx_P);
            return;
        default:                /* another state */
            WRREG(scc->ctrl,RES_Tx_P);  /* then don't send anything */
            return;
    }
}
  
/* External/Status interrupt handler */
static void
scc_sdlcex(scc)
register struct sccchan *scc;
{
    register unsigned char status,changes;
  
    scc->exints++;
    status = RDREG(scc->ctrl);
    changes = status ^ scc->status;
  
    if(changes & BRK_ABRT){     /* Received an ABORT */
        if(status & BRK_ABRT){      /* is this the beginning? */
            if(scc->rbp != NULLBUF){/* did we receive something? */
                /* check if a significant amount of data came in */
                /* this is because the drop of DCD tends to generate an ABORT */
                if(scc->rbp->next != NULLBUF || scc->rbp->cnt > sizeof(struct phdr))
                    scc->rxerrs++;  /* then count it as an error */
                scc_tossb(scc);     /* throw away buffer */
            }
            VOID(RDREG(scc->data)); /* flush the FIFO */
            VOID(RDREG(scc->data));
            VOID(RDREG(scc->data));
        }
    }
    if(changes & CTS){          /* CTS input changed state */
        if(status & CTS){       /* CTS is now ON */
            if(scc->a.tstate == KEYWT &&
                scc->a.txdelay == 0) /* zero TXDELAY = wait for CTS */
                scc->timercount = 1;    /* it will start within 10 ms */
        }
    }
    if(changes & DCD){          /* DCD input changed state */
        if(status & DCD){       /* DCD is now ON */
            if (!scc->extclock)
                WRSCC(scc->ctrl,R14,SEARCH|scc->wreg[R14]); /* DPLL: enter search mode */
            or(scc,R3,ENT_HM|RxENABLE); /* enable the receiver, hunt mode */
        } else {            /* DCD is now OFF */
            cl(scc,R3,ENT_HM|RxENABLE); /* disable the receiver */
            VOID(RDREG(scc->data)); /* flush the FIFO */
            VOID(RDREG(scc->data));
            VOID(RDREG(scc->data));
            if(scc->rbp != NULLBUF){/* did we receive something? */
                /* check if a significant amount of data came in */
                /* this is because some characters precede the drop of DCD */
                if(scc->rbp->next != NULLBUF || scc->rbp->cnt > sizeof(struct phdr))
                    scc->rxerrs++;  /* then count it as an error */
                scc_tossb(scc);     /* throw away buffer */
            }
        }
    }
    scc->status = status;
    WRREG(scc->ctrl,RES_EXT_INT);
}
  
/* Receiver interrupt handler */
static void
scc_sdlcrx(scc)
register struct sccchan *scc;
{
    register struct mbuf *bp;
  
    scc->rxints++;
  
    if((bp = scc->rbp1) == NULLBUF){ /* no buffer available now */
        if(scc->rbp == NULLBUF){
            if((bp = alloc_mbuf(scc->bufsiz+sizeof(struct phdr))) != NULLBUF){
                scc->rbp = scc->rbp1 = bp;
                bp->cnt = sizeof(struct phdr);  /* get past the header */
            }
        } else if((bp = alloc_mbuf(scc->bufsiz)) != NULLBUF){
            scc->rbp1 = bp;
            for(bp = scc->rbp; bp->next != NULLBUF; bp = bp->next);
            bp->next = scc->rbp1;
            bp = scc->rbp1;
        }
        if(bp == NULLBUF){
            VOID(RDREG(scc->data)); /* so we have to discard the char */
            or(scc,R3,ENT_HM);      /* enter hunt mode for next flag */
            scc_tossb(scc);     /* put buffers back on pool */
            scc->nospace++;     /* count these events */
            return;
        }
    }
  
    /* now, we have a buffer (at bp). read character and store it */
    bp->data[bp->cnt++] = RDREG(scc->data);
  
    if(bp->cnt == bp->size)     /* buffer full? */
        scc->rbp1 = NULLBUF;    /* acquire a new one next time */
}
  
/* Receive Special Condition interrupt handler */
static void
scc_sdlcsp(scc)
register struct sccchan *scc;
{
    register unsigned char status;
    register struct mbuf *bp;
    struct phdr phdr;
  
    scc->spints++;
  
    status = rd(scc,R1);        /* read receiver status */
    VOID(RDREG(scc->data));     /* flush offending character */
  
    if(status & Rx_OVR){        /* receiver overrun */
        scc->rovers++;          /* count them */
        or(scc,R3,ENT_HM);      /* enter hunt mode for next flag */
        scc_tossb(scc);         /* rewind the buffer and toss */
    }
    if(status & END_FR &&       /* end of frame */
    scc->rbp != NULLBUF){   /* at least received something */
        if((status & CRC_ERR) == 0 &&   /* no CRC error is indicated */
            (status & 0xe) == RES8 &&   /* 8 bits in last byte */
        scc->rbp->cnt > sizeof(phdr)){
  
            /* we seem to have a good frame. but the last byte received */
            /* from rx interrupt is in fact a CRC byte, so discard it */
            if(scc->rbp1 != NULLBUF){
                scc->rbp1->cnt--;   /* current mbuf was not full */
            } else {
                for(bp = scc->rbp; bp->next != NULLBUF; bp = bp->next);
                    /* find last mbuf */
  
                bp->cnt--;      /* last byte is first CRC byte */
            }
  
            phdr.iface = scc->iface;
            phdr.type = CL_AX25;
            memcpy(&scc->rbp->data[0],(char *)&phdr,sizeof(phdr));
            enqueue(&Hopper,scc->rbp);
  
            scc->rbp = scc->rbp1 = NULLBUF;
            scc->rxframes++;
        } else {            /* a bad frame */
            scc_tossb(scc);     /* throw away frame */
            scc->rxerrs++;
        }
    }
    WRREG(scc->ctrl,ERR_RES);
}
  
/* Throw away receive mbuf(s) when an error occurred */
static void
scc_tossb (scc)
register struct sccchan *scc;
{
    register struct mbuf *bp;
  
    if((bp = scc->rbp) != NULLBUF){
        free_p(bp->next);
        free_p(bp->dup);    /* Should be NULLBUF */
        bp->next = NULLBUF;
        scc->rbp1 = bp;     /* Don't throw this one away */
        bp->cnt = sizeof(struct phdr);  /* Simply rewind it */
    }
}
  
/* Switch the SCC to "transmit" mode */
/* Only to be called from an interrupt handler, while in AX.25 mode */
static void
scc_txon(scc)
register struct sccchan *scc;
{
    if (!scc->fulldup && !scc->extclock){ /* no fulldup divider? */
        cl(scc,R3,RxENABLE);        /* then switch off receiver */
        cl(scc,R5,TxENAB);      /* transmitter off during switch */
        scc_speed(scc,1,scc->speed);    /* reprogram baudrate generator */
        if(Sccinfo.hwtype & HWBAYCOM){
            /* DPLL -> Rx clk, BRG -> Tx CLK, TxCLK -> TRxC pin */
            wr(scc,R11,RCDPLL|TCBR|TRxCOI|TRxCBR);
        }
    }
    or(scc,R5,RTS|TxENAB);      /* set the RTS line and enable TX */
    if(Sccinfo.hwtype & HWPRIMUS)   /* PRIMUS has another PTT bit... */
        WRREG(scc->ctrl + 4,Sccinfo.hwparam | 0x80); /* set that bit! */
}
  
/* Switch the SCC to "receive" mode (or: switch off transmitter)
 * Only to be called from an interrupt handler, while in AX.25 mode
 */
static void
scc_txoff(scc)
register struct sccchan *scc;
{
    cl(scc,R5,RTS);         /* turn off RTS line */
    if(Sccinfo.hwtype & HWPRIMUS)   /* PRIMUS has another PTT bit... */
        WRREG(scc->ctrl + 4,Sccinfo.hwparam); /* clear that bit! */
  
    if (!scc->fulldup && !scc->extclock){ /* no fulldup divider? */
        cl(scc,R5,TxENAB);      /* then disable the transmitter */
        scc_speed(scc,32,scc->speed);   /* back to receiver baudrate */
        if(Sccinfo.hwtype & HWBAYCOM){
            /* DPLL -> Rx clk, DPLL -> Tx CLK, RxCLK -> TRxC pin */
            wr(scc,R11,RCDPLL|TCDPLL|TRxCOI|TRxCDP);
            or(scc,R3,ENT_HM|RxENABLE);     /* enable the receiver, hunt mode */
        }
    }
}
  
/* SCC timer interrupt handler. Will be called every 1/TPS s by the
 * routine systick in pc.c
 */
void scctimer()
{
    register struct sccchan *scc;
    register struct sccchan **sccp;
    int i_state;
  
    i_state = dirps();
    for(sccp = Sccchan + Sccinfo.maxchan; sccp >= Sccchan; sccp--){
        if((scc = *sccp) != NULLCHAN &&
            scc->timercount != 0 &&
        --(scc->timercount) == 0){
            /* handle an SCC timer event for this SCC channel
             * this can only happen when the channel is AX.25 type
             * (the SLIP/KISS driver does not use timers)
             */
            switch(scc->a.tstate){
                case IDLE:          /* it was idle, this is FULLDUP2 timeout */
                    scc_txoff(scc);     /* switch-off the transmitter */
                    break;
                case DEFER:         /* trying to get the channel */
                /* operation is as follows:
                 * CSMA: when channel clear AND persistence randomgenerator
                 *   wins, AND group restrictions allow it:
                 *      keyup the transmitter
                 *   if not, delay one SLOTTIME and try again
                 * FULL: always keyup the transmitter
                 */
                    if(scc->a.fulldup == 0){
                        Random = 21 * Random + 53;
                        if(scc->status & DCD || scc->a.persist < Random){
                        /* defer transmission again. check for limit */
                            defer_it:                   if(--(scc->a.maxdefer) == 0){
                            /* deferred too long. choice is to:
                             * - throw away pending frames, or
                             * - smash-on the transmitter and send them.
                             * the first would be the choice in a clean
                             * environment, but in the amateur radio world
                             * a distant faulty station could tie us up
                             * forever, so the second may be better...
                            */
#ifdef THROW_AWAY_AFTER_DEFER_TIMEOUT
                                struct mbuf *bp,*bp1;
  
                                while ((bp = scc->sndq) != NULLBUF){
                                    scc->sndq = scc->sndq->anext;
                                    free_p(bp);
                                }
#else
                                goto keyup; /* just keyup the transmitter... */
#endif
                            }
                            scc->timercount = scc->a.slottime;
                            break;
                        }
                        if(uchar(scc->group) != NOGROUP){
                            int i;
                            struct sccchan *scc2;
  
                            for(i = 0; i <= Sccinfo.maxchan; i++)
                                if((scc2 = Sccchan[i]) != NULLCHAN &&
                                    scc2 != scc &&
                                    uchar(scc2->group) & uchar(scc->group) &&
                                    ((scc->group & TXGROUP && scc2->wreg[R5] & RTS) ||
                                (scc->group & RXGROUP && scc2->status & DCD))){
                                    goto defer_it;
                                }
                        }
                    }
                case KEYUP:         /* keyup transmitter (note fallthrough) */
                    keyup:              if((scc->wreg[R5] & RTS) == 0){ /* when not yet keyed */
                        scc->a.tstate = KEYWT;
                        scc->timercount = scc->a.txdelay; /* 0 if CTSwait */
                        scc_txon(scc);
                        break;
                    }
                /* when already keyed, directly fall through */
                case KEYWT:         /* waited for CTS or TXDELAY */
                /* when a frame is available (it should be...):
                 * - dequeue it from the send queue
                 * - reset the transmitter CRC generator
                 * - set a timeout on transmission length, if defined
                 * - send the first byte of the frame
                 * - reset the EOM latch
                 * when no frame available, proceed to TAIL handling
                 */
                    if((scc->tbp = scc->sndq) != NULLBUF){
                        scc->sndq = scc->sndq->anext;
                        WRREG(scc->ctrl,RES_Tx_CRC);
                        scc->a.tstate = ACTIVE;
                        scc->timercount = TPS * scc->a.maxkeyup;
                        scc_sdlctx(scc);
                        WRREG(scc->ctrl,RES_EOM_L);
                        break;
                    }
                /* when no frame queued, fall through to TAIL case */
                case TAIL:          /* at end of frame */
                /* when fulldup is 0 or 1, switch off the transmitter.
                 * when frames are still queued (because of transmit time limit),
                 * restart the procedure to get the channel after MINTIME.
                 * when fulldup is 2, the transmitter remains keyed and we
                 * continue sending.    IDLETIME is an idle timeout in this case.
                 */
                    if(scc->a.fulldup < 2){
                        scc->a.tstate = IDLE;
                        scc_txoff(scc);
  
                        if(scc->sndq != NULLBUF){
                            scc->a.tstate = DEFER;
                            scc->a.maxdefer = TPS * scc->a.idletime /
                            scc->a.slottime;
                            scc->timercount = TPS * scc->a.mintime;
                        }
                        break;
                    }
                    if(scc->sndq != NULLBUF){ /* still frames on the queue? */
                        scc->a.tstate = KEYWT; /* continue sending */
                        scc->timercount = TPS * scc->a.mintime; /* after mintime */
                    } else {
                        scc->a.tstate = IDLE;
                        scc->timercount = TPS * scc->a.idletime;
                    }
                    break;
                case ACTIVE:    /* max keyup time expired */
                case FLUSH: /* same while in flush mode */
                    break;  /* no action required yet */
                default:            /* unexpected state */
                    scc->a.tstate = IDLE; /* that should not happen, but... */
                    scc_txoff(scc);     /* at least stop the transmitter */
                    break;
            }
        }
    }
    restore(i_state);
}
#endif /* SCC */
  
