/*
 * Interface driver for the VE3IFB 8530 card (PI card)
 * Copyright 1990,1992,1993,1994 by Dave Perry, VE3IFB
 *
 * History
 * Minor delinting - KA9Q 2/2/91
 * March 23 92 dp - Fixed bug in B port driver where ff in the data was
 *                not being handled correctly.
 *              - Fixed bug where receive packet would get clobbered if
 *                a new packet was enqueued for transmission. Greatly
 *                improves performance with sliding window protocols.
 *                (tdelay would overwrite contents of the interrupt control
 *                register in the scc).
 * March 29,92 dp - Last release was based on old sources - corrected.
 * Nov. 17, 93 dp - Added PI2 support. NAFTA passed.
 * Jan. 1,  94 dp - Added g clocking option for B port, calculate
 *                  value for tx tail based on baud rate.
 *
 *
 * Portions of this driver were derived from the Eagle card
 * driver by Art Goldman, WA3CVG. It has been very extensively
 * modified from his work, due to the addition of DMA support
 * and due to differences in the hardware.  The PI card is NOT
 * an Eagle card clone. It is an original design by Dave Perry,
 * VE3IFB.  Art's copyright notice follows:
 *
 *  Written by Art Goldman, WA3CVG - (c) Copyright 1987 All Rights Reserved
 *  Permission for non-commercial use is hereby granted provided this notice
 *  is retained.  For info call: (301) 997-3838.
 *
 */
  
#include <time.h>
#include <stdio.h>
#include <dos.h>
#include <bios.h>
#include <string.h>
#include "global.h"
#ifdef PI
#include "mbuf.h"
#include "iface.h"
#include "pktdrvr.h"
#include "netuser.h"
#include "pi.h"
#include "z8530.h"
#include "ax25.h"
#include "trace.h"
#include "pc.h"
  
#include "session.h"
#include "lapb.h"
#include "proc.h"
#include "ip.h"
#include "devparam.h"
#include "cmdparse.h"
  
#undef  IGNOREDCD
  
#ifndef FP_OFF
#define FP_OFF(fp)      ((unsigned)(fp))
#endif
#ifndef FP_SEG
#define FP_SEG(fp)      ((unsigned)((unsigned long)(fp) >> 16))
#endif
  
static int32 pi_ctl __ARGS((struct iface *iface,int cmd,int set,int32 val));
static int pi_raw __ARGS((struct iface *iface,struct mbuf *bp));
static int pi_stop __ARGS((struct iface *iface));
static void rts __ARGS((struct pichan *hp, int16 x));
void setup_rx_dma __ARGS((struct pichan *hp));
void setup_tx_dma __ARGS((struct pichan *hp, char *buffer, int length));
static void tdelay __ARGS((register struct pichan *hp,unsigned int time));
static int scc_init __ARGS((struct pichan *));
static void b_rxint __ARGS((struct pichan *));
static void b_txint __ARGS((struct pichan *));
static void b_exint __ARGS((struct pichan *));
static void a_rxint __ARGS((struct pichan *));
static void a_txint __ARGS((struct pichan *));
static void a_exint __ARGS((struct pichan *));
void set_acc_delay __ARGS((void));
  
static struct PITAB Pi[PIMAX];  /* Device table - one entry per card */
static INTERRUPT (*pihandle[])() = {    /* handler interrupt vector table */
    pi0vec,
    pi1vec,
    pi2vec
};
static int16 Page_regs[] = {
    0x87,0x83,0x81,0x82,0,0x8b,0x89,0x8a
};
static struct pichan Pichan[2*PIMAX];   /* channel table - 2 entries per card */
static int16 pinbr;
  
extern int16 acc_delay; /* Delay for the 8530 chip access recovery time */
  
/* display PI Channel stats */
int
dopishow()
{
    struct pichan *hp;
    int i;
  
    tputs("PI Board Statistics:\n\n");
    tputs("Base Addr  Rxints  Txints  Exints  TxFrms  RxFrms  Crcerr  RxOvrs  TxUndr \n");
    tputs("---------  ------  ------  ------  ------  ------  ------  ------  ------ \n");
    for(i=0; i<pinbr*2; i++){
        hp = &Pichan[i];
  
        tprintf("0x%03x    % 8lu% 8lu% 8lu% 8u% 8u% 8u% 8u% 8u\nRcv State=%s ",
        hp->base, hp->rxints, hp->txints, hp->exints, hp->enqueued,
        hp->rxframes, hp->crcerr, hp->rovers, hp->tunders,
        hp->rstate==0 ?
        "IDLE" : hp->rstate==1 ?
        "ACTIVE" : hp->rstate==2 ?
        "RXERROR" : hp->rstate==3 ?
        "RXABORT":"TOOBIG"
        );
  
        tprintf("Tstate = %s\n",
        hp->tstate == 0 ?
        "IDLE" : hp->tstate == 1 ?
        "ACTIVE" : hp->tstate == 2 ?
        "UNDERRUN" : hp->tstate == 3 ?
        "FLAGOUT" : hp->tstate == 4 ?
        "DEFER" : hp->tstate == 5 ?
        "TXDELAY" : "CRCOUT"
        );
    }
    return 0;
}
  
static int
dopiclear(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    struct pichan *hp;
    int i;
  
    for(i=0; i<pinbr*2; i++){
        hp = &Pichan[i];
        hp->rxints = hp->txints = hp->exints = hp->enqueued = 0;
        hp->rxframes = hp->crcerr = hp->rovers = hp->tunders = 0;
    }
    return 0;
}
  
  
static struct cmds picmds[] = {
    "show",         dopishow,       0, 0, NULLCHAR,
    "clear",        dopiclear,      0, 0, NULLCHAR,
    NULLCHAR,
};
  
int
dopistat(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return subcmd(picmds,argc,argv,p);
}
  
/* Setup 8253 chip for time delay */
static void
tdelay(hp,time)
register struct pichan *hp;
unsigned int time;               /* Time to delay in milliseconds */
{
    int n,port;
    unsigned int t1;
    unsigned char sc;
  
    if(hp->base & 2){ /* If A channel */
        sc = SC1;
        t1 = time;
        port = hp->cardbase+TMR1;
    } else {
        sc = SC2;
        t1 = 10 * time; /* 10s of milliseconds for the B channel */
        port = hp->cardbase+TMR2;
        wrtscc(hp->cardbase,hp->base+CTL,R1,EXT_INT_ENAB);
    }
  
    /* Setup timer sc */
    outportb(hp->cardbase+TMRCMD, sc|LSB_MSB|MODE0);
  
    /* satisfy access time restriction */
    for(n=0; n<2;n++)
        ;
    /* times 2 to make millisecs */
    outportb(port, (t1 << 1) & 0xFF);
  
    /* satisfy access time restriction */
    for(n=0; n<2;n++)
        ;
    outportb(port, (t1 >> 7) & 0xFF);
  
    /* Enable correct int for timeout */
    wrtscc(hp->cardbase,hp->base+CTL,R15,CTSIE);
    wrtscc(hp->cardbase,hp->base+CTL,R0,RES_EXT_INT);
}
  
/* Master interrupt handler.  One interrupt at a time is handled.
 * here. Service routines are called from here.
 */
void
piint(dev)
int dev;
{
    register char st;
    register int16 pcbase;
    struct pichan *hp;
  
    Pi[dev].ints++;
    pcbase = Pi[dev].addr;
  
    /* Read interrupt status register (only valid from channel A)
     * Process all pending interrupts in while loop
     */
    hp = &Pichan[2 * dev];  /* Assume channel A */
    while((st = rdscc(hp->cardbase,pcbase+CHANA+CTL,R3)) != 0){
        if(st & CHBTxIP){
            /* Channel B Transmit Int Pending */
            hp = &Pichan[(2 * dev)+1];
            b_txint(hp);
        } else if(st & CHARxIP){
            /* Channel A Rcv Interrupt Pending */
            hp = &Pichan[2 * dev];
            a_rxint(hp);
        } else if(st & CHATxIP){
            /* Channel A Transmit Int Pending */
            hp = &Pichan[2 * dev];
            a_txint(hp);
        } else if(st & CHAEXT){
            /* Channel A External Status Int */
            hp = &Pichan[2 * dev];
            a_exint(hp);
        } else if(st & CHBRxIP){
            /* Channel B Rcv Interrupt Pending */
            hp = &Pichan[(2 * dev)+1];
            b_rxint(hp);
        } else if(st & CHBEXT){
            /* Channel B External Status Int */
            hp = &Pichan[(2 * dev)+1];
            b_exint(hp);
        }
        /* Reset highest interrupt under service */
        wrtscc(hp->cardbase,hp->base+CTL,R0,RES_H_IUS);
    } /* End of while loop on int processing */
}
  
static void
a_exint(hp)
register struct pichan *hp;
{
    register int16 cmd;
    char st;
    int i_state;
    int length;
    int32 t,ca;
  
    i_state = dirps();          /* disable interrupts */
  
    st = rdscc(hp->cardbase,hp->base+CTL,R0);     /* Fetch status */
  
    /* reset external status latch */
    wrtscc(hp->cardbase,CTL+hp->base,R0,RES_EXT_INT);
    cmd = hp->base+CTL;
    hp->exints++;
  
    if((hp->rstate >= ACTIVE) && (st & BRK_ABRT)){
        setup_rx_dma(hp);
        hp->rstate = ACTIVE;
    }
    switch(hp->tstate){
        case ACTIVE:
            free_p(hp->sndbuf);
            hp->sndbuf = NULLBUF;
            hp->tstate = FLAGOUT;
            tdelay(hp,hp->squeldelay);
            break;
        case FLAGOUT:
            if((hp->sndbuf = dequeue(&hp->sndq)) == NULLBUF){
            /* Nothing to send - return to receive mode */
                hp->tstate = IDLE;
                rts(hp,OFF);
                restore(i_state);
                return;
            }
        /* NOTE - fall through if more to send */
        case ST_TXDELAY:
        /* Disable DMA chan */
            outportb(DMA_MASK, DMA_DISABLE|hp->dmachan);
  
        /* Set up for TX dma */
            wrtscc(hp->cardbase,cmd,R1,WT_FN_RDYFN|EXT_INT_ENAB);
  
        /* Get all chars */
            length = pullup(&hp->sndbuf,hp->txdmabuf,hp->bufsiz);
  
        /* Setup DMA controller for tx */
            setup_tx_dma(hp,hp->txdmabuf,length);
  
        /* select transmit interrupts to enable */
        /* Allow DMA on chan */
            outportb(DMA_MASK,DMA_ENABLE|hp->dmachan);
  
        /* reset CRC, Txint pend*/
            wrtscc(hp->cardbase,cmd,R0,RES_Tx_CRC|RES_Tx_P);
  
        /* allow Underrun int only */
            wrtscc(hp->cardbase,cmd,R15,TxUIE);
  
        /* Enable TX DMA */
            wrtscc(hp->cardbase,cmd,R1,WT_RDY_ENAB|WT_FN_RDYFN|EXT_INT_ENAB);
  
        /* Send CRC on underrun */
            wrtscc(hp->cardbase,cmd,R0,RES_EOM_L);
  
        /* packet going out now */
            hp->tstate = ACTIVE;
            break;
        case DEFER:
        /* we have deferred prev xmit attempt
         * See Intel Microcommunications Handbook, p2-308
         */
            wrtscc(hp->cardbase,cmd,R0,RES_EXT_INT);
            wrtscc(hp->cardbase,cmd,R0,RES_EXT_INT);
#ifndef IGNOREDCD
            if((rdscc(hp->cardbase,cmd,R0) & DCD) != 0){
                hp->tstate = DEFER;
                tdelay(hp,100);
            /* Defer until dcd transition or 100mS timeout */
                wrtscc(hp->cardbase,CTL+hp->base,R15,CTSIE|DCDIE);
                restore(i_state);
                return;
            }
  
            if((rand() & 0xff) > uchar(hp->persist)){
                hp->tstate = DEFER;
                tdelay(hp,hp->slotime);
                restore(i_state);
                return;
            }
#endif
        /* Assert RTS early minimize collision window */
            wrtscc(hp->cardbase,cmd,R5,TxCRC_ENAB|RTS|Tx8);
            rts(hp,ON);     /* Transmitter on */
            hp->tstate = ST_TXDELAY;
            tdelay(hp,hp->txdelay);
            restore(i_state);
            return;
    } /* switch(hp->tstate) */
  
    restore(i_state);
} /* a_exint() */
  
/* Receive interrupt handler for the A channel
 */
static void
a_rxint(hp)
register struct pichan *hp;
{
    register int16 cmd;
    register int16 bytecount;
    char rse;
    int i_state;
    struct mbuf *bp;
    struct phdr phdr;
  
    i_state = dirps();          /* disable interrupts */
    hp->rxints++;
    cmd = hp->base+CTL;
  
    rse = rdscc(hp->cardbase,cmd,R1); /* Get special condition bits from R1 */
    if(rse & Rx_OVR)
        hp->rstate = RXERROR;
  
    if(rse & END_FR){
        /* If end of frame */
        /* figure length of frame from 8237 */
        outportb(DMA_RESETFF,0); /* reset firstlast ff */
        bytecount = inportb(hp->dma_wcr);
        bytecount += inportb(hp->dma_wcr) << 8;
        /* Allow for the extra space for phdr */
        bytecount = (hp->bufsiz - 1 - sizeof(struct phdr)) - bytecount;
  
        if((rse & CRC_ERR)||(hp->rstate > ACTIVE)||(bytecount < 10)){
            if((bytecount >= 10) && (rse & CRC_ERR))
                hp->crcerr++; /* Ignore noise */
  
            if(hp->rstate == RXERROR)
                hp->rovers++;
            /* Reset buffer pointers */
            hp->rstate = ACTIVE;
            setup_rx_dma(hp);
        } else {
            /* Here we have a valid frame */
            /* Toss 2 crc bytes */
            hp->rcvbuf->cnt = bytecount - 2;
            /* "Can't fail" */
            bp = pushdown(hp->rcvbuf,sizeof(phdr));
            phdr.type = CL_AX25;
            phdr.iface = hp->iface;
            memcpy(bp->data,(char *)&phdr,sizeof(phdr));
            hp->rcvbuf = NULLBUF;
            enqueue(&Hopper,bp);       /* queue it in */
            hp->rxframes++;
  
            /* packet queued - get buffer for next frame */
            hp->rcvbuf = alloc_mbuf(hp->bufsiz + sizeof(phdr));
            if(hp->rcvbuf != NULLBUF)
                /* Allow room for phdr */
                hp->rcvbuf->data += sizeof(struct phdr);
            setup_rx_dma(hp);
        } /* end good frame queued */
    } /* end EOF check */
  
    wrtscc(hp->cardbase,hp->base+CTL,R0,ERR_RES);   /* error reset */
    restore(i_state);
}
  
void
a_txint(hp)
register struct pichan *hp;
{
    register int16 cmd;
    int i_state;
    int32 t,ca;
  
    i_state = dirps();
    cmd = CTL+hp->base;
  
    switch(hp->tstate){
        case IDLE:
        /* Transmitter idle. Find a frame for transmission */
            if((hp->sndbuf = dequeue(&hp->sndq)) == NULLBUF){
                rts(hp,OFF);
                restore(i_state);
                return;
            }
        /* If a buffer to send, we drop thru here */
        case DEFER:
        /* we may have deferred prev xmit attempt */
        /* Check DCD - debounce it
         * See Intel Microcommunications Handbook, p2-308
         */
            wrtscc(hp->cardbase,cmd,R0,RES_EXT_INT);
            wrtscc(hp->cardbase,cmd,R0,RES_EXT_INT);
#ifndef IGNOREDCD
            if((rdscc(hp->cardbase,cmd,R0) & DCD) != 0){
                hp->tstate = DEFER;
                tdelay(hp,100);
            /* defer until DCD transition or timeout */
                wrtscc(hp->cardbase,cmd,R15,CTSIE|DCDIE);
                restore(i_state);
                return;
            }
  
            if((rand() & 0xff) > uchar(hp->persist)){
                hp->tstate = DEFER;
                tdelay(hp,hp->slotime);
                restore(i_state);
                return;
            }
#endif
        /* Assert RTS early minimize collision window */
            wrtscc(hp->cardbase,cmd,R5,TxCRC_ENAB|RTS|Tx8);
            rts(hp,ON);     /* Transmitter on */
            hp->tstate = ST_TXDELAY;
            tdelay(hp,hp->txdelay);
            restore(i_state);
            return;
        default:
            break;
    } /* end switch(hp->state) */
  
    restore(i_state);
} /*a_txint */
  
static void
b_rxint(hp)
register struct pichan *hp;
{
    register int16 cmd;
    char rse;
    int i_state;
    struct mbuf *bp;
    struct phdr phdr;
  
    i_state = dirps();          /* disable interrupts */
    hp->rxints++;
    cmd = CTL+hp->base;
  
    if((rdscc(hp->cardbase,cmd,R0)) & Rx_CH_AV){
        /* there is a char to be stored
         * read special condition bits before reading the data char
         */
        rse = rdscc(hp->cardbase,cmd,R1); /* get status byte from R1 */
        if(rse & Rx_OVR){
            /* Rx overrun - toss buffer */
            /* reset buffer pointers */
            hp->rcp = hp->rcvbuf->data;
            hp->rcvbuf->cnt = 0;
            hp->rstate = RXERROR;   /* set error flag */
            hp->rovers++;
        } else if(hp->rcvbuf->cnt >= hp->bufsiz - sizeof(struct phdr)){
            /* Too large -- toss buffer */
            /* reset buffer pointers */
            hp->rcp = hp->rcvbuf->data;
            hp->rcvbuf->cnt = 0;
            hp->rstate = TOOBIG;    /* when set, chars are not stored */
        }
        /* ok, we can store the received character now */
        if(hp->rstate == ACTIVE){       /* If no errors... */
            *hp->rcp++ = rdscc(hp->cardbase,cmd,R8); /* char to rcv buff */
            hp->rcvbuf->cnt++;                 /* bump count */
        } else {
            /* got to empty FIFO */
        /*      (void) rdscc(hp->cardbase,cmd,R8);*/
            wrtscc(hp->cardbase,cmd,R0,ERR_RES);    /* reset err latch */
            hp->rstate = ACTIVE;
        }
    }
  
    if(rse & END_FR){
        /* END OF FRAME -- Make sure Rx was active */
        if(hp->rcvbuf->cnt > 0){
            if((rse & CRC_ERR)||(hp->rstate > ACTIVE)||(hp->rcvbuf->cnt < 10)){
                if((hp->rcvbuf->cnt >= 10) && (rse & CRC_ERR))
                    hp->crcerr++; /* Ignore noise */
  
                hp->rcp = hp->rcvbuf->data;
                hp->rcvbuf->cnt = 0;
            } else {
                /* Here we have a valid frame */
                hp->rcvbuf->cnt -= 2;  /* Toss 2 crc bytes */
                /* "Can't fail" */
                bp = pushdown(hp->rcvbuf,sizeof(phdr));
                phdr.type = CL_AX25;
                phdr.iface = hp->iface;
                memcpy(bp->data,(char *)&phdr,sizeof(phdr));
                enqueue(&Hopper,bp);       /* queue it in */
                hp->rxframes++;
  
                /* packet queued - get buffer for next frame */
                hp->rcvbuf = alloc_mbuf(hp->bufsiz+sizeof(struct phdr));
                if(hp->rcvbuf == NULLBUF){
                    /* No memory - abort rx */
                    wrtscc(hp->cardbase,cmd,R3,Rx8);
                    restore(i_state);
                    return;
                }
                hp->rcvbuf->data += sizeof(struct phdr);
                hp->rcp = hp->rcvbuf->data;
                hp->rcvbuf->cnt = 0;
            } /* end good frame queued */
        }  /* end check for active receive upon EOF */
        hp->rstate = ACTIVE;    /* and clear error status */
    } /* end EOF check */
  
    restore(i_state);
}
  
static void
b_txint(hp)
register struct pichan *hp;
{
    register int16 cmd;
    int i_state;
    int c;
  
    i_state = dirps();
    cmd = CTL+hp->base;
  
    if(hp->tstate != DEFER && hp->tstate)
        hp->txints++;
  
    switch(hp->tstate){
        case CRCOUT:
            hp->tstate = FLAGOUT;
            tdelay(hp,hp->squeldelay);
            restore(i_state);
            return;
        case IDLE:
        /* Transmitter idle. Find a frame for transmission */
            if((hp->sndbuf = dequeue(&hp->sndq)) == NULLBUF){
            /* Nothing to send - return to receive mode
             * Tx OFF now - flag should have gone
             */
                rts(hp,OFF);
                restore(i_state);
                return;
            }
        /* If a buffer to send, we drop thru here */
        case DEFER:         /* we may have deferred prev xmit attempt */
        /* Check DCD - debounce it */
        /* See Intel Microcommunications Handbook, p2-308 */
            wrtscc(hp->cardbase,cmd,R0,RES_EXT_INT);
            wrtscc(hp->cardbase,cmd,R0,RES_EXT_INT);
            if((rdscc(hp->cardbase,cmd,R0) & DCD) != 0){
                hp->tstate = DEFER;
                tdelay(hp,100);
            /* defer until DCD transition or timeout */
                wrtscc(hp->cardbase,cmd,R15,CTSIE|DCDIE);
                restore(i_state);
                return;
            }
        /* p - persist calculation */
            if(inportb(hp->cardbase+TMR0) > hp->persist){
                inportb(hp->cardbase+TMR0); /* Discard MSB */
                hp->tstate = DEFER;
                tdelay(hp,hp->slotime);
                restore(i_state);
                return;
            }
            inportb(hp->cardbase+TMR0); /* Discard MSB */
  
            rts(hp,ON);   /* Transmitter on */
            hp->tstate = ST_TXDELAY;
            tdelay(hp,hp->txdelay);
            restore(i_state);
            return;
  
        case ACTIVE:
        /* Here we are actively sending a frame */
            if((c = PULLCHAR(&hp->sndbuf)) != -1){
            /* next char is gone */
                wrtscc(hp->cardbase,cmd,R8,c);
            /* stuffing a char satisfies Interrupt condition */
            } else {
            /* No more to send */
                free_p(hp->sndbuf);
                if((rdscc(hp->cardbase,cmd,R0) & 0x40)){
                /* Did we underrun? */
                /* unexpected underrun */
                    hp->tunders++;
                    wrtscc(hp->cardbase,cmd,R0,SEND_ABORT);
                    hp->tstate = FLAGOUT;
                    tdelay(hp,hp->squeldelay);
                    restore(i_state);
                    return;
                }
                hp->tstate = UNDERRUN; /* Now we expect to underrun */
            /* Send flags on underrun */
                if(hp->speed){ /* If externally clocked */
                    wrtscc(hp->cardbase,cmd,R10,CRCPS|NRZI);
                } else {
                    wrtscc(hp->cardbase,cmd,R10,CRCPS);
                }
                wrtscc(hp->cardbase,cmd,R0,RES_Tx_P); /* reset Tx Int Pend */
            }
            restore(i_state);
            return;     /* back to wait for interrupt */
    } /* end switch */
    restore(i_state);
}
  
/* Pi SIO External/Status interrupts (for the B channel)
 * This can be caused by a receiver abort, or a Tx UNDERRUN/EOM.
 * Receiver automatically goes to Hunt on an abort.
 *
 * If the Tx Underrun interrupt hits, change state and
 * issue a reset command for it, and return.
 */
static void
b_exint(hp)
register struct pichan *hp;
{
    char st;
    int i_state;
    register int16 cmd;
    char c;
  
    cmd = CTL+hp->base;
    i_state = dirps();          /* disable interrupts */
    hp->exints++;
    st = rdscc(hp->cardbase,cmd,R0);     /* Fetch status */
    /* reset external status latch */
    wrtscc(hp->cardbase,cmd,R0,RES_EXT_INT);
  
  
    switch(hp->tstate){
        case ACTIVE:    /* Unexpected underrun */
            free_p(hp->sndbuf);
            wrtscc(hp->cardbase,cmd,R0,SEND_ABORT);
            hp->tstate = FLAGOUT;
            hp->tunders++;
            tdelay(hp,hp->squeldelay);
            restore(i_state);
            return;
        case UNDERRUN:
            hp->tstate = CRCOUT;
            restore(i_state);
            return;
        case FLAGOUT:
        /* Find a frame for transmission */
            if((hp->sndbuf = dequeue(&hp->sndq)) == NULLBUF){
            /* Nothing to send - return to receive mode
             * Tx OFF now - flag should have gone
             */
                rts(hp,OFF);
                hp->tstate = IDLE;
                restore(i_state);
                return;
            }
        /* Get next char to send */
            pullup(&hp->sndbuf,&c,1);       /* one char at a time */
            wrtscc(hp->cardbase,cmd,R0,RES_Tx_CRC);/* reset for next frame */
  
        /* Send abort on underrun */
            if(hp->speed){ /* If externally clocked */
                wrtscc(hp->cardbase,cmd,R10,CRCPS|NRZI|ABUNDER);
            } else {
                wrtscc(hp->cardbase,cmd,R10,CRCPS|ABUNDER);
            }
  
            wrtscc(hp->cardbase,cmd,R8,c);  /* First char out now */
            wrtscc(hp->cardbase,cmd,R0,RES_EOM_L);/* Reset end of message latch */
  
        /* select transmit interrupts to enable */
  
            wrtscc(hp->cardbase,cmd,R15,TxUIE);     /* allow Underrun int only */
            wrtscc(hp->cardbase,cmd,R0,RES_EXT_INT);
            wrtscc(hp->cardbase,cmd,R1,TxINT_ENAB|EXT_INT_ENAB);  /* Tx/Extern ints on */
  
            hp->tstate = ACTIVE;    /* char going out now */
            restore(i_state);
            return;
  
        case DEFER:
        /* Check DCD - debounce it
         * See Intel Microcommunications Handbook, p2-308
         */
            wrtscc(hp->cardbase,cmd,R0,RES_EXT_INT);
            wrtscc(hp->cardbase,cmd,R0,RES_EXT_INT);
            if((rdscc(hp->cardbase,cmd,R0) & DCD) != 0){
                hp->tstate = DEFER;
                tdelay(hp,100);
            /* defer until DCD transition or timeout */
                wrtscc(hp->cardbase,cmd,R15,CTSIE|DCDIE);
                restore(i_state);
                return;
            }
        /* p - persist calculation */
            if(inportb(hp->cardbase+TMR0) > hp->persist){
                inportb(hp->cardbase+TMR0); /* Discard MSB */
                hp->tstate = DEFER;
                tdelay(hp,hp->slotime);
                restore(i_state);
                return;
            }
            inportb(hp->cardbase+TMR0); /* Discard MSB */
  
            rts(hp,ON);   /* Transmitter on */
            hp->tstate = ST_TXDELAY;
            tdelay(hp,hp->txdelay);
            restore(i_state);
            return;
  
        case ST_TXDELAY:
  
        /* Get next char to send */
            pullup(&hp->sndbuf,&c,1);        /* one char at a time */
            wrtscc(hp->cardbase,cmd,R0,RES_Tx_CRC);/* reset for next frame */
  
        /* Send abort on underrun */
            if(hp->speed){ /* If externally clocked */
                wrtscc(hp->cardbase,cmd,R10,CRCPS|NRZI|ABUNDER);
            } else {
                wrtscc(hp->cardbase,cmd,R10,CRCPS|ABUNDER);
            }
  
            wrtscc(hp->cardbase,cmd,R8,c);  /* First char out now */
            wrtscc(hp->cardbase,cmd,R0,RES_EOM_L);/* Reset end of message latch */
  
        /* select transmit interrupts to enable */
  
            wrtscc(hp->cardbase,cmd,R15,TxUIE);     /* allow Underrun int only */
            wrtscc(hp->cardbase,cmd,R0,RES_EXT_INT);
        /* Tx/Extern ints on */
            wrtscc(hp->cardbase,cmd,R1,TxINT_ENAB|EXT_INT_ENAB);
  
            hp->tstate = ACTIVE;    /* char going out now */
            restore(i_state);
            return;
    }
  
    /* Receive Mode only
     * This triggers when hunt mode is entered, & since an ABORT
     * automatically enters hunt mode, we use that to clean up
     * any waiting garbage
     */
    if((hp->rstate == ACTIVE) && (st & BRK_ABRT)){
        (void) rdscc(hp->cardbase,cmd,R8);
        (void) rdscc(hp->cardbase,cmd,R8);
        (void) rdscc(hp->cardbase,cmd,R8);
        hp->rcp = hp->rcvbuf->data;
        hp->rcvbuf->cnt = 0;          /* rewind on DCD transition */
    }
    restore(i_state);
}
  
/* SET Transmit or Receive Mode
 * Set RTS (request-to-send) to modem on Transmit
 */
static void
rts(hp,x)
register struct pichan *hp;
int16 x;
{
    int16 tc;
    long br;
    int16 cmd;
    int dummy;
  
    cmd = CTL+hp->base;
  
    /* Reprogram BRG and turn on transmitter to send flags */
    if(x == ON){    /* Turn Tx ON and Receive OFF */
        /* Exints off first to avoid abort int */
        wrtscc(hp->cardbase,cmd,R15,0);
        wrtscc(hp->cardbase,cmd,R3,Rx8);        /* Rx off */
        hp->rstate = IDLE;
        if(cmd & 2){ /* if channel a */
            /* Set up for TX dma */
            wrtscc(hp->cardbase,cmd,R1,WT_FN_RDYFN|EXT_INT_ENAB);
        } else {
            wrtscc(hp->cardbase,cmd,R1,0);  /* No interrupts */
        }
  
        if(!hp->g3ruh) {
            if(hp->speed){                  /* if internally clocked */
                br = hp->speed;         /* get desired speed */
                tc = (Pi[hp->dev].xtal/br)-2;       /* calc 1X BRG divisor */
                wrtscc(hp->cardbase,cmd,R12,tc&0xFF);     /* lower byte */
                wrtscc(hp->cardbase,cmd,R13,(tc>>8)&0xFF);/* upper byte */
            }
        }
        wrtscc(hp->cardbase,cmd,R5,TxCRC_ENAB|RTS|TxENAB|Tx8|DTR);
        /* Transmitter now on */
    } else {        /* Tx OFF and Rx ON */
        hp->tstate = IDLE;
        wrtscc(hp->cardbase,cmd,R5,Tx8|DTR);    /*  TX off */
  
        if(!hp->g3ruh) {
            if(hp->speed){          /* if internally clocked */
                /* Reprogram BRG for 32x clock for receive DPLL */
                /* BRG off, keep Pclk source */
                wrtscc(hp->cardbase,cmd,R14,BRSRC);
                br = hp->speed;                 /* get desired speed */
                /* calc 32X BRG divisor */
                tc = ((Pi[hp->dev].xtal/32)/br)-2;
                wrtscc(hp->cardbase,cmd,R12,tc&0xFF);   /* lower byte */
                wrtscc(hp->cardbase,cmd,R13,(tc>>8)&0xFF);/* upper byte */
                /* SEARCH mode, BRG source */
                wrtscc(hp->cardbase,cmd,R14,BRSRC|SEARCH);
                /* Enable the BRG */
                wrtscc(hp->cardbase,cmd,R14,BRSRC|BRENABL);
            }
        }
        /* Flush rx fifo */
        wrtscc(hp->cardbase,cmd,R3,Rx8); /* Make sure rx is off */
        wrtscc(hp->cardbase,cmd,R0,ERR_RES);    /* reset err latch */
        dummy = rdscc(hp->cardbase,cmd,R1); /* get status byte from R1 */
        (void) rdscc(hp->cardbase,cmd,R8);
        (void) rdscc(hp->cardbase,cmd,R8);
        (void) rdscc(hp->cardbase,cmd,R8);
  
        /* Now, turn on the receiver and hunt for a flag */
/*      wrtscc(hp->cardbase,cmd,R3,RxENABLE|RxCRC_ENAB|Rx8); */
        wrtscc(hp->cardbase,cmd,R3,RxENABLE|Rx8);
        hp->rstate = ACTIVE;            /* Normal state */
  
        if(cmd & 2){/* if channel a */
            setup_rx_dma(hp);
        } else {
            /* reset buffer pointers */
            hp->rcp = hp->rcvbuf->data;
            hp->rcvbuf->cnt = 0;
            wrtscc(hp->cardbase,cmd,R1,(INT_ALL_Rx|EXT_INT_ENAB));
        }
        wrtscc(hp->cardbase,cmd,R15,BRKIE);             /* allow ABORT int */
    }
}
  
void
setup_rx_dma(hp)
register struct pichan *hp;
{
    unsigned buf_offset, buf_segment;
    int cmd;
    long longseg, dma_abs, dma_page;
    int i_state;
  
    i_state = dirps();          /* disable interrupts */
  
    cmd = hp->base+CTL;
  
    if(!hp->rcvbuf){
        /* No rx buffer available */
        restore(i_state);
        return;
    }
  
    /* Calculate high order 4 bits of the buffer area and store
     *      them in the DMA page register
     */
    buf_offset = FP_OFF(hp->rcvbuf->data);
    buf_segment= FP_SEG(hp->rcvbuf->data);
    longseg = (long) buf_segment;
    dma_abs = (longseg << 4) + (long) buf_offset;
    dma_page = dma_abs >> 16;
  
    if(((dma_abs + hp->bufsiz -1) >> 16) != dma_page)
        tputs("PI: ERROR - RX DMA page boundary violation\n");
  
    /* Get ready for RX DMA */
    wrtscc(hp->cardbase,cmd,R1,WT_FN_RDYFN|WT_RDY_RT|INT_ERR_Rx|EXT_INT_ENAB);
    outportb(DMA_MASK, DMA_DISABLE|hp->dmachan); /* Disable DMA chan */
    /* Set DMA mode register to single transfers, incrementing address,
     *      auto init, writes
     */
    outportb(DMA_MODE,DMA_RX_MODE|hp->dmachan);
  
    outportb(hp->page_addr,dma_page);/* Store in  64K DMA page */
    outportb(DMA_RESETFF,0);         /* reset byte pointer flipflop */
    /* Output buffer start (dest) address */
    outportb(hp->dma_dest,dma_abs);
    outportb(hp->dma_dest,dma_abs >> 8);
    /* output DMA maximum byte count */
    outportb(hp->dma_wcr,hp->bufsiz - 1 - sizeof(struct phdr));
    outportb(hp->dma_wcr, (hp->bufsiz - 1 - sizeof(struct phdr)) >> 8);
    /* Unmask channel 1 (start DMA) */
    outportb(DMA_MASK, DMA_ENABLE|hp->dmachan); /* Enable DMA chan */
  
    /* If a packet is already coming in, this line is supposed
     *      to mess up the crc to avoid receiving a partial packet
     */
    wrtscc(hp->cardbase,cmd,R0,RES_Rx_CRC);
  
    /* Enable RX dma */
    wrtscc(hp->cardbase,cmd,R1,WT_RDY_ENAB|WT_FN_RDYFN|WT_RDY_RT|INT_ERR_Rx|EXT_INT_ENAB);
  
    restore(i_state);
}
  
void
setup_tx_dma(hp,buffer,length)
struct pichan *hp;
char *buffer;
int length;
{
    unsigned buf_offset, buf_segment;
    long longseg, dma_abs, dma_page;
    int i_state;
  
    i_state = dirps();          /* disable interrupts */
  
    /* Calculate high order 4 bits of the buffer area and store
     *      them in the DMA page register
     */
  
    buf_offset = FP_OFF(buffer);
    buf_segment= FP_SEG(buffer);
    longseg = (long) buf_segment;
    dma_abs = (longseg << 4) + (long) buf_offset;
    dma_page = dma_abs >> 16;
  
    outportb(DMA_MASK, DMA_DISABLE|hp->dmachan); /* Disable DMA chan */
    if(((dma_abs + length) >> 16) != dma_page)
        tputs("PI: ERROR - TX DMA page boundary violation\n");
    --length;        /* Adjust length for DMA chip */
    /* Set DMA mode register to single transfers, incrementing address,
     *      no auto init, reads
     */
    outportb(DMA_MODE,DMA_TX_MODE|hp->dmachan);
  
    outportb(hp->page_addr,dma_page); /* Store in 64K DMA page */
    outportb(DMA_RESETFF,0);        /* reset byte pointer flipflop */
    outportb(hp->dma_dest,dma_abs); /* Output buffer start (source) address */
    outportb(hp->dma_dest,dma_abs >> 8);
    /* output byte count */
    outportb(hp->dma_wcr,length);
    outportb(hp->dma_wcr, (length) >> 8);
  
    restore(i_state);
}
  
/* Initialize pi controller parameters */
static int
scc_init(hp)
register struct pichan *hp;
{
    int16 tc;
    long br;
    int i_state;
    register int16 cmd;
  
    /* Initialize 8530 channel for SDLC operation */
  
    cmd = CTL+hp->base;
    i_state = dirps();
  
    switch(cmd & 2){
        case 2:
            wrtscc(hp->cardbase,cmd,R9,CHRA);       /* Reset channel A */
            wrtscc(hp->cardbase,cmd,R2,0xff); /* Initialize interrupt vector */
            break;
        case 0:
            wrtscc(hp->cardbase,cmd,R9,CHRB);       /* Reset channel B */
            break;
    }
  
    /* Deselect all Rx and Tx interrupts */
    wrtscc(hp->cardbase,cmd,R1,0);
  
    /* Turn off external interrupts (like CTS/CD) */
    wrtscc(hp->cardbase,cmd,R15,0);
  
    /* X1 clock, SDLC mode */
    wrtscc(hp->cardbase,cmd,R4,SDLC|X1CLK);
  
    /* Now some misc Tx/Rx parameters */
    if(hp->speed) { /* Use internal clocking */
        wrtscc(hp->cardbase,cmd,R10,CRCPS|NRZI);
        if(!hp->g3ruh)
            /* Tx Clk from BRG. Rcv Clk from DPLL, TRxC pin outputs DPLL */
            wrtscc(hp->cardbase,cmd,R11,TCBR|RCDPLL|TRxCDP|TRxCOI);
        else
            /* Tx Clk from DPLL, Rcv Clk from DPLL, TRxC Outputs BRG */
            wrtscc(hp->cardbase,cmd,R11,TCDPLL|RCDPLL|TRxCBR|TRxCOI);
    } else { /* Use external clocking */
        wrtscc(hp->cardbase,cmd,R10,CRCPS);
        /* Tx Clk from Trxcl. Rcv Clk from Rtxcl, TRxC pin is input */
        wrtscc(hp->cardbase,cmd,R11,TCTRxCP);
    }
  
    /* Null out SDLC start address */
    wrtscc(hp->cardbase,cmd,R6,0);
  
    /* SDLC flag */
    wrtscc(hp->cardbase,cmd,R7,FLAG);
  
    /* Set up the Transmitter but don't enable it
     *  DTR, 8 bit TX chars only - TX NOT ENABLED
     */
    wrtscc(hp->cardbase,cmd,R5,Tx8|DTR);
  
    /* Receiver - intial setup only - more later */
    wrtscc(hp->cardbase,cmd,R3,Rx8);               /* 8 bits/char */
  
    /* Setting up BRG now - turn it off first */
    wrtscc(hp->cardbase,cmd,R14,BRSRC);          /* BRG off, but keep Pclk source */
  
    /* set the 32x time constant for the BRG in Receive mode */
  
    if(hp->speed){
        br = hp->speed;         /* get desired speed */
        tc = ((Pi[hp->dev].xtal/32)/br)-2;  /* calc 32X BRG divisor */
    } else {
        tc = 14;
    }
  
    wrtscc(hp->cardbase,cmd,R12,tc&0xFF);      /* lower byte */
    wrtscc(hp->cardbase,cmd,R13,(tc>>8)&0xFF); /* upper byte */
  
    /* Following subroutine sets up and ENABLES the receiver */
    rts(hp,OFF);               /* TX OFF and RX ON */
  
    if(hp->speed){
        /* DPLL frm BRG, BRG src PCLK */
        wrtscc(hp->cardbase,cmd,R14,BRSRC|SSBR);
    } else {
        /* DPLL frm rtxc,BRG src PCLK */
        wrtscc(hp->cardbase,cmd,R14,BRSRC|SSRTxC);
    }
    wrtscc(hp->cardbase,cmd,R14,BRSRC|SEARCH); /* SEARCH mode, keep BRG source */
    wrtscc(hp->cardbase,cmd,R14,BRSRC|BRENABL);/* Enable the BRG */
  
    if(!(cmd & 2)) /* if channel b */
        wrtscc(hp->cardbase,cmd,R1,(INT_ALL_Rx|EXT_INT_ENAB));
  
    wrtscc(hp->cardbase,cmd,R15,BRKIE);         /* ABORT int */
  
    /* Now, turn on the receiver and hunt for a flag */
    wrtscc(hp->cardbase,cmd,R3,RxENABLE|RxCRC_ENAB|Rx8);
  
    restore(i_state);
    return 0;
}
  
/* Process to recover from ibuffails.
 * This could be done in the function network() in config.c,
 * to save a context switch.  I put it here so the driver would
 * be more self contained.
*/
void
buf_recover(unused,b,a)
int unused;
void *b;        /* Unused */
void *a;        /* Unused */
{
    struct pichan *hp0, *hp1;
    int i_state;
    int i;
  
    for(;;){
        pwait(NULL);
  
        for(i=0; i<pinbr; i++){ /* for each card */
            hp0 = &Pichan[i];
            hp1 = &Pichan[i + 1];
            if(!hp0->rcvbuf){ /* No rx buffer allocated */
                i_state = dirps();
                hp0->rcvbuf = alloc_mbuf(hp0->bufsiz + sizeof(struct phdr));
                if(hp0->rcvbuf != NULLBUF)
                    hp0->rcvbuf->data += sizeof(struct phdr);
                restore(i_state);
                setup_rx_dma(hp0);
            }
            i_state = dirps();
            if(!hp1->rcvbuf && (hp1->rstate == ACTIVE)){
                /* No rx buf allocated */
                if((hp1->rcvbuf = alloc_mbuf(hp1->bufsiz+sizeof(struct phdr))) != NULL){
                    hp1->rcvbuf->data += sizeof(struct phdr);
                    hp1->rcp = hp1->rcvbuf->data;
                    hp1->rcvbuf->cnt = 0;
                    wrtscc(hp1->cardbase,CTL+hp1->base,R3,RxENABLE|RxCRC_ENAB|Rx8);
                }
            }
            restore(i_state);
        }
    }
}
  
static void
set_xtal(int dev)
{
    int time = 1000; /* Number of milliseconds for test */
    long start_time, end_time;
  
    int n,port;
    int a = 1;
    int b = 1;
  
    port = Pi[dev].addr+TMR1;
  
    /* Setup timer control word */
    outportb(Pi[dev].addr+TMRCMD, SC1|LSB_MSB|MODE0);
    for(n=0; n<2;n++); /* Access time restriction */
    /* times 2 to make millisecs */
    outportb(port, (time << 1) & 0xFF);
    for(n=0; n<2;n++); /* Access time restriction */
    outportb(port, (time >> 7) & 0xFF);
  
    /* wait until counter reg is loaded */
    do {
        for(n=0; n<2;n++); /* Access time restriction */
        /* Latch count for reading */
        outportb(Pi[dev].addr+TMRCMD, SC1);
        for(n=0; n<2;n++); /* Access time restriction */
        a = inportb(port);
        for(n=0; n<2;n++); /* Access time restriction */
        b = inportb(port);
    } while (b == 0);
    start_time = biostime(0,0);
    while(b != 0) {
        for(n=0; n<2;n++); /* Access time restriction */
        /* Latch count for reading */
        outportb(Pi[dev].addr+TMRCMD, SC1);
        for(n=0; n<2;n++); /* Access time restriction */
        a = inportb(port);
        for(n=0; n<2;n++); /* Access time restriction */
        b = inportb(port);
    }
    end_time = biostime(0,0);
    if((end_time - start_time) > 12) {
        Pi[dev].xtal = (long)3686400L/2;
    } else {
        /* Set up counter chip */
        /* 500 uS square wave */
        outportb(Pi[dev].addr+TMRCMD, SC0|LSB_MSB|MODE3);
        for(n=0; n<5;n++) /* satisfy access time restriction */
            ;
        outportb(Pi[dev].addr+TMR0, 1844 & 0xFF);
        for(n=0; n<5;n++) /* satisfy access time restriction */
            ;
        outportb(Pi[dev].addr+TMR0, 1844 >> 8);
        for(n=0; n<5;n++) /* satisfy access time restriction */
            ;
  
        Pi[dev].xtal = (long)7372800L/2;
    }
}
  
/* Attach a PI interface to the system
 * argv[0]: hardware type, must be "pi"
 * argv[1]: I/O address, e.g., "0x300"
 * argv[2]: vector, e.g., "2"
 * argv[3]: dma channel (1..3)
 * argv[4]: mode, must be:
 *          "ax25" (AX.25 UI frame format)
 * argv[5]: interface label, e.g., "pi0" or "pi0a$pi0b"
 * argv[6]: receiver packet buffer size in bytes
 * argv[7]: maximum transmission unit, bytes
 * argv[8]: channel A interface speed, e.g, "1200", 0 = ext. clock
 *          g suffix indicates G3RUH modem clocking, e.g, 9600g
 * argv[9]: channel B interface speed
 * argv[10]: First IP address, optional (defaults to Ip_addr);
 * argv[11]: Second IP address, optional (defaults to Ip_addr);
 */
int
pi_attach(argc,argv)
int argc;
char *argv[];
{
    char *separator, *ptr;
    struct mbuf *bp;
    extern void refiq();
    register struct iface *if_pca,*if_pcb;
    struct pichan *hp;
    int dev;
    int i_state;
    int n;
  
    refiq(); /* replenish interrupt buffer pool (in mbuf.c) */
    set_acc_delay();
    /* Quick check to make sure args are good and mycall is set */
    if(strcmp(argv[4],"ax25") != 0){
        tprintf("PI: Mode %s unknown for interface %s\n",
        argv[4],argv[5]);
        return -1;
    }
    if(if_lookup(argv[5]) != NULLIF){
        tprintf("PI: Interface %s already exists\n",argv[5]);
        return -1;
    }
    if(Mycall[0] == '\0'){
        tputs("PI: Set mycall first\n");
        return -1;
    }
    /* Note: each card must have a unique address, IRQ and DMA */
  
    if(pinbr >= PIMAX){
        tprintf("PI: Maximum of %d PI cards supported\n",PIMAX);
        return -1;
    }
    dev = pinbr++;
  
    /* Initialize hardware-level control structure */
    Pi[dev].addr = htoi(argv[1]);
    Pi[dev].vec = htoi(argv[2]);
  
    /* Set up counter chip */
    /* 500 uS square wave */
    outportb(Pi[dev].addr+TMRCMD, SC0|LSB_MSB|MODE3);
    for(n=0; n<5;n++) /* satisfy access time restriction */
        ;
    outportb(Pi[dev].addr+TMR0, 922 & 0xFF);
    for(n=0; n<5;n++) /* satisfy access time restriction */
        ;
    outportb(Pi[dev].addr+TMR0, 922 >> 8);
    for(n=0; n<5;n++) /* satisfy access time restriction */
        ;
  
    /* Save original interrupt vector */
    Pi[dev].oldvec = getirq(Pi[dev].vec);
  
    /* Set new interrupt vector */
    if(setirq(Pi[dev].vec,pihandle[dev]) == -1){
        tprintf("PI: IRQ %u out of range\n",Pi[dev].vec);
        pinbr--;
        return -1;
    }
  
    if((atoi(argv[3]) < 1) || (atoi(argv[3]) > 3)){
        tprintf("PI: DMA %d out of range\n",atoi(argv[3]));
        pinbr--;
        return -1;
    }
  
    /* Create interface structures and fill in details */
    if_pca = (struct iface *)callocw(1,sizeof(struct iface));
    if_pcb = (struct iface *)callocw(1,sizeof(struct iface));
  
    if_pca->addr = if_pcb->addr = Ip_addr;
    if(argc > 10)
        if_pca->addr = resolve(argv[10]);
    if(argc > 11)
        if_pcb->addr = resolve(argv[11]);
  
    if(if_pca->addr == 0 || if_pcb->addr == 0){
        tputs("PI: No IP address");
        free((char *)if_pca);
        free((char *)if_pcb);
        return -1;
    }
    if((separator = strchr(argv[5],'$')) != NULL) {  /* individual port names */
        *separator++ = 0; /* null terminate 1st name */
        if_pca->name = malloc((unsigned)strlen(argv[5])+1);
        strcpy(if_pca->name,argv[5]);
        if_pcb->name = malloc((unsigned)strlen(separator)+1);
        strcpy(if_pcb->name,separator);
    }
    else {
        /* Append "a" to interface associated with A channel */
        if_pca->name = malloc((unsigned)strlen(argv[5])+2);
        strcpy(if_pca->name,argv[5]);
        strcat(if_pca->name,"a");
        /* Append "b" to interface associated with B channel */
        if_pcb->name = malloc((unsigned)strlen(argv[5])+2);
        strcpy(if_pcb->name,argv[5]);
        strcat(if_pcb->name,"b");
    }
  
    if_pcb->mtu = if_pca->mtu = atoi(argv[7]);
    if_pcb->type = if_pca->type = CL_AX25;
    if_pcb->ioctl = if_pca->ioctl = pi_ctl;
    if_pca->dev = 2*dev;                    /* pi0a */
    if_pcb->dev = 2*dev + 1;                /* pi0b */
    if_pcb->stop = if_pca->stop = pi_stop;
    if_pcb->output = if_pca->output = ax_output;
    if_pcb->raw = if_pca->raw = pi_raw;
  
    if(strcmp(argv[4],"ax25") == 0){
        /* Must be true, was checked at top */
        if_pcb->send = if_pca->send = ax_send;
        if(if_pcb->hwaddr == NULLCHAR)
            if_pcb->hwaddr = mallocw(AXALEN);
        memcpy(if_pcb->hwaddr,Mycall,AXALEN);
        if(if_pca->hwaddr == NULLCHAR)
            if_pca->hwaddr = mallocw(AXALEN);
        memcpy(if_pca->hwaddr,Mycall,AXALEN);
    }
    setencap(if_pca,"AX25");
    setencap(if_pcb,"AX25");
    /* Link em in to the interface chain */
    if_pca->next = if_pcb;
    if_pcb->next = Ifaces;
    Ifaces = if_pca;
  
    set_xtal(dev); /* Detect the oscillator frequency */
  
    /* set params in pichan table for CHANNEL B */
    hp = &Pichan[2*dev+1];                  /* pi1 is offset 1 */
    hp->dmachan = 0; /* Channel B does not have dma */
    hp->cardbase = Pi[dev].addr;
    hp->iface = if_pcb;
    hp->stata = Pi[dev].addr + CHANA + CTL; /* permanent status */
    hp->statb = Pi[dev].addr + CHANB + CTL; /* addrs for CHANA/B*/
    if((ptr = strchr(argv[9],'g')) != NULL) {  /* use G3RUH clocking */
        *ptr = 0; /* get rid of the g so we can read the speed */
        hp->g3ruh = 1;
    } else hp->g3ruh = 0;
    hp->speed = (int16)atoi(argv[9]);
    hp->base = Pi[dev].addr + CHANB;
    hp->bufsiz = atoi(argv[6]);
    hp->tstate = IDLE;
    /* default channel access Params */
    hp->txdelay = 30;               /* 300 Ms */
    hp->persist = 128;              /* 50% persistence */
    hp->slotime = 10;               /* 100 Ms */
  
    /* Calculate appropriate tail time for baud rate */
    /* Allow time for CRC and closing flag */
    if(hp->speed) {
        hp->squeldelay = 36000L/(unsigned)hp->speed/10;
        if(hp->squeldelay < 1)
            hp->squeldelay = 1;
    } else hp->squeldelay = 3;/* external clock, default to 30 mS */
  
    wrtscc(hp->cardbase,CTL+hp->stata,R9,FHWRES);   /* Hardware reset */
                        /* one time only */
    /* Disable interrupts with Master interrupt ctrl reg */
    wrtscc(hp->cardbase,CTL+hp->stata,R9,0);
  
    scc_init(hp);
  
    /* Pre-allocate a receive buffer */
    i_state = dirps();
    hp->rcvbuf = alloc_mbuf(hp->bufsiz+sizeof(struct phdr));
    restore(i_state);
    if(hp->rcvbuf == NULLBUF){
        /* No memory, abort receiver */
        tputs("PI: No memory available for receive buffers, or <buffers> parameter too large.\n");
        /* Restore original interrupt vector */
        setirq(Pi[dev].vec,Pi[dev].oldvec);
        pinbr--;
        return -1;
    }
    hp->rcvbuf->data += sizeof(struct phdr);
    hp->rcp = hp->rcvbuf->data;
    hp->rcvbuf->cnt = 0;
    hp->sndq = NULLBUF;
  
    /* set params in pichan table for CHANNEL A */
    hp = &Pichan[2*dev];    /* pi0a is offset 0 */
    hp->dmachan = (unsigned char)atoi(argv[3]);
    /* Figure out where the dma page register is. */
    if(hp->dmachan < 8 && hp->dmachan >= 0){
        hp->page_addr = Page_regs[hp->dmachan];
    } else {
        printf("PI: DMA channel %d out of range\n",hp->dmachan);
        free_p(hp->rcvbuf);
        /* Restore original interrupt vector */
        setirq(Pi[dev].vec,Pi[dev].oldvec);
        pinbr--;
        return -1;
    }
  
    hp->dma_dest = hp->dmachan * 2;
    hp->dma_wcr = hp->dma_dest + 1;
  
    hp->cardbase = Pi[dev].addr;
    hp->iface = if_pca;
    if((ptr = strchr(argv[8],'g')) != NULL) {  /* use G3RUH clocking */
        *ptr = 0; /* get rid of the g so we can read the speed */
        hp->g3ruh = 1;
    } else hp->g3ruh = 0;
    hp->speed = (int16)atoi(argv[8]);
    hp->base = Pi[dev].addr + CHANA;
    hp->bufsiz = atoi(argv[6]);
    hp->tstate = IDLE;
    /* default channel access Params */
    hp->txdelay = 15;               /* 15 mS */
    hp->persist = 128;              /* 50% persistence */
    hp->slotime = 15;               /* 15 mS */
  
    /* Calculate appropriate tail time for baud rate */
    /* Allow time for CRC and closing flag */
    if(hp->speed) {
        hp->squeldelay = 36000L/(unsigned)hp->speed/10;
        if(hp->squeldelay < 1)
            hp->squeldelay = 1;
    } else hp->squeldelay = 1; /* external clock, default to 1 mS */
  
    newproc("buf_recover",256,buf_recover,0,hp,NULL,0);
  
    /* Pre-allocate a receive buffer */
    /* buffer is allocated with ints off */
    i_state = dirps();
    hp->rcvbuf = alloc_mbuf(hp->bufsiz+sizeof(struct phdr));
    restore(i_state);
    if(hp->rcvbuf == NULLBUF){
        /* No memory, abort receiver */
        tputs("PI: No memory available for receive buffers\n");
        /* Restore original interrupt vector */
        setirq(Pi[dev].vec,Pi[dev].oldvec);
        pinbr--;
        return -1;
    }
    hp->rcvbuf->data += sizeof(struct phdr);
    hp->rcvbuf->cnt = 0;
    hp->sndq = NULLBUF;
  
    /* Get a buffer for tx which does not cross a dma boundary */
    /* buffer is allocated with ints off */
    i_state = dirps();
    bp = alloc_mbuf(hp->bufsiz);
    if(bp != NULLBUF)
        hp->txdmabuf = bp->data;
    else
        hp->txdmabuf = NULLCHAR;
    restore(i_state);
    if(!hp->txdmabuf)
        tputs("PI: No memory available for transmit buffer");
  
    scc_init(hp);
  
    /* master interrupt enable */
    wrtscc(hp->cardbase,CTL+hp->base,R9,MIE|NV);
  
    /* Enable interrupt */
    maskon(Pi[dev].vec);
  
    return 0;
}
  
/* Shut down interface */
int
pi_stop(iface)
struct iface *iface;
{
    int16 dev;
    struct pichan *hp;
  
    dev = iface->dev;
    if(dev & 1) /* Because there are 2 devices per card */
        return 0;
    dev >>= 1;              /* Convert back into pi number */
    hp = &Pichan[2*dev];    /* pi0a is offset 0 */
  
    outportb(DMA_MASK, DMA_DISABLE|hp->dmachan); /* Disable DMA channel */
  
    /* Turn off interrupts */
    maskoff(Pi[dev].vec);
  
    /* Restore original interrupt vector */
    setirq(Pi[dev].vec,Pi[dev].oldvec);
  
    /* Force hardware reset */
    wrtscc(((struct pichan *)(&Pichan[2*dev]))->cardbase,
    CTL+Pi[dev].addr + CHANA,R9,FHWRES);
  
    return 0;
}
  
/* Send raw packet on pi card */
int
pi_raw(iface,bp)
struct iface *iface;
struct mbuf *bp;
{
    int i_state;
    char kickflag;
    struct pichan *hp;
  
    dump(iface,IF_TRACE_OUT,CL_AX25,bp);
    iface->rawsndcnt++;
    iface->lastsent = secclock();
  
    hp = &Pichan[iface->dev];
    kickflag = (hp->sndq == NULLBUF) & (hp->sndbuf == NULLBUF);
    enqueue(&hp->sndq,bp);
    hp->enqueued++;
    if(kickflag){
        /* simulate interrupt to xmit */
        switch(hp->base & 2){
            case 2:
                a_txint(hp);            /* process interrupt */
                break;
            case 0:
                i_state = dirps();
                if(hp->tstate == IDLE)
                    b_txint(hp);
                restore(i_state);
                break;
        }
    }
    return 0;
}
  
/* Subroutine to set kiss params in channel tables */
int32
pi_ctl(iface,cmd,set,val)
struct iface *iface;
int cmd;
int set;
int32 val;
{
    struct pichan *hp;
    int32 t,ca;
  
    hp = &Pichan[iface->dev]; /* point to channel table */
    switch(cmd){
        case PARAM_TXDELAY:
            if(set)
                hp->txdelay = val;
            return hp->txdelay;
        case PARAM_PERSIST:
            if(set)
                hp->persist = val;
            return uchar(hp->persist);
        case PARAM_SLOTTIME:
            if(set)
                hp->slotime = val;
            return hp->slotime;
        case PARAM_TXTAIL:
            if(set)
                hp->squeldelay = val;
            return hp->squeldelay;
    }
    return -1;
}
  
#endif
