/*
* UAE - The Un*x Amiga Emulator
*
* SanaII emulation
*
* partially based on code from 3c589 PCMCIA driver by Neil Cafferke
*
* Copyright 2007 Toni Wilen
*
*/

#include "sysconfig.h"
#include "sysdeps.h"

#include "threaddep/thread.h"
#include "options.h"
#include "uae/memory.h"
#include "custom.h"
#include "events.h"
#include "newcpu.h"
#include "autoconf.h"
#include "traps.h"
#include "execlib.h"
#include "native2amiga.h"
#include "blkdev.h"
#include "uae.h"
#include "sana2.h"
#if defined(_WIN32) && defined(WITH_UAENET_PCAP)
#include "win32_uaenet.h"
#else
#include "ethernet.h"
#endif
#include "execio.h"

// these variables are referenced by custom.cpp and newcpu.cpp also when
// SANA2 is not defined

volatile int uaenet_int_requested = 0;
volatile int uaenet_vsync_requested = 0;

#ifdef SANA2

static void uaenet_gotdata (void *dev, const uae_u8 *data, int len);
static int uaenet_getdata (void *dev, uae_u8 *d, int *len);

#define SANA2NAME _T("uaenet.device")

#define MAX_ASYNC_REQUESTS 200
#define MAX_OPEN_DEVICES 20

#define S2_START                (CMD_NONSTD)	// 9
#define S2_DEVICEQUERY          (S2_START+ 0)	// 9
#define S2_GETSTATIONADDRESS    (S2_START+ 1)	// 10
#define S2_CONFIGINTERFACE      (S2_START+ 2)	// 11
#define S2_ADDMULTICASTADDRESS  (S2_START+ 5)	// 14
#define S2_DELMULTICASTADDRESS  (S2_START+ 6)	// 15
#define S2_MULTICAST            (S2_START+ 7)	// 16
#define S2_BROADCAST            (S2_START+ 8)	// 17
#define S2_TRACKTYPE            (S2_START+ 9)	// 18
#define S2_UNTRACKTYPE          (S2_START+10)	// 19
#define S2_GETTYPESTATS         (S2_START+11)	// 20
#define S2_GETSPECIALSTATS      (S2_START+12)	// 21
#define S2_GETGLOBALSTATS       (S2_START+13)	// 22
#define S2_ONEVENT              (S2_START+14)	// 23
#define S2_READORPHAN           (S2_START+15)	// 24
#define S2_ONLINE               (S2_START+16)	// 25
#define S2_OFFLINE              (S2_START+17)	// 26
#define S2_ADDMULTICASTADDRESSES  0xC000
#define S2_DELMULTICASTADDRESSES  0xC001
#define S2_GETPEERADDRESS         0xC002
#define S2_GETDNSADDRESS          0xC003
#define S2_GETEXTENDEDGLOBALSTATS 0xC004
#define S2_CONNECT                0xC005
#define S2_DISCONNECT             0xC006
#define S2_SAMPLE_THROUGHPUT      0xC007

#define S2WireType_Ethernet             1
#define S2WireType_IEEE802              6

#define SANA2_MAX_ADDR_BITS     (128)
#define SANA2_MAX_ADDR_BYTES    ((SANA2_MAX_ADDR_BITS+7)/8)
#define ADDR_SIZE 6
#define ETH_HEADER_SIZE (ADDR_SIZE + ADDR_SIZE + 2)

#define S2ERR_NO_ERROR          0       /* peachy-keen                  */
#define S2ERR_NO_RESOURCES      1       /* resource allocation failure  */
#define S2ERR_BAD_ARGUMENT      3       /* garbage somewhere            */
#define S2ERR_BAD_STATE         4       /* inappropriate state          */
#define S2ERR_BAD_ADDRESS       5       /* who?                         */
#define S2ERR_MTU_EXCEEDED      6       /* too much to chew             */
#define S2ERR_NOT_SUPPORTED     8       /* hardware can't support cmd   */
#define S2ERR_SOFTWARE          9       /* software error detected      */
#define S2ERR_OUTOFSERVICE      10      /* driver is OFFLINE            */
#define S2ERR_TX_FAILURE        11      /* Transmission attempt failed  */

#define S2WERR_GENERIC_ERROR    0       /* no specific info available   */
#define S2WERR_NOT_CONFIGURED   1       /* unit not configured          */
#define S2WERR_UNIT_ONLINE      2       /* unit is currently online     */
#define S2WERR_UNIT_OFFLINE     3       /* unit is currently offline    */
#define S2WERR_ALREADY_TRACKED  4       /* protocol already tracked     */
#define S2WERR_NOT_TRACKED      5       /* protocol not tracked         */
#define S2WERR_BUFF_ERROR       6       /* buff mgt func returned error */
#define S2WERR_SRC_ADDRESS      7       /* source address problem       */
#define S2WERR_DST_ADDRESS      8       /* destination address problem  */
#define S2WERR_BAD_BROADCAST    9       /* broadcast address problem    */
#define S2WERR_BAD_MULTICAST    10      /* multicast address problem    */
#define S2WERR_MULTICAST_FULL   11      /* multicast address list full  */
#define S2WERR_BAD_EVENT        12      /* unsupported event class      */
#define S2WERR_BAD_STATDATA     13      /* statdata failed sanity check */
#define S2WERR_IS_CONFIGURED    15      /* attempt to config twice      */
#define S2WERR_NULL_POINTER     16      /* null pointer detected        */
#define S2WERR_TOO_MANY_RETIRES 17      /* tx failed - too many retries */
#define S2WERR_RCVREL_HDW_ERR   18      /* Driver fixable HW error      */

#define S2EVENT_ERROR           (1L<<0) /* error catch all              */
#define S2EVENT_TX              (1L<<1) /* transmitter error catch all  */
#define S2EVENT_RX              (1L<<2) /* receiver error catch all     */
#define S2EVENT_ONLINE          (1L<<3) /* unit is in service           */
#define S2EVENT_OFFLINE         (1L<<4) /* unit is not in service       */
#define S2EVENT_BUFF            (1L<<5) /* buff mgt function error      */
#define S2EVENT_HARDWARE        (1L<<6) /* hardware error catch all     */
#define S2EVENT_SOFTWARE        (1L<<7) /* software error catch all     */

#define KNOWN_EVENTS (S2EVENT_ERROR|S2EVENT_TX|S2EVENT_RX|S2EVENT_ONLINE|\
	S2EVENT_OFFLINE|S2EVENT_BUFF|S2EVENT_HARDWARE|S2EVENT_SOFTWARE)

#define SANA2OPB_MINE   0
#define SANA2OPF_MINE   (1<<SANA2OPB_MINE)
#define SANA2OPB_PROM   1
#define SANA2OPF_PROM   (1<<SANA2OPB_PROM)
#define SANA2IOB_RAW    7 
#define SANA2IOF_RAW    (1<<SANA2IOB_RAW)
#define SANA2IOB_BCAST  6
#define SANA2IOF_BCAST  (1<<SANA2IOB_BCAST)
#define SANA2IOB_MCAST  5
#define SANA2IOF_MCAST  (1<<SANA2IOB_MCAST)

#define S2_Dummy                (TAG_USER + 0xB0000)
#define S2_CopyToBuff           (S2_Dummy + 1)
#define S2_CopyFromBuff         (S2_Dummy + 2)
#define S2_PacketFilter         (S2_Dummy + 3)
#define S2_CopyToBuff16         (S2_Dummy + 4)
#define S2_CopyFromBuff16       (S2_Dummy + 5)
#define S2_CopyToBuff32         (S2_Dummy + 6)
#define S2_CopyFromBuff32       (S2_Dummy + 7)
#define S2_DMACopyToBuff32      (S2_Dummy + 8)
#define S2_DMACopyFromBuff32    (S2_Dummy + 9)

#define SANA2_IOREQSIZE (32 + 4 + 4 + SANA2_MAX_ADDR_BYTES * 2 + 4 + 4 + 4 + 4)

struct s2packet {
	struct s2packet *next;
	uae_u8 *data;
	int len;
};

static int uaenet_int_late;

static uaecptr timerdevname;

static uaecptr ROM_netdev_resname = 0,
	ROM_netdev_resid = 0,
	ROM_netdev_init = 0;

static const TCHAR *getdevname (void)
{
	return _T("uaenet.device");
}

struct asyncreq {
	struct asyncreq *next;
	uaecptr request;
	struct s2packet *s2p;
	int ready;
};

struct mcast {
	struct mcast *next;
	uae_u64 start;
	uae_u64 end;
	int cnt;
};

struct s2devstruct {
	int unit, opencnt, exclusive, promiscuous;
	struct asyncreq *ar;
	struct asyncreq *s2p;
	struct mcast *mc;
	smp_comm_pipe requests;
	int thread_running;
	uae_sem_t sync_sem;
	void *sysdata;
	uae_u32 packetsreceived;
	uae_u32 packetssent;
	uae_u32 baddata;
	uae_u32 overruns;
	uae_u32 unknowntypesreceived;
	uae_u32 reconfigurations;
	uae_u32 online_micro;
	uae_u32 online_secs;
	int configured;
	int adapter;
	int online;
	struct netdriverdata *td;
	struct s2packet *readqueue;
	uae_u8 mac[ADDR_SIZE];
	int flush_timeout;
	int flush_timeout_cnt;
};

#define FLUSH_TIMEOUT 20

struct priv_s2devstruct {
	int inuse;
	int unit;
	int flags; /* OpenDevice() */
	int promiscuous;

	uae_u8 tracks[65536];
	int trackcnt;
	uae_u32 packetsreceived;
	uae_u32 packetssent;
	uae_u32 bytessent;
	uae_u32 bytesreceived;
	uae_u32 packetsdropped;

	uaecptr copytobuff;
	uaecptr copyfrombuff;
	uaecptr packetfilter;
	uaecptr tempbuf;

	uaecptr timerbase;

	struct netdriverdata *td;

	int tmp;
};

static struct netdriverdata *td[MAX_TOTAL_NET_DEVICES + 1];
static struct s2devstruct devst[MAX_TOTAL_NET_DEVICES];
static struct priv_s2devstruct pdevst[MAX_OPEN_DEVICES];
static uae_u32 nscmd_cmd;
static uae_sem_t change_sem, async_sem;

static struct s2devstruct *gets2devstruct (int unit)
{
	if (unit >= MAX_TOTAL_NET_DEVICES || unit < 0)
		return 0;
	return &devst[unit];
}

static struct priv_s2devstruct *getps2devstruct (uaecptr request)
{
	int i = get_long (request + 24);
	if (i < 0 || i >= MAX_OPEN_DEVICES || pdevst[i].inuse == 0) {
		write_log (_T("%s: corrupt iorequest %08X %d\n"), SANA2NAME, request, i);
		return 0;
	}
	return &pdevst[i];
}

static void *dev_thread (void *devs);
static int start_thread (struct s2devstruct *dev)
{
	if (dev->thread_running)
		return 1;
	init_comm_pipe (&dev->requests, 100, 1);
	uae_sem_init (&dev->sync_sem, 0, 0);
	uae_start_thread (SANA2NAME, dev_thread, dev, NULL);
	uae_sem_wait (&dev->sync_sem);
	return dev->thread_running;
}

static uae_u32 REGPARAM2 dev_close_2 (TrapContext *context)
{
	uae_u32 request = m68k_areg (regs, 1);
	struct priv_s2devstruct *pdev = getps2devstruct (request);
	struct s2devstruct *dev;

	if (!pdev) {
		write_log (_T("%s close with unknown request %08X!?\n"), SANA2NAME, request);
		return 0;
	}
	dev = gets2devstruct (pdev->unit);
	if (!dev) {
		write_log (_T("%s:%d close with unknown request %08X!?\n"), SANA2NAME, pdev->unit, request);
		return 0;
	}
	if (log_net)
		write_log (_T("%s:%d close, open=%d req=%08X\n"), SANA2NAME, pdev->unit, dev->opencnt, request);
	put_long (request + 24, 0);
	dev->opencnt--;
	pdev->inuse = 0;
	if (!dev->opencnt) {
		dev->exclusive = 0;
		if (pdev->tempbuf) {
			m68k_areg (regs, 1) = pdev->tempbuf;
			m68k_dreg (regs, 0) = pdev->td->mtu + ETH_HEADER_SIZE + 2;
			CallLib (context, get_long (4), -0xD2); /* FreeMem */
			pdev->tempbuf = 0;
		}
		ethernet_close (pdev->td, dev->sysdata);
		xfree (dev->sysdata);
		dev->sysdata = NULL;
		write_comm_pipe_u32 (&dev->requests, 0, 1);
		write_log (_T("%s: opencnt == 0, all instances closed\n"), SANA2NAME);
	}
	put_word (m68k_areg (regs, 6) + 32, get_word (m68k_areg (regs, 6) + 32) - 1);
	return 0;
}

static uae_u32 REGPARAM2 dev_close (TrapContext *context)
{
	return dev_close_2 (context);
}
static uae_u32 REGPARAM2 diskdev_close (TrapContext *context)
{
	return dev_close_2 (context);
}

static int openfail (uaecptr ioreq, int error)
{
	put_long (ioreq + 20, -1);
	put_byte (ioreq + 31, error);
	put_long (ioreq + 32, 0); /* io_device */
	if (log_net)
		write_log (_T("-> failed with error %d\n"), error);
	return (uae_u32)-1;
}

static uae_u32 REGPARAM2 uaenet_int_handler (TrapContext *ctx);
static int irq_init;
static int initint (TrapContext *ctx)
{
	uae_u32 tmp1;
	uaecptr p;

	if (irq_init)
		return 1;
	m68k_dreg (regs, 0) = 26;
	m68k_dreg (regs, 1) = 65536 + 1;
	p = CallLib (ctx, get_long (4), -0xC6); /* AllocMem */
	if (!p)
		return 0;
	tmp1 = here ();
	calltrap (deftrap2 (uaenet_int_handler, TRAPFLAG_EXTRA_STACK, _T("uaenet_int_handler")));
	put_word (p + 8, 0x020a);
	put_long (p + 10, ROM_netdev_resid);
	put_long (p + 18, tmp1);
	m68k_areg (regs, 1) = p;
	m68k_dreg (regs, 0) = 3; /* PORTS */
	dw (0x4a80); /* TST.L D0 */
	dw (0x4e75); /* RTS */
	CallLib (ctx, get_long (4), -168); /* AddIntServer */
	irq_init = 1;
	return 1;
}

static uae_u32 REGPARAM2 dev_open_2 (TrapContext *context)
{
	uaecptr ioreq = m68k_areg (regs, 1);
	uae_u32 unit = m68k_dreg (regs, 0);
	uae_u32 flags = m68k_dreg (regs, 1);
	uaecptr buffermgmt;
	struct s2devstruct *dev = gets2devstruct (unit);
	struct priv_s2devstruct *pdev = 0;
	int i;
	uaecptr tagp, tagpnext;

	if (!dev)
		return openfail (ioreq, IOERR_OPENFAIL);
	if (!initint(context))
		return openfail (ioreq, IOERR_SELFTEST);
	if (log_net)
		write_log (_T("opening %s:%d opencnt=%d ioreq=%08X\n"), SANA2NAME, unit, dev->opencnt, ioreq);
	if (get_word (ioreq + 0x12) < IOSTDREQ_SIZE)
		return openfail (ioreq, IOERR_BADLENGTH);
	if ((flags & SANA2OPF_PROM) && dev->opencnt > 0)
		return openfail (ioreq, IOERR_UNITBUSY);

	for (i = 0; i < MAX_OPEN_DEVICES; i++) {
		pdev = &pdevst[i];
		if (pdev->inuse == 0)
			break;
	}
	if (i == MAX_OPEN_DEVICES)
		return openfail (ioreq, IOERR_UNITBUSY);

	put_long (ioreq + 24, pdev - pdevst);
	pdev->unit = unit;
	pdev->flags = flags;
	pdev->inuse = 1;
	pdev->td = td ? td[unit] : NULL;
	pdev->promiscuous = (flags & SANA2OPF_PROM) ? 1 : 0;

	if (pdev->td == NULL || pdev->td->active == 0)
		return openfail (ioreq, IOERR_OPENFAIL);

	if (dev->opencnt == 0) {
		dev->unit = unit;
		dev->sysdata = xcalloc (uae_u8, ethernet_getdatalenght (pdev->td));
		if (!ethernet_open (pdev->td, dev->sysdata, dev, uaenet_gotdata, uaenet_getdata, pdev->promiscuous)) {
			xfree (dev->sysdata);
			dev->sysdata = NULL;
			return openfail (ioreq, IOERR_OPENFAIL);
		}
		write_log (_T("%s: initializing unit %d\n"), getdevname (), unit);
		dev->td = pdev->td;
		dev->adapter = pdev->td ? pdev->td->active : 0;
		if (dev->adapter) {
			dev->online = 1;
			dev->configured = 1;
		}
		start_thread (dev);
	}

	if (kickstart_version >= 36) {
		m68k_areg (regs, 0) = get_long (4) + 350;
		m68k_areg (regs, 1) = timerdevname;
		CallLib (context, get_long (4), -0x114); /* FindName('timer.device') */
		pdev->timerbase = m68k_dreg (regs, 0);
	}

	pdev->copyfrombuff = pdev->copytobuff = pdev->packetfilter = 0;
	pdev->tempbuf = 0;
	if (get_word (ioreq + 0x12) >= SANA2_IOREQSIZE) {
		buffermgmt = get_long (ioreq + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES * 2 + 4 + 4 + 4);
		tagpnext = buffermgmt;
		while (tagpnext) {
			uae_u32 tag = get_long (tagpnext);
			uae_u32 val = get_long (tagpnext + 4);
			tagp = tagpnext;
			tagpnext += 8;
			switch (tag)
			{
			case TAG_DONE:
				tagpnext = 0;
				break;
			case TAG_IGNORE:
				break;
			case TAG_MORE:
				tagpnext = val;
				break;
			case TAG_SKIP:
				tagpnext = tagp + val * 8;
				break;
			case S2_CopyToBuff:
				pdev->copytobuff = val;
				break;
			case S2_CopyFromBuff:
				pdev->copyfrombuff = val;
				break;
			case S2_PacketFilter:
				pdev->packetfilter = val;
				break;
			}
		}
		if (log_net)
			write_log (_T("%s:%d CTB=%08x CFB=%08x PF=%08x\n"),
			getdevname(), unit, pdev->copytobuff, pdev->copyfrombuff, pdev->packetfilter);
		m68k_dreg (regs, 0) = dev->td->mtu + ETH_HEADER_SIZE + 2;
		m68k_dreg (regs, 1) = 1;
		pdev->tempbuf = CallLib (context, get_long (4), -0xC6); /* AllocMem */
		if (!pdev->tempbuf) {
			if (dev->opencnt == 0) {
				ethernet_close (pdev->td, dev->sysdata);
				xfree (dev->sysdata);
				dev->sysdata = NULL;
			}
			return openfail (ioreq, S2ERR_BAD_ARGUMENT);
		}
		/* buffermanagement */
		put_long (ioreq + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES * 2 + 4 + 4 + 4, pdev->tempbuf);
	}
	dev->exclusive = flags & SANA2OPF_MINE;
	dev->opencnt++;
	put_word (m68k_areg (regs, 6) + 32, get_word (m68k_areg (regs, 6) + 32) + 1);
	put_byte (ioreq + 31, 0);
	put_byte (ioreq + 8, 7);
	return 0;
}

static uae_u32 REGPARAM2 dev_open (TrapContext *context)
{
	return dev_open_2 (context);
}
static uae_u32 REGPARAM2 dev_expunge (TrapContext *context)
{
	return 0;
}
static uae_u32 REGPARAM2 diskdev_expunge (TrapContext *context)
{
	return 0;
}

static void freepacket (struct s2packet *s2p)
{
	xfree (s2p->data);
	xfree (s2p);
}

static void add_async_packet (struct s2devstruct *dev, struct s2packet *s2p, uaecptr request)
{
	struct asyncreq *ar, *ar2;

	ar = xcalloc (struct asyncreq, 1);
	ar->s2p = s2p;
	if (!dev->s2p) {
		dev->s2p = ar;
	} else {
		ar2 = dev->s2p;
		while (ar2->next)
			ar2 = ar2->next;
		ar2->next = ar;
	}
	ar->request = request;
}

static void rem_async_packet (struct s2devstruct *dev, uaecptr request)
{
	struct asyncreq *ar, *prevar;

	uae_sem_wait (&async_sem);
	ar = dev->s2p;
	prevar = NULL;
	while (ar) {
		if (ar->request == request) {
			if (prevar == NULL)
				dev->s2p = ar->next;
			else
				prevar->next = ar->next;
			uae_sem_post (&async_sem);
			freepacket (ar->s2p);
			xfree (ar);
			return;
		}
		prevar = ar;
		ar = ar->next;
	}
	uae_sem_post (&async_sem);
}

static struct asyncreq *get_async_request (struct s2devstruct *dev, uaecptr request, int ready)
{
	struct asyncreq *ar;
	int ret = 0;

	uae_sem_wait (&async_sem);
	ar = dev->ar;
	while (ar) {
		if (ar->request == request) {
			if (ready)
				ar->ready = 1;
			break;
		}
		ar = ar->next;
	}
	uae_sem_post (&async_sem);
	return ar;
}

static int add_async_request (struct s2devstruct *dev, uaecptr request)
{
	struct asyncreq *ar, *ar2;

	if (log_net)
		write_log (_T("%s:%d async request %x added\n"), getdevname(), dev->unit, request);

	uae_sem_wait (&async_sem);
	ar = xcalloc (struct asyncreq, 1);
	ar->request = request;
	if (!dev->ar) {
		dev->ar = ar;
	} else {
		ar2 = dev->ar;
		while (ar2->next)
			ar2 = ar2->next;
		ar2->next = ar;
	}
	uae_sem_post (&async_sem);
	return 1;
}

static int release_async_request (struct s2devstruct *dev, uaecptr request)
{
	struct asyncreq *ar, *prevar;

	uae_sem_wait (&async_sem);
	ar = dev->ar;
	prevar = NULL;
	while (ar) {
		if (ar->request == request) {
			if (prevar == NULL)
				dev->ar = ar->next;
			else
				prevar->next = ar->next;
			uae_sem_post (&async_sem);
			xfree (ar);
			if (log_net)
				write_log (_T("%s:%d async request %x removed\n"), getdevname(), dev->unit, request);
			return 1;
		}
		prevar = ar;
		ar = ar->next;
	}
	uae_sem_post (&async_sem);
	write_log (_T("%s:%d async request %x not found for removal!\n"), getdevname(), dev->unit, request);
	return 0;
}

static void do_abort_async (struct s2devstruct *dev, uaecptr request)
{
	put_byte (request + 30, get_byte (request + 30) | 0x20);
	put_byte (request + 31, IOERR_ABORTED);
	put_long (request + 32, S2WERR_GENERIC_ERROR);
	write_comm_pipe_u32 (&dev->requests, request, 1);
}

static void abort_async (struct s2devstruct *dev, uaecptr request)
{
	struct asyncreq *ar = get_async_request (dev, request, 1);
	if (!ar) {
		write_log (_T("%s:%d: abort async but no request %x found!\n"), getdevname(), dev->unit, request);
		return;
	}
	if (log_net)
		write_log (_T("%s:%d asyncronous request=%08X aborted\n"), getdevname(), dev->unit, request);
	do_abort_async (dev, request);
}

static void signalasync (struct s2devstruct *dev, struct asyncreq *ar, int actual, int err)
{
	uaecptr request = ar->request;
	int command = get_word (request + 28);
	if (log_net)
		write_log (_T("%s:%d CMD=%d async request %x completed\n"), getdevname(), dev->unit, command, request);
	put_long (request + 32, actual);
	put_byte (request + 31, err);
	ar->ready = 1;
	write_comm_pipe_u32 (&dev->requests, request, 1);
}

static uae_u32 copytobuff (TrapContext *ctx, uaecptr from, uaecptr to, uae_u32 len, uaecptr func)
{
	m68k_areg (regs, 0) = to;
	m68k_areg (regs, 1) = from;
	m68k_dreg (regs, 0) = len;
	return CallFunc (ctx, func);
}
static uae_u32 copyfrombuff (TrapContext *ctx, uaecptr from, uaecptr to, uae_u32 len, uaecptr func)
{
	m68k_areg (regs, 0) = to;
	m68k_areg (regs, 1) = from;
	m68k_dreg (regs, 0) = len;
	return CallFunc (ctx, func);
}
static uae_u32 packetfilter (TrapContext *ctx, uaecptr hook, uaecptr ios2, uaecptr data)
{
	uae_u32 a2, v;

	a2 = m68k_areg (regs, 2);
	m68k_areg (regs, 0) = hook;
	m68k_areg (regs, 2) = ios2;
	m68k_areg (regs, 1) = data;
	v = CallFunc (ctx, get_long (hook + 8));
	m68k_areg (regs, 2) = a2;
	return v;
}

static int isbroadcast (const uae_u8 *d)
{
	if (d[0] == 0xff && d[1] == 0xff && d[2] == 0xff &&
		d[3] == 0xff && d[4] == 0xff && d[5] == 0xff)
		return 1;
	return 0;
}
static int ismulticast (const uae_u8 *d)
{
	if (isbroadcast (d))
		return 0;
	if (d[0] & 1)
		return 1;
	return 0;
}

static uae_u64 addrto64 (const uae_u8 *d)
{
	int i;
	uae_u64 addr = 0;
	for (i = 0; i < ADDR_SIZE; i++) {
		addr <<= 8;
		addr |= d[i];
	}
	return addr;
}
static uae_u64 amigaaddrto64 (uaecptr d)
{
	int i;
	uae_u64 addr = 0;
	for (i = 0; i < ADDR_SIZE; i++) {
		addr <<= 8;
		addr |= get_byte (d + i);
	}
	return addr;
}

static void addmulticastaddresses (struct s2devstruct *dev, uae_u64 start, uae_u64 end)
{
	struct mcast *mc, *mc2;

	if (!end)
		end = start;
	mc = dev->mc;
	while (mc) {
		if (start == mc->start && end == mc->end) {
			mc->cnt++;
			return;
		}
		mc = mc->next;
	}
	mc = xcalloc (struct mcast, 1);
	mc->start = start;
	mc->end = end;
	mc->cnt = 1;
	if (!dev->mc) {
		dev->mc = mc;
	} else {
		mc2 = dev->mc;
		while (mc2->next)
			mc2 = mc2->next;
		mc2->next = mc;
	}
}
static int delmulticastaddresses (struct s2devstruct *dev, uae_u64 start, uae_u64 end)
{
	struct mcast *mc, *prevmc;

	if (!end)
		end = start;
	mc = dev->mc;
	prevmc = NULL;
	while (mc) {
		if (start == mc->start && end == mc->end) {
			mc->cnt--;
			if (mc->cnt > 0)
				return 1;
			if (prevmc == NULL)
				dev->mc = mc->next;
			else
				prevmc->next = mc->next;
			xfree (mc);
			return 1;
		}
		prevmc = mc;
		mc = mc->next;
	}
	return 0;
}

static struct s2packet *createreadpacket (struct s2devstruct *dev, const uae_u8 *d, int len)
{
	struct s2packet *s2p = xcalloc (struct s2packet, 1);
	s2p->data = xmalloc (uae_u8, dev->td->mtu + ETH_HEADER_SIZE + 2);
	memcpy (s2p->data, d, len);
	s2p->len = len;
	return s2p;
}

static int handleread (TrapContext *ctx, struct priv_s2devstruct *pdev, uaecptr request, uae_u8 *d, int len, int cmd)
{
	uae_u8 flags = get_byte (request + 30);
	uaecptr data = get_long (request + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES * 2 + 4);
	uaecptr srcaddr = request + 32 + 4 + 4;
	uaecptr dstaddr = request + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES;
	uae_u16 type = (d[2 * ADDR_SIZE] << 8) | d[2 * ADDR_SIZE + 1];
	uae_u32 v = 0;
	uaecptr data2;

	memcpyha_safe (pdev->tempbuf, d, len);
	memcpyha_safe (dstaddr, d, ADDR_SIZE);
	memcpyha_safe (srcaddr, d + ADDR_SIZE, ADDR_SIZE);
	put_long (request + 32 + 4, type);
	if (pdev->tracks[type]) {
		pdev->bytesreceived += len;
		pdev->packetsreceived++;
	}
	flags &= ~(SANA2IOF_BCAST | SANA2IOF_MCAST);
	if (isbroadcast (d))
		flags |= SANA2IOF_BCAST;
	else if (ismulticast (d))
		flags |= SANA2IOF_MCAST;
	put_byte (request + 30, flags);

	data2 = pdev->tempbuf;
	if (!(flags & SANA2IOF_RAW)) {
		len -= ETH_HEADER_SIZE;
		data2 += ETH_HEADER_SIZE;
	}
	put_long (request + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES * 2, len);

	if (pdev->packetfilter && cmd == CMD_READ && packetfilter (ctx, pdev->packetfilter, request, data2) == 0)
		return 0;
	if (!copytobuff (ctx, data2, data, len, pdev->copytobuff)) {
		put_long (request + 32, S2WERR_BUFF_ERROR);
		put_byte (request + 31, S2ERR_NO_RESOURCES);
	}
	return 1;
}

static void uaenet_gotdata (void *devv, const uae_u8 *d, int len)
{
	uae_u16 type;
	struct mcast *mc;
	struct s2packet *s2p;
	struct s2devstruct *dev = (struct s2devstruct*)devv;

	if (!dev->online)
		return;
	/* drop if bogus size */
	if (len < 20  || len >= dev->td->mtu + ETH_HEADER_SIZE + 2)
		return;
	/* drop if dst == broadcast and src == me */
	if (isbroadcast (d) && !memcmp (d + 6, dev->td->mac, ADDR_SIZE))
		return;
	/* drop if not promiscuous and dst != broadcast and dst != me */
	if (!dev->promiscuous && !isbroadcast (d) && memcmp (d, dev->td->mac, ADDR_SIZE))
		return;
	/* drop if multicast with unknown address */
	if (ismulticast (d)) {
		uae_u64 mac64 = addrto64 (d);
		/* multicast */
		mc = dev->mc;
		while (mc) {
			if (mac64 >= mc->start && mac64 <= mc->end)
				break;
			mc = mc->next;
		}
		if (!mc)
			return;
	}

	type = (d[12] << 8) | d[13];
	s2p = createreadpacket (dev, d, len);
	if (log_net)
		write_log (_T("<-DST:%02X.%02X.%02X.%02X.%02X.%02X SRC:%02X.%02X.%02X.%02X.%02X.%02X E=%04X L=%d P=%p\n"),
		d[0], d[1], d[2], d[3], d[4], d[5],
		d[6], d[7], d[8], d[9], d[10], d[11],
		type, len, s2p);
	uae_sem_wait (&async_sem);
	if (!dev->readqueue) {
		dev->readqueue = s2p;
	} else {
		struct s2packet *s2p2 = dev->readqueue;
		while (s2p2->next)
			s2p2 = s2p2->next;
		s2p2->next = s2p;
	}
	uaenet_int_requested = 1;
	uae_sem_post (&async_sem);
}

static struct s2packet *createwritepacket (TrapContext *ctx, uaecptr request)
{
	uae_u8 flags = get_byte (request + 30);
	uae_u32 datalength = get_long (request + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES * 2);
	uaecptr data = get_long (request + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES * 2 + 4);
	uaecptr srcaddr = request + 32 + 4 + 4;
	uaecptr dstaddr = request + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES;
	uae_u16 packettype = get_long (request + 32 + 4);
	struct priv_s2devstruct *pdev = getps2devstruct (request);
	struct s2packet *s2p;

	if (!pdev)
		return NULL;
	if (!copyfrombuff (ctx, data, pdev->tempbuf, datalength, pdev->copyfrombuff))
		return NULL;
	s2p = xcalloc (struct s2packet, 1);
	s2p->data = xmalloc (uae_u8, pdev->td->mtu + ETH_HEADER_SIZE + 2);
	if (flags & SANA2IOF_RAW) {
		memcpyah_safe (s2p->data, pdev->tempbuf, datalength);
		packettype = (s2p->data[2 * ADDR_SIZE + 0] << 8) | (s2p->data[2 * ADDR_SIZE + 1]);
		s2p->len = datalength;
	} else {
		memcpyah_safe (s2p->data + ETH_HEADER_SIZE, pdev->tempbuf, datalength);
		memcpy (s2p->data + ADDR_SIZE, pdev->td->mac, ADDR_SIZE);
		memcpyah_safe (s2p->data, dstaddr, ADDR_SIZE);
		s2p->data[2 * ADDR_SIZE + 0] = packettype >> 8;
		s2p->data[2 * ADDR_SIZE + 1] = packettype;
		s2p->len = datalength + ETH_HEADER_SIZE;
	}
	if (pdev->tracks[packettype]) {
		pdev->packetssent++;
		pdev->bytessent += datalength;
	}
	return s2p;
}

static int uaenet_getdata (void *devv, uae_u8 *d, int *len)
{
	int gotit;
	struct asyncreq *ar;
	struct s2devstruct *dev = (struct s2devstruct*)devv;

	uae_sem_wait (&async_sem);
	ar = dev->ar;
	gotit = 0;
	while (ar && !gotit) {
		if (!ar->ready) {
			uaecptr request = ar->request;
			int command = get_word (request + 28);
			uae_u32 packettype = get_long (request + 32 + 4);
			if (command == CMD_WRITE || command == S2_BROADCAST || command == S2_MULTICAST) {
				struct priv_s2devstruct *pdev = getps2devstruct (request);
				struct asyncreq *ars2p = dev->s2p;
				while (ars2p) {
					if (ars2p->request == request) {
						*len = ars2p->s2p->len;
						memcpy (d, ars2p->s2p->data, *len);
						if (log_net)
							write_log (_T("->DST:%02X.%02X.%02X.%02X.%02X.%02X SRC:%02X.%02X.%02X.%02X.%02X.%02X E=%04X S=%d\n"),
							d[0], d[1], d[2], d[3], d[4], d[5],
							d[6], d[7], d[8], d[9], d[10], d[11],
							packettype, *len);
						gotit = 1;
						dev->packetssent++;
						signalasync (dev, ar, *len, 0);
						break;
					}
					ars2p = ars2p->next;
				}
			}
		}
		ar = ar->next;
	}
	uae_sem_post (&async_sem);
	return gotit;
}

static void checkevents (struct s2devstruct *dev, int mask, int sem)
{
	struct asyncreq *ar;

	if (sem)
		uae_sem_wait (&async_sem);
	ar = dev->ar;
	while (ar) {
		if (!ar->ready) {
			uaecptr request = ar->request;
			int command = get_word (request + 28);
			uae_u32 cmask = get_long (request + 32);
			if (command == S2_ONEVENT && (mask & cmask))
				signalasync (dev, ar, 0, 0);
		}
		ar = ar->next;
	}
	if (sem)
		uae_sem_post (&async_sem);
}

static int checksize (uaecptr request, struct s2devstruct *dev)
{
	uae_u32 datalength = get_long (request + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES * 2);

	if (datalength > dev->td->mtu)
		return 0;
	return 1;
}

static void flush (struct priv_s2devstruct *pdev)
{
	struct asyncreq *ar;
	struct s2devstruct *dev;

	dev = gets2devstruct (pdev->unit);
	ar = dev->ar;
	while (ar) {
		if (!ar->ready && getps2devstruct (ar->request) == pdev) {
			ar->ready = 1;
			do_abort_async (dev, ar->request);
		}
		ar = ar->next;
	}
}

static int dev_do_io_2 (struct s2devstruct *dev, uaecptr request, int quick)
{
	uae_u8 flags = get_byte (request + 30);
	uae_u32 command = get_word (request + 28);
	uae_u32 packettype = get_long (request + 32 + 4);
	uaecptr data = get_long (request + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES * 2 + 4);
	uae_u32 datalength = get_long (request + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES * 2);
	uaecptr srcaddr = request + 32 + 4 + 4;
	uaecptr dstaddr = request + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES;
	uaecptr statdata = get_long (request + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES * 2 + 4 + 4);
	uaecptr buffermgmt = get_long (request + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES * 2 + 4 + 4 + 4);
	uae_u32 io_error = 0;
	uae_u32 wire_error = 0;
	int i;
	int async = 0;
	struct priv_s2devstruct *pdev = getps2devstruct (request);

	if (log_net)
		write_log (_T("S2: C=%02d T=%04X S=%02X%02X%02X%02X%02X%02X D=%02X%02X%02X%02X%02X%02X L=%d D=%08X SD=%08X BM=%08X\n"),
		command, packettype,
		get_byte (srcaddr + 0), get_byte (srcaddr + 1), get_byte (srcaddr + 2), get_byte (srcaddr + 3), get_byte (srcaddr + 4), get_byte (srcaddr + 5),
		get_byte (dstaddr + 0), get_byte (dstaddr + 1), get_byte (dstaddr + 2), get_byte (dstaddr + 3), get_byte (dstaddr + 4), get_byte (dstaddr + 5), 
		datalength, data, statdata, buffermgmt);

	if (command == CMD_READ || command == S2_READORPHAN || command == CMD_WRITE || command == S2_BROADCAST || command == S2_MULTICAST) {
		if (!pdev->copyfrombuff || !pdev->copytobuff) {
			io_error = S2ERR_BAD_ARGUMENT;
			wire_error = S2WERR_BUFF_ERROR;
			goto end;
		}
	}

	switch (command)
	{
	case CMD_READ:
		if (!dev->online)
			goto offline;
		async = 1;
		break;

	case S2_READORPHAN:
		if (!dev->online)
			goto offline;
		async = 1;
		break;

	case S2_BROADCAST:
	case CMD_WRITE:
		if (!dev->online)
			goto offline;
		if (!checksize (request, dev))
			goto toobig;
		async = 1;
		break;

	case S2_MULTICAST:
		if (!dev->online)
			goto offline;
		if ((get_byte (dstaddr + 0) & 1) == 0) {
			io_error = S2ERR_BAD_ADDRESS;
			wire_error = S2WERR_BAD_MULTICAST;
			goto end;
		}
		if (!checksize (request, dev))
			goto toobig;
		async = 1;
		break;

	case CMD_FLUSH:
		dev->flush_timeout_cnt = 0;
		dev->flush_timeout = FLUSH_TIMEOUT;
		if (log_net)
			write_log (_T("CMD_FLUSH started %08x\n"), request);
		uae_sem_wait (&async_sem);
		flush (pdev);
		uae_sem_post (&async_sem);
		async = 1;
		uaenet_vsync_requested++;
		break;

	case S2_ADDMULTICASTADDRESS:
		addmulticastaddresses (dev, amigaaddrto64 (srcaddr), 0);
		break;
	case S2_DELMULTICASTADDRESS:
		if (!delmulticastaddresses (dev, amigaaddrto64 (srcaddr), 0)) {
			io_error = S2ERR_BAD_STATE;
			wire_error = S2WERR_BAD_MULTICAST;
		}
		break;
	case S2_ADDMULTICASTADDRESSES:
		addmulticastaddresses (dev, amigaaddrto64 (srcaddr), amigaaddrto64 (dstaddr));
		break;
	case S2_DELMULTICASTADDRESSES:
		if (!delmulticastaddresses (dev, amigaaddrto64 (srcaddr), amigaaddrto64 (dstaddr))) {
			io_error = S2ERR_BAD_STATE;
			wire_error = S2WERR_BAD_MULTICAST;
		}
		break;

	case S2_DEVICEQUERY:
		{
			int size = get_long (statdata);
			if (size > 30)
				size = 30;
			put_long (statdata + 4, size);
			if (size >= 12)
				put_long (statdata + 8, 0);
			if (size >= 16)
				put_long (statdata + 12, 0);
			if (size >= 18)
				put_word (statdata + 16, ADDR_SIZE * 8);
			if (size >= 22)
				put_long (statdata + 18, dev->td->mtu);
			if (size >= 26)
				put_long (statdata + 22, 10000000);
			if (size >= 30)
				put_long (statdata + 26, S2WireType_Ethernet);
		}
		break;

	case S2_GETTYPESTATS:
		if (pdev->trackcnt) {
			put_long (statdata + 0, pdev->packetssent);
			put_long (statdata + 4, pdev->packetsreceived);
			put_long (statdata + 8, pdev->bytessent);
			put_long (statdata + 12, pdev->bytesreceived);
			put_long (statdata + 16, pdev->packetsdropped);
		} else {
			io_error = S2ERR_BAD_STATE;
			wire_error = S2WERR_NOT_TRACKED;
		}
		break;

	case S2_GETGLOBALSTATS:
		put_long (statdata + 0, dev->packetsreceived);
		put_long (statdata + 4, dev->packetssent);
		put_long (statdata + 8, dev->baddata);
		put_long (statdata + 12, dev->overruns);
		put_long (statdata + 16, 0);
		put_long (statdata + 20, dev->unknowntypesreceived);
		put_long (statdata + 24, dev->reconfigurations);
		put_long (statdata + 28, dev->online_secs);
		put_long (statdata + 32, dev->online_micro);
		break;

	case S2_GETSPECIALSTATS:
		put_long (statdata + 1, 0);
		break;

	case S2_GETSTATIONADDRESS:
		for (i = 0; i < ADDR_SIZE; i++) {
			put_byte (srcaddr + i, dev->td->mac[i]);
			put_byte (dstaddr + i, dev->td->mac[i]);
		}
		break;

	case S2_CONFIGINTERFACE:
		if (dev->configured) {
			io_error = S2ERR_BAD_STATE;
			wire_error = S2WERR_IS_CONFIGURED;
		} else {
			dev->configured = TRUE;
		}
		break;

	case S2_ONLINE:
		if (!dev->configured) {
			io_error = S2ERR_BAD_STATE;
			wire_error = S2WERR_NOT_CONFIGURED;
		}
		if (!dev->adapter) {
			io_error = S2ERR_OUTOFSERVICE;
			wire_error = S2WERR_RCVREL_HDW_ERR;
		}
		if (!io_error) {
			uaenet_vsync_requested++;
			async = 1;
		}
		break;

	case S2_TRACKTYPE:
		if (packettype <= 65535) {
			if (pdev->tracks[packettype]) {
				io_error = S2ERR_BAD_STATE;
				wire_error = S2WERR_ALREADY_TRACKED;
			} else {
				pdev->tracks[packettype] = 1;
				pdev->trackcnt++;
			}
		} else {
			io_error = S2ERR_BAD_ARGUMENT;
		}
		break;
	case S2_UNTRACKTYPE:
		if (packettype <= 65535) {
			if (!pdev->tracks[packettype]) {
				io_error = S2ERR_BAD_STATE;
				wire_error = S2WERR_NOT_TRACKED;
			} else {
				pdev->tracks[packettype] = 0;
				pdev->trackcnt--;
			}
		} else {
			io_error = S2ERR_BAD_ARGUMENT;
		}
		break;

	case S2_OFFLINE:
		if (dev->online) {
			dev->online = 0;
			checkevents (dev, S2EVENT_OFFLINE, 1);
		}
		break;

	case S2_ONEVENT:
		{
			uae_u32 events;
			uae_u32 wanted_events = get_long (request + 32);
			if (wanted_events & ~KNOWN_EVENTS) {
				io_error = S2ERR_NOT_SUPPORTED;
				events = S2WERR_BAD_EVENT;
			} else {
				if (dev->online)
					events = S2EVENT_ONLINE;
				else
					events = S2EVENT_OFFLINE;
				events &= wanted_events;
				if (events) {
					wire_error = events;
				} else {
					async = 1;
				}
			}
		}
		break;

	default:
		io_error = IOERR_NOCMD;
		break;

offline:
		io_error = S2ERR_OUTOFSERVICE;
		wire_error = S2WERR_UNIT_OFFLINE;
		break;
toobig:
		io_error = S2ERR_MTU_EXCEEDED;
		wire_error = S2WERR_GENERIC_ERROR;
		break;

	}
end:
	if (log_net && (io_error || wire_error))
		write_log (_T("-> %d (%d)\n"), io_error, wire_error);
	put_long (request + 32, wire_error);
	put_byte (request + 31, io_error);
	return async;
}

static int dev_do_io (struct s2devstruct *dev, uaecptr request, int quick)
{
	uae_u32 command = get_word (request + 28);
	struct priv_s2devstruct *pdev = getps2devstruct (request);
	uaecptr data = get_long (request + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES * 2 + 4);

	put_byte (request + 31, 0);
	if (!pdev) {
		write_log (_T("%s unknown iorequest %08x\n"), getdevname (), request);
		return 0;
	}
	if (command == NSCMD_DEVICEQUERY) {
		uae_u32 data = get_long (request + 40); /* io_data */
		put_long (data + 0, 0);
		put_long (data + 4, 16); /* size */
		put_word (data + 8, 7); /* NSDEVTYPE_SANA2 */
		put_word (data + 10, 0);
		put_long (data + 12, nscmd_cmd);
		put_long (request + 32, 16); /* io_actual */
		return 0;
	} else if (get_word (request + 0x12) < SANA2_IOREQSIZE) {
		put_byte (request + 31, IOERR_BADLENGTH);
		return 0;
	}
	return dev_do_io_2 (dev, request, quick);
}

static int dev_can_quick (uae_u32 command)
{
	switch (command)
	{
	case NSCMD_DEVICEQUERY:
	case S2_DEVICEQUERY:
	case S2_CONFIGINTERFACE:
	case S2_GETSTATIONADDRESS:
	case S2_TRACKTYPE:
	case S2_UNTRACKTYPE:
		return 1;
	}
	return 0;
}

static int dev_canquick (struct s2devstruct *dev, uaecptr request)
{
	uae_u32 command = get_word (request + 28);
	return dev_can_quick (command);
}

static uae_u32 REGPARAM2 dev_beginio (TrapContext *context)
{
	uae_u32 request = m68k_areg (regs, 1);
	uae_u8 flags = get_byte (request + 30);
	int command = get_word (request + 28);
	struct priv_s2devstruct *pdev = getps2devstruct (request);
	struct s2devstruct *dev;

	put_byte (request + 8, NT_MESSAGE);
	if (!pdev) {
		write_log (_T("%s unknown iorequest (1) %08x\n"), getdevname (), request);
		put_byte (request + 31, 32);
		return get_byte (request + 31);
	}
	dev = gets2devstruct (pdev->unit);
	if (!dev) {
		write_log (_T("%s unknown iorequest (2) %08x\n"), getdevname (), request);
		put_byte (request + 31, 32);
		return get_byte (request + 31);
	}
	put_byte (request + 31, 0);
	if ((flags & 1) && dev_canquick (dev, request)) {
		if (dev_do_io (dev, request, 1))
			write_log (_T("%s: command %d bug with IO_QUICK\n"), SANA2NAME, command);
		return get_byte (request + 31);
	} else {
		if (command == CMD_WRITE || command == S2_BROADCAST || command == S2_MULTICAST) {
			struct s2packet *s2p;
			if (!pdev->copyfrombuff || !pdev->copytobuff) {
				put_long (request + 32, S2ERR_BAD_ARGUMENT);
				put_byte (request + 31, S2WERR_BUFF_ERROR);
			} else {
				if (command == S2_BROADCAST) {
					uaecptr dstaddr = request + 32 + 4 + 4 + SANA2_MAX_ADDR_BYTES;
					put_byte (dstaddr +  0, 0xff);
					put_byte (dstaddr +  1, 0xff);
					put_byte (dstaddr +  2, 0xff);
					put_byte (dstaddr +  3, 0xff);
					put_byte (dstaddr +  4, 0xff);
					put_byte (dstaddr +  5, 0xff);
				}
				s2p = createwritepacket (context, request);
				if (s2p) {
					uae_sem_wait (&async_sem);
					add_async_packet (dev, s2p, request);
					uae_sem_post (&async_sem);
				}
				if (!s2p) {
					put_long (request + 32, S2WERR_BUFF_ERROR);
					put_byte (request + 31, S2ERR_NO_RESOURCES);
				}
			}
		}
		put_byte (request + 30, get_byte (request + 30) & ~1);
		write_comm_pipe_u32 (&dev->requests, request, 1);
		return 0;
	}
}

static void *dev_thread (void *devs)
{
	struct s2devstruct *dev = (struct s2devstruct*)devs;

	uae_set_thread_priority (NULL, 1);
	dev->thread_running = 1;
	uae_sem_post (&dev->sync_sem);
	for (;;) {
		uaecptr request = (uaecptr)read_comm_pipe_u32_blocking (&dev->requests);
		uae_sem_wait (&change_sem);
		if (!request) {
			dev->thread_running = 0;
			uae_sem_post (&dev->sync_sem);
			uae_sem_post (&change_sem);
			write_log (_T("%s: dev_thread killed\n"), getdevname ());
			return 0;
		} else if (get_async_request (dev, request, 1)) {
			uae_ReplyMsg (request);
			release_async_request (dev, request);
			rem_async_packet (dev, request);
		} else if (dev_do_io (dev, request, 0) == 0) {
			uae_ReplyMsg (request);
			rem_async_packet (dev, request);
		} else {
			struct priv_s2devstruct *pdev = getps2devstruct (request);
			add_async_request (dev, request);
			ethernet_trigger (pdev->td, dev->sysdata);
		}
		uae_sem_post (&change_sem);
	}
	return 0;
}

static uae_u32 REGPARAM2 dev_init_2 (TrapContext *context)
{
	uae_u32 base = m68k_dreg (regs,0);
	if (log_net)
		write_log (_T("%s init\n"), SANA2NAME);
	return base;
}

static uae_u32 REGPARAM2 dev_init (TrapContext *context)
{
	return dev_init_2 (context);
}

static uae_u32 REGPARAM2 dev_abortio (TrapContext *context)
{
	uae_u32 request = m68k_areg (regs, 1);
	struct priv_s2devstruct *pdev = getps2devstruct (request);
	struct s2devstruct *dev;

	if (!pdev) {
		write_log (_T("%s abortio but no request %08x found!\n"), getdevname(), request);
		put_byte (request + 31, 32);
		return get_byte (request + 31);
	}
	dev = gets2devstruct (pdev->unit);
	if (!dev) {
		write_log (_T("%s (%d) abortio but no request %08x found!\n"), getdevname(), pdev->unit, request);
		put_byte (request + 31, 32);
		return get_byte (request + 31);
	}
	if (log_net)
		write_log (_T("%s:%d abortio %08x\n"), getdevname(), dev->unit, request);
	abort_async (dev, request);
	return 0;
}

static uae_u32 REGPARAM2 uaenet_int_handler (TrapContext *ctx)
{
	int i, j;
	int gotit;
	struct asyncreq *ar;

	if (uae_sem_trywait (&async_sem)) {
		uaenet_int_requested = 0;
		uaenet_int_late = 1;
		return 0;
	}
	for (i = 0; i < MAX_OPEN_DEVICES; i++)
		pdevst[i].tmp = 0;

	for (i = 0; i < MAX_TOTAL_NET_DEVICES; i++) {
		struct s2devstruct *dev = &devst[i];
		struct s2packet *p;
		if (dev->online) {
			while (dev->readqueue) {
				uae_u16 type;
				p = dev->readqueue;
				type = (p->data[2 * ADDR_SIZE] << 8) | p->data[2 * ADDR_SIZE + 1];
				ar = dev->ar;
				gotit = 0;
				while (ar) {
					if (!ar->ready) {
						uaecptr request = ar->request;
						int command = get_word (request + 28);
						uae_u32 packettype = get_long (request + 32 + 4);
						if (command == CMD_READ && (packettype == type || (packettype <= 1500 && type <= 1500))) {
							struct priv_s2devstruct *pdev = getps2devstruct (request);
							if (pdev && pdev->tmp == 0) {
								if (handleread (ctx, pdev, request, p->data, p->len, command)) {
									if (log_net)
										write_log (_T("-> %p Accepted, CMD_READ, REQ=%08X LEN=%d\n"), p, request, p->len);
									ar->ready = 1;
									write_comm_pipe_u32 (&dev->requests, request, 1);
									dev->packetsreceived++;
									gotit = 1;
									pdev->tmp = 1;
								} else {
									if (log_net)
										write_log (_T("-> %p PacketFilter() rejected, CMD_READ, REQ=%08X LEN=%d\n"), p, request, p->len);
									pdev->tmp = -1;
								}
							}
						}
					}
					ar = ar->next;
				}
				ar = dev->ar;
				while (ar) {
					if (!ar->ready) {
						uaecptr request = ar->request;
						int command = get_word (request + 28);
						if (command == S2_READORPHAN) {
							struct priv_s2devstruct *pdev = getps2devstruct (request);
							if (pdev && pdev->tmp <= 0) {
								if (log_net)
									write_log (_T("-> %p Accepted, S2_READORPHAN, REQ=%08X LEN=%d\n"), p, request, p->len);
								handleread (ctx, pdev, request, p->data, p->len, command);
								ar->ready = 1;
								write_comm_pipe_u32 (&dev->requests, request, 1);
								dev->packetsreceived++;
								dev->unknowntypesreceived++;
								gotit = 1;
								pdev->tmp = 1;
							}
						}
					}
					ar = ar->next;
				}
				if (!gotit) {
					if (log_net)
						write_log (_T("-> %p packet dropped, LEN=%d\n"), p, p->len);
					for (j = 0; j < MAX_OPEN_DEVICES; j++) {
						if (pdevst[j].unit == dev->unit) {
							if (pdevst[j].tracks[type])
								pdevst[j].packetsdropped++;
						}
					}
				}
				dev->readqueue = dev->readqueue->next;
				freepacket (p);
			}
		} else {
			while (dev->readqueue) {
				p = dev->readqueue;
				dev->readqueue = dev->readqueue->next;
				freepacket (p);
			}
		}

		ar = dev->ar;
		while (ar) {
			if (!ar->ready) {
				uaecptr request = ar->request;
				int command = get_word (request + 28);
				if (command == S2_ONLINE) {
					struct priv_s2devstruct *pdev = getps2devstruct (request);
					dev->packetsreceived = 0;
					dev->packetssent = 0;
					dev->baddata = 0;
					dev->overruns = 0;
					dev->unknowntypesreceived = 0;
					dev->reconfigurations = 0;
					if (pdev && pdev->timerbase) {
						m68k_areg (regs, 0) = pdev->tempbuf;
						CallLib (ctx, pdev->timerbase, -0x42); /* GetSysTime() */
					} else {
						put_long (pdev->tempbuf + 0, 0);
						put_long (pdev->tempbuf + 4, 0);
					}
					dev->online_secs = get_long (pdev->tempbuf + 0);
					dev->online_micro = get_long (pdev->tempbuf + 4);
					checkevents (dev, S2EVENT_ONLINE, 0);
					dev->online = 1;
					ar->ready = 1;
					write_comm_pipe_u32 (&dev->requests, request, 1);
					uaenet_vsync_requested--;
				} else if (command == CMD_FLUSH) {
					/* do not reply CMD_FLUSH until all other requests are gone */
					if (dev->ar->next == NULL) {
						if (log_net)
							write_log (_T("CMD_FLUSH replied %08x\n"), request);
						ar->ready = 1;
						write_comm_pipe_u32 (&dev->requests, request, 1);
						uaenet_vsync_requested--;
					} else {
						struct priv_s2devstruct *pdev = getps2devstruct (request);
						if (pdev) {
							dev->flush_timeout--;
							if (dev->flush_timeout <= 0) {
								dev->flush_timeout = FLUSH_TIMEOUT;
								if (dev->flush_timeout_cnt > 1)
									write_log (_T("WARNING: %s:%d CMD_FLUSH possibly frozen..\n"), getdevname(), pdev->unit);
								dev->flush_timeout_cnt++;
								flush (pdev);
							}
						}
					}
				}
			}
			ar = ar->next;
		}
	}
	if (uaenet_int_late)
		uaenet_int_requested = 1;
	else
		uaenet_int_requested = 0;
	uaenet_int_late = 0;
	uae_sem_post (&async_sem);
	return 0;
}

static void dev_reset (void)
{
	int i;
	struct s2devstruct *dev;
	int unitnum = 0;

	write_log (_T("%s reset\n"), getdevname());
	for (i = 0; i < MAX_TOTAL_NET_DEVICES; i++) {
		dev = &devst[i];
		if (dev->opencnt) {
			struct asyncreq *ar = dev->ar;
			while (ar) {
				if (!ar->ready) {
					dev->ar->ready = 1;
					do_abort_async (dev, ar->request);
				}
				ar = ar->next;
			}
			write_comm_pipe_u32 (&dev->requests, 0, 1);
			uae_sem_wait (&dev->sync_sem);
		}
		while (dev->mc)
			delmulticastaddresses (dev, dev->mc->start, dev->mc->end);
		memset (dev, 0, sizeof (struct s2devstruct));
	}
	for (i = 0; i < MAX_OPEN_DEVICES; i++)
		memset (&pdevst[i], 0, sizeof (struct priv_s2devstruct));
	uaenet_vsync_requested = 0;
	uaenet_int_requested = 0;
	irq_init = 0;

}

uaecptr netdev_startup (uaecptr resaddr)
{
	if (!currprefs.sana2)
		return resaddr;
#ifdef FSUAE
#else
	if (log_net)
#endif
		write_log (_T("netdev_startup(0x%x)\n"), resaddr);
	/* Build a struct Resident. This will set up and initialize
	* the uaenet.device */
	put_word (resaddr + 0x0, 0x4AFC);
	put_long (resaddr + 0x2, resaddr);
	put_long (resaddr + 0x6, resaddr + 0x1A); /* Continue scan here */
	put_word (resaddr + 0xA, 0x8101); /* RTF_AUTOINIT|RTF_COLDSTART; Version 1 */
	put_word (resaddr + 0xC, 0x0305); /* NT_DEVICE; pri 05 */
	put_long (resaddr + 0xE, ROM_netdev_resname);
	put_long (resaddr + 0x12, ROM_netdev_resid);
	put_long (resaddr + 0x16, ROM_netdev_init); /* calls netdev_init */
	resaddr += 0x1A;
	return resaddr;
}

void netdev_install (void)
{
	uae_u32 functable, datatable;
	uae_u32 initcode, openfunc, closefunc, expungefunc;
	uae_u32 beginiofunc, abortiofunc;

	if (!currprefs.sana2)
		return;
#ifdef FSUAE
#else
	if (log_net)
#endif
		write_log (_T("netdev_install(): 0x%x\n"), here ());

	ethernet_enumerate_free ();
	ethernet_enumerate (td, NULL);

	ROM_netdev_resname = ds (getdevname());
	ROM_netdev_resid = ds (_T("UAE net.device 0.2"));
	timerdevname = ds (_T("timer.device"));

	/* initcode */
	initcode = here ();
	calltrap (deftrap2 (dev_init, TRAPFLAG_EXTRA_STACK, _T("uaenet.init"))); dw (RTS);

	/* Open */
	openfunc = here ();
	calltrap (deftrap2 (dev_open, TRAPFLAG_EXTRA_STACK, _T("uaenet.open"))); dw (RTS);

	/* Close */
	closefunc = here ();
	calltrap (deftrap2 (dev_close, TRAPFLAG_EXTRA_STACK, _T("uaenet.close"))); dw (RTS);

	/* Expunge */
	expungefunc = here ();
	calltrap (deftrap2 (dev_expunge, TRAPFLAG_EXTRA_STACK, _T("uaenet.expunge"))); dw (RTS);

	/* BeginIO */
	beginiofunc = here ();
	calltrap (deftrap2 (dev_beginio, TRAPFLAG_EXTRA_STACK, _T("uaenet.beginio"))); dw (RTS);

	/* AbortIO */
	abortiofunc = here ();
	calltrap (deftrap2 (dev_abortio, TRAPFLAG_EXTRA_STACK, _T("uaenet.abortio"))); dw (RTS);

	/* FuncTable */
	functable = here ();
	dl (openfunc); /* Open */
	dl (closefunc); /* Close */
	dl (expungefunc); /* Expunge */
	dl (EXPANSION_nullfunc); /* Null */
	dl (beginiofunc); /* BeginIO */
	dl (abortiofunc); /* AbortIO */
	dl (0xFFFFFFFFul); /* end of table */

	/* DataTable */
	datatable = here ();
	dw (0xE000); /* INITBYTE */
	dw (0x0008); /* LN_TYPE */
	dw (0x0300); /* NT_DEVICE */
	dw (0xC000); /* INITLONG */
	dw (0x000A); /* LN_NAME */
	dl (ROM_netdev_resname);
	dw (0xE000); /* INITBYTE */
	dw (0x000E); /* LIB_FLAGS */
	dw (0x0600); /* LIBF_SUMUSED | LIBF_CHANGED */
	dw (0xD000); /* INITWORD */
	dw (0x0014); /* LIB_VERSION */
	dw (0x0004); /* 0.4 */
	dw (0xD000); /* INITWORD */
	dw (0x0016); /* LIB_REVISION */
	dw (0x0000); /* end of table already ??? */
	dw (0xC000); /* INITLONG */
	dw (0x0018); /* LIB_IDSTRING */
	dl (ROM_netdev_resid);
	dw (0x0000); /* end of table */

	ROM_netdev_init = here ();
	dl (0x00000100); /* size of device base */
	dl (functable);
	dl (datatable);
	dl (initcode);

	nscmd_cmd = here ();
	dw (CMD_READ);
	dw (CMD_WRITE);
	dw (CMD_FLUSH);
	dw (S2_DEVICEQUERY);
	dw (S2_GETSTATIONADDRESS);
	dw (S2_CONFIGINTERFACE);
	dw (S2_ADDMULTICASTADDRESS);
	dw (S2_DELMULTICASTADDRESS);
	dw (S2_MULTICAST);
	dw (S2_BROADCAST);
	dw (S2_TRACKTYPE);
	dw (S2_UNTRACKTYPE);
	dw (S2_GETTYPESTATS);
	dw (S2_GETSPECIALSTATS);
	dw (S2_GETGLOBALSTATS);
	dw (S2_ONEVENT);
	dw (S2_READORPHAN);
	dw (S2_ONLINE);
	dw (S2_OFFLINE);
	dw (S2_ADDMULTICASTADDRESSES);
	dw (S2_DELMULTICASTADDRESSES);
	dw (NSCMD_DEVICEQUERY);
	dw (0);

}

void netdev_start_threads (void)
{
	if (!currprefs.sana2)
		return;
	if (log_net)
		write_log (_T("netdev_start_threads()\n"));
	uae_sem_init (&change_sem, 0, 1);
	uae_sem_init (&async_sem, 0, 1);
}

void netdev_reset (void)
{
	if (!currprefs.sana2)
		return;
	dev_reset ();
}

#endif // SANA2
