/* 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 <stdio.h>
#include <dos.h>
#include "global.h"
#include "mbuf.h"
#include "iface.h"
#include "pktdrvr.h"
#include "netuser.h"
#include "hs.h"
#include "8530.h"
#include "ax25.h"
#include "trace.h"
#include "pc.h"
#include "proc.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 void hstxoff __ARGS((struct hdlc *hp));
static void hstxon __ARGS((struct hdlc *hp));
static void htxint __ARGS((struct hdlc *hp));

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,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);
		cnt = 0;
	}	
	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.
	 * TRxC = transmit clock, RTxC = receive clock
	 * (swapped 11 Feb 1990 to use new DRSI wiring)
	 */
	write_scc(ctl,R11,RCRTxCP | TCTRxCP);

	/* 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 label, e.g., "drsi0". 
 * argv[5]: receiver packet buffer size in bytes
 * argv[6]: maximum transmission unit, bytes
 * argv[7]: keyup delay, clock ticks
 * argv[8]: persistence value, 0-255
 */
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("Interface %s already exists\n",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 = strdup(argv[4]);
	if_hsb->name = strdup(argv[4]);
	if_hsb->name[strlen(argv[4]) - 1]++;	/* kludge */
	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(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;

	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 = 1L;
	if(argc > 8)
		hp->p = atoi(argv[8]);
	else
		hp->p = 64;
	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;
	hp->iface = if_hsb;
	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);

	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;

	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;
			}
			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;
		}
		/* 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,data;
	int i;

	ctl = hp->ctl;
	data = hp->data;

	/* P-persistence. The slot time is fixed at one tick. */
	while((rand() & 0xff) > uchar(hp->p))
		pause((int32)MSPTICK);

	/* 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 by sending dummy frame */
	for(i=hp->txdelay;i != 0;i--){
		while(!(inportb(ctl) & Tx_BUF_EMP))
			;
		outportb(data,0);
	}
	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))
		;
}
/* 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;
}
#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

