Logo Search packages:      
Sourcecode: cbmlink version File versions  Download package

c2n232.c

Go to the documentation of this file.
/**
 * @file c2n232.c
 * Serial transfer routines for the C2N232 device
 * @author Marko Mäkelä (msmakela@nic.funet.fi)
 */

/*
 * Copyright © 2001,2002 Marko Mäkelä.
 * 
 *     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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef COMM_SERIAL
# if defined __unix || defined __unix__ || defined __APPLE__
#  include <termios.h>
#  include <unistd.h>
#  include <fcntl.h>
# endif /* defined __unix || defined __unix__ || defined __APPLE__ */

# if defined __WIN32 || defined __WIN32__
#  include <windows.h>
#  ifndef WIN32
#   define WIN32
#  endif /* WIN32 */
# endif /* defined __WIN32 || defined __WIN32__ */

# if defined __AMIGA__
#  include <proto/exec.h>
#  include <devices/serial.h>
#  include <dos/dos.h>
#  include <clib/alib_protos.h>
#  include <signal.h>
# endif /* __AMIGA__ */

# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <errno.h>

# include "info.h"
# include "c2n232.h"

# ifdef WIN32
/** file handle for the serial device (0=none) */
static HANDLE serialfd = 0;
/** previous terminal input/output settings for the serial line */
static DCB tio;
/** previous timeout settings for the serial line */
static COMMTIMEOUTS tio2;
/** new terminal input/output settings for the serial line */
static DCB newtio;
/** new timeout settings for the serial line */
static COMMTIMEOUTS newtio2;
/** dummy number for WriteFile and ReadFile */
static unsigned long dummy;
# elif defined __AMIGA__
/** extended input/output request structure */
static struct IOExtSer ser;

/** Flush the input/output operation on the serial line
 * @return  the WaitIO exit status
 */
static BYTE
flush_serial (void)
{
  const long waitsigs = SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D |
    1L << ser.IOSer.io_Message.mn_ReplyPort->mp_SigBit;
  for (;;) {
    if (CheckIO ((struct IORequest*) &ser))
      return WaitIO ((struct IORequest*) &ser);
    if (Wait (waitsigs) & (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D)) {
      AbortIO ((struct IORequest*) &ser);
      WaitIO ((struct IORequest*) &ser);
      raise (SIGINT);
    }
  }
}

/** Read data from the serial line
 * @param buf           the data to be received
 * @param len           length of the data in bytes
 * @param context context for error messages
 * @return        zero on success, nonzero on failure
 */
static int
serdev_read (void* buf,
           unsigned len,
           const char* context)
{
  ser.IOSer.io_Command = CMD_READ;
  ser.IOSer.io_Length = len;
  ser.IOSer.io_Data = buf;
  SendIO ((struct IORequest*) &ser);
  if (flush_serial ()) {
    if (context)
      fputs (context, stderr), fputs (": ", stderr);
    fprintf (stderr, "WaitIO (CMD_READ) returned %d\n",
           ser.IOSer.io_Error);
  }
  return ser.IOSer.io_Error;
}

/** Write data to the serial line
 * @param buf           the data to be sent
 * @param len           length of the data in bytes
 * @param context context for error messages
 * @return        zero on success, nonzero on failure
 */
static int
serdev_write (const void* buf,
            unsigned len,
            const char* context)
{
  ser.IOSer.io_Command = CMD_WRITE;
  ser.IOSer.io_Length = len;
  ser.IOSer.io_Data = (void*) buf;
  SendIO ((struct IORequest*) &ser);
  if (flush_serial ()) {
    if (context)
      fputs (context, stderr), fputs (": ", stderr);
    fprintf (stderr, "WaitIO (CMD_WRITE) returned %d\n",
           ser.IOSer.io_Error);
  }
  return ser.IOSer.io_Error;
}
# else /* !WIN32 && !AMIGA */
/** file descriptor for the serial device (0=none) */
static int serialfd = 0;
/** previous terminal input/output settings for the serial line */
static struct termios tio;
/** new terminal input/output settings for the serial line */
static struct termios newtio;
# endif /* !WIN32 && !AMIGA */
/** Communication buffer */
static unsigned char c2n232_buf[3];

/** Disable XON/XOFF handshaking
 * @return  0 on success; nonzero on failure
 */
static int
disable_xonxoff (void)
{
# ifdef WIN32
  newtio.fOutX = 0;
  newtio.XonChar = 0;
  newtio.XoffChar = 0;
  if (!SetCommState (serialfd, &newtio)) {
    report_error ("disable_xonxoff: SetCommState", 0);
    return 1;
  }
# elif defined __AMIGA__
  ser.IOSer.io_Command = SDCMD_SETPARAMS;
  ser.io_ExtFlags = 0;
  ser.io_SerFlags = SERF_RAD_BOOGIE | SERF_XDISABLED;
  ser.io_Baud = 38400;
  if (DoIO ((struct IORequest*) &ser)) {
    fprintf (stderr, "disable_xonxoff: DoIO (SDCMD_SETPARAMS) returned %d\n",
           ser.IOSer.io_Error);
    return 1;
  }
# else /* !WIN32 && !AMIGA */
  newtio.c_iflag &= ~IXON;
  newtio.c_cc[VSTART] = 0;
  newtio.c_cc[VSTOP] = 0;
  if (tcsetattr (serialfd, TCSANOW, &newtio)) {
    perror ("disable_xonxoff: tcsetattr");
    return 1;
  }
# endif /* !WIN32 && !AMIGA */
  return 0;
}

/** Enable XON/XOFF handshaking
 * @return  0 on success; nonzero on failure
 */
static int
enable_xonxoff (void)
{
# ifdef WIN32
  newtio.fOutX = 1;
  newtio.XonChar = 021; /* DC1, ctrl-q */
  newtio.XoffChar = 023; /* DC3, ctrl-s */
  if (!SetCommState (serialfd, &newtio)) {
    report_error ("enable_xonxoff: SetCommState", 0);
    return 1;
  }
# elif defined __AMIGA__
  ser.IOSer.io_Command = SDCMD_SETPARAMS;
  ser.io_ExtFlags = 0;
  ser.io_SerFlags = 0;
  ser.io_Baud = 38400;
  if (DoIO ((struct IORequest*) &ser)) {
    fprintf (stderr, "enable_xonxoff: DoIO (SDCMD_SETPARAMS) returned %d\n",
           ser.IOSer.io_Error);
    return 1;
  }
# else /* !WIN32 && !AMIGA */
  newtio.c_iflag |= IXON;
  newtio.c_cc[VSTART] = 021; /* DC1, ctrl-q */
  newtio.c_cc[VSTOP] = 023; /* DC3, ctrl-s */
  if (tcsetattr (serialfd, TCSANOW, &newtio)) {
    perror ("enable_xonxoff: tcsetattr");
    return 1;
  }
# endif /* !WIN32 && !AMIGA */
  return 0;
}

/** Receive data with or without calibration
 * @param buf           the data to be received
 * @param len           length of the data in bytes
 * @return        zero on success, nonzero on failure
 */
static int
c2n232_do_read (void* buf, unsigned len)
{
  c2n232_buf[1] = --len;
  c2n232_buf[2] = len >> 8;
  if (disable_xonxoff ())
    return 1;
# ifdef WIN32
  if (!WriteFile (serialfd, c2n232_buf, 3, &dummy, 0)) {
    report_error ("c2n232_read: WriteFile", 0);
    return 2;
  }
  if (!ReadFile (serialfd, c2n232_buf + 1, 1, &dummy, 0)) {
    report_error ("c2n232_read: ReadFile", 0);
    return 3;
  }
  else if (!dummy || c2n232_buf[1] != (c2n232_buf[0] | '0')) {
    fputs ("c2n232_read: unexpected response\n", stderr);
    return 3;
  }
# elif defined __AMIGA__
  if (serdev_write (c2n232_buf, 3, "c2n232_read"))
    return 2;
  if (serdev_read (c2n232_buf + 1, 1, "c2n232_read"))
    return 3;
  if (c2n232_buf[1] != (c2n232_buf[0] | '0')) {
    fputs ("c2n232_read: unexpected response\n", stderr);
    return 3;
  }
# else /* !WIN32 && !AMIGA */
  if (3 != write (serialfd, c2n232_buf, 3)) {
    perror ("c2n232_read: write");
    return 2;
  }
  if (!read (serialfd, c2n232_buf + 1, 1) ||
      (c2n232_buf[1] != (c2n232_buf[0] | '0'))) {
    if (errno)
      perror ("c2n232_read: read");
    else
      fputs ("c2n232_read: unexpected response\n", stderr);
    return 3;
  }
# endif /* !WIN32 && !AMIGA */

# ifdef __AMIGA__
  if (serdev_read (buf, len + 1, "c2n232_read"))
    return 4;
# else /* !AMIGA */
  for (len++;;) {
#  ifdef WIN32
    unsigned long got;
    if (!ReadFile (serialfd, buf, len, &got, 0)) {
      report_error ("c2n232_read: ReadFile", 0);
      return 4;
    }
    else if (!got) {
      fputs ("c2n232_read: truncated data\n", stderr);
      return 4;
    }
#  else /* !WIN32 */
    unsigned got = read (serialfd, buf, len);
    if (!got) {
      if (errno)
      perror ("c2n232_read: read");
      else
      fputs ("c2n232_read: truncated data\n", stderr);
      return 4;
    }
#  endif /* !WIN32 */
    if (got > len) {
      fputs ("c2n232_read: too much data\n", stderr);
      return 5;
    }
    buf = (char*) buf + got;
    if (!(len -= got))
      break;
  }
# endif /* !AMIGA */

  return 0;
}

/** Read computer information
 * @param hostinfo      (output) the computer model information
 * @return        zero on success, nonzero on failure
 */
static int
c2n232_detect (struct hostinfo* hostinfo)
{
  unsigned char detectbuf[5];
  if (c2n232_write ("", 1))
    return 1;
  c2n232_buf[0] = 6;
  if (c2n232_do_read (detectbuf, sizeof detectbuf))
    return 2;
  hostinfo->host = detectbuf[0];
  hostinfo->driver = (unsigned) detectbuf[1] | (unsigned) detectbuf[2] << 8;
  hostinfo->basic = (unsigned) detectbuf[3] | (unsigned) detectbuf[4] << 8;
  return 0;
}

/** Open the communication channel
 * @param dev           name of the communication device
 * @param hostinfo      (output) the computer model information
 * @return        zero on success, nonzero on failure
 */
int
c2n232_init (const char* dev, struct hostinfo* hostinfo)
{
# ifdef WIN32
  if ((serialfd = CreateFile (dev, GENERIC_WRITE | GENERIC_READ, 0, 0,
                        OPEN_EXISTING, 0, 0)) == INVALID_HANDLE_VALUE) {
    report_error ("c2n232_init: CreateFile", dev);
    return 1;
  }

  if (!GetCommState (serialfd, &tio)) {
    report_error ("c2n232_init: GetCommState", dev);
  init_fail:
    CloseHandle (serialfd);
    serialfd = INVALID_HANDLE_VALUE;
    return 2;
  }
  else if (!GetCommTimeouts (serialfd, &tio2)) {
    report_error ("c2n232_init: GetCommTimeouts", dev);
    goto init_fail;
  }
# elif defined __AMIGA__
  memset (&ser, 0, sizeof ser);
  ser.IOSer.io_Message.mn_ReplyPort = (struct MsgPort*) CreatePort (0, 0);
  ser.io_SerFlags = SERF_RAD_BOOGIE | SERF_XDISABLED;
  if (OpenDevice ((const unsigned char*) dev, 0,
              (struct IORequest*) &ser, 0)) {
    fprintf (stderr, "c2n232_init %s: OpenDevice returned %d\n",
           dev, ser.IOSer.io_Error);
    c2n232_close ();
    return 1;
  }
# else /* !WIN32 && !AMIGA */
  if ((serialfd = open (dev, O_RDWR | O_NONBLOCK | O_NOCTTY)) < 0) {
    fputs ("c2n232_init ", stderr);
    fputs (dev, stderr);
    perror (": open");
    return 1;
  }
  else {
    int flags = fcntl (serialfd, F_GETFL);
    if (flags < 0) {
      fputs ("c2n232_init ", stderr);
      fputs (dev, stderr);
      perror (": fcntl(GETFL)");
      close (serialfd);
      serialfd = -1;
      return 2;
    }
    else if (flags & O_NONBLOCK &&
           fcntl (serialfd, F_SETFL, flags & ~O_NONBLOCK) < 0) {
      fputs ("c2n232_init ", stderr);
      fputs (dev, stderr);
      perror (": fcntl(SETFL)");
      close (serialfd);
      serialfd = -1;
      return 2;
    }
  }

  if (tcgetattr (serialfd, &tio)) {
    fputs ("c2n232_init ", stderr);
    fputs (dev, stderr);
    perror (": tcgetattr");
    close (serialfd);
    serialfd = -1;
    return 2;
  }
# endif /* !WIN32 && !AMIGA */

  /* set the communication parameters */
# ifdef WIN32
  memcpy (&newtio, &tio, sizeof newtio);
  memcpy (&newtio2, &tio2, sizeof newtio2);
  newtio.BaudRate = CBR_38400;
  newtio.fBinary = 1;
  newtio.fParity = newtio.fOutxCtsFlow = newtio.fOutxDsrFlow = 0;
  newtio.fDtrControl = DTR_CONTROL_DISABLE;
  newtio.fDsrSensitivity = 0;
  newtio.fOutX = 0, newtio.fInX = 0;
  newtio.fErrorChar = newtio.fNull = 0;
  newtio.fRtsControl = RTS_CONTROL_DISABLE;
  newtio.fAbortOnError = 0;
  newtio.ByteSize = 8;
  newtio.Parity = NOPARITY;
  newtio.StopBits = ONESTOPBIT;
  newtio.XonChar = newtio.XoffChar = 0;

  newtio2.ReadIntervalTimeout = 100; /* 1/10th second */
  newtio2.ReadTotalTimeoutConstant = 100; /* 1/10th second */
  newtio2.ReadTotalTimeoutMultiplier =
    newtio2.WriteTotalTimeoutMultiplier =
    newtio2.WriteTotalTimeoutConstant = 0;

  PurgeComm (serialfd, PURGE_TXCLEAR | PURGE_RXCLEAR);
  SetCommBreak (serialfd);
  Sleep (20); /* send break signal for at least 20 milliseconds */
  ClearCommBreak (serialfd);
  if (!SetCommState (serialfd, &newtio)) {
    report_error ("c2n232_init: SetCommState", dev);
    goto init_fail;
  }
  else if (!SetCommTimeouts (serialfd, &newtio2)) {
    report_error ("c2n232_init: SetCommTimeouts", dev);
    SetCommState (serialfd, &tio);
    goto init_fail;
  }
# elif defined __AMIGA__
  if (enable_xonxoff ()) {
    c2n232_close ();
    return 2;
  }
# else /* !WIN32 && !AMIGA */
  memcpy (&newtio, &tio, sizeof newtio);
  newtio.c_iflag = IGNBRK | IGNPAR;
  newtio.c_oflag = 0;
  newtio.c_cflag = CS8 | CLOCAL | CREAD;
  newtio.c_lflag = 0;
  memset (newtio.c_cc, 0, sizeof newtio.c_cc);
  newtio.c_cc[VMIN] = 10; /* read at least 10 chars */
  newtio.c_cc[VTIME] = 1; /* 1/10th second timeout */
  cfsetispeed (&newtio, B38400), cfsetospeed (&newtio, B38400);

  /* flush the input and output buffers and send a break signal to the line */
  tcflush (serialfd, TCIOFLUSH);
  tcsendbreak (serialfd, 0);

  if (tcsetattr (serialfd, TCSANOW, &newtio)) {
    fputs ("c2n232_init ", stderr);
    fputs (dev, stderr);
    perror (": tcsetattr");
    close (serialfd);
    serialfd = -1;
    return 2;
  }
# endif /* !WIN32 && !AMIGA */

# ifdef WIN32
  WriteFile (serialfd, "", 1, &dummy, 0);
# elif defined __AMIGA__
  if (serdev_write ("", 1, dev)) {
    c2n232_close ();
    return 2;
  }
# else /* !WIN32 && !AMIGA */
  /* send a NUL character and await a response */
  write (serialfd, "", 1);
# endif /* !WIN32 && !AMIGA */

# ifdef __AMIGA__
  do {
    char c;
    if (serdev_read (&c, 1, dev));
    else if (c != '0') {
      fputs ("c2n232_init ", stderr);
      fputs (dev, stderr);
      fputs (": unexpected response from C2N232 device\n", stderr);
    }
    else
      break;
    c2n232_close ();
    return 3;
  } while (0);
  return c2n232_detect (hostinfo);
# else /* !AMIGA */
  do {
    char response[10];
#  ifdef WIN32
    unsigned long len;
    if (!ReadFile (serialfd, response, sizeof response, &len, 0)) {
      report_error ("c2n232_init: ReadFile", dev);
      continue;
    }
    else if (!len) {
      fputs ("c2n232_init: no response from C2N232 device\n", stderr);
      continue;
    }
#  else /* !WIN32 */
    int len = read (serialfd, response, sizeof response);
    if (!len) {
      fputs ("c2n232_init ", stderr);
      fputs (dev, stderr);
      if (errno)
      perror (": read");
      else
      fputs (": no response from C2N232 device\n", stderr);
      continue;
    }
#  endif /* !WIN32 */
    if (response[len - 1] != '0') {
      fputs ("c2n232_init ", stderr);
      fputs (dev, stderr);
      fputs (": unexpected response from C2N232 device\n", stderr);
      continue;
    }
#  ifdef WIN32
    newtio2.ReadTotalTimeoutConstant = 10000; /* 10 seconds */
    SetCommTimeouts (serialfd, &newtio2);
#  else /* !WIN32 */
    newtio.c_cc[VMIN] = 255; /* read at least 255 characters */
    newtio.c_cc[VTIME] = 10; /* 1-second inter-character timeout */
    tcsetattr (serialfd, TCSANOW, &newtio);
#  endif /* !WIN32 */
    return c2n232_detect (hostinfo);
  } while (0);
# endif /* !AMIGA */

  c2n232_close ();
  return 3;
}

/** Close the communication channel */
void
c2n232_close (void)
{
# ifdef WIN32
  if (serialfd && serialfd != INVALID_HANDLE_VALUE) {
    SetCommBreak (serialfd);
    Sleep (20); /* send break signal for at least 20 milliseconds */
    ClearCommBreak (serialfd);
    SetCommState (serialfd, &tio);
    SetCommTimeouts (serialfd, &tio2);
    CloseHandle (serialfd);
    serialfd = 0;
  }
# elif defined __AMIGA__
  if (ser.IOSer.io_Device)
    CloseDevice ((struct IORequest*) &ser);
  if (ser.IOSer.io_Message.mn_ReplyPort)
    DeletePort (ser.IOSer.io_Message.mn_ReplyPort);
  memset (&ser, 0, sizeof ser);
# else /* !WIN32 && !AMIGA */
  if (serialfd > 0) {
    tcsendbreak (serialfd, 0);
    tcsetattr (serialfd, TCSANOW, &tio);
    close (serialfd);
    serialfd = 0;
  }
# endif /* !WIN32 && !AMIGA */
}

/** Send data
 * @param buf           the data to be sent
 * @param len           length of the data in bytes
 * @return        zero on success, nonzero on failure
 */
int
c2n232_write (const void* buf, unsigned len)
{
# ifdef WIN32
  unsigned long length;
# endif /* WIN32 */
  c2n232_buf[0] = 4;
  c2n232_buf[1] = len - 1;
  c2n232_buf[2] = (len - 1) >> 8;
  if (enable_xonxoff ())
    return 1;
# ifdef WIN32
  if (!WriteFile (serialfd, c2n232_buf, 3, &length, 0)) {
    report_error ("c2n232_write: WriteFile(command)", 0);
    return 2;
  }
  else if (length != 3) {
    fprintf (stderr, "c2n232_write: WriteFile(command): "
           "wrote %lu of 3 bytes\n", length);
    return 2;
  }
  if (!ReadFile (serialfd, c2n232_buf, 1, &length, 0)) {
    report_error ("c2n232_write: ReadFile(command)", 0);
    return 3;
  }
  else if (length != 1 || *c2n232_buf != '4') {
    fputs ("c2n232_write: unexpected response to command\n", stderr);
    return 3;
  }
  if (!WriteFile (serialfd, buf, len, &length, 0)) {
    report_error ("c2n232_write: WriteFile(data)", 0);
    return 4;
  }
  else if (length != len) {
    fprintf (stderr, "c2n232_write: WriteFile(data): "
           "wrote %lu of %u bytes\n", length, len);
    return 4;
  }
  if (!ReadFile (serialfd, c2n232_buf, 1, &length, 0)) {
    report_error ("c2n232_write: ReadFile(data)", 0);
    return 5;
  }
  else if (length != 1 || *c2n232_buf != '@') {
    fputs ("c2n232_write: unexpected response to data\n", stderr);
    return 5;
  }
# elif defined __AMIGA__
  if (serdev_write (c2n232_buf, 3, "c2n232_write(command)"))
    return 2;
  if (serdev_read (c2n232_buf, 1, "c2n232_write(command)"))
    return 3;
  if (*c2n232_buf != '4') {
    fputs ("c2n232_write: unexpected response to command\n", stderr);
    return 3;
  }
  if (serdev_write (buf, len, "c2n232_write(data)"))
    return 4;
  if (serdev_read (c2n232_buf, 1, "c2n232_write(data)"))
    return 5;
  if (*c2n232_buf != '@') {
    fputs ("c2n232_write: unexpected response to data\n", stderr);
    return 5;
  }
# else /* !WIN32 && !AMIGA */
  if (3 != write (serialfd, c2n232_buf, 3)) {
    perror ("c2n232_write: write(command)");
    return 2;
  }
  if (!read (serialfd, c2n232_buf, 1) || *c2n232_buf != '4') {
    if (errno)
      perror ("c2n232_write: read(command)");
    else
      fputs ("c2n232_write: unexpected response to command\n", stderr);
    return 3;
  }
  if (len != write (serialfd, buf, len)) {
    perror ("c2n232_write: write(data)");
    return 4;
  }
  if (!read (serialfd, c2n232_buf, 1) || *c2n232_buf != '@') {
    if (errno)
      perror ("c2n232_write: read(data)");
    else
      fputs ("c2n232_write: unexpected response to data\n", stderr);
    return 5;
  }
# endif /* !WIN32 && !AMIGA */
  return 0;
}

/** Receive data
 * @param buf           the data to be received
 * @param len           length of the data in bytes
 * @return        zero on success, nonzero on failure
 */
int
c2n232_read (void* buf, unsigned len)
{
  c2n232_buf[0] = 5;
  return c2n232_do_read (buf, len);
}
#endif /* COMM_SERIAL */

Generated by  Doxygen 1.6.0   Back to index