/* Driver for FTP Software's packet driver interface. (PC specific code)
 * Copyright 1991 Phil Karn, KA9Q
 */
#include <stdio.h>
#include <dos.h>
#include "global.h"
#include "proc.h"
#include "mbuf.h"
#include "netuser.h"
#include "enet.h"
#include "arcnet.h"
#include "ax25.h"
#include "slip.h"
#include "kiss.h"
#include "iface.h"
#include "ec.h"
#include "arp.h"
#include "trace.h"
#include "pktdrvr.h"
#include "config.h"

static long access_type __ARGS((int intno,int if_class,int if_type,int if_number,
	char *type,unsigned typelen,INTERRUPT (*receiver) __ARGS((void)) ));
static int driver_info __ARGS((int intno,int handle,int *version,
	int *class,int *type,int *number,int *basic));
static int release_type __ARGS((int intno,int handle));
static int get_address __ARGS((int intno,int handle,char *buf,int len));
static int pk_raw __ARGS((struct iface *iface,struct mbuf *bp));
static int pk_stop __ARGS((struct iface *iface));
static int send_pkt __ARGS((int intno,char *buffer,unsigned length));


static INTERRUPT (*Pkvec[])() = { pkvec0,pkvec1,pkvec2 };
static struct pktdrvr Pktdrvr[PK_MAX];
static int Derr;
static char Pkt_sig[] = "PKT DRVR";	/* Packet driver signature */

/*
 * Send routine for packet driver
 */

int
pk_send(bp,iface,gateway,prec,del,tput,rel)
struct mbuf *bp;	/* Buffer to send */
struct iface *iface;	/* Pointer to interface control block */
int32 gateway;		/* Ignored  */
int prec;
int del;
int tput;
int rel;
{
	if(iface == NULLIF){
		free_p(bp);
		return -1;
	}
	return (*iface->raw)(iface,bp);
}

/* Send raw packet (caller provides header) */
static int
pk_raw(iface,bp)
struct iface *iface;	/* Pointer to interface control block */
struct mbuf *bp;	/* Data field */
{
	register struct pktdrvr *pp;
	int16 size;
	struct mbuf *bp1;

	pp = &Pktdrvr[iface->dev];
	size = len_p(bp);

	dump(iface,IF_TRACE_OUT,pp->class,bp);
	iface->rawsndcnt++;
	iface->lastsent = secclock();

	/* Perform class-specific processing, if any */
	switch(pp->class){
	case CL_ETHERNET:
		if(size < RUNT){
			/* Pad the packet out to the minimum */
			if((bp1 = alloc_mbuf(RUNT-size)) == NULLBUF){
				free_p(bp);
				return -1;
			}
			memset(bp1->data,0,RUNT-size);
			bp1->cnt = RUNT-size;
			append(&bp,bp1);
			size = RUNT;
		}
		break;
	case CL_KISS:
		/* This *really* shouldn't be done here, but it was the
		 * easiest way. Put the type field for KISS TNC on front.
		 */
		if((bp1 = pushdown(bp,1)) == NULLBUF){
			free_p(bp);
			return -1;
		}
		bp = bp1;
		bp->data[0] = KISS_DATA;
		size++;
		break;
	}
	if(bp->next != NULLBUF){
		/* Copy to contiguous buffer, since driver can't handle mbufs */
		bp1 = copy_p(bp,size);
		free_p(bp);
		bp = bp1;
		if(bp == NULLBUF)
			return -1;
	}
	send_pkt(pp->intno,bp->data,bp->cnt);
	free_p(bp);
	return 0;
}

/* Packet driver receive routine. Called from an assembler hook that pushes
 * the caller's registers on the stack so we can access and modify them.
 * This is a rare example of call-by-location in C.
 */
void
pkint(dev,di,si,bp,dx,cx,bx,ax,ds,es)
int dev;
unsigned short di,si,bp,dx,cx,bx,ax,ds,es;
{
	register struct pktdrvr *pp;
	struct phdr phdr;

	if(dev < 0 || dev >= PK_MAX)
		return;	/* Unknown device */
	pp = &Pktdrvr[dev];
	if(pp->iface == NULLIF)
		return;	/* Unknown device */

	switch(ax){
	case 0:	/* Space allocate call */
		if((pp->buffer = alloc_mbuf(cx+sizeof(phdr))) != NULLBUF){
			es = FP_SEG(pp->buffer->data);
			di = FP_OFF(pp->buffer->data+sizeof(phdr));
			pp->buffer->cnt = cx + sizeof(phdr);
			phdr.iface = pp->iface;
			phdr.type = pp->class;
			memcpy(&pp->buffer->data[0],(char *)&phdr,sizeof(phdr));
		} else {
			es = di = 0;
		}
		break;
	case 1:	/* Packet complete call */
		enqueue(&Hopper,pp->buffer);
		pp->buffer = NULLBUF;
		break;
	default:
		break;
	}
}

/* Shut down the packet interface */
static int
pk_stop(iface)
struct iface *iface;
{
	struct pktdrvr *pp;

	pp = &Pktdrvr[iface->dev];
	/* Call driver's release_type() entry */
	if(release_type(pp->intno,pp->handle1) == -1)
		tprintf("%s: release_type error code %u\n",iface->name,Derr);

	if(pp->class == CL_ETHERNET || pp->class == CL_ARCNET){
		release_type(pp->intno,pp->handle2);
		release_type(pp->intno,pp->handle3);
	}
	pp->iface = NULLIF;
	return 0;
}
/* Attach a packet driver to the system
 * argv[0]: hardware type, must be "packet"
 * argv[1]: software interrupt vector, e.g., x7e
 * argv[2]: interface label, e.g., "trw0"
 * argv[3]: maximum number of packets allowed on transmit queue, e.g., "5"
 * argv[4]: maximum transmission unit, bytes, e.g., "1500"
 */
int
pk_attach(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct iface *if_pk;
	int class,type;
	unsigned int intno;
	static char iptype[] = {IP_TYPE >> 8,IP_TYPE};
	static char arptype[] = {ARP_TYPE >> 8,ARP_TYPE};
	static char revarptype[] = {REVARP_TYPE >> 8, REVARP_TYPE};
	long handle;
	int i;
#ifdef	ARCNET
	static char arcip[] = {ARC_IP};
	static char arcarp[] = {ARC_ARP};
#endif

	long drvvec;
	char sig[8];	/* Copy of driver signature "PKT DRVR" */
	register struct pktdrvr *pp;

	for(i=0;i<PK_MAX;i++){
		if(Pktdrvr[i].iface == NULLIF)
			break;
	}
	if(i >= PK_MAX){
		tprintf("Too many packet drivers\n");
		return -1;
	}
	if(if_lookup(argv[2]) != NULLIF){
		tprintf("Interface %s already exists\n",argv[2]);
		return -1;
	}

	intno = htoi(argv[1]);
	/* Verify that there's really a packet driver there, so we don't
	 * go off into the ozone (if there's any left)
	 */
	drvvec = (long)getvect(intno);
	movblock(FP_OFF(drvvec)+3, FP_SEG(drvvec),
		FP_OFF(sig),FP_SEG(sig),strlen(Pkt_sig));
	if(strncmp(sig,Pkt_sig,strlen(Pkt_sig)) != 0){
		tprintf("No packet driver loaded at int 0x%x\n",intno);
		return -1;
	}
	if_pk = (struct iface *)callocw(1,sizeof(struct iface));
	if_pk->name = strdup(argv[2]);
	if_pk->addr = Ip_addr;
	pp = &Pktdrvr[i];
	if_pk->mtu = atoi(argv[4]);
	if_pk->dev = i;
	if_pk->raw = pk_raw;
	if_pk->stop = pk_stop;
	pp->intno = intno;
	pp->iface = if_pk;

 	/* Version 1.08 of the packet driver spec dropped the handle
 	 * requirement from the driver_info call.  However, if we are using
 	 * a version 1.05 packet driver, the following call will fail.
  	 */
 	if(driver_info(intno,-1,NULL,&class,&type,NULL,NULL) < 0){
		/* Find out by exhaustive search what class this driver is (ugh) */
		for(class=1;class<=NCLASS;class++){
			/* Store handle in temp long so we can tell an
			 * error return (-1) from a handle of 0xffff
			 */
			handle = access_type(intno,class,ANYTYPE,0,iptype,2,
				Pkvec[if_pk->dev]);
			if(handle != -1 || Derr == TYPE_INUSE){
				pp->handle1 = handle;
				break;
			}
		}
		/* Now that we know, release it and do it all over again with the
		 * right type fields
		 */
		release_type(intno,pp->handle1);
	}
	switch(class){
	case CL_ETHERNET:
		pp->handle1 = access_type(intno,class,ANYTYPE,0,iptype,2,
			Pkvec[if_pk->dev]);
		pp->handle2 = access_type(intno,class,ANYTYPE,0,arptype,2,
			Pkvec[if_pk->dev]);
		pp->handle3 = access_type(intno,class,ANYTYPE,0,revarptype,2,
			Pkvec[if_pk->dev]);
		setencap(if_pk,"Ethernet");
		/* Get hardware Ethernet address from driver */
		if_pk->hwaddr = mallocw(EADDR_LEN);
		get_address(intno,pp->handle1,if_pk->hwaddr,EADDR_LEN);
		break;
#ifdef	ARCNET
	case CL_ARCNET:
		pp->handle1 = access_type(intno,class,ANYTYPE,0,arcip,1,
			Pkvec[if_pk->dev]);
		pp->handle2 = access_type(intno,class,ANYTYPE,0,arcarp,1,
			Pkvec[if_pk->dev]);
		if_pk->send = anet_send;
		if_pk->output = anet_output;
		/* Get hardware ARCnet address from driver */
		if_pk->hwaddr = mallocw(AADDR_LEN);
		get_address(intno,pp->handle1,if_pk->hwaddr,AADDR_LEN);
		break;
#endif
	case CL_SERIAL_LINE:
		pp->handle1 = access_type(intno,class,ANYTYPE,0,NULLCHAR,0,
		 Pkvec[if_pk->dev]);
		setencap(if_pk,"SLIP");
		break;
#ifdef	AX25
	case CL_KISS:	/* Note that the raw routine puts on the command */
	case CL_AX25:
		pp->handle1 = access_type(intno,class,ANYTYPE,0,NULLCHAR,0,
		 Pkvec[if_pk->dev]);
		setencap(if_pk,"AX25");
		if_pk->hwaddr = mallocw(AXALEN);
		memcpy(if_pk->hwaddr,Mycall,AXALEN);
		break;
#endif
	case CL_SLFP:
		pp->handle1 = access_type(intno,class,ANYTYPE,0,NULLCHAR,0,
		 Pkvec[if_pk->dev]);
		if_pk->send = pk_send;
		setencap(if_pk,"SLFP");
		get_address(intno,pp->handle1,(char *)&if_pk->addr,4);
		break;
	default:
		tprintf("Packet driver has unsupported class %u\n",class);
		free(if_pk->name);
		free((char *)if_pk);
		return -1;
	}
	pp->class = class;
	if_pk->next = Ifaces;
	Ifaces = if_pk;

	return 0;
}
static long
access_type(intno,if_class,if_type,if_number,type,typelen,receiver)
int intno;
int if_class;
int if_type;
int if_number;
char *type;
unsigned typelen;
INTERRUPT (*receiver)();
{
	union REGS regs;
	struct SREGS sregs;

	segread(&sregs);
	regs.h.dl = if_number;		/* Number */
	sregs.ds = FP_SEG(type);	/* Packet type template */
	regs.x.si = FP_OFF(type);
	regs.x.cx = typelen;		/* Length of type */
	sregs.es = FP_SEG(receiver);	/* Address of receive handler */
	regs.x.di = FP_OFF(receiver);
	regs.x.bx = if_type;		/* Type */
	regs.h.ah = ACCESS_TYPE;	/* Access_type() function */
	regs.h.al = if_class;		/* Class */
	int86x(intno,&regs,&regs,&sregs);
	if(regs.x.cflag){
		Derr = regs.h.dh;
		return -1;
	} else
		return regs.x.ax;
}
static int
release_type(intno,handle)
int intno;
int handle;
{
	union REGS regs;

	regs.x.bx = handle;
	regs.h.ah = RELEASE_TYPE;
	int86(intno,&regs,&regs);
	if(regs.x.cflag){
		Derr = regs.h.dh;
		return -1;
	} else
		return 0;
}
static int
send_pkt(intno,buffer,length)
int intno;
char *buffer;
unsigned length;
{
	union REGS regs;
	struct SREGS sregs;

	segread(&sregs);
	sregs.ds = FP_SEG(buffer);
	sregs.es = FP_SEG(buffer); /* for buggy univation pkt driver - CDY */
	regs.x.si = FP_OFF(buffer);
	regs.x.cx = length;
	regs.h.ah = SEND_PKT;
	int86x(intno,&regs,&regs,&sregs);
	if(regs.x.cflag){
		Derr = regs.h.dh;
		return -1;
	} else
		return 0;
}
static int
driver_info(intno,handle,version,class,type,number,basic)
int intno;
int handle;
int *version,*class,*type,*number,*basic;
{
	union REGS regs;

	regs.x.bx = handle;
	regs.h.ah = DRIVER_INFO;
	regs.h.al = 0xff;
	int86(intno,&regs,&regs);
	if(regs.x.cflag){
		Derr = regs.h.dh;
		return -1;
	}
	if(version != NULL)
		*version = regs.x.bx;
	if(class != NULL)
		*class = regs.h.ch;
	if(type != NULL)
		*type = regs.x.dx;
	if(number != NULL)
		*number = regs.h.cl;
	if(basic != NULL)
		*basic = regs.h.al;
	return 0;
}
static int
get_address(intno,handle,buf,len)
int intno;
int handle;
char *buf;
int len;
{
	union REGS regs;
	struct SREGS sregs;

	segread(&sregs);
	sregs.es = FP_SEG(buf);
	regs.x.di = FP_OFF(buf);
	regs.x.cx = len;
	regs.x.bx = handle;
	regs.h.ah = GET_ADDRESS;
	int86x(intno,&regs,&regs,&sregs);
	if(regs.x.cflag){
		Derr = regs.h.dh;
		return -1;
	}
	return 0;
}
