/* Interface driver for the DRSI PCPA or the Eagle 8530 boards for the IBM PC
 * connected to a WA4DSY 56kbps modem. Uses polling-loop transfers with
 * interrupts disabled for maximum speed.
 *
 * This driver is a bit of a kludge. A DMA-driven card and driver (e.g.,
 * the PI) is much better, but this is better than nothing if all you have
 * is a "dumb" 8530 card.
 *
 * Copyright 1991 Phil Karn, KA9Q
 */
#include <dos.h>
#include "global.h"
#ifdef HS
#include "mbuf.h"
#include "iface.h"
#include "pktdrvr.h"
#include "netuser.h"
#include "hs.h"
#include "z8530.h"
#include "ax25.h"
#include "trace.h"
#include "pc.h"
#include "proc.h"
#include "devparam.h"
  
static void flushrx __ARGS((int16 data));
static void hdlcparam __ARGS((struct hdlc *hp));
static void hexint __ARGS((struct hdlc *hp));
static void hrxint __ARGS((struct hdlc *hp));
static int hs_stop __ARGS((struct iface *iface));
static int hs_raw __ARGS((struct iface *iface,struct mbuf *bp));
static void hs_tx __ARGS((int unused,void *hp1,void *a));
static int32 hs_ctl __ARGS((struct iface *,int cmd,int set,int32 val));
static void hstxoff __ARGS((struct hdlc *hp));
static void hstxon __ARGS((struct hdlc *hp));
static void htxint __ARGS((struct hdlc *hp));
static void init_delay __ARGS((void));
static void msdelay __ARGS((void));
  
static struct hs Hs[NHS];
static INTERRUPT (*Hshandle[])() = { hs0vec };
static struct hdlc Hdlc[2*NHS];
static int16 Nhs;
  
/* Master interrupt handler for the PC-100 card. All interrupts come
 * here first, then are switched out to the appropriate routine.
 */
void
hsint(dev)
int dev;
{
    register char iv;
    int16 hsbase;
    register struct hdlc *hp;
  
    Hs[dev].ints++;
    hsbase = Hs[dev].addr;
  
#ifdef  foo
    outportb(hsbase+4,0x8+0x10);    /* HIT EAGLE INTACK */
    (void)inportb(hsbase+CHANA+CTL,R0);
    outportb(hsbase+4,0x8);     /***/
#endif
  
    /* Read interrupt status from channel A */
    while((iv = read_scc(hsbase+CHANA+CTL,R3)) != 0){
        if(iv & CHARxIP){
            /* Channel A Rcv Interrupt Pending */
            hp = &Hdlc[2*dev];
            hrxint(hp);
        } else if(iv & CHATxIP){
            /* Channel A Transmit Int Pending */
            hp = &Hdlc[2*dev];
            htxint(hp);
        } else if(iv & CHAEXT){
            /* Channel A External Status Int */
            hp = &Hdlc[2*dev];
            hexint(hp);
        } else if(iv & CHBRxIP){
            /* Channel B Rcv Interrupt Pending */
            hp = &Hdlc[(2*dev)+1];
            hrxint(hp);
        } else if(iv & CHBTxIP){
            /* Channel B Transmit Int Pending */
            hp = &Hdlc[(2*dev)+1];
            htxint(hp);
        } else if(iv & CHBEXT){
            /* Channel B External Status Int */
            hp = &Hdlc[(2*dev)+1];
            hexint(hp);
        }
        /* Reset interrupt pending state */
        write_scc(hp->ctl,R0,RES_H_IUS);
        outportb(hsbase+CHANA+CTL,0);   /* Restore pointer to 0 */
        outportb(hsbase+CHANB+CTL,0);   /* Restore pointer to 0 */
    }
    outportb(hsbase+CHANA+CTL,0);   /* Restore pointer to 0 */
    outportb(hsbase+CHANB+CTL,0);   /* Restore pointer to 0 */
}
/* HDLC SIO External/Status interrupts
 * The only one that can happen in this driver is a DCD change
 */
static void
hexint(hp)
register struct hdlc *hp;
{
    struct mbuf *rcvbuf;
    struct phdr phdr;
    char *cp;
    int cnt,data;
    register int ctl;
  
    ctl = hp->ctl;
    data = hp->data;
    hp->exints++;
  
    /* Allocate a receive buffer */
    if((rcvbuf = alloc_mbuf(hp->bufsiz+sizeof(phdr))) == NULLBUF){
        /* Alloc failed; refuse to proceed */
        hp->nomem++;
        write_scc(ctl,R3,ENT_HM|RxENABLE|RxCRC_ENAB|Rx8);
        write_scc(ctl,R0,RES_EXT_INT);
        return;
    }
    /* Allow space for phdr descriptor on front */
    cp = rcvbuf->data + sizeof(phdr);
    cnt = 0;
  
    /* Disable DCDIE bit so we can track changes in DCD */
    write_scc(ctl,R15,0);
  
    write_scc(ctl,R3,ENT_HM|RxENABLE|RxCRC_ENAB|Rx8);
    flushrx(data);
    while((cnt = rx8530(ctl,data,cp,hp->bufsiz)) != -1){
        if(cnt > 4){
            /* Good frame */
            hp->good++;
            /* Toss crc */
            rcvbuf->cnt = sizeof(phdr) + cnt - 1;
            phdr.iface = hp->iface;
            phdr.type = CL_AX25;
            memcpy(rcvbuf->data,(char *)&phdr,sizeof(phdr));
            enqueue(&Hopper,rcvbuf);
            /* Replenish buffer */
            rcvbuf = alloc_mbuf(hp->bufsiz + sizeof(phdr));
        }
        /* Start new buffer */
        if(rcvbuf == NULLBUF)
            break;  /* alloc failed */
        cp = rcvbuf->data + sizeof(phdr);
    }
    write_scc(ctl,R0,RES_EXT_INT);
    write_scc(ctl,R15,DCDIE);   /* Re-enable DCD */
    write_scc(ctl,R3,ENT_HM|RxENABLE|RxCRC_ENAB|Rx8);
  
    /* Get rid of fragmentary buffer */
    free_p(rcvbuf);
}
static void
flushrx(data)
register int16 data;
{
    register int i = 5;
    while(i-- != 0)
        (void)inportb(data);
}
/* HDLC receiver interrupt handler.
 * Not used in this driver
 */
static void
hrxint(hp)
register struct hdlc *hp;
{
}
/* HDLC transmit interrupt service routine
 * Not used in this driver
 */
static void
htxint(hp)
register struct hdlc *hp;
{
}
  
/* (re)Initialize HDLC controller parameters */
static void
hdlcparam(hp)
register struct hdlc *hp;
{
    char i_state;
    register int16 ctl;
  
    /* Initialize 8530 channel for SDLC operation */
    ctl = hp->ctl;
    i_state = dirps();
  
#ifdef  foo
    switch(ctl & 2){
        case CHANA:
            write_scc(ctl,R9,CHRA); /* Reset channel A */
            break;
        case CHANB:
            write_scc(ctl,R9,CHRB); /* Reset channel B */
            break;
    }
    pause(1L);  /* Allow plenty of time for resetting */
#endif
  
    /* Deselect interrupts for now */
    write_scc(ctl,R1,0);
    write_scc(ctl,R15,0);
  
    /* X1 clock, SDLC mode, Sync modes enable, parity disable */
    write_scc(ctl,R4,X1CLK | SDLC | SYNC_ENAB);
  
    /* CRC preset 1, NRZ encoding, no active on poll, flag idle,
     * flag on underrun, no loop mode, 8 bit sync
     */
    write_scc(ctl,R10,CRCPS|NRZ);
  
    /* 8530 gets both tx and rx clock from modem.
     * By default, TRxC = transmit clock, RTxC = receive clock
     * (swapped 11 Feb 1990 to use new DRSI wiring) UNLESS
     * the 'r' parameter is specified
     */
    if(!hp->clkrev)
        write_scc(ctl,R11,RCRTxCP | TCTRxCP);
    else
        write_scc(ctl,R11,RCTRxCP | TCRTxCP);
  
    /* Note: baud rate generator not used */
  
    /* Null out SDLC start address */
    write_scc(ctl,R6,0);
  
    /* SDLC flag */
    write_scc(ctl,R7,FLAG);
  
    /* DTR On, 8 bit TX chars, no break, TX enable, SDLC CRC,
     * RTS off, TxCRC enable
     */
    write_scc(ctl,R5,DTR|Tx8|TxENAB|TxCRC_ENAB);
  
    /* 8 bit RX chars, auto enables off, no hunt mode, RxCRC enable,
     * no address search, no inhibit sync chars, disable RX. Rx is
     * started only by an actual DCD interrupt
     */
    write_scc(ctl,R3,RxENABLE|RxCRC_ENAB|Rx8);
  
    /* Dummy interrupt vector
     * (This probably isn't necessary)
     */
    write_scc(ctl,R2,0);
  
    /* Enable only the external interrupts (modem interrupts) since
     * polling is used for all actual tx/rx operations
     */
    write_scc(ctl,R1,EXT_INT_ENAB);
  
    /* Enable only DCD interrupts */
    write_scc(ctl,R15,DCDIE);
  
    /* No reset, status low, master int enable, enable lower chain,
     * no vector
     */
    write_scc(ctl,R9,MIE|NV);
  
    restore(i_state);
}
/* Attach a high speed iterface to the system
 * argv[0]: hardware type, must be "hs"
 * argv[1]: I/O address, e.g., "0x380"
 * argv[2]: vector, e.g., "2"
 * argv[3]: mode, must be "ax25"
 * argv[4]: interface base label, e.g., "drsi0". Driver appends "a" and "b".
 * argv[5]: receiver packet buffer size in bytes
 * argv[6]: maximum transmission unit, bytes
 * argv[7]: keyup delay, milliseconds
 * argv[8]: persistence value, 0-255
 * argv[9]: "r" to reverse sense of clock leads (optional)
 */
int
hs_attach(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct iface *if_hsa,*if_hsb;
    struct hdlc *hp;
    int dev;
  
    if(Nhs >= NHS){
        tprintf("Too many hs controllers\n");
        return -1;
    }
    if(if_lookup(argv[4]) != NULLIF){
        tprintf(Existingiface,argv[4]);
        return -1;
    }
    dev = Nhs++;
  
    /* Initialize hardware-level control structure */
    Hs[dev].addr = htoi(argv[1]);
    Hs[dev].vec = htoi(argv[2]);
  
    /* Save original interrupt vector */
    Hs[dev].save.vec = getirq(Hs[dev].vec);
    /* Set new interrupt vector */
    if(setirq(Hs[dev].vec,Hshandle[dev]) == -1){
        tprintf("IRQ %u out of range\n",Hs[dev].vec);
        Nhs--;
        return -1;
    }
    /* Create interface structures and fill in details */
    if_hsa = (struct iface *)callocw(1,sizeof(struct iface));
    if_hsb = (struct iface *)callocw(1,sizeof(struct iface));
  
    if_hsa->addr = if_hsb->addr = Ip_addr;
    if_hsa->name = mallocw(strlen(argv[4])+2);
    strcpy(if_hsa->name,argv[4]);
    strcat(if_hsa->name,"a");
    if_hsb->name = mallocw(strlen(argv[4])+2);
    strcpy(if_hsb->name,argv[4]);
    strcat(if_hsb->name,"b");
    if_hsb->mtu = if_hsa->mtu = atoi(argv[6]);
    if_hsb->type = if_hsa->type = CL_AX25;
    if_hsa->dev = 2*dev;
    if_hsb->dev = 2*dev + 1;
    if_hsb->stop = if_hsa->stop = hs_stop;
    if_hsb->output = if_hsa->output = ax_output;
    if_hsb->raw = if_hsa->raw = hs_raw;
    if_hsa->ioctl = if_hsb->ioctl = hs_ctl;
  
    if(strcmp(argv[3],"ax25") == 0){
        if(Mycall[0] == '\0'){
            tprintf("set mycall first\n");
            free((char *)if_hsa);
            free((char *)if_hsb);
            return -1;
        }
        if_hsb->send = if_hsa->send = ax_send;
        if(if_hsb->hwaddr == NULLCHAR)
            if_hsb->hwaddr = mallocw(AXALEN);
        memcpy(if_hsb->hwaddr,Mycall,AXALEN);
        if(if_hsa->hwaddr == NULLCHAR)
            if_hsa->hwaddr = mallocw(AXALEN);
        memcpy(if_hsa->hwaddr,Mycall,AXALEN);
    } else {
        tprintf("Mode %s unknown for interface %s\n",
        argv[3],argv[4]);
        free((char *)if_hsa);
        free((char *)if_hsb);
        return -1;
    }
    if_hsa->next = if_hsb;
    if_hsb->next = Ifaces;
    Ifaces = if_hsa;
  
    /* Configure the tcp and ax25 parameters - WG7J */
    setencap(if_hsa,NULL);
    setencap(if_hsb,NULL);
  
    write_scc(Hs[dev].addr+CHANA+CTL,R9,FHWRES);
    hp = &Hdlc[2*dev+1];
    hp->ctl = Hs[dev].addr + CHANB + CTL;
    hp->data = Hs[dev].addr + CHANB + DATA;
    hp->bufsiz = atoi(argv[5]);
    if(argc > 7)
        hp->txdelay = atol(argv[7]);
    else
        hp->txdelay = 15L;
    if(argc > 8)
        hp->p = atoi(argv[8]);
    else
        hp->p = 64;
    if(argc > 9 && argv[9][0] == 'r')
        hp->clkrev = 1;
    else
        hp->clkrev = 0;
    hp->iface = if_hsb;
    hdlcparam(hp);
    if_hsa->txproc = newproc("hs_tx",1024,hs_tx,0,hp,NULL,0);
  
    hp = &Hdlc[2*dev];
    hp->ctl = Hs[dev].addr + CHANA + CTL;
    hp->data = Hs[dev].addr + CHANA + DATA;
    hp->bufsiz = atoi(argv[5]);
    hp->txdelay = Hdlc[2*dev+1].txdelay;
    hp->p = Hdlc[2*dev+1].p;
    if(argc > 9 && argv[9][0] == 'r')
        hp->clkrev = 1;
    else
        hp->clkrev = 0;
    hp->iface = if_hsa;
    hdlcparam(hp);
    if_hsb->txproc = newproc("hs_tx",1024,hs_tx,0,hp,NULL,0);
  
    outportb(Hs[dev].addr + 4,0x08);    /*EAGLE INT GATE */
    /* Clear mask (enable interrupt) in 8259 interrupt controller */
    maskon(Hs[dev].vec);
  
    /* Initialize timing delay loop */
    init_delay();
    return 0;
}
static int
hs_stop(iface)
struct iface *iface;
{
    int dev;
  
    dev = iface->dev;
    if(dev & 1)
        return -1;  /* Valid only for the first device */
    dev >>= 1;  /* Convert back into hs number */
  
    /* Turn off interrupts */
    maskoff(Hs[dev].vec);
  
    /* Restore original interrupt vector */
    setirq(Hs[dev].vec,Hs[dev].save.vec);
  
    /* Force hardware reset */
    write_scc(Hs[dev].addr + CHANA+CTL,R9,FHWRES);
    return 0;
}
/* Send raw packet */
static int
hs_raw(iface,bp)
struct iface *iface;
struct mbuf *bp;
{
  
    struct hdlc *hp;
  
    dump(iface,IF_TRACE_OUT,CL_AX25,bp);
    iface->rawsndcnt++;
    iface->lastsent = secclock();
    hp = &Hdlc[iface->dev];
    hp->txpkts++;
    enqueue(&hp->txq,bp);   /* Put on queue for hs_tx process */
    return 0;
}
  
/* High speed transmit process */
void
hs_tx(unused,hp1,a)
int unused;
void *hp1;
void *a;    /* Unused */
{
    struct mbuf *bp,*nbp;
    register int16 cnt;
    register char *cp;
    int16 ctl,data;
    int txon = 0;   /* Transmitter on/off state */
    struct hdlc *hp;
    int i;
  
    hp = (struct hdlc *)hp1;
    ctl = hp->ctl;
    data = hp->data;
  
    for(;;){
        /* Wait for work */
        while(hp->txq == NULLBUF){
            if(txon){
                /* No more frames, shut down tx */
                hstxoff(hp);
                txon = 0;
                /* Hold off to give other guy a chance to
                 * respond
                 */
                hp->deftime = msclock() + hp->txdelay + 500;
            }
            pwait(&hp->txq);
        }
        bp = dequeue(&hp->txq);
        cnt = len_p(bp);
        /* If buffer isn't contiguous (which is almost always
         * the case) copy it to a new buffer for speed
         */
        if(bp->next != NULLBUF){
            if((nbp = copy_p(bp,cnt)) == NULLBUF){
                hp->nomem++;
                free_p(bp);
                continue;
            }
            free_p(bp);
            bp = nbp;
        }
        cp = bp->data;
        /* Turn transmitter on if necessary */
        if(!txon){
            hstxon(hp);
            txon = 1;
        } else {
            /* Else wait another txd for receiver to get ready again */
            for(i=(int)hp->txdelay;i != 0;i--)
                msdelay();
        }
        /* Initialize transmitter CRC */
        write_scc(ctl,R0,RES_Tx_CRC);
        for(;;){
            /* Wait for the transmitter to become ready */
            while(!(inportb(ctl) & Tx_BUF_EMP))
                ;
            if(cnt-- == 0)
                break;
            outportb(data,*cp++); /* Send the character */
        }
        write_scc(ctl,R0,RES_EOM_L);    /* Allow CRC generation */
  
        /* End of frame. Wait for TxEOM to go high, indicating start of
         * CRC transmission. Note that we don't reset the transmit
         * interrupt pending flag as one ordinarily would, since we're
         * not using tx interrupts.
         */
        while(!(inportb(ctl) & TxEOM))
            ;
  
        free_p(bp);
    }
}
  
/* Turn on high speed transmitter. Does p-persistence, then sends a dummy
 * frame to allow for keyup delay. Returns with transmitter on and interrupts
 * disabled
 */
static void
hstxon(hp)
struct hdlc *hp;
{
    int16 ctl;
    int i;
    long ca;
    int32 t;
  
    ctl = hp->ctl;
  
    /* Defer logic. Wait until deftime is in the past (so we
     * defer to any overheard CTS messages) AND the p-persistence
     * dice roll succeeds. The computation of ca allows for clock
     * rollover (which happens every 49+ days).
     */
    for(;;){
        t = msclock();
        ca = hp->deftime - t;
        if(ca > 0){
            pause(ca);
            continue;
        }
        hp->deftime = t;    /* Keep from getting too old */
        if((rand() & 0xff) > uchar(hp->p)){
            pause((long)MSPTICK);
            continue;
        }
        break;
    }
    /* Prevent distractions. In particular, block off the DCD interrupt
     * so we don't hear our own carrier and hang in the interrupt handler!
     * Note that simply disabling CPU interrupts isn't enough since
     * the call to pause will block and the kernel will re-enable
     * them.
     */
    write_scc(ctl,R9,0);    /* Disable all SCC interrupts */
    (void)dirps();      /* Return value is always 1 */
  
    /* Turn on carrier, enable transmitter */
    write_scc(ctl,R5,TxCRC_ENAB | RTS | TxENAB | Tx8 | DTR);
  
    /* Delay for keyup interval */
    for(i=(int)hp->txdelay;i != 0;i--)
        msdelay();
  
}
/* Turn transmitter off at the end of a series of frames */
static void
hstxoff(hp)
struct hdlc *hp;
{
    int cnt;
    int16 ctl,data;
  
    ctl = hp->ctl;
    data = hp->data;
    /* To allow the SCC buffering to drain, we begin a dummy frame,
     * then abort it
     */
    for(cnt=5;cnt != 0;cnt--){
        while(!(inportb(ctl) & Tx_BUF_EMP))
            ;
        outportb(data,0);
    }
    write_scc(ctl,R0,SEND_ABORT);
  
    /* Turn off carrier and disable transmitter */
    write_scc(ctl,R5,TxCRC_ENAB | Tx8 | DTR);
    /* Re-Enable SCC interrupts */
    write_scc(ctl,R9,MIE|NV);
    restore(1); /* Turn interrupts back on */
}
  
int
dohs(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register int i;
    register struct hdlc *hp;
  
    for(i=0;i<2*Nhs;i++){
        hp = &Hdlc[i];
        if(tprintf("port %d: txpkts %lu ints %lu rxpkts %lu rxbytes %lu nomem %lu toobig %lu crcerr %lu aborts %lu overrun %lu\n",
            i,hp->txpkts,hp->exints,hp->good,hp->rxbytes,
            hp->nomem,hp->toobig,hp->crcerr,hp->aborts,
            hp->overrun) == EOF)
            break;
    }
    return 0;
}
static int32
hs_ctl(iface,cmd,set,val)
struct iface *iface;
int cmd;
int set;
int32 val;
{
    register struct hdlc *hp;
    int32 t,ca;
  
    hp = &Hdlc[iface->dev];
    switch(cmd){
        case PARAM_TXDELAY: /* Tx keyup delay */
            if(set)
                hp->txdelay = val;
            return hp->txdelay;
        case PARAM_PERSIST:
            if(set)
                hp->p = val;
            return uchar(hp->p);
        case PARAM_MUTE:
        /* Mute transmitter for specified # of ms */
            if(set){
                if(val == -1){
                /* Special case for duration of a CTS */
                    val = hp->txdelay + 500;
                }
                hp->deftime = msclock() + val;
            }
            t = msclock();
            ca = hp->deftime - t;
            if(ca < 0){
                hp->deftime = t;
                ca = 0;
            }
            return ca;
    }
    return -1;
}
#ifdef  notdef      /* replaced with assembler in 8530.asm */
/* Read data from the 8530 receiver.
 * Returns when either a good frame is received, or when carrier drops.
 * If a good frame is received, the length is returned; otherwise -1.
 */
int
rx8530(ctl,data,buf,bufsize)
int16 ctl,data;
char *buf;
int16 bufsize;
{
    int cnt = 0;
    register char status;
    char error;
    register char *cp = buf;
  
    for(;;){
        status = inportb(ctl);
        if(!(status & DCD)){
            cnt = -1;
            break;
        } else if(status & BRK_ABRT){
            cp = buf;
            cnt = 0;
        } else if(status & Rx_CH_AV){
            /* Receive character is ready, get it */
            *cp++ = inportb(data);
            if(++cnt > bufsize){
                /* Buffer overflow, start again */
                write_scc(ctl,R3,ENT_HM|RxENABLE|RxCRC_ENAB|Rx8);
                cp = buf;
                cnt = 0;
            }
        } else if((error = read_scc(ctl,R1)) & END_FR){
            if(!(error & CRC_ERR))
                break;  /* Good frame! */
            /* Bad frame, start again */
            cp = buf;
            cnt = 0;
        }
    }
    return cnt;
}
#endif
  
static int32 Del_const;
  
/* Find the value of Del_const that will cause one execution of mloop()
 * to take one millisecond
 */
static void
init_delay()
{
    int32 start,delay;
    register int i,j;
    int success = 0;
  
    /* Start with small value to make things tolerable on slow machines */
    Del_const = 10;
    printf("Del_const = %lu\n",Del_const);
    /* Limit the number of iterations in case we don't converge */
    for(i=0;i<5;i++){
        start = msclock();
        for(j=0;j<1000;j++)
            msdelay();
        delay = msclock()-start;
        printf("delay %lu\n",delay);
        if(delay == 0){
            /* Too fast for accurate measurement on coarse clk */
            Del_const *= 10;
            printf("Del_const = %lu\n",Del_const);
            continue;
        }
        Del_const = (Del_const * 1000)/delay;
        printf("Del_const = %lu\n",Del_const);
        if(delay > 950 && delay < 1050){
            success = 1;
            break;  /* Within 1 tick - Close enough */
        }
    }
    if(!success)
        tprintf("HS: Warning: auto delay set failed\n");
}
/* Delay for one millisecond (once calibrated by init_delay()) */
static void
msdelay()
{
    int32 i;
  
    for(i=Del_const;i !=0;i--)
        ;
}
#endif /* HS */
  
