/* Driver for FTP Software's packet driver interface. (PC specific code)
 * Rewritten Feb 1996 for DPMI with DJGPP Phil Karn
 */
#include "global.h"
#ifdef PACKET
#include <sys/types.h>
#include <go32.h>
#include <dos.h>
#include <dpmi.h>

#include "proc.h"
#include "netuser.h"
#include "enet.h"
#include "trace.h"
#include "pktdrvr.h"
#include "devparam.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: pktdrvr.c,v 1.16 1997/07/31 00:44:20 root Exp $";
#endif

#define	C_FLAG	1
static long access_type(int intno,int if_class,int if_type,
	int if_number, uint rm_segment,uint rm_offset );
static int driver_info(int intno,int handle,int *version,
	int *class,int *type,int *number,int *basic);
static int release_type(int intno,int handle);
static int get_address(int intno,int handle,uint8 *buf,int len);
#if 0
static int set_rcv_mode(int intno,int handle,int mode);
#endif
static int pk_raw(struct iface *iface,struct mbuf *bp);
static int pk_stop(struct iface *iface);
static void pkint(_go32_dpmi_registers *reg);

static struct pktdrvr Pktdrvr[PK_MAX];
static int Derr;

static void pk_rx(int dev,void *p1,void *p2);


/*
 * Send routine for packet driver
 */

int
pk_send(
struct mbuf *bp,		/* Buffer to send */
struct iface *iface,		/* Pointer to interface control block */
uint32 gateway OPTIONAL,	/* Ignored  */
int prec OPTIONAL,
int del OPTIONAL,
int tput OPTIONAL,
int rel OPTIONAL
){
	if (iface == NULL)	{
		free_p (bp);
		return -1;
	}
	return (*iface->raw) (iface, bp);
}



/* Send raw packet (caller provides header) */
static int
pk_raw(
struct iface *iface,	/* Pointer to interface control block */
struct mbuf *bp		/* Data field */
){
_go32_dpmi_registers reg;
struct pktdrvr *pp;
int16 size;
int offset;
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 */
				/* Do it securely with zeros */
				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] = PARAM_DATA;
			size++;
			break;
		default:
			break;
	}

	/* Copy packet to contiguous real mode buffer */
	offset = 0;
	while(bp != NULL){
		dosmemput(bp->data, bp->cnt, _go32_info_block.linear_address_of_transfer_buffer+offset);
		offset += bp->cnt;
		bp = free_mbuf (bp);
	}
	/* Call the packet driver to send it */
	memset (&reg, 0, sizeof(reg));
	reg.h.ah = SEND_PKT;
	reg.x.si = _go32_info_block.linear_address_of_transfer_buffer & 15;
	reg.x.ds = _go32_info_block.linear_address_of_transfer_buffer >> 4;
	reg.x.cx = size;
	(void) _go32_dpmi_simulate_int (pp->intno, &reg);
	if (reg.x.flags & C_FLAG){
		Derr = reg.h.dh;
		return -1;
	}
	return 0;
}



/* Packet driver receive upcall routine. Called by the packet driver TSR
 * twice for each incoming packet: first with ax == 0 to allocate a buffer,
 * and then with ax == 1 to signal completion of the copy.
 */
static void
pkint(_go32_dpmi_registers *reg)
{
int i;
uint len,blen;
int i_state;
struct pktdrvr *pp;

	i_state = disable();
	/* This is not really legal, since handles are not guaranteed to
	 * be globally unique. But it's extremely expedient.
	 */
	for (i = 0; i < PK_MAX; i++)	{
		if (Pktdrvr[i].handle == reg->x.bx)
			break;
	}
	if (i == PK_MAX)	{
		restore (i_state);
		reg->x.es = reg->x.di = 0;
		return;	/* Unknown handle */
	}
	pp = &Pktdrvr[i];
	len = reg->x.cx;

	switch (reg->x.ax){
		case 0:	/* Space allocate call */
			if ((int32) (len + sizeof(len)) > pp->dossize - pp->cnt)	{
				/* Buffer overflow */
				reg->x.es = 0;
				reg->x.di = 0;
				pp->overflows++;
				break;
			}
			if ((int32) (pp->wptr + len + sizeof(len)) > pp->dossize)	{
				/* Not enough room at end of DOS buffer for length
				 * plus data, so write zero length and wrap around
				 */
				uint zero = 0;
				pp->cnt += pp->dossize - pp->wptr;
				dosmemput (&zero, sizeof(zero), pp->dosbase + pp->wptr);
				pp->wptr = 0;
			}
			/* Write length into DOS buffer */
			dosmemput (&len, sizeof(len), pp->dosbase + pp->wptr);
			pp->wptr += (long) sizeof(len);
			pp->cnt += (long) sizeof(len);
			/* Pass new pointer to packet driver */
			reg->x.es = (pp->dosbase + pp->wptr) / 16;
			reg->x.di = (pp->dosbase + pp->wptr) % 16;
			break;
		case 1:	/* Packet complete call */
			/* blen is len rounded up to next boundary, to keep the
			 * next packet on a clean boundary
			 */
			blen = (len + sizeof(len) - 1) & ~(sizeof(len) - 1);
			pp->wptr += (long) blen;
			pp->cnt += (long) blen;
			if ((int32) (pp->wptr + sizeof(len)) > pp->dossize)	{
				/* No room left for another len field, wrap */
				pp->cnt += pp->dossize - pp->wptr;
				pp->wptr = 0;
			}
			ksignal (&pp->cnt, 1);
			break;
		default:
			break;
	}
	restore (i_state);
}



/* Shut down the packet interface */
static int
pk_stop(
struct iface *iface
){
struct pktdrvr *pp;
_go32_dpmi_seginfo dosmem;

	pp = &Pktdrvr[iface->dev];
	/* Call driver's release_type() entry */
	if (release_type (pp->intno, pp->handle) == -1)
		printf ("%s: release_type error code %u\n", iface->name, Derr);

	pp->iface = NULL;
	dosmem.size = pp->dossize / 16;
	dosmem.rm_segment = pp->dosbase / 16;
	(void) _go32_dpmi_free_dos_memory (&dosmem);
	(void) _go32_dpmi_free_real_mode_callback (&pp->rmcb_seginfo);
	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]: receive buffer size in kb
 * argv[4]: maximum transmission unit, bytes, e.g., "1500"
 * argv[5]: IP address (optional)
 */
int
pk_attach(
int argc,
char *argv[],
void *p
){
struct iface *if_pk;
int class,type;
char sig[9];
unsigned int intno;
int i;
struct pktdrvr *pp;
char tmp[25];
char *cp;
unsigned long pkt_addr;
unsigned short vec[2];
_go32_dpmi_seginfo dosmem;
#ifdef	ARCNET
static uint8 arcip[] = {ARC_IP};
static uint8 arcarp[] = {ARC_ARP};
#endif

	for (i = 0; i < PK_MAX; i++)	{
		if (Pktdrvr[i].iface == NULL)
			break;
	}
	if (i >= PK_MAX)	{
		printf ("Too many packet drivers\n");
		return -1;
	}
	if (if_lookup (argv[2]) != NULL){
		printf ("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)
	 */
	dosmemget (intno * 4, 4, vec);
	pkt_addr = vec[1] * 16 + vec[0];
	if (pkt_addr == 0)	{
		printf ("No packet driver loaded at int 0x%x\n", intno);
		return -1;
	}
	dosmemget (pkt_addr + 3, 9, sig);
	if (strcmp (sig, "PKT DRVR") != 0)	{
		printf ("No packet driver loaded at int 0x%x\n", intno);
		return -1;
	}
	/* Find out what we've got */
 	if (driver_info ((int) intno, -1, NULL, &class, &type, NULL, NULL) < 0)	{
		printf ("driver_info call failed\n");
		return -1;
	}
	pp = &Pktdrvr[i];
	dosmem.size = 64 * atoi(argv[3]); /* KB -> paragraphs */
	if (_go32_dpmi_allocate_dos_memory (&dosmem))	{
		printf ("DOS memory allocate failed for %d, max size = %d\n", atoi(argv[3]) * 1024, dosmem.size * 16);
		return -1;
	}
	pp->dossize = (long) (dosmem.size * 16);
	pp->dosbase = dosmem.rm_segment * 16;
	pp->overflows = pp->wptr = pp->rptr = pp->cnt = 0;

	if_pk = (struct iface *) callocw (1, sizeof(struct iface));
	if_pk->name = strdup(argv[2]);
	if (argc > 5)
		if_pk->addr = resolve (argv[5]);
	else
		if_pk->addr = Ip_addr;

	if_pk->mtu = atoi (argv[4]);
	if_pk->dev = i;
	if_pk->raw = pk_raw;
	if_pk->stop = pk_stop;
	pp->intno = (int) intno;
	pp->iface = if_pk;

	pp->rmcb_seginfo.pm_offset = (int) pkint;
	if ((i = _go32_dpmi_allocate_real_mode_callback_retf (&pp->rmcb_seginfo,
	    &pp->rmcb_registers)) != 0)	{
		printf ("real mode callback alloc failed: %d\n", i);
		return -1;
	}
	pp->handle = access_type ((int) intno, class, ANYTYPE, 0, pp->rmcb_seginfo.rm_segment,
	    pp->rmcb_seginfo.rm_offset);

	switch (class)	{
		case CL_ETHERNET:
			(void) setencap(if_pk,"Ethernet");

			/**** temp set multicast flag ****/
#if 0
			i = set_rcv_mode (intno, pp->handle, 5);
			printf ("set_rcv_mode returns %d, Derr = %d\n", i, Derr);
#endif

			/* Get hardware Ethernet address from driver */
			if_pk->hwaddr = mallocw (EADDR_LEN);
			(void) get_address ((int) intno, pp->handle, (unsigned char *) if_pk->hwaddr, EADDR_LEN);
			if (if_pk->hwaddr[0] & 1)	{
				printf ("Warning! Interface '%s' has a multicast address:", if_pk->name);
				printf (" (%s)\n", (*if_pk->iftype->format)(tmp,if_pk->hwaddr));
			}
			break;
#ifdef	ARCNET
		case CL_ARCNET:
			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->handle, if_pk->hwaddr, AADDR_LEN);
			break;
#endif
		case CL_SERIAL_LINE:
			(void) setencap (if_pk, "SLIP");
			break;
#ifdef	AX25
		case CL_KISS:	/* Note that the raw routine puts on the command */
		case CL_AX25:
			(void) setencap (if_pk, "AX25");
			if_pk->hwaddr = mallocw (AXALEN);
			memcpy (if_pk->hwaddr, Mycall, AXALEN);
			break;
#endif
		case CL_SLFP:
			(void) setencap (if_pk, "SLFP");
			if_pk->send = pk_send;
			(void) get_address ((int) intno, pp->handle, (uint8 *) &if_pk->addr, 4);
			break;
		default:
			printf ("Packet driver has unsupported class %u\n", class);
			free (if_pk->name);
			free (if_pk);
			return -1;
	}
	pp->class = class;
	if_pk->next = Ifaces;
	Ifaces = if_pk;

	cp = if_name (if_pk, " rx");
	if_pk->rxproc = newproc (cp, 768, pk_rx, if_pk->dev, if_pk, pp, 0);
	free (cp);
	return 0;
}



static long
access_type(
int intno,
int if_class,
int if_type,
int if_number,
uint rm_segment,
uint rm_offset
){
_go32_dpmi_registers reg;

	memset (&reg, 0, sizeof(reg));
	reg.h.ah = ACCESS_TYPE;	/* Access_type() function */
	reg.h.al = if_class;	/* Class */
	reg.x.bx = if_type;	/* Type */
	reg.h.dl = if_number;	/* Number */
	reg.x.es = rm_segment;	/* Address of rm receive handler */
	reg.x.di = rm_offset;
	(void) _go32_dpmi_simulate_int (intno, &reg);
	if (reg.x.flags & C_FLAG)	{
		Derr = reg.h.dh;
		return -1;
	} else
		return reg.x.ax;
}



static int
release_type(
int intno,
int handle
){
_go32_dpmi_registers reg;

	memset (&reg, 0, sizeof(reg));
	reg.x.bx = handle;
	reg.h.ah = RELEASE_TYPE;
	(void) _go32_dpmi_simulate_int (intno, &reg);
	if (reg.x.flags & C_FLAG)	{
		Derr = reg.h.dh;
		return -1;
	} else
		return 0;
}



static int
driver_info(
int intno,
int handle,
int *version,
int *class,
int *type,
int *number,
int *basic
){
_go32_dpmi_registers reg;

	memset (&reg, 0, sizeof(reg));
	reg.h.ah = DRIVER_INFO;
	reg.x.bx = handle;
	reg.h.al = 0xff;
	(void) _go32_dpmi_simulate_int (intno, &reg);
	if (reg.x.flags & C_FLAG)	{
		Derr = reg.h.dh;
		return -1;
	}
	if(version != NULL)
		*version = reg.x.bx;
	if(class != NULL)
		*class = reg.h.ch;
	if(type != NULL)
		*type = reg.x.dx;
	if(number != NULL)
		*number = reg.h.cl;
	if(basic != NULL)
		*basic = reg.h.al;
	return 0;
}



static int
get_address(
int intno,
int handle,
uint8 *buf,
int len
){
_go32_dpmi_registers reg;

	memset (&reg, 0, sizeof(reg));
	reg.h.ah = GET_ADDRESS;
	reg.x.bx = handle;
	reg.x.di = _go32_info_block.linear_address_of_transfer_buffer & 15;
	reg.x.es = _go32_info_block.linear_address_of_transfer_buffer >> 4;
	reg.x.cx = len;
	(void) _go32_dpmi_simulate_int (intno, &reg);
	if (reg.x.flags & C_FLAG)	{
		Derr = reg.h.dh;
		return -1;
	}
	dosmemget (_go32_info_block.linear_address_of_transfer_buffer, len, buf);
	return 0;
}



#if 0
static int
set_rcv_mode(
int intno,
int handle,
int mode
){
_go32_dpmi_registers reg;

	memset (&reg, 0, sizeof(reg));
	reg.h.ah = SET_RCV_MODE;
	reg.x.cx = mode;
	reg.x.bx = handle;
	(void) _go32_dpmi_simulate_int (intno, &reg);
	if (reg.x.flags & C_FLAG)	{
		Derr = reg.h.dh;
		return -1;
	}
	return 0;
}
#endif


static void
pk_rx(
int dev,
void *p1,
void *p2
) {
struct iface *iface = (struct iface *) p1;
struct pktdrvr *pp = (struct pktdrvr *) p2;
uint len,blen;	/* len type must match size field in pkint */
struct mbuf *bp;
int cadj;

loop:
#if 0
	while ((void) disable(), i = (volatile int) pp->cnt, (void) enable(), i == 0)
#else
	while (pp->cnt == 0)
#endif
		kwait (&pp->cnt);

	cadj = 0;
	/* Extract size */
	dosmemget (pp->dosbase + pp->rptr, sizeof(len), &len);
	if (len == 0)	{
		/* Writer wrapped around */
		cadj += pp->dossize - pp->rptr;
		pp->rptr = 0;
		dosmemget (pp->dosbase, sizeof(len), &len);
	}
	/* Copy the packet into an mbuf and queue for the router */
	bp = ambufw (len + sizeof(struct iface *));
	bp->data += sizeof(struct iface *);
#ifdef	debug
	printf ("overf %d cnt %d start %d len %d\n", pp->overflows,
		pp->cnt, pp->rptr + sizeof(len), len);
#endif
	dosmemget (pp->dosbase + pp->rptr + sizeof(len), len, bp->data);
	bp->cnt = len;
	(void) net_route (iface, pp->class, bp);

	/* figure length rounded up to next boundary */
	blen = sizeof(len) + (len + sizeof(len) - 1) & ~(sizeof(len)-1);

	cadj += (int) blen;
	pp->rptr += (long) blen;
	if ((int32) (pp->rptr + sizeof(len)) > pp->dossize)	{
		cadj += pp->dossize - pp->rptr;
		pp->rptr = 0;
	}
	(void) disable();
	pp->cnt -= cadj;
	(void) enable();

	goto loop;
}


#endif /* PACKET */

