/*
 * Copyright (c) 2020-2021 Niklas Ekström
 *
 * Thanks to Christian Vogelgsang and Mike Sterling for inspiration gained from their SANA-II drivers:
 * - https://github.com/cnvogelg/plipbox
 * - https://github.com/mikestir/k1208-drivers
 */

#include <exec/resident.h>
#include <exec/errors.h>
#include <exec/memory.h>
#include <exec/lists.h>
#include <exec/alerts.h>
#include <exec/devices.h>
#include <exec/types.h>
#include <exec/tasks.h>
#include <exec/io.h>
#include <exec/execbase.h>
#include <exec/ports.h>

#include <libraries/expansion.h>
#include <libraries/dos.h>

#include <devices/trackdisk.h>
#include <devices/timer.h>
#include <devices/scsidisk.h>

#include <dos/filehandler.h>

#include <proto/disk.h>
#include <proto/expansion.h>
#include <proto/exec.h>
#include <proto/dos.h>

#include <string.h>

#include <clib/alib_protos.h>

#include "../../a314device/a314.h"
#include "../../a314device/proto_a314.h"
#include "sana2.h"

#include <stdint.h>

// Defines.

#define kprintf(...)

#define STR(s) #s
#define XSTR(s) STR(s)

#define DEVICE_NAME "a314eth.device"
#define DEVICE_DATE "(19 May 2021)"
#define SERVICE_NAME "ethernet"
#define DEVICE_ID_STRING "A314Eth " XSTR(DEVICE_VERSION) "." XSTR(DEVICE_REVISION) " " DEVICE_DATE
#define DEVICE_VERSION 42
#define DEVICE_REVISION 2
#define DEVICE_PRIORITY 0


#define DEVICE_NAME		"a314eth.device"
#define SERVICE_NAME		"ethernet"

#define TASK_PRIO		10

#define MACADDR_SIZE		6
#define NIC_BPS			10000000

#define ETH_MTU			1500
#define RAW_MTU			1518

#define READ_FRAME_REQ		1
#define WRITE_FRAME_REQ		2
#define READ_FRAME_RES		3
#define WRITE_FRAME_RES		4

#define ET_RBUF_CNT		2
#define ET_WBUF_CNT		2
#define ET_BUF_CNT		(ET_RBUF_CNT + ET_WBUF_CNT)

int __attribute__((no_reorder)) _start()
{
    return -1;
}

asm("romtag:                                \n"
    "       dc.w    "XSTR(RTC_MATCHWORD)"   \n"
    "       dc.l    romtag                  \n"
    "       dc.l    endcode                 \n"
    "       dc.b    "XSTR(RTF_AUTOINIT)"    \n"
    "       dc.b    "XSTR(DEVICE_VERSION)"  \n"
    "       dc.b    "XSTR(NT_DEVICE)"       \n"
    "       dc.b    "XSTR(DEVICE_PRIORITY)" \n"
    "       dc.l    _device_name            \n"
    "       dc.l    _device_id_string       \n"
    "       dc.l    _auto_init_tables       \n"
    "endcode:                               \n"
	".align 4\n"
	"	dc.l	16\n"
	"_device_process_seglist:\n"
	"	dc.l	0\n"
	"	jmp	_device_process_run\n");

char device_name[] = DEVICE_NAME;
char device_id_string[] = DEVICE_ID_STRING;

// Typedefs.

typedef BOOL (*buf_copy_func_t)(void *dst asm("a0"), void *src asm("a1"), LONG size asm("d0"));

// Structs.

#pragma pack(push, 1)
struct EthHdr
{
	unsigned char eh_Dst[MACADDR_SIZE];
	unsigned char eh_Src[MACADDR_SIZE];
	unsigned short eh_Type;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct ServiceMsg
{
	ULONG sm_Address;
	UWORD sm_Length;
	UWORD sm_Kind;
};
#pragma pack(pop)

struct BufDesc
{
	struct MinNode bd_Node;
	void *bd_Buffer;
	int bd_Length;
};

// Constants.

static const char service_name[] = SERVICE_NAME;
static const char a314_device_name[] = A314_NAME;

static const unsigned char macaddr[MACADDR_SIZE] = { 0x40, 0x61, 0x33, 0x31, 0x34, 0x65 };

// Global variables.

BPTR saved_seg_list;
struct ExecBase *SysBase;
struct DosLibrary *DOSBase;
struct Library *A314Base;

buf_copy_func_t copyfrom;
buf_copy_func_t copyto;

volatile struct List ut_rbuf_list;
volatile struct List ut_wbuf_list;

struct BufDesc et_bufs[ET_BUF_CNT];

struct List et_rbuf_free_list;
struct List et_rbuf_pending_list;
struct List et_rbuf_has_data_list;

struct List et_wbuf_free_list;
struct List et_wbuf_pending_list;

LONG a314_socket;
short last_req_kind;

struct MsgPort a314_mp;

struct A314_IORequest read_ior;
struct A314_IORequest write_ior;
struct A314_IORequest reset_ior;

BOOL pending_a314_read;
BOOL pending_a314_write;
BOOL pending_a314_reset;

struct ServiceMsg a314_read_buf;
struct ServiceMsg a314_write_buf;

volatile ULONG sana2_sigmask;
volatile ULONG shutdown_sigmask;

volatile struct Task *init_task;

struct Process *device_process;
volatile int device_start_error;
void device_process_run();

// External declarations.

extern void device_process_seglist();

// Procedures.

static struct Library __attribute__((used)) *init_device(uint8_t *seg_list asm("a0"), struct Library *dev asm("d0"))
{
	saved_seg_list = (BPTR)seg_list;

	dev->lib_Node.ln_Type = NT_DEVICE;
	dev->lib_Node.ln_Name = (char *)device_name;
	dev->lib_Flags = LIBF_SUMUSED | LIBF_CHANGED;
	dev->lib_Version = 1;
	dev->lib_Revision = 0;
	dev->lib_IdString = (APTR)device_id_string;

	SysBase = *(struct ExecBase **)4;
	DOSBase = (struct DosLibrary *)OpenLibrary((STRPTR)DOSNAME, 0);

	return dev;
}

static uint8_t* __attribute__((used)) expunge(struct Library *dev asm("a6"))
{
	if (dev->lib_OpenCnt)
	{
		dev->lib_Flags |= LIBF_DELEXP;
		return 0;
	}

	// Shady way of waiting for device process to terminate before unloading.
	Delay(10);

	CloseLibrary((struct Library *)DOSBase);

	Remove(&dev->lib_Node);
	FreeMem((char *)dev - dev->lib_NegSize, dev->lib_NegSize + dev->lib_PosSize);
	return (uint8_t *)saved_seg_list;
}

static void send_a314_cmd(struct A314_IORequest *ior, UWORD cmd, char *buffer, int length)
{
	ior->a314_Request.io_Command = cmd;
	ior->a314_Request.io_Error = 0;
	ior->a314_Socket = a314_socket;
	ior->a314_Buffer = (STRPTR)buffer;
	ior->a314_Length = length;
	SendIO((struct IORequest *)ior);
}

static void do_a314_cmd(struct A314_IORequest *ior, UWORD cmd, char *buffer, int length)
{
	ior->a314_Request.io_Command = cmd;
	ior->a314_Request.io_Error = 0;
	ior->a314_Socket = a314_socket;
	ior->a314_Buffer = (STRPTR)buffer;
	ior->a314_Length = length;
	DoIO((struct IORequest *)ior);
}

static void copy_from_bd_and_reply(struct IOSana2Req *ios2, struct BufDesc *bd)
{
	struct EthHdr *eh = bd->bd_Buffer;

	if (ios2->ios2_Req.io_Flags & SANA2IOF_RAW)
	{
		ios2->ios2_DataLength = bd->bd_Length;
		copyto(ios2->ios2_Data, bd->bd_Buffer, ios2->ios2_DataLength);
		ios2->ios2_Req.io_Flags = SANA2IOF_RAW;
	}
	else
	{
		ios2->ios2_DataLength = bd->bd_Length - sizeof(struct EthHdr);
		copyto(ios2->ios2_Data, &eh[1], ios2->ios2_DataLength);
		ios2->ios2_Req.io_Flags = 0;
	}

	memcpy(ios2->ios2_SrcAddr, eh->eh_Src, MACADDR_SIZE);
	memcpy(ios2->ios2_DstAddr, eh->eh_Dst, MACADDR_SIZE);

	BOOL bcast = TRUE;
	for (int i = 0; i < MACADDR_SIZE; i++)
	{
		if (eh->eh_Dst[i] != 0xff)
		{
			bcast = FALSE;
			break;
		}
	}

	if (bcast)
		ios2->ios2_Req.io_Flags |= SANA2IOF_BCAST;

	ios2->ios2_PacketType = eh->eh_Type;

	ios2->ios2_Req.io_Error = 0;
	ReplyMsg(&ios2->ios2_Req.io_Message);
}

static void copy_to_bd_and_reply(struct BufDesc *bd, struct IOSana2Req *ios2)
{
	struct EthHdr *eh = bd->bd_Buffer;

	if (ios2->ios2_Req.io_Flags & SANA2IOF_RAW)
	{
		copyfrom(bd->bd_Buffer, ios2->ios2_Data, ios2->ios2_DataLength);
		bd->bd_Length = ios2->ios2_DataLength;
	}
	else
	{
		eh->eh_Type = ios2->ios2_PacketType;
		memcpy(eh->eh_Src, macaddr, sizeof(macaddr));
		memcpy(eh->eh_Dst, ios2->ios2_DstAddr, MACADDR_SIZE);
		copyfrom(&eh[1], ios2->ios2_Data, ios2->ios2_DataLength);
		bd->bd_Length = ios2->ios2_DataLength + sizeof(struct EthHdr);
	}

	ios2->ios2_Req.io_Error = 0;
	ReplyMsg(&ios2->ios2_Req.io_Message);
}

static void handle_a314_reply(struct A314_IORequest *ior)
{
	if (ior == &write_ior)
	{
		pending_a314_write = FALSE;

		if (ior->a314_Request.io_Error == A314_WRITE_OK)
		{
			// Start new write later.
		}
		else // A314_WRITE_RESET
		{
			// TODO: Handle. What if pi-side is shutting down.
		}
	}
	else if (ior == &read_ior)
	{
		pending_a314_read = FALSE;

		if (ior->a314_Request.io_Error == A314_READ_OK)
		{
			if (a314_read_buf.sm_Kind == WRITE_FRAME_RES)
			{
				struct BufDesc *bd = (struct BufDesc *)RemHead(&et_wbuf_pending_list);
				AddTail(&et_wbuf_free_list, (struct Node *)bd);
			}
			else // READ_FRAME_RES
			{
				struct BufDesc *bd = (struct BufDesc *)RemHead(&et_rbuf_pending_list);
				bd->bd_Length = a314_read_buf.sm_Length;
				AddTail(&et_rbuf_has_data_list, (struct Node *)bd);
			}

			send_a314_cmd(&read_ior, A314_READ, (void *)&a314_read_buf, sizeof(a314_read_buf));
			pending_a314_read = TRUE;
		}
		else // A314_READ_RESET
		{
			// TODO: Handle. What if pi-side is shutting down.
		}
	}
	else if (ior == &reset_ior)
	{
		pending_a314_reset = FALSE;
	}
}

static struct IOSana2Req *remove_matching_rbuf(ULONG type)
{
	struct Node *node = ut_rbuf_list.lh_Head;
	while (node->ln_Succ)
	{
		struct IOSana2Req *ios2 = (struct IOSana2Req *)node;
		if (ios2->ios2_PacketType == type)
		{
			Remove(node);
			return ios2;
		}
		node = node->ln_Succ;
	}
	return NULL;
}

static void complete_read_reqs()
{
	struct Node *node = et_rbuf_has_data_list.lh_Head;
	if (!node->ln_Succ)
		return;

	Forbid();
	while (node->ln_Succ)
	{
		struct BufDesc *bd = (struct BufDesc *)node;
		struct EthHdr *eh = (struct EthHdr *)bd->bd_Buffer;

		node = node->ln_Succ;

		struct IOSana2Req *ios2 = remove_matching_rbuf(eh->eh_Type);
		if (ios2)
		{
			copy_from_bd_and_reply(ios2, bd);
		}

		Remove((struct Node *)bd);
		AddTail(&et_rbuf_free_list, (struct Node *)bd);
	}
	Permit();
}

static void maybe_write_req()
{
	if (pending_a314_write)
		return;

	BOOL free_et_wbuf = et_wbuf_free_list.lh_Head->ln_Succ != NULL;
	BOOL idle_et_rbuf = et_rbuf_free_list.lh_Head->ln_Succ != NULL;

	Forbid();

	BOOL waiting_ut_wbuf = ut_wbuf_list.lh_Head->ln_Succ != NULL;

	BOOL want_wbuf = free_et_wbuf && waiting_ut_wbuf;
	BOOL want_rbuf = idle_et_rbuf;

	if (!want_rbuf && !want_wbuf)
	{
		Permit();
		return;
	}

	short next_req_kind = 0;

	if (last_req_kind == WRITE_FRAME_REQ)
		next_req_kind = want_rbuf ? READ_FRAME_REQ : WRITE_FRAME_REQ;
	else
		next_req_kind = want_wbuf ? WRITE_FRAME_REQ : READ_FRAME_REQ;

	struct IOSana2Req *ios2 = NULL;
	if (next_req_kind == WRITE_FRAME_REQ)
		ios2 = (struct IOSana2Req*)RemHead((struct List *)&ut_wbuf_list);

	Permit();

	struct BufDesc *bd;

	if (next_req_kind == READ_FRAME_REQ)
	{
		bd = (struct BufDesc *)RemHead(&et_rbuf_free_list);
		bd->bd_Length = RAW_MTU;
		AddTail(&et_rbuf_pending_list, (struct Node *)&bd->bd_Node);
	}
	else // WRITE_FRAME_REQ
	{
		bd = (struct BufDesc *)RemHead(&et_wbuf_free_list);
		copy_to_bd_and_reply(bd, ios2);
		AddTail(&et_wbuf_pending_list, (struct Node *)bd);
	}

	a314_write_buf.sm_Address = TranslateAddressA314(bd->bd_Buffer);
	a314_write_buf.sm_Length = bd->bd_Length;
	a314_write_buf.sm_Kind = next_req_kind;

	send_a314_cmd(&write_ior, A314_WRITE, (void *)&a314_write_buf, sizeof(a314_write_buf));
	pending_a314_write = TRUE;

	last_req_kind = next_req_kind;
}

void device_process_run()
{
	ULONG sana2_signal = AllocSignal(-1);
	sana2_sigmask = 1UL << sana2_signal;

	ULONG shutdown_signal = AllocSignal(-1);
	shutdown_sigmask = 1UL << shutdown_signal;

	a314_mp.mp_SigBit = AllocSignal(-1);
	a314_mp.mp_SigTask = FindTask(NULL);

	do_a314_cmd(&reset_ior, A314_CONNECT, (char *)service_name, strlen(service_name));
	device_start_error = reset_ior.a314_Request.io_Error == A314_CONNECT_OK ? 0 : -1;

	Signal((struct Task *)init_task, SIGF_SINGLE);

	if (device_start_error)
		return;

	ULONG a314_sigmask = 1UL << a314_mp.mp_SigBit;

	send_a314_cmd(&read_ior, A314_READ, (void *)&a314_read_buf, sizeof(a314_read_buf));
	pending_a314_read = TRUE;

	BOOL shutting_down = FALSE;

	while (TRUE)
	{
		complete_read_reqs();
		maybe_write_req();

		if (shutting_down && !pending_a314_read && !pending_a314_write && !pending_a314_reset)
			break;

		ULONG sigs = Wait(a314_sigmask | sana2_sigmask | shutdown_sigmask);

		if ((sigs & shutdown_sigmask) && !shutting_down)
		{
			send_a314_cmd(&reset_ior, A314_RESET, NULL, 0);
			pending_a314_reset = TRUE;
			shutting_down = TRUE;
		}

		if (sigs & a314_sigmask)
		{
			struct A314_IORequest *ior;
			while ((ior = (struct A314_IORequest *)GetMsg(&a314_mp)))
				handle_a314_reply(ior);
		}
	}

	Signal((struct Task *)init_task, SIGF_SINGLE);
}

static struct TagItem *FindTagItem(Tag tagVal, struct TagItem *tagList)
{
	struct TagItem *ti = tagList;
	while (ti && ti->ti_Tag != tagVal)
	{
		switch (ti->ti_Tag)
		{
		case TAG_DONE:
			return NULL;
		case TAG_MORE:
			ti = (struct TagItem *)ti->ti_Data;
			break;
		case TAG_SKIP:
			ti += ti->ti_Data + 1;
			break;
		case TAG_IGNORE:
		default:
			ti++;
			break;
		}
	}
	return ti;
}

static ULONG GetTagData(Tag tagVal, ULONG defaultData, struct TagItem *tagList)
{
	struct TagItem *ti = FindTagItem(tagVal, tagList);
	return ti ? ti->ti_Data : defaultData;
}

static void __attribute__((used)) open(struct Library *dev asm("a6"), struct IOSana2Req *ios2 asm("a1"), uint32_t unitnum asm("d0"), uint32_t flags asm("d1"))
{
	kprintf("We opening this shit.\n");
	ios2->ios2_Req.io_Error = IOERR_OPENFAIL;
	ios2->ios2_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG;

	if (unitnum != 0 || dev->lib_OpenCnt)
		return;

	dev->lib_OpenCnt++;

	kprintf("Try the copyfrom crap.\n");
	copyfrom = (buf_copy_func_t)GetTagData(S2_CopyFromBuff, 0, (struct TagItem *)ios2->ios2_BufferManagement);
	copyto = (buf_copy_func_t)GetTagData(S2_CopyToBuff, 0, (struct TagItem *)ios2->ios2_BufferManagement);
	ios2->ios2_BufferManagement = (void *)0xdeadbeefUL;

	kprintf("Memsetting some shit.\n");
	memset(&a314_mp, 0, sizeof(a314_mp));
	a314_mp.mp_Node.ln_Pri = 0;
	a314_mp.mp_Node.ln_Type = NT_MSGPORT;
	a314_mp.mp_Node.ln_Name = (char *)device_name;
	a314_mp.mp_Flags = PA_SIGNAL;
	NewList(&a314_mp.mp_MsgList);

	kprintf("Memsetting more shit.\n");
	memset(&write_ior, 0, sizeof(write_ior));
	write_ior.a314_Request.io_Message.mn_ReplyPort = &a314_mp;
	write_ior.a314_Request.io_Message.mn_Length = sizeof(write_ior);
	write_ior.a314_Request.io_Message.mn_Node.ln_Type = NT_REPLYMSG;

	kprintf("Opendevice.\n");
	A314Base = NULL;
	if (OpenDevice((STRPTR)a314_device_name, 0, (struct IORequest *)&write_ior, 0))
		goto error;

	A314Base = &(write_ior.a314_Request.io_Device->dd_Library);

	kprintf("Copy memory.\n");
	memcpy(&read_ior, &write_ior, sizeof(read_ior));
	memcpy(&reset_ior, &write_ior, sizeof(reset_ior));

	kprintf("Making datestamps.\n");
	struct DateStamp ds;
	DateStamp(&ds);
	a314_socket = (ds.ds_Minute * 60 * TICKS_PER_SECOND) + ds.ds_Tick;

	last_req_kind = WRITE_FRAME_REQ;

	kprintf("Making lists.\n");
	NewList((struct List *)&ut_rbuf_list);
	NewList((struct List *)&ut_wbuf_list);

	NewList(&et_rbuf_free_list);
	NewList(&et_rbuf_pending_list);
	NewList(&et_rbuf_has_data_list);

	NewList(&et_wbuf_free_list);
	NewList(&et_wbuf_pending_list);

	kprintf("Memzero buffers.\n");
	for (int i = 0; i < ET_BUF_CNT; i++)
		memset(&et_bufs[i], 0, sizeof(struct BufDesc));

	kprintf("Add tails.\n");
	for (int i = 0; i < ET_BUF_CNT; i++)
	{
		struct BufDesc *bd = &et_bufs[i];

		bd->bd_Buffer = AllocMem(RAW_MTU, MEMF_FAST);
		if (!bd->bd_Buffer)
			goto error;

		if (i < ET_RBUF_CNT)
			AddTail(&et_rbuf_free_list, (struct Node*)&bd->bd_Node);
		else
			AddTail(&et_wbuf_free_list, (struct Node*)&bd->bd_Node);
	}

	kprintf("Find task.\n");
	init_task = FindTask(NULL);

	kprintf("Do msgport.\n");
	struct MsgPort *device_mp = CreateProc((STRPTR)device_name, TASK_PRIO, ((ULONG)&device_process_seglist) >> 2, 2048);
	if (!device_mp)
		goto error;

	kprintf("Process thing.\n");
	device_process = (struct Process *)((char *)device_mp - sizeof(struct Task));

	kprintf("Waitf.\n");
	Wait(SIGF_SINGLE);
	kprintf("Waitedf.\n");

	if (device_start_error) {
		kprintf("Device start error.\n");
		goto error;
	}

	kprintf("Everything ok?\n");
	ios2->ios2_Req.io_Error = 0;
	return;

error:
	kprintf("Error small farts.\n");
	for (int i = ET_BUF_CNT - 1; i >= 0; i--)
		if (et_bufs[i].bd_Buffer)
			FreeMem(et_bufs[i].bd_Buffer, RAW_MTU);

	if (A314Base)
	{
		CloseDevice((struct IORequest *)&write_ior);
		A314Base = NULL;
	}

	dev->lib_OpenCnt--;
}

static uint8_t* __attribute__((used)) close(struct Library *dev asm("a6"), struct IOSana2Req *ios2 asm("a1"))
{
	kprintf("Close.\n");
	init_task = FindTask(NULL);
	Signal(&device_process->pr_Task, shutdown_sigmask);
	Wait(SIGF_SINGLE);

	for (int i = ET_BUF_CNT - 1; i >= 0; i--)
		FreeMem(et_bufs[i].bd_Buffer, RAW_MTU);

	CloseDevice((struct IORequest *)&write_ior);
	A314Base = NULL;

	ios2->ios2_Req.io_Device = NULL;
	ios2->ios2_Req.io_Unit = NULL;

	dev->lib_OpenCnt--;

	if (dev->lib_OpenCnt == 0 && (dev->lib_Flags & LIBF_DELEXP))
		return expunge(dev);

	return 0;
}

static void device_query(struct IOSana2Req *req)
{
	struct Sana2DeviceQuery *query;

	query = req->ios2_StatData;
	query->DevQueryFormat = 0;
	query->DeviceLevel = 0;

	if (query->SizeAvailable >= 18)
		query->AddrFieldSize = MACADDR_SIZE * 8;

	if (query->SizeAvailable >= 22)
		query->MTU = ETH_MTU;

	if (query->SizeAvailable >= 26)
		query->BPS = NIC_BPS;

	if (query->SizeAvailable >= 30)
		query->HardwareType = S2WireType_Ethernet;

	query->SizeSupplied = query->SizeAvailable < 30 ? query->SizeAvailable : 30;
}

static void __attribute__((used)) begin_io(struct Library *dev asm("a6"), struct IOSana2Req *ios2 asm("a1"))
{
	kprintf("BeginIO.\n");
	ios2->ios2_Req.io_Error = S2ERR_NO_ERROR;
	ios2->ios2_WireError = S2WERR_GENERIC_ERROR;

	switch (ios2->ios2_Req.io_Command)
	{
	case CMD_READ:
		if (!ios2->ios2_BufferManagement)
		{
			ios2->ios2_Req.io_Error = S2ERR_BAD_ARGUMENT;
			ios2->ios2_WireError = S2WERR_BUFF_ERROR;
			break;
		}

		Forbid();
		AddTail((struct List *)&ut_rbuf_list, &ios2->ios2_Req.io_Message.mn_Node);
		Permit();

		ios2->ios2_Req.io_Flags &= ~SANA2IOF_QUICK;
		ios2 = NULL;

		Signal(&device_process->pr_Task, sana2_sigmask);
		break;

	case S2_BROADCAST:
		memset(ios2->ios2_DstAddr, 0xff, MACADDR_SIZE);
		/* Fall through */

	case CMD_WRITE:
		if (((ios2->ios2_Req.io_Flags & SANA2IOF_RAW) != 0 && ios2->ios2_DataLength > RAW_MTU) ||
			((ios2->ios2_Req.io_Flags & SANA2IOF_RAW) == 0 && ios2->ios2_DataLength > ETH_MTU))
		{
			ios2->ios2_Req.io_Error = S2ERR_MTU_EXCEEDED;
			break;
		}

		if (!ios2->ios2_BufferManagement)
		{
			ios2->ios2_Req.io_Error = S2ERR_BAD_ARGUMENT;
			ios2->ios2_WireError = S2WERR_BUFF_ERROR;
			break;
		}

		Forbid();
		AddTail((struct List *)&ut_wbuf_list, &ios2->ios2_Req.io_Message.mn_Node);
		Permit();

		ios2->ios2_Req.io_Flags &= ~SANA2IOF_QUICK;
		ios2 = NULL;

		Signal(&device_process->pr_Task, sana2_sigmask);
		break;

	case S2_ONLINE:
	case S2_OFFLINE:
	case S2_CONFIGINTERFACE:
		break;
	case S2_GETSTATIONADDRESS:
		memcpy(ios2->ios2_SrcAddr, macaddr, sizeof(macaddr));
		memcpy(ios2->ios2_DstAddr, macaddr, sizeof(macaddr));
		break;
	case S2_DEVICEQUERY:
		device_query(ios2);
		break;

	case S2_ONEVENT:
	case S2_TRACKTYPE:
	case S2_UNTRACKTYPE:
	case S2_GETTYPESTATS:
	case S2_READORPHAN:
	case S2_GETGLOBALSTATS:
	case S2_GETSPECIALSTATS:
		break;

	default:
		ios2->ios2_Req.io_Error = IOERR_NOCMD;
		ios2->ios2_WireError = S2WERR_GENERIC_ERROR;
		break;
	}

	if (ios2)
	{
		if (ios2->ios2_Req.io_Flags & SANA2IOF_QUICK)
			ios2->ios2_Req.io_Message.mn_Node.ln_Type = NT_MESSAGE;
		else
			ReplyMsg(&ios2->ios2_Req.io_Message);
	}
}

static void remove_from_list(struct List *list, struct Node *node)
{
	for (struct Node *n = list->lh_Head; n->ln_Succ; n = n->ln_Succ)
	{
		if (n == node)
		{
			Remove(n);
			return;
		}
	}
}

static uint32_t __attribute__((used)) abort_io(struct Library *dev asm("a6"), struct IOSana2Req *ios2 asm("a1"))
{
	kprintf("AbortIO.\n");
	Forbid();
	remove_from_list((struct List *)&ut_rbuf_list, &ios2->ios2_Req.io_Message.mn_Node);
	remove_from_list((struct List *)&ut_wbuf_list, &ios2->ios2_Req.io_Message.mn_Node);
	Permit();

	ios2->ios2_Req.io_Error = IOERR_ABORTED;
	ios2->ios2_WireError = 0;
	ReplyMsg(&ios2->ios2_Req.io_Message);

	return 0;
}

static ULONG device_vectors[] =
{
	(ULONG)open,
	(ULONG)close,
	(ULONG)expunge,
	0,
	(ULONG)begin_io,
	(ULONG)abort_io,
	-1,
};

ULONG auto_init_tables[] =
{
	sizeof(struct Library),
	(ULONG)device_vectors,
	0,
	(ULONG)init_device,
};
