/*
 * Driver for PAMs DMA ethernet adaptors (C) 1995 by Torsten Lang.
 * Ethernet driver skeleton (C) Kay Roemer
 *
 * This driver has been developed and tested on a 8MHz ST. If you run in
 * on substantially faster machines a few wait()'s may be needed here and
 * there. Please contact me in case of success or failure...
 *
 * Important: The driver must be loaded to ST RAM, otherwise weird things
 *            will happen!!!
 *
 * TO DOs:
 * - I don't know how the adaptor reacts when heavy traffic floods the 
 *   receive buffers. The final driver should handle this.
 * - At the moment the adaptor is polled for received packets. It should be
 *   able to generate interrupts when selected. So the max. receive rate is
 *   somewhat limited which may be a problem with heavy broadcasting on the
 *   net.
 * - I don't know how to get the exact length of received packets. At the
 *   moment the DMA counter is checked to determine the packet length.
 * - Multicasting may never work since I don't know how to configure these
 *   internal things of the adaptor.
 * - Cache management isn't implemented yet - so you may run into trouble on
 *   processors > 68020.
 *
 * Thanks to...
 * I have to thank Kay Roemer for his great networking package and his support.
 *
 * No thanks to...
 * I'm waiting for months to get support (information about the PAM's boards)
 * from:
 * GK Computer
 * M. V”lkel
 * 7800 Freiburg
 * Phone: 0761/409061
 * Fax:   0761/408617
 * who developed the PAM's DMA ethernet adaptors.
 *
 * On a 16MHz ST the driver reaches the following throughput:
 * ftp send:  ~ k/sec (ncftp 1.7.5 -> wu-ftpd 2.0)
 * ftp recv:  ~ k/sec (ncftp 1.7.5 <- wu-ftpd 2.0)
 * tcp send:  ~ k/sec (tcpcl -> tcpsv)
 * tcp recv:  ~ k/sec (tcpsv <- tcpcl)
 * nfs read:  ~ k/sec
 * nfs write: ~ k/sec
 *
 * 01/15/94, Kay Roemer,
 * 09/14/95, Torsten Lang.
 */

#include <string.h>
#include "config.h"
#include "netinfo.h"
#include "kerbind.h"
#include "atarierr.h"
#include "sockios.h"
#include "sockerr.h"
#include "buf.h"
#include "if.h"
#include "ifeth.h"
#include "pamsdma.h"
#include "util.h"

void (*ihandler) (void) = 0;

static struct netif if_lance;

static long	lance_open	(struct netif *);
static long	lance_close	(struct netif *);
static long	lance_output	(struct netif *, BUF *, char *, short, short);
static long	lance_ioctl	(struct netif *, short, long);

static long	lance_probe	(struct netif *);
static long	lance_reset	(struct netif *);
static void	lance_install_ints (void);
static void	lance_int	(struct netif *);
static void	timeout_int(void);

static void	start (int target);
static int	stop (int target);
static int	testpkt (int target);
static int	sendpkt (int target, u_char *buffer, int length);
static int	receivepkt (int target, u_char *buffer);
static int	inquiry (int target);
static HADDR	*read_hw_addr(int target);
static int	timeout (long timeoutcounter);
static void	setup_dma (void *address, u_short rw_flag, int num_blocks);
static int	send_first (int target, u_char byte);
static int	send_1_5 (int lun, u_char *command, int dma);
static int	get_status (void);
static int	calc_received (void *start_address);

char dma_buffer[2048];
u_short rw;
int lance_target = -1;
int up = 0;
int pending = 0;

static long
lance_open (nif)
	struct netif *nif;
{
	if (!LOCK)
		return -1;
	up = 1;
	start (lance_target);
	UNLOCK;
	return 0;
}

static long
lance_close (nif)
	struct netif *nif;
{
	int i;
	if (!LOCK)
		return -1;
	up = 0;
	i = stop (lance_target);
	UNLOCK;
	return i;
}

static long
lance_output (nif, buf, hwaddr, hwlen, pktype)
	struct netif *nif;
	BUF *buf;
	char *hwaddr;
	short hwlen, pktype;
{
	short len;
	BUF *nbuf;
	int i, j;
	long r;

	if (0 == (nbuf = eth_build_hdr (buf, nif, hwaddr, pktype))) {
		++nif->out_errors;
		return ENSMEM;
	}
	if (nif->bpf)
		bpf_input (nif, nbuf);

	if (!LOCK) {
		if ((r = if_enqueue (&nif->snd, buf, buf->info))) {
			++nif->out_errors;
			return r;
		}
		return 0;
	}

	len = nbuf->dend - nbuf->dstart;
	memcpy (dma_buffer, nbuf->dstart, len);
	if (len < MIN_LEN)
		len = MIN_LEN;
	buf_deref (nbuf, BUF_NORMAL);
	if ((j = sendpkt (lance_target, dma_buffer, len)))
		++nif->out_errors;
	else
		for (i = 1; i < 5; i++)
			if (addroottimeout(TIMEOUTCOUNTER>>i, timeout_int, 0))
				pending++;
	UNLOCK;
	return(j);
}

static long
lance_ioctl (nif, cmd, arg)
	struct netif *nif;
	short cmd;
	long arg;
{
	switch (cmd) {
	case SIOCSIFNETMASK:
	case SIOCSIFFLAGS:
	case SIOCSIFADDR:
		return 0;

	case SIOCSIFMTU:
		/*
		 * Limit MTU to 1500 bytes. MintNet has already set nif->mtu
		 * to the new value, we only limit it here.
		 */
		if (nif->mtu > ETH_MAX_DLEN)
			nif->mtu = ETH_MAX_DLEN;
		return 0;
	}
	return EINVFN;
}

long
driver_init (void)
{
	static char message[100];

	strcpy (if_lance.name, "en");
	if_lance.unit = if_getfreeunit ("en");
	if_lance.metric = 0;
	if_lance.flags = IFF_BROADCAST;
	if_lance.mtu = 1500;
	if_lance.timer = TIMEOUTCOUNTER;
	if_lance.hwtype = HWTYPE_ETH;
	if_lance.hwlocal.len =
	if_lance.hwbrcst.len = ETH_ALEN;

	if_lance.rcv.maxqlen = IF_MAXQ;
	if_lance.snd.maxqlen = IF_MAXQ;

	if_lance.open = lance_open;
	if_lance.close = lance_close;
	if_lance.output = lance_output;
	if_lance.ioctl = lance_ioctl;

	if_lance.timeout = lance_int ;

	if_lance.data = 0;

	/*
	 * Tell upper layers the max. number of packets we are able to
	 * receive in fast succession.
	 */
	if_lance.maxpackets = 0;

	if (lance_probe (&if_lance) < 0) {
		c_conws ("lance DMA adaptor: driver not installed.\r\n");
		return 1;
	}
	lance_install_ints ();

	if_register (&if_lance);

	sprintf (message, "LANCE driver for PAM's DMA v%s (en%d) "
		 "(%x:%x:%x:%x:%x:%x) on target %d\r\n",
		"0.1á",
		if_lance.unit,
		if_lance.hwlocal.addr[0],
		if_lance.hwlocal.addr[1],
		if_lance.hwlocal.addr[2],
		if_lance.hwlocal.addr[3],
		if_lance.hwlocal.addr[4],
		if_lance.hwlocal.addr[5],
		lance_target);
	c_conws (message);
	return 0;
}

static long
lance_reset (nif)
	struct netif *nif;
{
	if (!LOCK)
		return -1;
	stop (lance_target);
	start (lance_target);
	UNLOCK;
	return 0;
}

static long
lance_probe (nif)
	struct netif *nif;
{
	int i;
	HADDR *hwaddr;
	
	if (!LOCK)
		return -1;

	for (i=0; i<8; i++)
		if (!inquiry(i)) {
			lance_target = i;
			break;
		}
	if (lance_target < 0) {
		UNLOCK;
		return -1;
	}
	if (!(hwaddr = read_hw_addr(lance_target))) {
		UNLOCK;
		return -1;
	}
	
	memcpy (nif->hwbrcst.addr, "\377\377\377\377\377\377", ETH_ALEN);
	memcpy (nif->hwlocal.addr, hwaddr, ETH_ALEN);

	UNLOCK;
	return(0);
}

/*
 * LANCE interrupt routine
 */
static void
timeout_int(void)
{
	pending--;
	lance_int (&if_lance);
}

static void
lance_int (nif)
	struct netif *nif;
{
	int len, i;
	BUF *buf;

	if (!up)
		return;

	if (!LOCK)
		goto bad;

	i = 0;
	while ((buf = if_dequeue(&nif->snd))) {
		i = 1;
		len = buf->dend - buf->dstart;
		memcpy (dma_buffer, buf->dstart, len);
		if (len < MIN_LEN)
			len = MIN_LEN;
		buf_deref (buf, BUF_NORMAL);
		if (sendpkt (lance_target, dma_buffer, len))
			++nif->out_errors;
	}
	while (testpkt(lance_target) > 0) {
		len = receivepkt(lance_target, dma_buffer);
		buf = buf_alloc (len+100, 50, BUF_NORMAL);
		buf->dend = buf->dstart + len;
		memcpy (buf->dstart, dma_buffer, len);
		if (nif->bpf)
			bpf_input (nif, buf);
		if (!if_input (nif, buf, 0, eth_remove_hdr (buf)))
			++nif->in_packets;
		else
			++nif->in_errors;
	}
	if (i) {
		for (i = 0 ; i < 5 ; i++)
			if (addroottimeout(TIMEOUTCOUNTER>>i, timeout_int, 0))
				pending++;
	}
	UNLOCK;
bad:
	/*
	 * This can be removed when the if_lance.timer works (at the moment 
	 * if_lance.timer is called every second independent of the value of
	 * if_lance.timer).
	 */
	if (!pending && addroottimeout(TIMEOUTCOUNTER, timeout_int, 0))
		++pending;
}

extern void lance_vbi_int (void);
extern long old_vbi;

/*
 * Install interrupt handler
 */

static void
lance_install_ints (void)
{
#if 0
	short sr;

	sr = spl7 ();

	old_vbi = s_etexec ((short)VBI, (long)lance_vbi_int);

	spl (sr);
#endif
}

/* start() starts the PAM's DMA adaptor and returns a value of zero in case of success */

static void
start (target)
	int target;
{
	send_first(target, START);
}

/* stop() stops the PAM's DMA adaptor and returns a value of zero in case of success */

static int
stop (target)
	int target;
{
	int ret = -1;

	if (send_first(target, STOP))
		goto bad;
	dma_buffer[0] = dma_buffer[1] = dma_buffer[2] =
	dma_buffer[3] = dma_buffer[4] = 0;
	if (send_1_5(lance_target, dma_buffer, 0) ||
	    timeout(TIMEOUTDMA) ||
	    get_status())
		goto bad;
	ret = 0;
bad:
	return (ret);
}

/* testpkt() returns the number of received packets waiting in the queue */

static int
testpkt(target)
	int target;
{
	int ret = -1;

	if (send_first(target, NUMPKTS))
		goto bad;
	ret = get_status();
bad:
	return (ret);
}

/* sendpkt() sends a packet and returns a value of zero when the packet was sent
             successfully */

static int
sendpkt (target, buffer, length)
	int target;
	u_char *buffer;
	int length;
{
	int ret = -1;

	if (send_first(target, WRITEPKT))
		goto bad;
	setup_dma(dma_buffer, WRITE, 3);
	dma_buffer[0] = dma_buffer[1] = dma_buffer[4] = 0;
	dma_buffer[2] = length >> 8;
	dma_buffer[3] = length & 0xFF;
	if (send_1_5(7, dma_buffer, 1) ||
	    timeout(TIMEOUTDMA) ||
	    get_status())
		goto bad;
	ret = 0;
bad:
	return (ret);
}

/* receivepkt() loads a packet to a given buffer and returns its length */

static int
receivepkt (target, buffer)
	int target;
	u_char *buffer;
{
	int ret = -1;

	if (send_first(target, READPKT))
		goto bad;
	setup_dma(dma_buffer, READ, 3);
	dma_buffer[0] = dma_buffer[1] = dma_buffer[2] = dma_buffer[4] = 0;
	dma_buffer[3] = 3;
	if (send_1_5(7, dma_buffer, 1) ||
	    timeout(TIMEOUTDMA) ||
	    get_status())
		goto bad;
	ret = calc_received(dma_buffer);
bad:
	return (ret);
}

/* inquiry() returns 0 when PAM's DMA found, -1 when timeout, -2 otherwise */

static int 
inquiry (target)
	int target;
{
	int ret = -1;

	if (send_first(target, INQUIRY))
		goto bad;
	setup_dma(dma_buffer, READ, 1);
	dma_buffer[0] = dma_buffer[1] = dma_buffer[2] = dma_buffer[4] = 0;
	dma_buffer[3] = 48;
	if (send_1_5(5, dma_buffer, 1) ||
	    timeout(TIMEOUTDMA) ||
	    get_status() ||
	    memcmp(inquire8, dma_buffer+8, 20))
		goto bad;
	ret = 0;
bad:
	return (ret);
}

/* 
 * readhwaddr() reads the sector containing the hwaddr and returns
 * a pointer to it or 0 in case of an error
 */
static HADDR 
*read_hw_addr(target)
	int target;
{
	HADDR *ret = 0;

	if (send_first(target, READSECTOR))
		goto bad;
	setup_dma(dma_buffer, READ, 1);
	dma_buffer[0] = dma_buffer[1] = dma_buffer[2] = dma_buffer[4] = 0;
	dma_buffer[3] = 1;
	if (send_1_5(5, dma_buffer, 1) ||
	    timeout(TIMEOUTDMA) ||
	    get_status())
		goto bad;
	ret = (HADDR *)&(((DMAHWADDR *)dma_buffer)->hwaddr);
bad:
	return (ret);
}

static int
timeout (timeoutcounter)
	long timeoutcounter;
{
	long i;
	
	i = HZ200 + timeoutcounter;
	while (HZ200 - i < 0)
		if (INT) return (0);

	return (-1);
}

static void 
setup_dma (address, rw_flag, num_blocks)
	void *address;
	u_short rw_flag;
	int num_blocks;
{
	WRITEMODE((u_short) rw_flag          | DMA_FDC | SEC_COUNT | REG_ACSI |
		  A1);
	WRITEMODE((u_short)(rw_flag ^ WRITE) | DMA_FDC | SEC_COUNT | REG_ACSI |
		  A1);
	WRITEMODE((u_short) rw_flag          | DMA_FDC | SEC_COUNT | REG_ACSI |
		  A1);
	DMALOW  = (u_char)((u_long)address & 0xFF);
	DMAMID  = (u_char)(((u_long)address >>  8) & 0xFF);
	DMAHIGH = (u_char)(((u_long)address >> 16) & 0xFF);
	WRITEBOTH((u_short)num_blocks & 0xFF,
		  rw_flag | DMA_FDC | DMA_WINDOW | REG_ACSI | A1);
	rw = rw_flag;
}

static int 
send_first (target, byte)
	int target;
	u_char byte;
{
	rw = READ;
	/*
	 * wake up ACSI
	 */
	WRITEMODE(DMA_FDC | DMA_WINDOW | REG_ACSI);
	/*
	 * write command byte
	 */
	WRITEBOTH((target << 5) | (byte & 0x1F), DMA_FDC |
	          DMA_WINDOW | REG_ACSI | A1);
	return (timeout (TIMEOUTCMD));
}

static int 
send_1_5 (lun, command, dma)
	int lun;
	u_char *command;
	int dma;
{
	int i, j;
	
	for (i=0; i<5; i++) {
		WRITEBOTH((!i ? (((lun & 0x7) << 5) | (command[i] & 0x1F))
			      : command[i]),
			  rw | REG_ACSI | DMA_WINDOW |
			   ((i < 4) ? DMA_FDC
				    : (dma ? DMA_ACSI
					   : DMA_FDC)) | A1);
		if (i < 4 && (j = timeout(TIMEOUTCMD)))
			return (j);
	}
	return (0);
}

static int 
get_status (void)
{
	WRITEMODE(DMA_FDC | DMA_WINDOW | REG_ACSI | A1);
	return ((int)(DACCESS & 0xFF));
}

static int 
calc_received (start_address)
	void *start_address;
{
	return (int)(
		(((u_long)DMAHIGH << 16) | ((u_short)DMAMID << 8) | DMALOW)
	      - (u_long)start_address);
}
