/*  Driver for HAPN-1 8273 card on PC
 *  Jon Bloom, KE3Z; adapted from KA9Q's PC-100 driver
 *  Modified Rx interrupt routine to prevent lockup
 *  John Tanner VK2ZXQ 6th Feb 1988
 *  Adapted back into 871225.9 by KA9Q 15 Feb 1988
 */
#include <dos.h>
#include "global.h"
#ifdef HAPN
#ifdef  ANSIPROTO
#include <stdarg.h>
#endif
#include "timer.h"
#include "mbuf.h"
#include "iface.h"
#include "pktdrvr.h"
#include "netuser.h"
#include "hapn.h"
#include "ax25.h"
#include "trace.h"
#include "pc.h"
#include "proc.h"
  
static void cmd_8273 __ARGS((int16 base,int cmd,int np,...));
static int hapn_init __ARGS((struct hapn *hp));
static int hapn_raw __ARGS((struct iface *iface,struct mbuf *bp));
static int hapn_stop __ARGS((struct iface *iface));
static int hcdchk __ARGS((int16 base));
static void hrxint __ARGS((struct hapn *hp));
static void hrxgo __ARGS((struct hapn *hp));
static void htxint __ARGS((void *p));
  
static struct hapn Hapn[NHAPN];
static INTERRUPT (*H_handle[])() = { ha0vec };
static int16 Nhapn;
  
/*  send command to the 8273
 *  "base" = base port of 8273
 *  "cmd"  = command byte
 *  "np"   = number of parameter bytes
 *  "p1"   = first parameter (parameters are int)
 */
#ifdef  ANSIPROTO
static void
cmd_8273(int16 base, int cmd, int np, ...)
{
    int p;
    va_list ap;
  
    while(inportb(base+STA) & CBSY)
        ;
    outportb(base+CMD, cmd);
  
    va_start(ap,np);
    while(np--){
        while(inportb(base+STA) & CPBF)
            ;
        p = va_arg(ap,int);
        outportb(base+PAR, p);
    }
    va_end(ap);
}
#else
/*VARARGS3*/
static void
cmd_8273(base, cmd, np, p1)
int16 base;
int cmd, np, p1;
{
    int *p;
  
    while(inportb(base+STA) & CBSY)
        ;
    outportb(base+CMD, cmd);
    p = &p1;
    while(np--){
        while(inportb(base+STA) & CPBF)
            ;
        outportb(base+PAR, *p++);
    }
}
#endif
/*  Start receiver of 8273 */
static void
hrxgo(hp)
register struct hapn *hp;
{
    cmd_8273(hp->base, GENERAL_RX, 2, hp->bufsiz & 0xff, hp->bufsiz >> 8);
}
  
/*  Interrupt service function.  Entered with hapn index
 *  The "flag" variable is used in this routine to indicate a
 *  valid TX or RX interrupt. If an invalid interrupt is detected
 *  the 8273 is reset.
 */
void
haint(dev)
int dev;
{
    register struct hapn *hp;
    register int16 base;
    char flag = 0;
  
    hp = &Hapn[dev];
    base = hp->base;
  
    /*  Check for TX interrupt  */
    if(inportb(base+STA) & TXINT){
        flag = 1;   /* Valid interrupt, set flag */
        htxint(hp);
    }
    /*  Check for RX interrupt  */
    if(inportb(base+STA) & RXINT){
        flag = 1;   /* Valid interrupt, set flag */
        hrxint(hp);
    }
    /* Check for unknown interrupt  */
    if(!flag){
        hp->badint++;   /* Increment error counter */
        hapn_init(hp);  /* Reinitialise the 8273 */
    }
}
/*  RX interrupt service
 *  if status register bit "RXIRA" is set, interrupt is final,
 *  otherwise, interrupt is data request
 */
static void
hrxint(hp)
register struct hapn *hp;
{
    register struct mbuf *bp;
    register int16 base;
    unsigned char results[10];
    struct phdr phdr;
  
    hp->rxints++;
    base = hp->base;
  
    if(inportb(base+STA) & RXIRA){
        /* RX result interrupt
         * If the result is a good frame 3 bytes need to be read
         * If an error has occurred only one byte need to be read
         */
  
        /* Read first result byte and test for good data */
        if((results[0]=(inportb(base + RXI))) == 0xe0){
            /* Good result; read two more result bytes */
            while((inportb(base + STA) & RXIRA) == 0)
                ;
            /* Read second result byte */
            results[1] = inportb(base + RXI);
            /* Wait for third result byte  */
            while((inportb(base + STA) & RXIRA) == 0)
                ;
            results[2] = inportb(base + RXI);/* Read it */
  
            /* Since this frame is ok put it on the queue */
            bp = alloc_mbuf(sizeof(phdr));
            bp->cnt = sizeof(phdr);
            phdr.iface = hp->iface;
            phdr.type = CL_AX25;
            memcpy(&bp->data[0],(char *)&phdr,sizeof(phdr));
            bp->next = hp->rcvbuf;
            hp->rcvbuf = NULLBUF;
            enqueue(&Hopper, bp);
            hp->rframes++;
        } else {
            /* Error termination
             * Parse RIC and act accordingly
             * Only one result byte returned on error
             */
            switch(results[0]){
                case CRCERR:
                    hp->crcerr++;
                    break;
                case ABORT_DET:
                    hp->aborts++;
                    break;
                case DMA_OVRN:
                    hp->dmaorun++;
                    break;
                case MEM_OVFL:
                    hp->toobig++;
                    break;
                case CD_LOSS:
                    hp->cdloss++;
                    hapn_init(hp);  /* 8273 reset on cd error */
                    break;
                case RX_ORUN:
                    hp->rxorun++;
                    break;
            }
            /* Throw rx buffer contents away to start over */
            hp->rcp = hp->rcvbuf->data;
            hp->rcvbuf->cnt = 0;
        }
        /* Restart the receiver */
        cmd_8273(base,RX_DISABLE,0);
        hrxgo(hp);
    } else {
        /* RX data interrupt; allocate new rx buffer if none present */
        if((bp = hp->rcvbuf) == NULLBUF){
            bp = hp->rcvbuf = alloc_mbuf(hp->bufsiz);
            if(bp == NULLBUF){
                /* No memory available */
                hp->nomem++;
                cmd_8273(base, RX_DISABLE, 0);
                hrxgo(hp);
                return;
            }
            /* Init buffer pointer */
            hp->rcp = hp->rcvbuf->data;
        }
        /*  Barf if rx data is more than buffer can hold (should never
         *  happen since 8273 is also counting bytes).
         */
        if(bp->cnt++ >= hp->bufsiz){
            hp->toobig++;
            cmd_8273(base, RX_DISABLE, 0);
            hrxgo(hp);
            free_p(bp);
            hp->rcvbuf = NULLBUF;
            return;
        }
        /* Store the received byte */
        *hp->rcp++ = inportb(base+RXD);
    }
}
  
/*  test for busy channel (CD active)
 *  returns TRUE if channel busy
 */
static int
hcdchk(base)
int16 base;
{
    char isav;
  
    isav = dirps();
    cmd_8273(base, READ_A, 0);
    while(!(inportb(base+STA) & CRBF))
        ;
    restore(isav);
    return((inportb(base+RES) & CD) != 0);
}
  
/*  TX interrupt service
 *  if status register bit "TXIRA" is set, interrupt is final,
 *  otherwise, interrupt is data request
 */
static void
htxint(p)
void *p;
{
    register struct hapn *hp;
    char isav;
    register int16 base;
    int16 len;
    int c;
  
    hp = (struct hapn *)p;
    isav = dirps();
    hp->txints++;
    base = hp->base;
  
    c = 0;
    if(inportb(base+STA) & TXIRA){      /* TX result interupt */
        hp->tstate = IDLE;
        free_p(hp->sndbuf);
        hp->sndbuf = NULLBUF;
  
        /*  Read result  */
        while((inportb(base+STA) & (TXINT | TXIRA)) != (TXINT | TXIRA))
            ;
        c = inportb(base+TXI);
  
        /*  Test for tx abort  */
        switch(c & 0x1f){
            case DMA_URUN:
                hp->t_urun++;
                break;
            case CTS_LOSS:
                hp->ctsloss++;
                break;
            case ABORT_CMPLT:
                hp->taborts++;
                break;
        }
    }
    switch(hp->tstate){
        case IDLE:  /*  See if a buffer is ready to be sent  */
            if((hp->sndbuf = dequeue(&hp->sndq)) == NULLBUF)
                break;
  
        case DEFER: /*  Busy-channel check  */
            if(hp->mode == CSMA && (c & 0x1f) != EARLY_TXI){
                if(hcdchk(base)){
                    hp->tstate = DEFER;
                    start_timer(&hp->defer);
                    break;
                }
            }
        /*  Start transmitter  */
            stop_timer(&hp->defer);
            len = len_p(hp->sndbuf);
            cmd_8273(base, TX_FRAME, 2, len & 0xff, len >> 8);
            hp->tstate = ACTIVE;
            hp->tframes++;
            break;
        case ACTIVE:    /*  Get next byte to send  */
            if((c = PULLCHAR(&hp->sndbuf)) == -1){
                cmd_8273(base, ABORT_TXF, 0);
                hp->tstate = IDLE;
            } else
                outportb(base+TXD, c);
            break;
    }
    restore(isav);
}
  
/*  Attach a HAPN adaptor to the system
 *  argv[0]:  hardware type, must be "hapn"
 *  argv[1]:  I/O address, e.g. "0x310"
 *  argv[2]:  vector, e.g. "2"
 *  argv[3]:  mode, must be "ax25"
 *  argv[4]:  interface name, e.g. "ha0"
 *  argv[5]:  rx packet buffer size in bytes
 *  argv[6]:  maximum transmission unit in bytes
 *  argv[7]:  channel-access mechanism, "csma" or "full"
 *  argv[8]: IP address, optional (defaults to Ip_addr)
 */
int
hapn_attach(argc, argv,p)
int argc;
char *argv[];
void *p;
{
    register struct iface *if_h;
    struct hapn *hp;
    int dev, i;
    char isav;
    static struct {
        char *str;
        char type;
    } ch_access [] = { "csma", 0, "full", 1 };
  
    if(Nhapn >= NHAPN){
        tprintf("Too many HAPN adaptors\n");
        return -1;
    }
    if(if_lookup(argv[4]) != NULLIF){
        tprintf(Existingiface,argv[4]);
        return -1;
    }
    /*  Create new interface structure  */
    if_h = (struct iface *) callocw(1,sizeof(struct iface));
  
    /*  Set interface address  */
    if_h->addr = Ip_addr;
    if(argc > 8)
        if_h->addr = resolve(argv[8]);
    if(if_h->addr == 0){
        tprintf(Noipaddr);
        free((char *)if_h);
        return -1;
    }
    dev = Nhapn++;
    hp = &Hapn[dev];
  
    /*  Initialize hardware constants */
    hp->base = htoi(argv[1]);
    hp->vec = htoi(argv[2]);
  
    /*  Save original interrupt vector  */
    hp->oldvec = getirq(Hapn[dev].vec);
  
    /*  Set new interrupt vector  */
    setirq(hp->vec, H_handle[dev]);
  
    /* Continue filling interface structure */
    if_h->name = strdup(argv[4]);
    if_h->type = CL_AX25;
    if_h->mtu = atoi(argv[6]);
    if_h->dev = dev;
    if_h->stop = hapn_stop;
    if_h->output = ax_output;
    if_h->raw = hapn_raw;
    hp->iface = if_h;
  
    if(strcmp(argv[3], "ax25")){
        tprintf("Mode %s unknown for interface %s\n", argv[3], argv[4]);
        free(if_h->name);
        free((char *) if_h);
        return -1;
    }
    if(Mycall[0] == '\0'){
        tprintf("set mycall first\n");
        free(if_h->name);
        free((char *) if_h);
        return -1;
    }
    if_h->send = ax_send;
    if(if_h->hwaddr == NULLCHAR)
        if_h->hwaddr = mallocw(AXALEN);
    memcpy(if_h->hwaddr,Mycall,AXALEN);
    /*  Link the interface into the interface list  */
    if_h->next = Ifaces;
    Ifaces = if_h;
  
    /* Set the ax25 and tcp parameters - WG7J */
    setencap(if_h,NULL);
  
    /*  Fill the local data structure  */
    hp->bufsiz = atoi(argv[5]);
    for(i = 0; i < (sizeof ch_access / sizeof ch_access[0]); i++)
        if(!strcmp(argv[7], ch_access[i].str))
            hp->mode = ch_access[i].type;
  
    /*  Initialize the hardware  */
    isav = dirps();
    hapn_init(hp);
  
    /* Initialize the defer timer */
    set_timer(&hp->defer,MSPTICK);
    hp->defer.func = htxint;
    hp->defer.arg = hp;
  
    /*  Enable the interrupt  */
    maskon(hp->vec);
  
    restore(isav);
    return 0;
}
  
/*  initialize the HAPN adaptor */
static int
hapn_init(hp)
register struct hapn *hp;
{
    register int16 base;
    char isav;
  
    isav = dirps();
    base = hp->base;
  
    /*  Reset the 8273 */
    outportb(base+RST, 1);
    outportb(base+RST, 0);
    inportb(base+TXI);      /* Clear any old IR contents */
    inportb(base+RXI);
  
    /*  Select the operating modes  */
    cmd_8273(base, SET_XFER, 1, 1);
    cmd_8273(base, SET_MODE, 1, HDLC | EARLY | PREFRM | FLG_STM);
    cmd_8273(base, SET_SERIAL, 1, NRZI);
    cmd_8273(base, SET_B, 1, IRQ_ENB | RTS);
    cmd_8273(base, RST_B, 1, 0xff ^ RTS);
    hrxgo(hp);
    restore(isav);
    return 0;
}
  
/*  shut down the HAPN adaptor */
static int
hapn_stop(iface)
struct iface *iface;
{
    int dev;
    int16 base;
    struct hapn *hp;
  
    dev = iface->dev;
    hp = &Hapn[dev];
    base = hp->base;
  
    /*  Mask off interrupt input  */
    maskoff(hp->vec);
  
    /*  Restore original interrupt vector  */
    setirq(hp->vec,hp->oldvec);
  
    /*  Reset the 8273  */
    outportb(base+RST, 1);
    outportb(base+RST, 0);
    return 0;
}
  
/* Display adaptor statistics */
int
dohapnstat(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    struct hapn *hp;
    int i;
  
    if(Nhapn == 0){
        tprintf("No HAPN adaptor attached\n");
        return 1;
    }
    for(i = 0; i < Nhapn; i++){
        hp = &Hapn[i];
        tprintf("HAPN %d:   rxints: %ld   txints: %ld   badint: %-5d\n", i,
        hp->rxints,hp->txints,hp->badint);
        tprintf(" receive  - frames:  %-5d  crcerrs: %-5d  aborts: %-5d  dmaorun: %-5d\n",
        hp->rframes,hp->crcerr, hp->aborts, hp->dmaorun);
        tprintf("          - toobig:  %-5d  dcdloss: %-5d  rxorun: %-5d\n",
        hp->toobig,hp->cdloss,hp->rxorun);
        if(tprintf(" transmit - frames:  %-5d  aborts : %-5d  uruns : %-5d  ctsloss: %-5d\n",
            hp->tframes,hp->taborts, hp->t_urun, hp->ctsloss) == EOF)
            break;
    }
    return 0;
}
  
/* Send raw packet on HAPN interface */
static int
hapn_raw(iface,bp)
struct iface *iface;
struct mbuf *bp;
{
    struct hapn *hp;
  
    hp = &Hapn[iface->dev];
    enqueue(&hp->sndq, bp);
  
    /*  See if anything being transmitted  */
    if(hp->tstate == IDLE)
        htxint(hp);
    return 0;
}
#endif /* HAPN */
  
