/* * serial_amiga.cpp - Serial device driver, AmigaOS specific stuff * * Basilisk II (C) 1997-2001 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 #include #include #include #include #include #include #include #define __USE_SYSBASE #include #include #include #include #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; iIOSer.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; iIOSer.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); }