/*
 * 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...
 *
 * when the constant SCC_HWTIMER is defined during compilation of SCC.C,
 * we'll install the timer directly on the timer hardware interrupt, not on the
 * timer-tick user-exit (int 1c) because some programs take over that one
 * without saving and calling the original handler installed there...
 *
 */

#define SCC_HWTIMER			/* timer tick on hardware interrupt */

#ifdef ATARI_ST
# ifdef MWC
#  include <osbind.h>
# endif
# ifdef __TURBOC__
#  include <tos.h>
# endif
#endif

#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include "global.h"
#include "mbuf.h"
#include "buffers.h"
#include "iface.h"
#include "slip.h"
#include "scc.h"
#include "8530.h"
#include "8536.h"
#include "ax25.h"
#include "trace.h"

/* variables used by the SCC interrupt handler in sccvec.s */

#ifdef ATARI_ST
ioaddr sccgpiploc;			/* location of MFP GPIP register */
int    sccgpipbit;			/* bit# to test in MFP GPIP reg */
ioaddr sccisrloc;			/* location of MFP ISR */
int    sccisrbit;			/* bit# to clear in MFP ISR */
extern void (*sccotvec)();		/* location to store old int vec */

struct sccinfo sccinfo;			/* global info about SCCs */
struct sccchan *sccchan[2 * MAXSCC];	/* information per channel */
ioaddr sccvecloc;			/* location to access for SCC vector */
unsigned char sccmaxvec;		/* maximum legal vector from SCC */
ioaddr sccpolltab[MAXSCC+1][2];		/* polling table when no vectoring */
#endif

#ifdef MSDOS
static void (*orgivec)();		/* original interrupt vector */
extern void (*far sccotvec)();		/* location to store old int vec */

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(MSC) && defined(INLINE)) || (defined(__TURBOC__) && defined(inportb)))
static unsigned scc_delay (v)		/* delay for about 5 PCLK cycles */
   unsigned v;				/* pass-through used for input */

{
   return v;				/* return the passed parameter */
}
# endif
#endif

unsigned char random = 0;		/* random number for p-persist */

extern char nospace[];

static int scc_stop(),scc_raw(),scc_slipraw(),doscc();
static void scc_async(),scc_sdlc(),scc_recv();
static void scc_tossb(),scc_txon(),scc_txoff();
static int scc_ax25(),scc_aioctl(),scc_sioctl();
static unsigned int scc_speed();

/* 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.
 * argv[6]: callsign used on the radio channels
 */

int
scc_attach(argc,argv)
int argc;
char *argv[];
{
    register struct interface *ifp;
    struct sccchan *scc;
    unsigned int chan,brgrate;
    int pclk = 0;
    char *p;
    char r11;
    int hwtype = 0,hwparam = 0;
#ifdef ATARI_ST
    long save_ssp;
#endif
    extern struct interface *ifaces;
    int slip_send(),kiss_ioctl(),kiss_raw();
    void kiss_recv(),slip_recv();
    long atol(),htol();

    /* 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);
    }

    /* 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")){
	printf("Mode %s unknown for SCC\n",argv[2]);
	return -1;
    }

    if (!sccinfo.init){
	printf("First init SCC driver\n");
	return -1;
    }

    if ((chan = atoi(argv[1])) > sccinfo.maxchan){
	printf("SCC channel %d out of range\n",chan);
	return -1;
    }

    if (sccchan[chan] != NULLCHAN){
	printf("SCC channel %d already attached\n",chan);
	return -1;
    }

    /* create interface structure and fill in details */

    if ((ifp = (struct interface *) calloc(1,sizeof(struct interface))) == NULLIF){
	printf(nospace);
	return -1;
    }

    if ((ifp->name = malloc(strlen(argv[3]) + 1)) == NULLCHAR){
	printf(nospace);
	free(ifp);
	return -1;
    }
    strcpy(ifp->name,argv[3]);

    ifp->mtu = atoi(argv[4]);
    ifp->dev = chan;
    ifp->recv = doscc;
    ifp->stop = scc_stop;

    if ((scc = (struct sccchan *) calloc(1,sizeof(struct sccchan))) == NULLCHAN){
	printf(nospace);
	free(ifp->name);
	free(ifp);
	return -1;
    }

    scc->ctrl = sccinfo.iobase + (chan / 2) * sccinfo.space + sccinfo.off[chan % 2];
    scc->data = scc->ctrl + sccinfo.doff;
    scc->iface = ifp;

    sccchan[chan] = scc;		/* put addr in table for interrupts */

    switch (argv[2][0])			/* mode already checked above */
    {
    case 'a':				/* AX.25 */
	/* default: RXclk DPLL, TXclk BRG. BRG reprogrammed at every TX/RX switch */
	r11 = RCDPLL|TCBR;

	/* check for options ('d', 'ext') in the baudrate field */

	if (isupper(argv[5][0]))
	    argv[5][0] = tolower(argv[5][0]);

	switch (argv[5][0])
	{
	case 'd':			/* fulldup divider installed? */
	    scc->flags |= DIVIDER;	/* set appropriate flag */
	    argv[5]++;			/* skip the 'd' */

	    if (sccinfo.pclk){		/* when using PCLK as clock source */
		/* RXclk DPLL, TXclk RTxC, out=BRG.  external /32 TRxC->RTxC */
		r11 = RCDPLL|TCRTxCP|TRxCOI|TRxCBR;
	    } else {
		/* RXclk DPLL, TXclk TRxC.  external TX clock to TRxC */
		r11 = RCDPLL|TCTRxCP;
	    }
	    break;

	case 'e':			/* external clocking? */
	    scc->flags |= EXTCLOCK;	/* set the flag */
	    r11 = RCRTxCP|TCTRxCP;	/* RXclk RTxC, TXclk TRxC. */
	    break;
	}

	/* option :hexvalue at end of baudrates allows custom R11 setup */

	if ((p = strchr(argv[5],':')) != NULLCHAR){
	    *p++ = 0;
	    r11 = htoi(p);
	}

#ifdef ATARI_ST
	save_ssp = Super(NULL);		/* enter supervisor mode */
#endif

	scc_sdlc(scc,r11);		/* init SCC in SDLC mode */

	if (!(scc->flags & EXTCLOCK)) {
	    brgrate = scc_speed(scc,32,atol(argv[5]));/* init SCC speed */
	    scc->speed = sccinfo.clk / (64L * (brgrate + 2));/* calc real speed */
	}

#ifdef ATARI_ST
	Super(save_ssp);		/* back to user mode */
#endif

	/* default KISS Params */
	scc->c.a.params[TXDELAY] = 36*TPS/100;	/* 360 ms */
	scc->c.a.params[PERSIST] = 25;		/* 10% persistence */
	scc->c.a.params[SLOTTIME] = 16*TPS/100; /* 160 ms */
	scc->c.a.params[TAILTIME] = TPS>67? 3*TPS/100:2; /* 30 ms */
	scc->c.a.params[FULLDUP] = 0;		/* CSMA */
	scc->c.a.params[TNCSPECIF] = 1;		/* DTR set */
	scc->c.a.params[WAITTIME] = 50*TPS/100; /* 500 ms */
	scc->c.a.params[MAXKEYUP] = 7;		/* 7 s */
	scc->c.a.params[MINTIME] = 3;		/* 3 s */
	scc->c.a.params[IDLETIME] = 120;	/* 120 s */

	if (scc_ax25(ifp,argv[2],argv[6])){ /* init AX.25 mode */
	    scc_stop(ifp);
	    free(ifp->name);
	    free(ifp);
	    return -1;
	}

	ifp->ioctl = scc_sioctl;
	ifp->raw = scc_raw;
	scc->recv = scc_recv;
	break;

    case 'k':				/* kiss */
#ifdef ATARI_ST
	save_ssp = Super(NULL);		/* enter supervisor mode */
#endif
	scc_async(scc);			/* init SCC in async mode */
	brgrate = scc_speed(scc,16,atol(argv[5]));/* init SCC speed */
	scc->speed = sccinfo.clk / (32L * (brgrate + 2));/* calc real speed */
#ifdef ATARI_ST
	Super(save_ssp);		/* back to user mode */
#endif

	if (scc_ax25(ifp,argv[2],argv[6])){ /* init AX.25 mode */
	    scc_stop(ifp);
	    free(ifp->name);
	    free(ifp);
	    return -1;
	}

	ifp->ioctl = kiss_ioctl;
	ifp->raw = kiss_raw;
	ifp->slipraw = scc_slipraw;
	scc->recv = kiss_recv;
	break;

    case 's':				/* slip */
#ifdef ATARI_ST
	save_ssp = Super(NULL);		/* enter supervisor mode */
#endif
	scc_async(scc);			/* init SCC in async mode */
	brgrate = scc_speed(scc,16,atol(argv[5]));/* init SCC speed */
	scc->speed = sccinfo.clk / (32L * (brgrate + 2));/* calc real speed */
#ifdef ATARI_ST
	Super(save_ssp);		/* back to user mode */
#endif

	ifp->ioctl = scc_aioctl;
	ifp->send = slip_send;
	ifp->output = NULLFP;		/* ARP isn't used */
	ifp->raw = ifp->slipraw = scc_slipraw;
	scc->recv = slip_recv;
	break;
    }

    ifp->next = ifaces;			/* link interface in list */
    ifaces = ifp;
    return 0;
}

/* SCC driver initialisation. called on "attach scc <num> init ..." */
/* on the Atari ST, processor must be in supervisor mode */
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;
#ifdef ATARI_ST
    int z = 0;				/* to avoid clear instruction (68000) */
    long save_ssp;			/* saved stackpointer for Super() */
    static char gpipbit[] = {0,1,2,3,8,8,4,5,8,8,8,8,8,8,6,7};
    extern void sccvec(),sccnovec(),scctvec(); /* interrupt handlers */
#endif
#ifdef MSDOS
# define z 0
# ifdef SCC_HWTIMER
    time_t time1,time2;
# endif
    extern void sccvec(),sccnovec(),scctvec(); /* interrupt handlers */
    void (*getirq())();		/* Getirq is a function returning a pointer to
				 * a function returning void */
# ifndef __TURBOC__
    void (*getvect())();	/* Getvect too! */
# endif
#endif

    if (sccinfo.init){
	printf("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;

#ifdef ATARI_ST
    save_ssp = Super(NULL);		/* enter supervisor mode */
#endif

    /* 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 = disable();		/* 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 */
	}

#ifdef MSDOS
	if (hwtype & EAGLE)		/* this is an EAGLE card */
	    WRREG(chipbase + 4,0x08);	/* enable interrupt on the board */

	if (hwtype & PC100)		/* this is a PC100 card */
	    WRREG(chipbase,hwparam);	/* set the MODEM mode (22H normally) */

	if (hwtype & PRIMUS)		/* this is a PRIMUS-PC */
	    WRREG(chipbase + 4,hwparam); /* set the MODEM mode (02H normally) */

	if (hwtype & DRSI)		/* 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 */
	}
#endif

	restore(i_state);
    }

#ifdef ATARI_ST
    Super(save_ssp);			/* back to user mode */
#endif

    sccpolltab[chip][0] = 0;		/* terminate the polling table */
    sccvecloc = intack;			/* location of INTACK/vector read */
    sccmaxvec = 16 * nchips;		/* upper limit on valid vector */

#ifdef ATARI_ST
    sccgpiploc = (ioaddr) 0xfffa01L;	/* location of GPIP */
    sccgpipbit = gpipbit[ivec];		/* bit number in GPIP */
    sccisrloc = (ioaddr) (ivec > 7? 0xfffa0fL : 0xfffa11L); /* location of ISR */
    sccisrbit = ivec % 8;		/* bit number in ISR */

    if (intack){			/* INTACK method selected? */
	/*  set interrupt vector to INTACK-generating routine  */
	Mfpint(ivec,sccvec);
    } else {
	/*  set interrupt vector to polling routine */
	Mfpint(ivec,sccnovec);
    }

    Jenabint(ivec);			/* enable the interrupt */

    i_state = disable();
    sccotvec = (void (*)()) Setexc(0x114/4,scctvec); /* install timer interrupt vector */
    restore(i_state);
#endif
#ifdef MSDOS
    /*	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);

    /* get original timer interrupt vector */

# ifdef SCC_HWTIMER
    sccotvec = getirq(0);
# else
    sccotvec = getvect(0x1c);
# endif

    /* install timer interrupt handler (that jumps to the original) */

    TPS = 18;					/* standard ticks/second */

# ifdef SCC_HWTIMER
    setirq(0,scctvec);				/* install on hardware int */

#  if (defined(MSC) || defined(__TURBOC__))
    /* find the number of ticks per second the hardware timer generates */

    time1 = time(NULL);

    while ((time2 = time(NULL)) == time1)	/* wait for seconds mark */
       ;

    sccinfo.ticks = 0;				/* clear tally */
    time2 += 4;					/* setup a 4-second wait */

    while (time(NULL) < time2)			/* wait 4 seconds */
       ;

    if ((TPS = (sccinfo.ticks + 2) / 4) < 18)	/* compute ticks */
	printf("WARNING: SCC timer problem\n");
    else
	printf("SCC: %u ticks/s, %u ms/tick\n",TPS,
	       (8000 + sccinfo.ticks) / (2 * sccinfo.ticks));
#  endif
# else
    setvect(0x1c,scctvec);			/* install on software int */
# endif
#endif

    return 0;
}

/* initialize an SCC channel in asynchronous mode */
/* on the Atari ST, processor must be in supervisor mode */
static void
scc_async(scc)
    register struct sccchan *scc;

{
    int i_state;
    void scc_asytx(),scc_asyex(),scc_asyrx(),scc_asysp(); /* int handlers */
#ifdef ATARI_ST
    int z = 0;				/* to avoid clear instruction (68000) */
#endif

    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 = disable();

    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);
}

/* initialize an SCC channel in SDLC mode */
/* on the Atari ST, processor must be in supervisor mode */
static void
scc_sdlc(scc,r11)
    register struct sccchan *scc;
    char r11;				/* clockmode to load in WR11 */

{
    int i_state;
    void scc_sdlctx(),scc_sdlcex(),scc_sdlcrx(),scc_sdlcsp(); /* int handlers */
#ifdef ATARI_ST
    int z = 0;				/* to avoid clear instruction (68000) */
#endif

    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 = disable();

    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 */
    wr(scc,R10,CRCPS|NRZI|ABUNDER);	/* CRC preset 1, select NRZI, ABORT on underrun */
    wr(scc,R11,r11);			/* clockmode as determined before */

    if (scc->flags & EXTCLOCK){		/* when using external clocks */
	wr(scc,R14,z);			/* No BRG options */
	WRSCC(scc->ctrl,R14,DISDPLL|scc->wreg[R14]); /* No DPLL operation */
    } else {
	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 */
    }

    wr(scc,R15,BRKIE|CTSIE|DCDIE);	/* enable ABORT, CTS & DCD interrupts */

    if (RDREG(scc->ctrl) & DCD){	/* DCD is now ON */
	if (!(scc->flags & 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 */
/* on the Atari ST, processor must be in supervisor mode */
/* 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 = disable();		/* 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 interface *ifp;

{
    struct sccchan *scc = sccchan[ifp->dev];
    int i_state;
#ifdef ATARI_ST
    long save_ssp;

    save_ssp = Super(NULL);		/* enter supervisor mode */
#endif

    i_state = disable();

    VOID(RDREG(scc->ctrl));		/* make sure pointer is written */
    wr(scc,R9,(ifp->dev % 2)? CHRB : CHRA); /* reset the channel */
    free(scc);
    sccchan[ifp->dev] = NULLCHAN;

    restore(i_state);

#ifdef ATARI_ST
    Super(save_ssp);			/* back to user mode */
#endif
}

/* de-activate SCC driver on program exit */
void sccstop()

{
    if (sccinfo.init){			/* was it initialized? */
#ifdef ATARI_ST
	Jdisint(sccinfo.ivec);		/* disable the interrupt */
	Setexc(0x114/4,sccotvec);	/* reset the timer vector */
#endif
#ifdef MSDOS
	maskoff(sccinfo.ivec);		/* disable the interrupt */
	setirq(sccinfo.ivec,orgivec);	/* restore original interrupt vector */
# ifdef SCC_HWTIMER
	setirq(0,sccotvec);		/* restore original timer interrupt */
# else
	setvect(0x1c,sccotvec);		/* same, but using INT 1CH */
# endif
#endif
    }
}

/* perform ioctl on SCC (async) channel */
/* this is used for SLIP mode only, and will read/set the line speed */
static int
scc_aioctl(ifp,argc,argv)
    struct interface *ifp;
    int argc;
    char *argv[];

{
    struct sccchan *scc;
    unsigned int brgrate;
#ifdef ATARI_ST
    long save_ssp;
#endif

    scc = sccchan[ifp->dev];

    if (argc < 1){
	printf("%ld\n",scc->speed);
	return 0;
    }

#ifdef ATARI_ST
    save_ssp = Super(NULL);		/* enter supervisor mode */
#endif

    brgrate = scc_speed(scc,16,atol(argv[0]));
    scc->speed = sccinfo.clk / (32L * (brgrate + 2));

#ifdef ATARI_ST
    Super(save_ssp);			/* back to user mode */
#endif

    return 0;
}

/* perform ioctl on SCC (sdlc) channel */
/* this is used for AX.25 mode only, and will set the "kiss" parameters */
static int
scc_sioctl(ifp,argc,argv)
    struct interface *ifp;
    int argc;
    char *argv[];

{
    struct sccchan *scc;
    int param,i_state;
    unsigned int brgrate;
    char *p;
#ifdef ATARI_ST
    long save_ssp;
#endif

    scc = sccchan[ifp->dev];

    if (argc == 0){
	if (scc->flags & EXTCLOCK)
	    printf("ext.clk:%02x",scc->wreg[R11]);
	else
	    printf("speed=%s%ld:%02x",(scc->flags & DIVIDER)? "d":"",
				      scc->speed,scc->wreg[R11]);

	printf(" group=%03x tx=%c",scc->group,(scc->flags & TX_INHIBIT)? 'n':'y');

	for (param = 1; param < sizeof(scc->c.a.params); param++)
	    printf(" %d=%d",param,scc->c.a.params[param]);

	printf("\n");
	return 0;
    }

    if (argc < 2){
	printf("Data field missing\n");
	return 1;
    }

    switch (argv[0][0])			/* check special param setting */
    {
    case 'g':				/* setting of group */
	scc->group = htoi(argv[1]);
	return 0;

    case 's':				/* setting of speed */
	if (!(scc->flags & EXTCLOCK)) {
#ifdef ATARI_ST
	    save_ssp = Super(NULL);	/* enter supervisor mode */
#endif
	    /* option :hexvalue at end of baudrates allows custom R11 setup */

	    if ((p = strchr(argv[1],':')) != NULLCHAR){
		*p++ = 0;
		i_state = disable();
		wr(scc,R11,htoi(p));	/* set new R11 value */
		restore(i_state);
	    }

	    brgrate = scc_speed(scc,32,atol(argv[1]));/* init SCC speed */
	    scc->speed = sccinfo.clk / (64L * (brgrate + 2));/* calc real speed */
#ifdef ATARI_ST
	    Super(save_ssp);		/* back to user mode */
#endif
	}
	return 0;

    case 't':				/* set tx inhibit */
	if (tolower(argv[1][0]) == 'n')
	    scc->flags |= TX_INHIBIT;	/* no tx = inhibit it */
	else
	    scc->flags &= ~TX_INHIBIT;
	return 0;
    }

    if ((param = atoi(argv[0])) <= 0 || param >= sizeof(scc->c.a.params)){
	printf("Param number out of range\n");
	return 1;
    }

    scc->c.a.params[param] = atoi(argv[1]);

    /* and now, a special hack to allow setting of DTR with param #6 */

    if (param == TNCSPECIF){
	i_state = disable();

	if (scc->c.a.params[TNCSPECIF]) /* set or clear DTR image bit */
	    scc->wreg[R5] |= DTR;
	else
	    scc->wreg[R5] &= ~DTR;

	if (scc->c.a.tstate == IDLE && scc->timercount == 0)
	    scc->timercount = 1;	/* force an update */

	restore(i_state);
    }

    return 0;
}

/* start SCC transmitter when it is idle (SLIP/KISS mode only) */
static void
scc_sstart(scc)
    register struct sccchan *scc;
{
#ifdef ATARI_ST
    long save_ssp;
#endif

    if (scc->c.s.tbp != NULLBUF ||	/* busy */
	scc->c.s.sndq == NULLBUF)	/* no work */
	return;

    scc->c.s.tbp = dequeue(&scc->c.s.sndq);

#ifdef ATARI_ST
    save_ssp = Super(NULL);		/* enter supervisor mode */
#endif

    WRREG(scc->data,FR_END);		/* write first char to start things */

#ifdef ATARI_ST
    Super(save_ssp);			/* back to user mode */
#endif
}

/* show SCC status */
dosccstat()
{
    register struct sccchan *scc;
    int i;

    if (!sccinfo.init){
	printf("SCC driver not initialized\n");
	return 0;
    }

    printf("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;

	printf("%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;
}

/* process incoming packets. called from main loop to poll rx queue. */
static int
doscc(ifp)
    struct interface *ifp;

{
    register struct sccchan *scc;
    int rv = 0;

    scc = sccchan[ifp->dev];

    while (scc->rqueue != NULLBUF) {	/* incoming packets waiting? */
	(*scc->recv)(ifp,dequeue(&scc->rqueue)); /* process first */
	rv++;
    }

#ifdef ATARI_ST
    /* it seems that the MFP sometimes misses the interrupt edge
     * if this happens, no more interrupts occur and the SCC's hang.
     * test if the int input is low, and panic if so.
     */
    if (ifp->dev == 0){
	long save_ssp;
	unsigned char bit;
	int i_state;
	static int intcntr = 0;
	static int trycntr = 0;
	static char mesg1[] = "WARNING: SCC IRQ stuck, recover";
	static char mesg2[] = "PANIC: SCC IRQ stuck, exit";

	save_ssp = Super(NULL);		/* enter supervisor mode */
	bit = RDREG(sccgpiploc);	/* test interrupt level */
	bit |= RDREG(sccgpiploc);	/* 2nd time to avoid glitch */
	Super(save_ssp);		/* back to user mode */

	if (!(bit & (1 << sccgpipbit))){ /* int active now? */
	    if (++intcntr > 10) {	/* was that true the last 10 times? */
		if (trycntr++ == 0) {	/* check for recovery phase */
		    log_msg(mesg1);
		    printf("%s\n",mesg1);
		    save_ssp = Super(NULL); /* enter supervisor mode */
		    i_state = disable();

		    if (sccvecloc)	/* INTACK method selected? */
			sccvec();	/* call vectored handler */
		    else
			sccnovec();	/* else use non-vectored handler */

		    restore(i_state);
		    Super(save_ssp);	/* back to user mode */
		} else {		/* already recovering... */
		    log_msg(mesg2);	/* make it a PANIC */
		    printf("%s\n",mesg2);
		    doexit(0);
		}
	    }
	} else {
	    intcntr = trycntr = 0;	/* not active, count back to 0 */
	}
    }
#endif

    return rv;				/* number of processed packets */
}

/* process received frame on SCC. used for AX.25 mode only */
static void
scc_recv(ifp,bp)
    struct interface *ifp;
    struct mbuf *bp;
{
    dump(ifp,IF_TRACE_IN,TRACE_AX25,bp);/* trace the incoming packet */
    ax_recv(ifp,bp);			/* receive as AX.25 packet */
}

/* send raw frame to SCC. used for AX.25 */
static int
scc_raw(ifp,bp)
    struct interface *ifp;
    struct mbuf *bp;
{
    struct sccchan *scc;
    int i_state;

    dump(ifp,IF_TRACE_OUT,TRACE_AX25,bp);

    scc = sccchan[ifp->dev];

    if (scc->flags & TX_INHIBIT){	/* transmitter inhibit */
	free_p(bp);
	return -1;
    }

    enqueue(&scc->c.a.sndq,bp);		/* enqueue packet */
    scc->enqueued++;

    i_state = disable();

    if (scc->c.a.tstate == IDLE){	/* when transmitter is idle */
	scc->c.a.tstate = DEFER;	/* start the key-up sequence */
	scc->c.a.maxdefer = TPS * scc->c.a.params[IDLETIME] /
				  scc->c.a.params[SLOTTIME];
	scc->timercount = scc->c.a.params[WAITTIME];
    }

    restore(i_state);
    return 0;
}

/* send raw slip frame to SCC. used for SLIP and KISS */
static int
scc_slipraw(ifp,bp)
    struct interface *ifp;
    struct mbuf *bp;
{
    struct sccchan *scc;
    register struct slip *sp;

    scc = sccchan[ifp->dev];
    sp = &scc->c.s;			/* address SLIP control structure */
    enqueue(&sp->sndq,bp);		/* enqueue packet (unencoded) */
    scc->enqueued++;

    if (sp->tbp == NULLBUF)
       scc_sstart(scc);
}

/* initialize interface for AX.25 use */
static int
scc_ax25(ifp,mode,call)
    register struct interface *ifp;
    char *mode;
    char *call;

{
    struct ax25_addr hwaddr;
    struct ax25_call *axc;
    int ax_send(),ax_output();

    if (setcall(&hwaddr,call) < 0){
	printf("Interface %s needs a callsign in %s mode\n",ifp->name,mode);
	return -1;
    }

    axarp();				/* init AX.25 ARP */

    ifp->send = ax_send;
    ifp->output = ax_output;
    ifp->flags |= IF_AX25;

    if ((ifp->hwaddr = malloc(sizeof(struct ax25_addr))) == NULLCHAR ||
	(axc = cr_axcall(ax25_call,&hwaddr)) == NULLAXCALL){
	printf(nospace);
	return -1;
    }

    memcpy(ifp->hwaddr,&hwaddr,sizeof(struct ax25_addr));
    if (axc->interface != NULLIF)
	axc->flags |= MULTI_IF;
    axc->interface = ifp;
    axc->mode = IP_ARP_CON;
    return 0;
}


/* Interrupt handlers for asynchronous modes (kiss, slip) */
/* Remember that on the Atari, these routines can ONLY be called */
/* in SUPERVISOR mode! */

/* 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->c.s.tbp == NULLBUF){	/* nothing to send? */
	if ((scc->c.s.tbp = scc->c.s.sndq) != NULLBUF){ /* dequeue next frame */
	    scc->c.s.sndq = scc->c.s.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->c.s.tbp)->cnt == 0){ /* nothing left in this mbuf? */
	bp = bp->next;			/* save link to next */
	scc->c.s.tbp->next = sys_txfree;/* move this mbuf to txfree list */
	sys_txfree = scc->c.s.tbp;

	if ((scc->c.s.tbp = bp) == NULLBUF){ /* see if more mbufs follow */
	    WRREG(scc->data,FR_END);	/* frame complete, send FR_END */
	    return;
	}
    }
    /* now bp = scc->c.s.tbp (either from while or from if stmt above) */

    switch (uchar(*(bp->data)))		/* next data byte to transmit */
    {
    case FR_END:
	scc->txchar = T_FR_END;		/* next time, send transposed FR_END */
	WRREG(scc->data,FR_ESC);	/* now, send a FR_ESC */
	break;

    case FR_ESC:
	scc->txchar = T_FR_ESC;		/* next time, send transposed FR_ESC */

    default:
	WRREG(scc->data,*(bp->data));	/* just send the character */
	break;
    }

    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? */
	scc->c.s.errors++;		/* treat as an error to discard frame */

	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 */
static void
scc_asyrx(scc)
    register struct sccchan *scc;

{
    register unsigned char ch;
    register struct mbuf *bp;

    scc->rxints++;

    if (scc->c.s.escaped){
	/* Translate 2-char escape sequence back to original char */
	scc->c.s.escaped = 0;

	switch (ch = RDREG(scc->data))
	{
	case T_FR_ESC:
	    ch = FR_ESC;
	    break;

	case T_FR_END:
	    ch = FR_END;
	    break;

	case FR_END:			/* escaped FR_END, assume an error */
	    goto discard;

	default:
	    scc->c.s.errors++;
	    break;
	}
    } else {
	switch (ch = RDREG(scc->data))
	{
	case FR_ESC:
	    scc->c.s.escaped = 1;
	    return;

	case FR_END:
	    if (scc->c.s.rbp != NULLBUF){
		if (scc->c.s.errors == 0){
		    if (scc->rqueue == NULLBUF){	/* enqueue it */
			scc->rqueue = scc->c.s.rbp;
		    } else {
			for (bp = scc->rqueue; bp->anext != NULLBUF; bp = bp->anext)
			    ;
			bp->anext = scc->c.s.rbp;
		    }					/* like enqueue() does */

		    scc->c.s.rbp = scc->c.s.rbp1 = NULLBUF;
		    scc->rxframes++;
		} else {
discard:	    /* error in frame, put buffers back on sys_rxbufs queue */
		    while ((bp = scc->c.s.rbp) != NULLBUF){
			scc->c.s.rbp = bp->next;
			bp->next = sys_rxbufs;
			sys_rxbufs = bp;
			bp->cnt = 0;		/* reset count for next user */
			cur_rxbufs++;
		    }

		    scc->c.s.rbp1 = NULLBUF;
		    scc->rxerrs++;
		}
	    }
	    scc->c.s.errors = 0;
	    return;

	default:			/* a normal char, fall through */
	    break;
	}
    }
    /* now ch holds the next character for the receive buffer */

    if (scc->c.s.errors)		/* we already have an error in this frame */
	return;				/* so don't waste time and buffers */

    if ((bp = scc->c.s.rbp1) == NULLBUF){ /* no buffer available now */
	if ((bp = sys_rxbufs) == NULLBUF){ /* system out of buffers */
	    if (!scc->c.s.errors)
		scc->nospace++;		/* count these events (1 per frame) */
	    scc->c.s.errors++;		/* cannot store the character now */
	    return;
	}

	sys_rxbufs = bp->next;		/* get the buffer from list */
	bp->next = NULLBUF;
	cur_rxbufs--;

	if (scc->c.s.rbp == NULLBUF){	/* append it to rx list */
	    scc->c.s.rbp = scc->c.s.rbp1 = bp;
	} else {
	    scc->c.s.rbp1 = bp;
	    for (bp = scc->c.s.rbp; bp->next != NULLBUF; bp = bp->next)
		;
	    bp->next = scc->c.s.rbp1;
	    bp = scc->c.s.rbp1;
	}				/* like append() does */
    }
    /* now bp = scc->c.s.rbp1 */

    bp->data[bp->cnt++] = ch;		/* store the character */

    if (bp->cnt == bp->size)		/* buffer full? */
	scc->c.s.rbp1 = NULLBUF;	/* acquire a new one next time */
}

/* 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){		/* did a framing error occur? */
	scc->c.s.errors++;		/* treat as an error to discard frame */
    } else {
	if (status & Rx_OVR){		/* did we get an overrun? */
	    scc->rovers++;		/* count them */
	    scc->c.s.errors++;		/* treat as an error to discard frame */
	}
    }

    WRREG(scc->ctrl,ERR_RES);
}


/* Interrupt handlers for sdlc mode (AX.25) */
/* Remember that on the Atari, these routines can ONLY be called */
/* in SUPERVISOR mode! */

/* Transmitter interrupt handler */
static void
scc_sdlctx(scc)
    register struct sccchan *scc;

{
    register struct mbuf *bp;

    scc->txints++;

    switch (scc->c.a.tstate)		/* look at transmitter state */
    {
    case ACTIVE:			/* busy sending data bytes */
	while ((bp = scc->c.a.tbp)->cnt == 0){	/* nothing left in this mbuf? */
	    bp = bp->next;			/* save link to next */
	    scc->c.a.tbp->next = sys_txfree;	/* move this mbuf to txfree list */
	    sys_txfree = scc->c.a.tbp;

	    if ((scc->c.a.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->c.a.tstate = TAIL;	/* key down tx after TAILTIME */
		    scc->timercount = scc->c.a.params[TAILTIME];
		    return;
		}

		cl(scc,R10,ABUNDER);		/* frame complete, allow CRC transmit */
		scc->c.a.tstate = FLUSH;
		WRREG(scc->ctrl,RES_Tx_P);	/* reset pending int */
		return;
	    }
	}
	/* now bp = scc->c.a.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->c.a.params[MAXKEYUP] == 0) &&
	    (scc->c.a.tbp = scc->c.a.sndq) != NULLBUF){ /* dequeue a frame */
	    scc->c.a.sndq = scc->c.a.sndq->anext;
	    WRREG(scc->ctrl,RES_Tx_CRC); /* reset the TX CRC generator */
	    scc->c.a.tstate = ACTIVE;
	    scc_sdlctx(scc);		/* write 1st byte */
	    WRREG(scc->ctrl,RES_EOM_L); /* reset the EOM latch */
	    return;
	}

	scc->c.a.tstate = TAIL;		/* no more, key down tx after TAILTIME */
	scc->timercount = scc->c.a.params[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->c.a.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->c.a.rbp->next != NULLBUF || scc->c.a.rbp->cnt > 10)
		    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->c.a.tstate == KEYWT &&
		scc->c.a.params[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->flags & 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->c.a.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->c.a.rbp->next != NULLBUF || scc->c.a.rbp->cnt > 10)
		    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->c.a.rbp1) == NULLBUF){ /* no buffer available now */
	if ((bp = sys_rxbufs) == NULLBUF){ /* system out of buffers */
	    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;
	}

	sys_rxbufs = bp->next;		/* get the buffer from list */
	bp->next = NULLBUF;
	cur_rxbufs--;

	if (scc->c.a.rbp == NULLBUF){	/* append it to rx list */
	    scc->c.a.rbp = scc->c.a.rbp1 = bp;
	} else {
	    scc->c.a.rbp1 = bp;
	    for (bp = scc->c.a.rbp; bp->next != NULLBUF; bp = bp->next)
		;
	    bp->next = scc->c.a.rbp1;
	    bp = scc->c.a.rbp1;
	}				/* like append() does */
    }

    /* 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->c.a.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;

    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);			/* put buffers back on pool */
    }

    if (status & END_FR &&		/* end of frame */
	scc->c.a.rbp != NULLBUF){	/* at least received something */
	if ((status & CRC_ERR) == 0 &&	/* no CRC error is indicated */
	    (status & 0xe) == RES8){	/* 8 bits in last byte */

	    /* 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->c.a.rbp1 != NULLBUF){
		scc->c.a.rbp1->cnt--;	/* current mbuf was not full */
	    } else {
		for (bp = scc->c.a.rbp; bp->next != NULLBUF; bp = bp->next)
		    ;			/* find last mbuf */

		bp->cnt--;		/* last byte is first CRC byte */
	    }

	    if (scc->rqueue == NULLBUF){/* enqueue it */
		scc->rqueue = scc->c.a.rbp;
	    } else {
		for (bp = scc->rqueue; bp->anext != NULLBUF; bp = bp->anext)
		    ;
		bp->anext = scc->c.a.rbp;
	    }				/* like enqueue() does */

	    scc->c.a.rbp = scc->c.a.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 (scc->c.a.rbp != NULLBUF){
	/* move all buffers back to the sys_rxbufs list */
	while ((bp = scc->c.a.rbp) != NULLBUF){
	    scc->c.a.rbp = bp->next;
	    bp->next = sys_rxbufs;
	    sys_rxbufs = bp;
	    bp->cnt = 0;		/* reset count for next user */
	    cur_rxbufs++;
	}

	scc->c.a.rbp1 = NULLBUF;
    }
}

/* 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->flags & (DIVIDER|EXTCLOCK))){ /* halfduplex without 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 */
    }

    or(scc,R5,RTS|TxENAB);		/* set the RTS line and enable TX */

#ifdef MSDOS
    if (sccinfo.hwtype & PRIMUS)	/* PRIMUS has another PTT bit... */
	WRREG(scc->ctrl + 4,sccinfo.hwparam | 0x80); /* set that bit! */
#endif
}

/* 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 */

#ifdef MSDOS
    if (sccinfo.hwtype & PRIMUS)	/* PRIMUS has another PTT bit... */
	WRREG(scc->ctrl + 4,sccinfo.hwparam); /* clear that bit! */
#endif

    if (!(scc->flags & (DIVIDER|EXTCLOCK))){ /* halfduplex without divider? */
	cl(scc,R5,TxENAB);		/* then disable the transmitter */
	scc_speed(scc,32,scc->speed);	/* back to receiver baudrate */
    }
}

/* SCC timer interrupt handler. Will be called every 1/TPS s by the system */
/* timer interrupt.  The usual timer library is not used because it will */
/* only fire handlers from the main program loop. This is much too slow. */

void scctim()

{
    register struct sccchan *scc;
    register struct sccchan **sccp;

#ifdef MSDOS
    sccinfo.ticks++;			/* keep track of number of ticks */
#endif

    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->c.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->c.a.params[FULLDUP] == 0){
		    random = 21 * random + 53;

		    if (scc->status & DCD || scc->c.a.params[PERSIST] < random){
			/* defer transmission again. check for limit */
defer_it:		if (--(scc->c.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->c.a.sndq) != NULLBUF){
				scc->c.a.sndq = scc->c.a.sndq->anext;
				/* move this mbuf chain to the txfree list */
				for (bp1 = bp; bp1->next != NULLBUF; bp1 = bp1->next)
				    ;
				bp1->next = sys_txfree;
				sys_txfree = bp;
			    }
#else
			    goto keyup; /* just keyup the transmitter... */
#endif
			}

			scc->timercount = scc->c.a.params[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->c.a.tstate = KEYWT;
		    scc->timercount = scc->c.a.params[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->c.a.tbp = scc->c.a.sndq) != NULLBUF){
		    scc->c.a.sndq = scc->c.a.sndq->anext;
		    WRREG(scc->ctrl,RES_Tx_CRC);
		    scc->c.a.tstate = ACTIVE;
		    scc->timercount = TPS * scc->c.a.params[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->c.a.params[FULLDUP] < 2){
		    scc->c.a.tstate = IDLE;
		    scc_txoff(scc);

		    if (scc->c.a.sndq != NULLBUF){
			scc->c.a.tstate = DEFER;
			scc->c.a.maxdefer = TPS * scc->c.a.params[IDLETIME] /
						  scc->c.a.params[SLOTTIME];
			scc->timercount = TPS * scc->c.a.params[MINTIME];
		    }
		    break;
		}

		if (scc->c.a.sndq != NULLBUF){ /* still frames on the queue? */
		    scc->c.a.tstate = KEYWT; /* continue sending */
		    scc->timercount = TPS * scc->c.a.params[MINTIME]; /* after mintime */
		} else {
		    scc->c.a.tstate = IDLE;
		    scc->timercount = TPS * scc->c.a.params[IDLETIME];
		}
		break;

	    case ACTIVE:		/* max keyup time expired */
	    case FLUSH:			/* same while in flush mode */
		break;			/* no action required yet */

	    default:			/* unexpected state */
		scc->c.a.tstate = IDLE; /* that should not happen, but... */
		scc_txoff(scc);		/* at least stop the transmitter */
		break;
	    }
	}
    }
}
