/*
 *  serial_amiga.cpp - Serial device driver, AmigaOS specific stuff
 *
 *  Basilisk II (C) 1997-1999 Christian Bauer
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/errors.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <dos/dostags.h>
#include <devices/serial.h>
#include <devices/parallel.h>
#include <proto/exec.h>
#include <proto/dos.h>

#include "sysdeps.h"
#include "cpu_emulation.h"
#include "main.h"
#include "macos_util.h"
#include "prefs.h"
#include "serial.h"
#include "serial_defs.h"

#define DEBUG 0
#include "debug.h"

#define MONITOR 0


// These messages are sent to the serial process
const uint32 MSG_QUERY = 'qery';			// Query port status, return status in control_io
const uint32 MSG_SET_PARAMS = 'setp';		// Set serial parameters (parameters in control_io)
const uint32 MSG_SET_PAR_PARAMS = 'pstp';	// Set parallel parameters (parameters in control_io)
const uint32 MSG_KILL_IO = 'kill';			// Kill pending I/O requests
const uint32 MSG_BREAK = 'brek';			// Send break
const uint32 MSG_RESET = 'rset';			// Reset channel
const uint32 MSG_PRIME_IN = 'prin';			// Data input
const uint32 MSG_PRIME_OUT = 'pout';		// Data output

struct SerMessage : public Message {
	SerMessage(uint32 what_, const struct MsgPort *reply_port = NULL)
	{
		what = what_;
		mn_ReplyPort = (struct MsgPort *)reply_port;
		mn_Length = sizeof(*this);
	}
	uint32 what;
	uint32 pb;
};


// Driver private variables
class ASERDPort : public SERDPort {
public:
	ASERDPort(const char *dev)
	{
		device_name = dev;
		if (dev && dev[0] == '*') {
			is_parallel = true;
			device_name++;
		} else
			is_parallel = false;
		control_io = NULL;
		serial_proc = NULL;
		reply_port = NULL;
	}

	virtual ~ASERDPort()
	{
	}

	virtual int16 open(uint16 config);
	virtual int16 prime_in(uint32 pb, uint32 dce);
	virtual int16 prime_out(uint32 pb, uint32 dce);
	virtual int16 control(uint32 pb, uint32 dce, uint16 code);
	virtual int16 status(uint32 pb, uint32 dce, uint16 code);
	virtual int16 close(void);

private:
	bool configure(uint16 config);
	void set_handshake(uint32 s, bool with_dtr);
	void send_to_proc(uint32 what, uint32 pb = 0);
	bool query(void);
	bool set_params(void);
	bool set_par_params(void);
	void conv_error(struct IOExtSer *io, uint32 dt);
	static void serial_func(void);

	const char *device_name;			// Device name
	bool is_parallel;					// Flag: Port is parallel
	IOExtSer *control_io;				// IORequest for setting serial port characteristics etc.

	struct Process *serial_proc;		// Serial device handler process
	bool proc_error;					// Flag: process didn't initialize
	struct MsgPort *proc_port;			// Message port of process, for communication with main task
	struct MsgPort *reply_port;			// Reply port for communication with process

	uint8 err_mask;						// shkErrs
};


// Global variables
static void *proc_arg;						// Argument to process
extern struct Task *MainTask;				// Pointer to main task (from main_amiga.cpp)


/*
 *  Initialization
 */

void SerialInit(void)
{
	// Read serial preferences and create structs for both ports
	the_serd_port[0] = new ASERDPort(PrefsFindString("seriala"));
	the_serd_port[1] = new ASERDPort(PrefsFindString("serialb"));
}


/*
 *  Deinitialization
 */

void SerialExit(void)
{
	delete (ASERDPort *)the_serd_port[0];
	delete (ASERDPort *)the_serd_port[1];
}


/*
 *  Open serial port
 */

int16 ASERDPort::open(uint16 config)
{
	// Don't open NULL name devices
	if (device_name == NULL)
		return openErr;

	// Init variables
	err_mask = 0;

	// Create message port
	reply_port = CreateMsgPort();
	if (reply_port == NULL)
		goto open_error;

	// Start process
	proc_error = false;
	proc_arg = this;
	SetSignal(0, SIGF_SINGLE);
	serial_proc = CreateNewProcTags(
		NP_Entry, (ULONG)serial_func,
		NP_Name, (ULONG)"Basilisk II Serial Task",
		NP_Priority, 1,
		TAG_END	
	);
	if (serial_proc == NULL)
		goto open_error;

	// Wait for signal from process
	Wait(SIGF_SINGLE);

	// Initialization error? Then bail out
	if (proc_error)
		goto open_error;

	// Configure port
	configure(config);
	return noErr;

open_error:
	serial_proc = NULL;
	if (reply_port) {
		DeleteMsgPort(reply_port);
		reply_port = NULL;
	}
	return openErr;
}


/*
 *  Read data from port
 */

int16 ASERDPort::prime_in(uint32 pb, uint32 dce)
{
	// Send input command to serial process
	D(bug("primein\n"));
	read_done = false;
	read_pending = true;
	WriteMacInt32(input_dt + serdtDCE, dce);
	send_to_proc(MSG_PRIME_IN, pb);
	return 1;	// Command in progress
}


/*
 *  Write data to port
 */

int16 ASERDPort::prime_out(uint32 pb, uint32 dce)
{
	// Send output command to serial process
	D(bug("primeout\n"));
	write_done = false;
	write_pending = true;
	WriteMacInt32(output_dt + serdtDCE, dce);
	send_to_proc(MSG_PRIME_OUT, pb);
	return 1;	// Command in progress
}


/*
 *	Control calls
 */

int16 ASERDPort::control(uint32 pb, uint32 dce, uint16 code)
{
	D(bug("control(%ld)\n", (uint32)code));
	switch (code) {
		case 1:			// KillIO
			send_to_proc(MSG_KILL_IO);
			return noErr;

		case kSERDConfiguration:
			if (configure(ReadMacInt16(pb + csParam)))
				return noErr;
			else
				return paramErr;

		case kSERDInputBuffer: {
			if (is_parallel)
				return noErr;
			int buf = ReadMacInt16(pb + csParam + 4) & 0xffffffc0;
			if (buf < 1024)	// 1k minimum
				buf = 1024;
			D(bug(" buffer size is now %08lx\n", buf));
			control_io->io_RBufLen = buf;
			return set_params() ? noErr : paramErr;
		}

		case kSERDSerHShake:
			set_handshake(pb + csParam, false);
			return noErr;

		case kSERDSetBreak:
			if (!is_parallel)
				send_to_proc(MSG_BREAK);
			return noErr;

		case kSERDClearBreak:
			return noErr;

		case kSERDBaudRate:
			if (is_parallel)
				return noErr;
			control_io->io_Baud = ReadMacInt16(pb + csParam);
			D(bug(" baud rate %ld\n", control_io->io_Baud));
			return set_params() ? noErr : paramErr;

		case kSERDHandshake:
		case kSERDHandshakeRS232:
			set_handshake(pb + csParam, true);
			return noErr;

		case kSERDClockMIDI:
			if (is_parallel)
				return noErr;
			control_io->io_Baud = 31250;
			control_io->io_SerFlags = SERF_XDISABLED | SERF_SHARED;
			control_io->io_StopBits = 1;
			control_io->io_ReadLen = control_io->io_WriteLen = 8;
			return set_params() ? noErr : paramErr;

		case kSERDMiscOptions:
		case kSERDAssertDTR:
		case kSERDNegateDTR:
		case kSERDSetPEChar:
		case kSERDSetPEAltChar:
		case kSERDAssertRTS:
		case kSERDNegateRTS:
			return noErr;	// Not supported under AmigaOS

		case kSERD115KBaud:
			if (is_parallel)
				return noErr;
			control_io->io_Baud = 115200;
			return set_params() ? noErr : paramErr;

		case kSERD230KBaud:
		case kSERDSetHighSpeed:
			if (is_parallel)
				return noErr;
			control_io->io_Baud = 230400;
			return set_params() ? noErr : paramErr;

		case kSERDResetChannel:
			send_to_proc(MSG_RESET);
			return noErr;

		default:
			printf("WARNING: SerialControl(): unimplemented control code %d\n", code);
			return controlErr;
	}
}


/*
 *	Status calls
 */

int16 ASERDPort::status(uint32 pb, uint32 dce, uint16 code)
{
	D(bug("status(%ld)\n", (uint32)code));
	switch (code) {
		case kSERDInputCount:
			WriteMacInt32(pb + csParam, 0);
			if (!is_parallel) {
				if (!query())
					return noErr;
				D(bug("status(2) successful, returning %08lx\n", control_io->IOSer.io_Actual));
				WriteMacInt32(pb + csParam, control_io->IOSer.io_Actual);
			}
			return noErr;

		case kSERDStatus: {
			uint32 p = pb + csParam;
			WriteMacInt8(p + staCumErrs, cum_errors);
			cum_errors = 0;
			WriteMacInt8(p + staRdPend, read_pending);
			WriteMacInt8(p + staWrPend, write_pending);
			if (is_parallel) {
				WriteMacInt8(p + staXOffSent, 0);
				WriteMacInt8(p + staXOffHold, 0);
				WriteMacInt8(p + staCtsHold, 0);
				WriteMacInt8(p + staDsrHold, 0);
				WriteMacInt8(p + staModemStatus, dsrEvent | dcdEvent | ctsEvent);
			} else {
				query();
				WriteMacInt8(p + staXOffSent,
					(control_io->io_Status & IO_STATF_XOFFREAD ? xOffWasSent : 0)
					| (control_io->io_Status & (1 << 6) ? dtrNegated : 0));		// RTS
				WriteMacInt8(p + staXOffHold, control_io->io_Status & IO_STATF_XOFFWRITE);
				WriteMacInt8(p + staCtsHold, control_io->io_Status & (1 << 4));	// CTS
				WriteMacInt8(p + staDsrHold, control_io->io_Status & (1 << 3));	// DSR
				WriteMacInt8(p + staModemStatus,
					(control_io->io_Status & (1 << 3) ? 0 : dsrEvent)
					| (control_io->io_Status & (1 << 2) ? riEvent : 0)
					| (control_io->io_Status & (1 << 5) ? 0 : dcdEvent)
					| (control_io->io_Status & (1 << 4) ? 0 : ctsEvent)
					| (control_io->io_Status & IO_STATF_READBREAK ? breakEvent : 0));
			}
			return noErr;
		}

		default:
			printf("WARNING: SerialStatus(): unimplemented status code %d\n", code);
			return statusErr;
	}
}


/*
 *	Close serial port
 */

int16 ASERDPort::close()
{
	// Stop process
	if (serial_proc) {
		SetSignal(0, SIGF_SINGLE);
		Signal(&serial_proc->pr_Task, SIGBREAKF_CTRL_C);
		Wait(SIGF_SINGLE);
	}

	// Delete reply port
	if (reply_port) {
		DeleteMsgPort(reply_port);
		reply_port = NULL;
	}
	return noErr;
}


/*
 *  Configure serial port with MacOS config word
 */

bool ASERDPort::configure(uint16 config)
{
	D(bug(" configure %04lx\n", (uint32)config));
	if (is_parallel)
		return true;

	// Set number of stop bits
	switch (config & 0xc000) {
		case stop10:
			control_io->io_StopBits = 1;
			break;
		case stop20:
			control_io->io_StopBits = 2;
			break;
		default:
			return false;
	}

	// Set parity mode
	switch (config & 0x3000) {
		case noParity:
			control_io->io_SerFlags &= ~SERF_PARTY_ON;
			break;
		case oddParity:
			control_io->io_SerFlags |= SERF_PARTY_ON | SERF_PARTY_ODD;
			break;
		case evenParity:
			control_io->io_SerFlags |= SERF_PARTY_ON;
			control_io->io_SerFlags &= ~SERF_PARTY_ODD;
			break;
		default:
			return false;
	}

	// Set number of data bits
	switch (config & 0x0c00) {
		case data5:
			control_io->io_ReadLen = control_io->io_WriteLen = 5;
			break;
		case data6:
			control_io->io_ReadLen = control_io->io_WriteLen = 6;
			break;
		case data7:
			control_io->io_ReadLen = control_io->io_WriteLen = 7;
			break;
		case data8:
			control_io->io_ReadLen = control_io->io_WriteLen = 8;
			break;
	}

	// Set baud rate
	control_io->io_Baud = 115200 / ((config & 0x03ff) + 2);
	return set_params();
}


/*
 *  Set serial handshaking
 */

void ASERDPort::set_handshake(uint32 s, bool with_dtr)
{
	D(bug(" set_handshake %02x %02x %02x %02x %02x %02x %02x %02x\n",
		ReadMacInt8(s + 0), ReadMacInt8(s + 1), ReadMacInt8(s + 2), ReadMacInt8(s + 3),
		ReadMacInt8(s + 4), ReadMacInt8(s + 5), ReadMacInt8(s + 6), ReadMacInt8(s + 7)));

	err_mask = ReadMacInt8(s + shkErrs);

	if (is_parallel) {

		// Parallel handshake
		if (with_dtr) {
			if (ReadMacInt8(s + shkFCTS) || ReadMacInt8(s + shkFDTR))
				((IOExtPar *)control_io)->io_ParFlags |= PARF_ACKMODE;
			else
				((IOExtPar *)control_io)->io_ParFlags &= ~PARF_ACKMODE;
		} else {
			if (ReadMacInt8(s + shkFCTS))
				((IOExtPar *)control_io)->io_ParFlags |= PARF_ACKMODE;
			else
				((IOExtPar *)control_io)->io_ParFlags &= ~PARF_ACKMODE;
		}
		set_par_params();

	} else {

		// Serial handshake
		if (ReadMacInt8(s + shkFXOn) || ReadMacInt8(s + shkFInX))
			control_io->io_SerFlags &= ~SERF_XDISABLED;
		else
			control_io->io_SerFlags |= SERF_XDISABLED;

		if (with_dtr) {
			if (ReadMacInt8(s + shkFCTS) || ReadMacInt8(s + shkFDTR))
				control_io->io_SerFlags |= SERF_7WIRE;
			else
				control_io->io_SerFlags &= ~SERF_7WIRE;
		} else {
			if (ReadMacInt8(s + shkFCTS))
				control_io->io_SerFlags |= SERF_7WIRE;
			else
				control_io->io_SerFlags &= ~SERF_7WIRE;
		}
		control_io->io_CtlChar = ReadMacInt16(s + shkXOn) << 16;
		set_params();
	}
}


/*
 *  Send message to serial process
 */

void ASERDPort::send_to_proc(uint32 what, uint32 pb)
{
	D(bug("sending %08lx to serial_proc\n", what));
	SerMessage msg(what, reply_port);
	msg.pb = pb;
	PutMsg(proc_port, &msg);
	WaitPort(reply_port);
	GetMsg(reply_port);
	D(bug(" sent\n"));
}


/*
 *  Query serial port status
 */

bool ASERDPort::query(void)
{
	send_to_proc(MSG_QUERY);
	return control_io->IOSer.io_Error == 0;
}


/*
 *  Set serial parameters
 */

bool ASERDPort::set_params(void)
{
	// Set/clear RadBoogie
	UBYTE flags = control_io->io_SerFlags;
	if (!(flags & SERF_PARTY_ON) && (flags & SERF_XDISABLED) && control_io->io_ReadLen == 8)
		control_io->io_SerFlags |= SERF_RAD_BOOGIE;
	else
		control_io->io_SerFlags &= ~SERF_RAD_BOOGIE;

	// Send message to serial process
	send_to_proc(MSG_SET_PARAMS);
	return control_io->IOSer.io_Error == 0;
}


/*
 *  Set parallel parameters
 */

bool ASERDPort::set_par_params(void)
{
	send_to_proc(MSG_SET_PAR_PARAMS);
	return control_io->IOSer.io_Error == 0;
}


/*
 *  Convert AmigaOS error code to MacOS error code, set serdtResult and cum_errors
 */

void ASERDPort::conv_error(struct IOExtSer *io, uint32 dt)
{
	int16 oserr;
	uint8 cum;

	BYTE err = io->IOSer.io_Error;
	if (err == 0 || err == IOERR_NOCMD) {
		oserr = 0;
		cum = 0;
	} else {
		if (is_parallel) {
			oserr = (err_mask & framingErr) ? rcvrErr : 0;
			cum = framingErr;
		} else {
			switch (io->IOSer.io_Error) {
				case SerErr_DetectedBreak:
					oserr = breakRecd;
					cum = breakErr;
					break;
				case SerErr_ParityErr:
					oserr = (err_mask & parityErr) ? rcvrErr : 0;
					cum = parityErr;
					break;
				case SerErr_BufOverflow:
					oserr = (err_mask & swOverrunErr) ? rcvrErr : 0;
					cum = swOverrunErr;
					break;
				case SerErr_LineErr:
					oserr = (err_mask & hwOverrunErr) ? rcvrErr : 0;
					cum = hwOverrunErr;
					break;
				default:
					oserr = (err_mask & framingErr) ? rcvrErr : 0;
					cum = framingErr;
					break;
			}
		}
	}

	WriteMacInt32(dt + serdtResult, oserr);
	cum_errors |= cum;
}


/*
 *  Process for communication with the serial.device
 */

__saveds void ASERDPort::serial_func(void)
{
	struct ASERDPort *obj = (ASERDPort *)proc_arg;
	struct MsgPort *proc_port = NULL, *io_port = NULL, *control_port = NULL;
	struct IOExtSer *read_io = NULL, *write_io = NULL, *control_io = NULL;
	uint8 orig_params[sizeof(struct IOExtSer)];
	bool opened = false;
	ULONG io_mask = 0, proc_port_mask = 0;

	// Default: error occured
	obj->proc_error = true;

	// Create message port for communication with main task
	proc_port = CreateMsgPort();
	if (proc_port == NULL)
		goto quit;
	proc_port_mask = 1 << proc_port->mp_SigBit;

	// Create message ports for serial.device I/O
	io_port = CreateMsgPort();
	if (io_port == NULL)
		goto quit;
	io_mask = 1 << io_port->mp_SigBit;
	control_port = CreateMsgPort();
	if (control_port == NULL)
		goto quit;

	// Create IORequests
	read_io = (struct IOExtSer *)CreateIORequest(io_port, sizeof(struct IOExtSer));
	write_io = (struct IOExtSer *)CreateIORequest(io_port, sizeof(struct IOExtSer));
	control_io = (struct IOExtSer *)CreateIORequest(control_port, sizeof(struct IOExtSer));
	if (read_io == NULL || write_io == NULL || control_io == NULL)
		goto quit;
	read_io->IOSer.io_Message.mn_Node.ln_Type = 0;	// Avoid CheckIO() bug
	write_io->IOSer.io_Message.mn_Node.ln_Type = 0;
	control_io->IOSer.io_Message.mn_Node.ln_Type = 0;

	// Parse device name
	char dev_name[256];
	ULONG dev_unit;
	if (sscanf(obj->device_name, "%[^/]/%ld", dev_name, &dev_unit) < 2)
		goto quit;

	// Open device
	if (obj->is_parallel)
		((IOExtPar *)read_io)->io_ParFlags = PARF_SHARED;
	else
		read_io->io_SerFlags = SERF_SHARED | SERF_7WIRE;
	if (OpenDevice((UBYTE *)dev_name, dev_unit, (struct IORequest *)read_io, 0) || read_io->IOSer.io_Device == NULL)
		goto quit;
	opened = true;

	// Copy IORequests
	memcpy(write_io, read_io, sizeof(struct IOExtSer));
	memcpy(control_io, read_io, sizeof(struct IOExtSer));

	// Attach control_io to control_port and set default values
	control_io->IOSer.io_Message.mn_ReplyPort = control_port;
	if (!obj->is_parallel) {
		control_io->io_CtlChar = SER_DEFAULT_CTLCHAR;
		control_io->io_RBufLen = 64;
		control_io->io_ExtFlags = 0;
		control_io->io_Baud = 9600;
		control_io->io_BrkTime = 250000;
		control_io->io_ReadLen = control_io->io_WriteLen = 8;
		control_io->io_StopBits = 1;
		control_io->io_SerFlags = SERF_SHARED;
		control_io->IOSer.io_Command = SDCMD_SETPARAMS;
		DoIO((struct IORequest *)control_io);
		memcpy(orig_params, &(control_io->io_CtlChar), (uint8 *)&(control_io->io_Status) - (uint8 *)&(control_io->io_CtlChar));
	}

	// Initialization went well, inform main task
	obj->proc_port = proc_port;
	obj->control_io = control_io;
	obj->proc_error = false;
	Signal(MainTask, SIGF_SINGLE);

	// Main loop
	for (;;) {

		// Wait for I/O and messages (CTRL_C is used for quitting the task)
		ULONG sig = Wait(proc_port_mask | io_mask | SIGBREAKF_CTRL_C);

		// Main task wants to quit us
		if (sig & SIGBREAKF_CTRL_C)
			break;

		// Main task sent a command to us
		if (sig & proc_port_mask) {
			struct SerMessage *msg;
			while (msg = (SerMessage *)GetMsg(proc_port)) {
				D(bug("serial_proc received %08lx\n", msg->what));
				switch (msg->what) {
					case MSG_QUERY:
						control_io->IOSer.io_Command = SDCMD_QUERY;
						DoIO((struct IORequest *)control_io);
						D(bug(" query returned %08lx, actual %08lx\n", control_io->IOSer.io_Error, control_io->IOSer.io_Actual));
						break;

					case MSG_SET_PARAMS:
						// Only send SDCMD_SETPARAMS when configuration has changed
						if (memcmp(orig_params, &(control_io->io_CtlChar), (uint8 *)&(control_io->io_Status) - (uint8 *)&(control_io->io_CtlChar))) {
							memcpy(orig_params, &(control_io->io_CtlChar), (uint8 *)&(control_io->io_Status) - (uint8 *)&(control_io->io_CtlChar));
							memcpy(&(read_io->io_CtlChar), &(control_io->io_CtlChar), (uint8 *)&(control_io->io_Status) - (uint8 *)&(control_io->io_CtlChar));
							memcpy(&(write_io->io_CtlChar), &(control_io->io_CtlChar), (uint8 *)&(control_io->io_Status) - (uint8 *)&(control_io->io_CtlChar));
							control_io->IOSer.io_Command = SDCMD_SETPARAMS;
							D(bug(" params %08lx %08lx %08lx %08lx %08lx %08lx\n", control_io->io_CtlChar, control_io->io_RBufLen, control_io->io_ExtFlags, control_io->io_Baud, control_io->io_BrkTime, *(uint32 *)((uint8 *)control_io + 76)));
							DoIO((struct IORequest *)control_io);
							D(bug(" set_parms returned %08lx\n", control_io->IOSer.io_Error));
						}
						break;

					case MSG_SET_PAR_PARAMS:
						control_io->IOSer.io_Command = PDCMD_SETPARAMS;
						DoIO((struct IORequest *)control_io);
						D(bug(" set_par_parms returned %08lx\n", control_io->IOSer.io_Error));
						break;

					case MSG_BREAK:
						control_io->IOSer.io_Command = SDCMD_BREAK;
						DoIO((struct IORequest *)control_io);
						D(bug(" break returned %08lx\n", control_io->IOSer.io_Error));
						break;

					case MSG_RESET:
						control_io->IOSer.io_Command = CMD_RESET;
						DoIO((struct IORequest *)control_io);
						D(bug(" reset returned %08lx\n", control_io->IOSer.io_Error));
						break;

					case MSG_KILL_IO:
						AbortIO((struct IORequest *)read_io);
						AbortIO((struct IORequest *)write_io);
						WaitIO((struct IORequest *)read_io);
						WaitIO((struct IORequest *)write_io);
						obj->read_pending = obj->write_pending = false;
						obj->read_done = obj->write_done = false;
						break;

					case MSG_PRIME_IN:
						read_io->IOSer.io_Message.mn_Node.ln_Name = (char *)msg->pb;
						read_io->IOSer.io_Data = Mac2HostAddr(ReadMacInt32(msg->pb + ioBuffer));
						read_io->IOSer.io_Length = ReadMacInt32(msg->pb + ioReqCount);
						read_io->IOSer.io_Actual = 0;
						read_io->IOSer.io_Command = CMD_READ;
						D(bug("serial_proc receiving %ld bytes from %08lx\n", read_io->IOSer.io_Length, read_io->IOSer.io_Data));
						SendIO((struct IORequest *)read_io);
						break;

					case MSG_PRIME_OUT: {
						write_io->IOSer.io_Message.mn_Node.ln_Name = (char *)msg->pb;
						write_io->IOSer.io_Data = Mac2HostAddr(ReadMacInt32(msg->pb + ioBuffer));
						write_io->IOSer.io_Length = ReadMacInt32(msg->pb + ioReqCount);
						write_io->IOSer.io_Actual = 0;
						write_io->IOSer.io_Command = CMD_WRITE;
						D(bug("serial_proc transmitting %ld bytes from %08lx\n", write_io->IOSer.io_Length, write_io->IOSer.io_Data));
#if MONITOR
						bug("Sending serial data:\n");
						uint8 *adr = Mac2HostAddr(ReadMacInt32(msg->pb + ioBuffer));
						for (int i=0; i<len; i++) {
							bug("%02lx ", adr[i]);
						}
						bug("\n");
#endif
						SendIO((struct IORequest *)write_io);
						break;
					}
				}
				D(bug(" serial_proc replying\n"));
				ReplyMsg(msg);
			}
		}

		// I/O operation completed
		if (sig & io_mask) {
			struct IOExtSer *io;
			while (io = (struct IOExtSer *)GetMsg(io_port)) {
				if (io == read_io) {
					D(bug("read_io complete, %ld bytes received, error %ld\n", read_io->IOSer.io_Actual, read_io->IOSer.io_Error));
					uint32 pb = (uint32)read_io->IOSer.io_Message.mn_Node.ln_Name;
#if MONITOR
					bug("Receiving serial data:\n");
					uint8 *adr = Mac2HostAddr(ReadMacInt32(msg->pb + ioBuffer));
					for (int i=0; i<read_io->IOSer.io_Actual; i++) {
						bug("%02lx ", adr[i]);
					}
					bug("\n");
#endif
					WriteMacInt32(pb + ioActCount, read_io->IOSer.io_Actual);
					obj->conv_error(read_io, obj->input_dt);
					obj->read_done = true;
					SetInterruptFlag(INTFLAG_SERIAL);
					TriggerInterrupt();
				} else if (io == write_io) {
					D(bug("write_io complete, %ld bytes sent, error %ld\n", write_io->IOSer.io_Actual, write_io->IOSer.io_Error));
					uint32 pb = (uint32)write_io->IOSer.io_Message.mn_Node.ln_Name;
					WriteMacInt32(pb + ioActCount, write_io->IOSer.io_Actual);
					obj->conv_error(write_io, obj->output_dt);
					obj->write_done = true;
					SetInterruptFlag(INTFLAG_SERIAL);
					TriggerInterrupt();
				}
			}
		}
	}
quit:

	// Close everything
	if (opened) {
		if (CheckIO((struct IORequest *)write_io) == 0) {
			AbortIO((struct IORequest *)write_io);
			WaitIO((struct IORequest *)write_io);
		}
		if (CheckIO((struct IORequest *)read_io) == 0) {
			AbortIO((struct IORequest *)read_io);
			WaitIO((struct IORequest *)read_io);
		}
		CloseDevice((struct IORequest *)read_io);
	}
	if (control_io)
		DeleteIORequest(control_io);
	if (write_io)
		DeleteIORequest(write_io);
	if (read_io)
		DeleteIORequest(read_io);
	if (control_port)
		DeleteMsgPort(control_port);
	if (io_port)
		DeleteMsgPort(io_port);

	// Send signal to main task to confirm termination
	Forbid();
	Signal(MainTask, SIGF_SINGLE);
}
