macemu/BasiliskII/src/Unix/serial_unix.cpp

840 lines
19 KiB
C++

/*
* serial_unix.cpp - Serial device driver, Unix specific stuff
*
* Basilisk II (C) 1997-2008 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 "sysdeps.h"
#include <sys/ioctl.h>
#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif
#include <sys/stat.h>
#include <sys/wait.h>
#include <pthread.h>
#include <semaphore.h>
#include <termios.h>
#include <errno.h>
#ifdef __linux__
#include <linux/lp.h>
#include <linux/major.h>
#include <linux/kdev_t.h>
#endif
#include "cpu_emulation.h"
#include "main.h"
#include "macos_util.h"
#include "prefs.h"
#include "serial.h"
#include "serial_defs.h"
extern "C" {
#include "sshpty.h"
}
#define DEBUG 0
#include "debug.h"
#define MONITOR 0
// IRIX missing or unsupported defines
#ifdef sgi
#ifndef CRTSCTS
#define CRTSCTS CNEW_RTSCTS
#endif
#ifndef B230400
#define B230400 B115200
#endif
#endif
// Missing functions
#ifndef HAVE_CFMAKERAW
static int cfmakeraw(struct termios *termios_p)
{
termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
termios_p->c_cflag &= ~(CSIZE|PARENB);
termios_p->c_cflag |= CS8;
return 0;
}
#endif
// Driver private variables
class XSERDPort : public SERDPort {
public:
XSERDPort(const char *dev)
{
device_name = dev;
protocol = serial;
fd = -1;
pid = 0;
input_thread_active = output_thread_active = false;
Set_pthread_attr(&thread_attr, 2);
}
virtual ~XSERDPort()
{
if (input_thread_active) {
input_thread_cancel = true;
#ifdef HAVE_PTHREAD_CANCEL
pthread_cancel(input_thread);
#endif
pthread_join(input_thread, NULL);
sem_destroy(&input_signal);
input_thread_active = false;
}
if (output_thread_active) {
output_thread_cancel = true;
#ifdef HAVE_PTHREAD_CANCEL
pthread_cancel(output_thread);
#endif
pthread_join(output_thread, NULL);
sem_destroy(&output_signal);
output_thread_active = false;
}
}
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 open_pty(void);
bool configure(uint16 config);
void set_handshake(uint32 s, bool with_dtr);
static void *input_func(void *arg);
static void *output_func(void *arg);
const char *device_name; // Device name
enum {serial, parallel, pty, midi}
protocol; // Type of device
int fd; // FD of device
pid_t pid; // PID of child process
bool io_killed; // Flag: KillIO called, I/O threads must not call deferred tasks
bool quitting; // Flag: Quit threads
pthread_attr_t thread_attr; // Input/output thread attributes
bool input_thread_active; // Flag: Input thread installed
volatile bool input_thread_cancel; // Flag: Cancel input thread
pthread_t input_thread; // Data input thread
sem_t input_signal; // Signal for input thread: execute command
uint32 input_pb; // Command parameter for input thread
bool output_thread_active; // Flag: Output thread installed
volatile bool output_thread_cancel; // Flag: Cancel output thread
pthread_t output_thread; // Data output thread
sem_t output_signal; // Signal for output thread: execute command
uint32 output_pb; // Command parameter for output thread
struct termios mode; // Terminal configuration
};
/*
* Initialization
*/
void SerialInit(void)
{
// Read serial preferences and create structs for both ports
the_serd_port[0] = new XSERDPort(PrefsFindString("seriala"));
the_serd_port[1] = new XSERDPort(PrefsFindString("serialb"));
}
/*
* Deinitialization
*/
void SerialExit(void)
{
delete (XSERDPort *)the_serd_port[0];
delete (XSERDPort *)the_serd_port[1];
}
/*
* Open serial port
*/
int16 XSERDPort::open(uint16 config)
{
// Don't open NULL name devices
if (device_name == NULL)
return openErr;
// Init variables
io_killed = false;
quitting = false;
// Open port, according to the syntax of the path
if (device_name[0] == '|') {
// Open a process via ptys
if (!open_pty())
goto open_error;
}
else if (!strcmp(device_name, "midi")) {
// MIDI: not yet implemented
return openErr;
}
else {
// Device special file
fd = ::open(device_name, O_RDWR);
if (fd < 0)
goto open_error;
#if defined(__linux__)
// Parallel port?
struct stat st;
if (fstat(fd, &st) == 0)
if (S_ISCHR(st.st_mode))
protocol = ((MAJOR(st.st_rdev) == LP_MAJOR) ? parallel : serial);
#elif defined(__FreeBSD__) || defined(__NetBSD__)
// Parallel port?
struct stat st;
if (fstat(fd, &st) == 0)
if (S_ISCHR(st.st_mode))
protocol = (((st.st_rdev >> 16) == 16) ? parallel : serial);
#endif
}
// Configure port for raw mode
if (protocol == serial || protocol == pty) {
if (tcgetattr(fd, &mode) < 0)
goto open_error;
cfmakeraw(&mode);
mode.c_cflag |= HUPCL;
mode.c_cc[VMIN] = 1;
mode.c_cc[VTIME] = 0;
tcsetattr(fd, TCSAFLUSH, &mode);
}
configure(config);
// Start input/output threads
input_thread_cancel = false;
output_thread_cancel = false;
if (sem_init(&input_signal, 0, 0) < 0)
goto open_error;
if (sem_init(&output_signal, 0, 0) < 0)
goto open_error;
input_thread_active = (pthread_create(&input_thread, &thread_attr, input_func, this) == 0);
output_thread_active = (pthread_create(&output_thread, &thread_attr, output_func, this) == 0);
if (!input_thread_active || !output_thread_active)
goto open_error;
return noErr;
open_error:
if (input_thread_active) {
input_thread_cancel = true;
#ifdef HAVE_PTHREAD_CANCEL
pthread_cancel(input_thread);
#endif
pthread_join(input_thread, NULL);
sem_destroy(&input_signal);
input_thread_active = false;
}
if (output_thread_active) {
output_thread_cancel = true;
#ifdef HAVE_PTHREAD_CANCEL
pthread_cancel(output_thread);
#endif
pthread_join(output_thread, NULL);
sem_destroy(&output_signal);
output_thread_active = false;
}
if (fd > 0) {
::close(fd);
fd = -1;
}
return openErr;
}
/*
* Read data from port
*/
int16 XSERDPort::prime_in(uint32 pb, uint32 dce)
{
// Send input command to input_thread
read_done = false;
read_pending = true;
input_pb = pb;
WriteMacInt32(input_dt + serdtDCE, dce);
sem_post(&input_signal);
return 1; // Command in progress
}
/*
* Write data to port
*/
int16 XSERDPort::prime_out(uint32 pb, uint32 dce)
{
// Send output command to output_thread
write_done = false;
write_pending = true;
output_pb = pb;
WriteMacInt32(output_dt + serdtDCE, dce);
sem_post(&output_signal);
return 1; // Command in progress
}
/*
* Control calls
*/
int16 XSERDPort::control(uint32 pb, uint32 dce, uint16 code)
{
switch (code) {
case 1: // KillIO
io_killed = true;
if (protocol == serial)
tcflush(fd, TCIOFLUSH);
while (read_pending || write_pending)
usleep(10000);
io_killed = false;
return noErr;
case kSERDConfiguration:
if (configure(ReadMacInt16(pb + csParam)))
return noErr;
else
return paramErr;
case kSERDInputBuffer:
return noErr; // Not supported under Unix
case kSERDSerHShake:
set_handshake(pb + csParam, false);
return noErr;
case kSERDSetBreak:
if (protocol == serial)
tcsendbreak(fd, 0);
return noErr;
case kSERDClearBreak:
return noErr;
case kSERDBaudRate: {
if (protocol != serial)
return noErr;
uint16 rate = ReadMacInt16(pb + csParam);
speed_t baud_rate;
if (rate <= 50) {
rate = 50; baud_rate = B50;
} else if (rate <= 75) {
rate = 75; baud_rate = B75;
} else if (rate <= 110) {
rate = 110; baud_rate = B110;
} else if (rate <= 134) {
rate = 134; baud_rate = B134;
} else if (rate <= 150) {
rate = 150; baud_rate = B150;
} else if (rate <= 200) {
rate = 200; baud_rate = B200;
} else if (rate <= 300) {
rate = 300; baud_rate = B300;
} else if (rate <= 600) {
rate = 600; baud_rate = B600;
} else if (rate <= 1200) {
rate = 1200; baud_rate = B1200;
} else if (rate <= 1800) {
rate = 1800; baud_rate = B1800;
} else if (rate <= 2400) {
rate = 2400; baud_rate = B2400;
} else if (rate <= 4800) {
rate = 4800; baud_rate = B4800;
} else if (rate <= 9600) {
rate = 9600; baud_rate = B9600;
} else if (rate <= 19200) {
rate = 19200; baud_rate = B19200;
} else if (rate <= 38400) {
rate = 38400; baud_rate = B38400;
} else if (rate <= 57600) {
rate = 57600; baud_rate = B57600;
} else {
// Just for safety in case someone wants a rate between 57600 and 65535
rate = 57600; baud_rate = B57600;
}
WriteMacInt16(pb + csParam, rate);
cfsetispeed(&mode, baud_rate);
cfsetospeed(&mode, baud_rate);
tcsetattr(fd, TCSANOW, &mode);
return noErr;
}
case kSERDHandshake:
case kSERDHandshakeRS232:
set_handshake(pb + csParam, true);
return noErr;
case kSERDMiscOptions:
if (protocol != serial)
return noErr;
if (ReadMacInt8(pb + csParam) & kOptionPreserveDTR)
mode.c_cflag &= ~HUPCL;
else
mode.c_cflag |= HUPCL;
tcsetattr(fd, TCSANOW, &mode);
return noErr;
case kSERDAssertDTR: {
if (protocol != serial)
return noErr;
unsigned int status = TIOCM_DTR;
ioctl(fd, TIOCMBIS, &status);
return noErr;
}
case kSERDNegateDTR: {
if (protocol != serial)
return noErr;
unsigned int status = TIOCM_DTR;
ioctl(fd, TIOCMBIC, &status);
return noErr;
}
case kSERDSetPEChar:
case kSERDSetPEAltChar:
return noErr; // Not supported under Unix
case kSERDResetChannel:
if (protocol == serial)
tcflush(fd, TCIOFLUSH);
return noErr;
case kSERDAssertRTS: {
if (protocol != serial)
return noErr;
unsigned int status = TIOCM_RTS;
ioctl(fd, TIOCMBIS, &status);
return noErr;
}
case kSERDNegateRTS: {
if (protocol != serial)
return noErr;
unsigned int status = TIOCM_RTS;
ioctl(fd, TIOCMBIC, &status);
return noErr;
}
case kSERD115KBaud:
if (protocol != serial)
return noErr;
cfsetispeed(&mode, B115200);
cfsetospeed(&mode, B115200);
tcsetattr(fd, TCSANOW, &mode);
return noErr;
case kSERD230KBaud:
case kSERDSetHighSpeed:
if (protocol != serial)
return noErr;
cfsetispeed(&mode, B230400);
cfsetospeed(&mode, B230400);
tcsetattr(fd, TCSANOW, &mode);
return noErr;
default:
printf("WARNING: SerialControl(): unimplemented control code %d\n", code);
return controlErr;
}
}
/*
* Status calls
*/
int16 XSERDPort::status(uint32 pb, uint32 dce, uint16 code)
{
switch (code) {
case kSERDInputCount: {
int num;
ioctl(fd, FIONREAD, &num);
WriteMacInt32(pb + csParam, num);
return noErr;
}
case kSERDStatus: {
uint32 p = pb + csParam;
WriteMacInt8(p + staCumErrs, cum_errors);
cum_errors = 0;
WriteMacInt8(p + staXOffSent, 0);
WriteMacInt8(p + staXOffHold, 0);
WriteMacInt8(p + staRdPend, read_pending);
WriteMacInt8(p + staWrPend, write_pending);
if (protocol != serial) {
WriteMacInt8(p + staCtsHold, 0);
WriteMacInt8(p + staDsrHold, 0);
WriteMacInt8(p + staModemStatus, dsrEvent | dcdEvent | ctsEvent);
} else {
unsigned int status;
ioctl(fd, TIOCMGET, &status);
WriteMacInt8(p + staCtsHold, status & TIOCM_CTS ? 0 : 1);
WriteMacInt8(p + staDsrHold, status & TIOCM_DTR ? 0 : 1);
WriteMacInt8(p + staModemStatus,
(status & TIOCM_DSR ? dsrEvent : 0)
| (status & TIOCM_RI ? riEvent : 0)
| (status & TIOCM_CD ? dcdEvent : 0)
| (status & TIOCM_CTS ? ctsEvent : 0));
}
return noErr;
}
default:
printf("WARNING: SerialStatus(): unimplemented status code %d\n", code);
return statusErr;
}
}
/*
* Close serial port
*/
int16 XSERDPort::close()
{
// Kill threads
if (input_thread_active) {
quitting = true;
sem_post(&input_signal);
pthread_join(input_thread, NULL);
input_thread_active = false;
sem_destroy(&input_signal);
}
if (output_thread_active) {
quitting = true;
sem_post(&output_signal);
pthread_join(output_thread, NULL);
output_thread_active = false;
sem_destroy(&output_signal);
}
// Close port
if (fd > 0)
::close(fd);
fd = -1;
// Wait for the subprocess to exit
if (pid)
waitpid(pid, NULL, 0);
pid = 0;
return noErr;
}
/*
* Open a process via ptys
*/
bool XSERDPort::open_pty(void)
{
// Talk to a process via a pty
char slave[128];
int slavefd;
protocol = pty;
if (!pty_allocate(&fd, &slavefd, slave, sizeof(slave)))
return false;
fflush(stdout);
fflush(stderr);
switch (pid = fork()) {
case -1: // error
return false;
break;
case 0: // child
::close(fd);
/* Make the pseudo tty our controlling tty. */
pty_make_controlling_tty(&slavefd, slave);
::close(0); dup(slavefd); // Use the slave fd for stdin,
::close(1); dup(slavefd); // stdout,
::close(2); dup(slavefd); // and stderr.
// <should we be more paranoid about closing unused fds?>
// <should we drop privileges if running setuid?>
// Let the shell do the dirty work
execlp("/bin/sh", "/bin/sh", "-c", ++device_name, (char *)NULL);
// exec failed!
printf("serial_open: could not exec %s: %s\n",
"/bin/sh", strerror(errno));
exit(1);
break;
default: // parent
// Pid was stored above
break;
}
return true;
}
/*
* Configure serial port with MacOS config word
*/
bool XSERDPort::configure(uint16 config)
{
D(bug(" configure %04x\n", config));
if (protocol != serial)
return true;
// Set number of stop bits
switch (config & 0xc000) {
case stop10:
mode.c_cflag &= ~CSTOPB;
break;
case stop20:
mode.c_cflag |= CSTOPB;
break;
default:
return false;
}
// Set parity mode
switch (config & 0x3000) {
case noParity:
mode.c_iflag &= ~INPCK;
mode.c_oflag &= ~PARENB;
break;
case oddParity:
mode.c_iflag |= INPCK;
mode.c_oflag |= PARENB;
mode.c_oflag |= PARODD;
break;
case evenParity:
mode.c_iflag |= INPCK;
mode.c_oflag |= PARENB;
mode.c_oflag &= ~PARODD;
break;
default:
return false;
}
// Set number of data bits
switch (config & 0x0c00) {
case data5:
mode.c_cflag = (mode.c_cflag & ~CSIZE) | CS5;
break;
case data6:
mode.c_cflag = (mode.c_cflag & ~CSIZE) | CS6;
break;
case data7:
mode.c_cflag = (mode.c_cflag & ~CSIZE) | CS7;
break;
case data8:
mode.c_cflag = (mode.c_cflag & ~CSIZE) | CS8;
break;
}
// Set baud rate
speed_t baud_rate;
switch (config & 0x03ff) {
case baud150: baud_rate = B150; break;
case baud300: baud_rate = B300; break;
case baud600: baud_rate = B600; break;
case baud1200: baud_rate = B1200; break;
case baud1800: baud_rate = B1800; break;
case baud2400: baud_rate = B2400; break;
case baud4800: baud_rate = B4800; break;
case baud9600: baud_rate = B9600; break;
case baud19200: baud_rate = B19200; break;
case baud38400: baud_rate = B38400; break;
case baud57600: baud_rate = B57600; break;
default:
return false;
}
cfsetispeed(&mode, baud_rate);
cfsetospeed(&mode, baud_rate);
tcsetattr(fd, TCSANOW, &mode);
return true;
}
/*
* Set serial handshaking
*/
void XSERDPort::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)));
if (protocol != serial)
return;
if (with_dtr) {
if (ReadMacInt8(s + shkFCTS) || ReadMacInt8(s + shkFDTR))
mode.c_cflag |= CRTSCTS;
else
mode.c_cflag &= ~CRTSCTS;
} else {
if (ReadMacInt8(s + shkFCTS))
mode.c_cflag |= CRTSCTS;
else
mode.c_cflag &= ~CRTSCTS;
}
D(bug(" %sware flow control\n", mode.c_cflag & CRTSCTS ? "hard" : "soft"));
tcsetattr(fd, TCSANOW, &mode);
}
/*
* Data input thread
*/
void *XSERDPort::input_func(void *arg)
{
XSERDPort *s = (XSERDPort *)arg;
while (!s->input_thread_cancel) {
// Wait for commands
sem_wait(&s->input_signal);
if (s->quitting)
break;
// Execute command
void *buf = Mac2HostAddr(ReadMacInt32(s->input_pb + ioBuffer));
uint32 length = ReadMacInt32(s->input_pb + ioReqCount);
D(bug("input_func waiting for %ld bytes of data...\n", length));
int32 actual = read(s->fd, buf, length);
D(bug(" %ld bytes received\n", actual));
#if MONITOR
bug("Receiving serial data:\n");
uint8 *adr = (uint8 *)buf;
for (int i=0; i<actual; i++) {
bug("%02x ", adr[i]);
}
bug("\n");
#endif
// KillIO called? Then simply return
if (s->io_killed) {
WriteMacInt16(s->input_pb + ioResult, uint16(abortErr));
WriteMacInt32(s->input_pb + ioActCount, 0);
s->read_pending = s->read_done = false;
} else {
// Set error code
if (actual >= 0) {
WriteMacInt32(s->input_pb + ioActCount, actual);
WriteMacInt32(s->input_dt + serdtResult, noErr);
} else {
WriteMacInt32(s->input_pb + ioActCount, 0);
WriteMacInt32(s->input_dt + serdtResult, uint16(readErr));
}
// Trigger serial interrupt
D(bug(" triggering serial interrupt\n"));
s->read_done = true;
SetInterruptFlag(INTFLAG_SERIAL);
TriggerInterrupt();
}
}
return NULL;
}
/*
* Data output thread
*/
void *XSERDPort::output_func(void *arg)
{
XSERDPort *s = (XSERDPort *)arg;
while (!s->output_thread_cancel) {
// Wait for commands
sem_wait(&s->output_signal);
if (s->quitting)
break;
// Execute command
void *buf = Mac2HostAddr(ReadMacInt32(s->output_pb + ioBuffer));
uint32 length = ReadMacInt32(s->output_pb + ioReqCount);
D(bug("output_func transmitting %ld bytes of data...\n", length));
#if MONITOR
bug("Sending serial data:\n");
uint8 *adr = (uint8 *)buf;
for (int i=0; i<length; i++) {
bug("%02x ", adr[i]);
}
bug("\n");
#endif
int32 actual = write(s->fd, buf, length);
D(bug(" %ld bytes transmitted\n", actual));
// KillIO called? Then simply return
if (s->io_killed) {
WriteMacInt16(s->output_pb + ioResult, uint16(abortErr));
WriteMacInt32(s->output_pb + ioActCount, 0);
s->write_pending = s->write_done = false;
} else {
// Set error code
if (actual >= 0) {
WriteMacInt32(s->output_pb + ioActCount, actual);
WriteMacInt32(s->output_dt + serdtResult, noErr);
} else {
WriteMacInt32(s->output_pb + ioActCount, 0);
WriteMacInt32(s->output_dt + serdtResult, uint16(writErr));
}
// Trigger serial interrupt
D(bug(" triggering serial interrupt\n"));
s->write_done = true;
SetInterruptFlag(INTFLAG_SERIAL);
TriggerInterrupt();
}
}
return NULL;
}