// c.Module
//
//  This program is free software; you can redistribute it and/or modify it
//  under the terms of version 2 of the GNU General Public License as
//  published by the Free Software Foundation;
//
//  This program is distributed in the hope that it will be useful, but WITHOUT
//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
//  more details.
//
//  You should have received a copy of the GNU General Public License along with
//  this program; if not, write to the Free Software Foundation, Inc., 59
//  Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
//  The full GNU General Public License is included in this distribution in the
//  file called LICENSE.

//  The code in this file is (C) 2003 J Ballance as far as
//  the above GPL permits
//
//  Modifications for RPCEmu (C) 2007 Alex Waugh

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <ctype.h>

#include <kernel.h>
#include <swis.h>

#include "ModHdr.h"
#include "Defines.h"
#include "Structs.h"
#include "Module.h"

#include "DCI.h"

/// Directly include a binary version of the AutoSense file (as a C array)
#include "AutoSense.c"

#define  UNUSED(x)              (x = x)

_kernel_oserror *networktxswi(struct mbuf *mbufs, int dest, int src, int frametype);
_kernel_oserror *networkrxswi(struct mbuf *mbuf, rx_hdr *hdr, int *valid);
_kernel_oserror *networkirqswi(volatile int *irqstatus);
_kernel_oserror *networkhwaddrswi(unsigned char *hwaddr);
void callrx(dib *dibaddr, struct mbuf *mbuf_chain, int claimaddr, int pwp);

// versionof DCI supported
#define DCIVersion 403

static _kernel_oserror
    ErrorIllegalFlags   = { 0x58cc0, "Illegal flags" },
    ErrorInvalidMTU     = { 0x58cc1, "Invalid MTU requested" },
    ErrorIllegalSWI     = { 0x58cc2, "Not an EtherL SWI" },
    ErrorNoUnit         = { 0x58cc3, "Unit not configured" },
    ErrorBadClaim       = { 0x58cc4, "Illegal frame claim" },
    ErrorAlreadyClaimed = { 0x58cc5, "frame already claimed" },
    ErrorNoBuff         = { 0x58cc6, "No buffer space" };

workspace *work;

static const eystatsset supported = {
	// General information
	UCHAR_MAX,	// st_interface_type
	UCHAR_MAX,	// st_link_status
	UCHAR_MAX,	// st_link_polarity
	0,		// st_blank1
	0,		// st_link_failures
	0,		// st_network_collisions

	// Transmit statistics
	0,		// st_collisions
	0,		// st_excess_collisions
	0,		// st_heartbeat_failures
	0,		// st_not_listening
	ULONG_MAX,	// st_tx_frames
	0,		// st_tx_bytes
	ULONG_MAX,	// st_tx_general_errors
	{ 0, 0, 0, 0, 0, 0, 0, 0, },	// st_last_dest_addr

	// Receive statistics
	0,		// st_crc_failures
	0,		// st_frame_alignment_errors
	0,		// st_dropped_frames
	0,		// st_runt_frames
	0,		// st_overlong_frames
	0,		// st_jabbers
	0,		// st_late_events
	0,		// st_unwanted_frames
	ULONG_MAX,	// st_rx_frames
	0,		// st_rx_bytes
	ULONG_MAX,	// st_rx_general_errors
	{ 0, 0, 0, 0, 0, 0, 0, 0 },	// st_last_src_addr
};

// Status word set to 1 by the emulated hardware when an IRQ generated
volatile int irqstatus = 0;

// mbufs waiting to be used, to save frequent allocation and deallocation
static struct mbuf *rxhdr_mbuf = NULL;
static struct mbuf *data_mbuf = NULL;

#define MBUF_MANAGER_VERSION 100


/// List of locations to try to install the AutoSense file
static const char *autosense_locations[] = {
	"<Boot$Dir>.Resources.Configure.!InetSetup.AutoSense.EtherRPCEm",
	"<Boot$Dir>.RO420Hook.Res.Configure.!NetSetup.!IFSetup.AutoSense.EtherRPCEm",
	"<Boot$Dir>.RO430Hook.Res.Configure.!NetSetup.!IFSetup.AutoSense.EtherRPCEm",
	"<Boot$Dir>.RO440Hook.Res.Configure.!NetSetup.!IFSetup.AutoSense.EtherRPCEm",
	NULL
};

/**
 * Try to install the AutoSense file into the required location in the Boot
 * sequence.
 */
static void
autosense_install(void)
{
	_kernel_swi_regs r;
	size_t i;

	// Determine if Boot$Dir is set
	r.r[0] = (int) "Boot$Dir";
	r.r[1] = 0;
	r.r[2] = -1;
	r.r[3] = 0;
	_kernel_swi(OS_ReadVarVal, &r, &r);
	if (r.r[2] >= 0) {
		// Boot$Dir is not set
		return;
	}

	for (i = 0; autosense_locations[i] != NULL; i++) {
		const char *location = autosense_locations[i];
		_kernel_oserror *err;

		// Read information about current autosense file (if it exists)
		r.r[0] = 17;
		r.r[1] = (int) location;
		err = _kernel_swi(OS_File, &r, &r);
		if (err != NULL || r.r[0] != 0) {
			// Already exists (or an error occurred)
			continue;
		}

		// Copy autosense data to location
		r.r[0] = 10;
		r.r[1] = (int) location;
		r.r[2] = 0xffb;
		r.r[4] = (int) autosense_file;
		r.r[5] = (int) (autosense_file + sizeof(autosense_file));
		_kernel_swi(OS_File, &r, &r);
	}
}

// startstop = 0 for starting, 1 for stopping
void
SendServiceDCIDriverStatus(int startstop, workspace *work)
{
	_kernel_swi_regs r;

	r.r[0] = (int) &work->base_dib;
	r.r[1] = Service_DCIDriverStatus;
	r.r[2] = startstop;
	r.r[3] = DCIVersion;
	_kernel_swi(OS_ServiceCall, &r, &r);
}

// called in callback time for a variety of reasons
void
callback(workspace *work)
{
	SendServiceDCIDriverStatus(0, work);
}

static _kernel_oserror *
open_mbuf_manager_session(dci4_mbctl *mbctl)
{
	_kernel_swi_regs r;

	memset(mbctl, 0, sizeof(struct mbctl));
	mbctl->mbcsize = sizeof(struct mbctl);
	mbctl->mbcvers = MBUF_MANAGER_VERSION;
	mbctl->flags = 0;
	mbctl->advminubs = 0;
	mbctl->advmaxubs = 0;
	mbctl->mincontig = 0;
	mbctl->spare1 = 0;

	r.r[0] = (int) mbctl;

	return _kernel_swi(Mbuf_OpenSession, &r, &r);
}

static _kernel_oserror *
close_mbuf_manager_session(dci4_mbctl *mbctl)
{
	_kernel_swi_regs r;

	r.r[0] = (int) mbctl;
	return _kernel_swi(Mbuf_CloseSession, &r, &r);
}

// report frame release, unlink it, and free memory
void
bounceclaim(claimbuf *cb)
{
	_kernel_swi_regs rg;

	rg.r[0] = (int) &work->base_dib;
	rg.r[1] = Service_DCIFrameTypeFree;
	rg.r[2] = cb->frame | (cb->adtype << 16);;
	rg.r[3] = cb->addresslevel;
	rg.r[4] = cb->errorlevel;
	_kernel_swi(OS_ServiceCall, &rg, &rg); // report its now no longer claimed
	if (cb->last) {
		((claimbuf *) (cb->last))->next = cb->next;
	} else {
		work->claims = cb->next;
	}
	if (cb->next) {
		((claimbuf *) (cb->next))->last = cb->last; // unlink from chain
	}
	free(cb);
}

// called at mod finalisation to clean up...
_kernel_oserror *
finalise(int fatal, int podule, void *private_word)
{
	_kernel_swi_regs sregs;

	// ints off to ensure it stays quiet..
	_kernel_swi(OS_IntOff, &sregs, &sregs);

	networkirqswi(0);
	_swix(OS_ReleaseDeviceVector, _INR(0,4), 13, CallEveryVeneer, work, &irqstatus, 1);

	// free any mem owned here
	if (work) {
		SendServiceDCIDriverStatus(1, work); // flag we're stopping
		while (work->claims != NULL) { // work list to free claimbufs
			bounceclaim(work->claims);
		}
		if (work->mbctl) {
			if (rxhdr_mbuf) {
				work->mbctl->freem(work->mbctl, rxhdr_mbuf);
			}
			if (data_mbuf) {
				work->mbctl->freem(work->mbctl, data_mbuf);
			}

			close_mbuf_manager_session(work->mbctl);
			free(work->mbctl);
			work->mbctl = NULL;
		}
		free(work);
	}
	return 0;
}

static void
InitChip(workspace *work)
{
	_kernel_swi_regs sregs;

	work->base_dib.dib_swibase = EtherRPCEm_00;
	work->base_dib.dib_name = "rpcem";
	work->base_dib.dib_unit = 0;
	work->base_dib.dib_address = (unsigned char *) work->dev_addr;
	work->base_dib.dib_module = "EtherRPCEm";
	work->base_dib.dib_location = "Emulated";
	work->base_dib.dib_slot.slotid = 0;
	work->base_dib.dib_slot.minor = 0;
	work->base_dib.dib_slot.pcmciaslot = 0;
	work->base_dib.dib_slot.mbz = 0;
	work->base_dib.dib_inquire = EYFlagsSupported;
	work->claims = NULL; // flag no claimbufs

	sregs.r[0] = (int) "Inet$EtherType";
	sregs.r[1] = (int) work->base_dib.dib_name;
	sregs.r[2] = (int) strlen((char *) sregs.r[1]);
	sregs.r[3] = 0;
	sregs.r[4] = 4;

	_kernel_swi(OS_SetVarVal, &sregs, &sregs);

	sregs.r[0] = (int) CallBkVeneer; // where to call
	sregs.r[1] = (int) work; // work pointer for this
	_kernel_swi(OS_AddCallBack, &sregs, &sregs);
}

_kernel_oserror *
initialise(const char *cmd_tail, int podule_base, void *private_word)
{
	_kernel_oserror *err;
	int ribuff[2] = { 0, 0 };
	unsigned char *PodMaskAddr;
	unsigned char PodMask;

	UNUSED(cmd_tail);
	UNUSED(podule_base);

	work = 0;

	if (work = calloc(1, sizeof(workspace)), work == NULL) { // ensure all pointers cleared to Null
		return &ErrorNoBuff;
	}

	networkhwaddrswi((unsigned char *) work->dev_addr);

	work->pwp = private_word;

	// claim mbuf manager link
	if (work->mbctl = calloc(1, sizeof(dci4_mbctl)), work->mbctl == NULL) {
		return &ErrorNoBuff;
	}

	if (err = open_mbuf_manager_session(work->mbctl), err != NULL) {
		return err;
	}

	InitChip(work);

	err = _swix(Podule_ReadInfo, _INR(0,3), 0x18000, ribuff, sizeof(ribuff), 0);
	if (err) {
		return err;
	}

	PodMaskAddr = (unsigned char *) ribuff[0]; // IOC irq reg
	PodMask = (unsigned char) ribuff[1]; // IOC irq mask for pod

	err = _swix(OS_ClaimDeviceVector, _INR(0,4), 13, CallEveryVeneer, work, &irqstatus, 1);
	if (err) {
		return err;
	}

	*PodMaskAddr |= PodMask; // set the pod irq

	networkirqswi(&irqstatus);

	return NULL;
}

_kernel_oserror *
callevery_handler(workspace *work)
{
	rx_hdr *rxhdr;

	static volatile int sema = 0;

	if (sema) {
		return NULL;
	}
	sema = 1;

	do {
		if (rxhdr_mbuf == NULL) {
			rxhdr_mbuf = work->mbctl->alloc_s(work->mbctl, sizeof(rx_hdr), NULL);
		}
		if (data_mbuf == NULL) {
			data_mbuf = work->mbctl->alloc_s(work->mbctl, 1500, NULL);
		}
		if (rxhdr_mbuf && data_mbuf) {
			int valid;

			rxhdr = (rx_hdr *) (((char *) rxhdr_mbuf) + rxhdr_mbuf->m_off);
			rxhdr_mbuf->m_len = sizeof(rx_hdr);
			if (networkrxswi(data_mbuf, rxhdr, &valid) || !valid) {
				// No data
				sema = 0;
				return NULL;
			}
			// Increment receive frames
			work->st_rx_frames++;

			if (1) {
				// do stuff to make a new packet
				claimbuf *cb;
				int AddrLevel = AlSpecific;
				ClaimType Claim = NotClaimed;

				cb = work->claims;
				while (cb != NULL) {
					switch (cb->adtype) {
					case AdSpecific:
						if ((AddrLevel <= AlNormal) &&
						    (rxhdr->rx_frame_type == cb->frame) &&
						    (AddrLevel <= cb->addresslevel))
						{
							Claim = ClaimSpecific;
						}
						break;
					case AdMonitor:
						if ((rxhdr->rx_frame_type > 1500) &&
						    (AddrLevel <= cb->addresslevel))
						{
							Claim = ClaimMonitor;
						}
						break;
					case AdIeee:
						if (rxhdr->rx_frame_type < 1501) {
							Claim = ClaimIEEE;
						}
						break;
					case AdSink:
						if (rxhdr->rx_frame_type > 1500) {
							Claim = ClaimSink;
						}
						break;
					default:
						break;
					}

					if (Claim != NotClaimed) {
						// someone wants it, so check further
						rxhdr_mbuf->m_next = data_mbuf; // link them together
						rxhdr_mbuf->m_list = 0; // this is needed
						//data_mbuf->m_next = NULL;      // link them together
						data_mbuf->m_list = 0; // this is needed
						// at this point we dont need to check for safe mbuf
						// as we've used the alloc routine that only allocs
						// mbufs that actually are safe

						// now build the rxhdr structure
						rxhdr_mbuf->m_type = MT_HEADER;

						_swix(OS_IntOn, 0);
						callrx(&work->base_dib, rxhdr_mbuf, cb->claimaddress, cb->pwp);
						// The protocol stack is now responsible for freeing the mbufs
						rxhdr_mbuf = NULL;
						data_mbuf = NULL;
						_swix(OS_IntOff, 0);
						break;
					}
					cb = cb->next; // missed that frame type, so try another
				}
			}
		} else {
			// mbuf exhaustion
			sema = 0;
			return NULL;
		}
	} while (1);

	sema = 0;

	return NULL;
}

// module service call handler
void
service_call(int service_number, _kernel_swi_regs *r, void *pw)
{
	_kernel_swi_regs rg;
	_kernel_oserror *erp;

	switch (service_number) {
	case Service_StartWimp:
		autosense_install();
		break;

	case Service_EnumerateNetworkDrivers: // on entry, r0-> chain of dibs
		rg.r[0] = RMAClaim;
		rg.r[3] = sizeof(chaindib);
		if (erp = _kernel_swi(OS_Module, &rg, &rg), erp == NULL) {
			// got extra memory to add chaindib
			((chaindib *) rg.r[2])->chd_dib = &work->base_dib;
			((chaindib *) rg.r[2])->chd_next = (chaindib *) r->r[0];
			r->r[0] = rg.r[2]; // link our chaindib into the chain
		}
		break;

	case Service_DCIProtocolStatus:
		break;

	case Service_MbufManagerStatus:
		switch (r->r[0]) {
		case 0: // Manager started ... better try to restart our stuff
			if (!work->mbctl) {
				if (erp = open_mbuf_manager_session(work->mbctl), erp == NULL) {
					InitChip(work);
				}
			}
			break;
		case 1: // Manager stopping .. cannot if sessions are active
			if (work->mbctl) {
				// claim .. we're active
				r->r[1] = 0;
			}
			break;
		default: // Manager scavenge
			break;
		}
		break;
	}
}

_kernel_oserror *
swi_handler(int swi_no, _kernel_swi_regs *r, void *private_word)
{
	claimbuf *thism, *next;
	_kernel_oserror *err;

	UNUSED(private_word);

	switch (swi_no) {
	// return version num of DCI spec implelmented
	// on entry:	r0 = flags (all zero)
	// on exit:	r1 = version (4.03 at present)
	case SWIDCIVersion:
		if (r->r[0] == 0) {
			r->r[1] = DCIVersion;
			return 0;
		} else {
			return &ErrorIllegalFlags;
		}
		break;

	// return bitmap of supported features
	// on entry:	r0 = flags (all zero)
	//		r1 = unit number
	// on exit:	r2 = info word
	case SWIInquire:
		if (r->r[0] == 0) {
			r->r[2] = EYFlagsSupported;
		} else {
			return &ErrorIllegalFlags;
		}
		break;

	// return physical MTU size of supported network
	// on entry:	r0 = flags (all zero)
	//		r1 = unit number
	// on exit:	r2 = MTU size
	case SWIGetNetworkMTU:
		if (r->r[0] == 0) {
			r->r[2] = EY_MTU;
		} else {
			return &ErrorIllegalFlags;
		}
		break;

	// set physical MTU size of supported network
	// on entry:	r0 = flags (all zero)
	//		r1 = unit number
	//		r2 = new MTU
	// if size change not supported, return illegal op error
	// on exit, illegal op error!
	case SWISetNetworkMTU:
		if (r->r[0] == 0) {
			return &ErrorInvalidMTU;
		} else {
			return &ErrorIllegalFlags;
		}
		break;

	// transmit data
	// on entry:	r0 = flags (see below)
	//		r1 = unit number
	//		r2 = frame type
	//		r3 -> mbuf chain of data to tx
	//		r4 -> dest h/w address (byte aligned)
	//		r5 -> srce h/w address (byte aligned)(if needed)
	// on exit:	all regs preserved
	//
	// Flags:	bit 0:	0 = use own H/W address
	//			1 = use r5 as srce H/W addr
	//		bit 1:	0 = driver to assume ownership of mbuf chain
	//			1 = driver does not own mbuf chain
	case SWITransmit: {
		struct mbuf *mbufchain = (struct mbuf *) r->r[3];

		err = NULL;

		while (mbufchain) {
			struct mbuf *next = mbufchain->m_list;

			if (err == NULL) {
				err = networktxswi(mbufchain, r->r[4], (r->r[0] & 1) ? r->r[5] : 0, r->r[2]);
				// Increment transmit frames
				work->st_tx_frames++;
			}

			if ((r->r[0] & 2) == 0) {
				work->mbctl->freem(work->mbctl, mbufchain);
			}
			mbufchain = next;
		}
		if (err) {
			return err;
		}
		break;
	}

	// on entry:	r0 = flags (see below)
	//		r1 = unit number
	//		r2 = frame type		bottom 16 bits, frame type, top 16.. addr class
	//			1.. specific        2.. sink
	//			3.. monitor         4.. ieee
	//		r3 = address level (for write)
	//		r4 = error level (for write)
	//		r5 = handlers private word pointer
	//		r6 = address of routine to receive this frame
	//
	// on exit:	all regs preserved
	//
	// Flags:	bit 0:	0 = claim frame type
	//			1 = release frame type
	//		bit 1:  0 = drivers can pass unsafe mbuf chains back
	//			1 = ensure_safe required before mbufs passed back
	case SWIFilter:
		if ((r->r[0] & 1) == 1) {
			// release frame
			if (r->r[1] != 0) {
				// only unit 0 supported at present
				return &ErrorNoUnit;
			}
			thism = work->claims;
			while (thism != NULL) {
				if ((thism->unit == r->r[1]) &&
				    (thism->frame == (r->r[2] & 0xffff)) &&
				    (thism->adtype == (r->r[2] >> 16)) &&
				    (thism->addresslevel == r->r[3]) &&
				    (thism->errorlevel == r->r[4]) &&
				    (thism->pwp == r->r[5]) &&
				    (thism->claimaddress == r->r[6]))
				{
					// we've found the claim, unlink it
					bounceclaim(thism);
					break;
				}
				thism = thism->next;
			}
		} else {
			// claim frame type
			if (r->r[1] != 0) {
				// only unit 0 supported at present
				return &ErrorNoUnit;
			}
			thism = next = work->claims;
			while (next != NULL) {
				int adtype, used;

				adtype = r->r[2] >> 16;
				thism = next;
				used = 0;
				if ((thism->frame == r->r[2] & 0xffff) // already a claimer..
				    || (adtype == AdMonitor)) // or we're greedy
				{
					switch (adtype) {
					case AdSpecific:
						return &ErrorAlreadyClaimed;
						break;
					case AdSink:
						if (thism->addresslevel == AdSpecific) {
							bounceclaim(thism);
							goto claimframe;
						} else {
							return &ErrorAlreadyClaimed;
						}
						break;
					case AdMonitor:
						if (thism->adtype < AdMonitor) { // bounce anything less
							next = thism->next;
							bounceclaim(thism);
							used = 1;
							break;
						}
						if (thism->adtype != AdIeee) {
							return &ErrorAlreadyClaimed;
						}
						break;
					case AdIeee:
						return &ErrorAlreadyClaimed;
						break;
					default:
						return &ErrorBadClaim;
						break;
					}
					if(thism->adtype >adtype) //
					{
					}
				}
				if (!used) {
					next = next->next;
				}
			}
			// thism points to first NULL claimbuf ptr
			if (next = calloc(1, sizeof(claimbuf)), next == NULL) {
				return &ErrorNoBuff;
			}
			if (thism != NULL) {
				thism->next = next;
				next->last = thism;
			} else {
				work->claims = next;
				next->last = NULL;
			}
claimframe:
			next->flags = (char) r->r[0] & 1;
			next->unit = r->r[1];
			next->frame = r->r[2] & 0xffff;
			next->adtype = r->r[2] >> 16;
			next->addresslevel = r->r[3];
			next->errorlevel = r->r[4];
			next->pwp = r->r[5];
			next->claimaddress = r->r[6];
		}
		// now check all claims and set multicast or promiscuous as needed
		next = work->claims;
		work->flags = 0;
		while (next) {
			if (next->addresslevel == AlMulticast) {
				work->flags |= IFF_ALLMULTI;
			}
			if (next->addresslevel == AlPromiscuous) {
				work->flags |= IFF_PROMISC;
			}
			next = next->next;
		}
		break;

	// return unit statistics
	// on entry:	r0 = flags (see below)
	//		r1 = unit number
	//		r2 -> buffer for results
	// on exit:	all regs preserved
	//
	// Flags:	bit 0:	0 = indicate which stats are gathered
	//			1 = return actual stats
	case SWIStats:
		if ((r->r[0] & 1) != 0) {
			// return actual stats
			eystatsset *stats = (eystatsset *) r->r[2];

			// Set all statistics to zero (including implemented error counters)
			memset(stats, 0, sizeof(eystatsset));

			stats->st_interface_type = 3; // 10BaseT
			stats->st_link_status = (1 << 0) | // OK
			                        (1 << 1) | // Active
			                        (3 << 2) | // Promiscuous
			                        (1 << 4);  // Full duplex
			stats->st_link_polarity = 1; // Polarity correct
			stats->st_tx_frames = work->st_tx_frames;
			stats->st_rx_frames = work->st_rx_frames;
		} else {
			// indicate what's supported
			memcpy((void *) r->r[2], &supported.st_interface_type, sizeof(eystatsset));
		}
		break;

	// on entry:	r0 = flags (see below)
	//		r1 = unit number
	//		r2 = frame type
	//		r3 ->(byte aligned) multicast hardware (MAC) address
	//		r4 ->(word aligned) multicast IP address
	//		r5 = priv word ptr
	//		r6 = address of handler routine to receive frames
	// on exit:	all regs preserved
	//
	// Flags:	bit 0:	0 = request an mc addr
	//			1 = release an mc addr
	//		bit 1:	0 = request/release specific mc addr (in r3 & r4)
	//			1 = request/release all mc addrs
	case SWIMulticastreq:
		if (r->r[0] && ~3) {
			printf("\n EYmulticast not fully implemented yet\n");
		}
		break;

	default:
		return &ErrorIllegalSWI;
		break;
	}

	return 0;
}
