/* sim_serial.c: OS-dependent serial port routines

   Copyright (c) 2008, J. David Bryan

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   Except as contained in this notice, the name of the author shall not be
   used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from the author.

   The author gratefully acknowledges the assistance of Holger Veit with the
   UNIX-specific code and testing.

   07-Oct-08    JDB     [serial] Created file


   This module provides OS-dependent routines to access serial ports on the host
   machine.  The terminal multiplexer library uses these routines to provide
   serial connections to simulated terminal interfaces.

   Currently, the module supports Windows and UNIX.  Use on other systems
   returns error codes indicating that the functions failed, inhibiting serial
   port support in SIMH.

   The following routines are provided:

     sim_open_serial        open a serial port
     sim_config_serial      change baud rate and character framing configuration
     sim_control_serial     manipulate and/or return the modem bits on a serial port
     sim_read_serial        read from a serial port
     sim_write_serial       write to a serial port
     sim_close_serial       close a serial port
     sim_show_serial        shows the available host serial ports


   The calling sequences are as follows:


   SERHANDLE sim_open_serial (char *name)
   --------------------------------------

   The serial port referenced by the OS-dependent "name" is opened.  If the open
   is successful, and "name" refers to a serial port on the host system, then a
   handle to the port is returned.  If not, then the value INVALID_HANDLE is
   returned.


   t_stat sim_config_serial (SERHANDLE port, const char *config)
   -------------------------------------------------------------

   The baud rate and framing parameters (character size, parity, and number of
   stop bits) of the serial port associated with "port" are set.  If any
   "config" field value is unsupported by the host system, or if the combination
   of values (e.g., baud rate and number of stop bits) is unsupported, SCPE_ARG
   is returned.  If the configuration is successful, SCPE_OK is returned.


   sim_control_serial (SERHANDLE port, int32 bits_to_set, int32 bits_to_clear, int32 *incoming_bits)
   -------------------------------------------------------------------------------------------------

   The DTR and RTS line of the serial port is set or cleared as indicated in 
   the respective bits_to_set or bits_to_clear parameters.  If the 
   incoming_bits parameter is not NULL, then the modem status bits DCD, RNG, 
   DSR and CTS are returned.

   If unreasonable or nonsense bits_to_set or bits_to_clear bits are 
   specified, then the return status is SCPE_ARG;
   If an error occurs, SCPE_IOERR is returned.


   int32 sim_read_serial (SERHANDLE port, char *buffer, int32 count, char *brk)
   ----------------------------------------------------------------------------

   A non-blocking read is issued for the serial port indicated by "port" to get
   at most "count" bytes into the string "buffer".  If a serial line break was
   detected during the read, the variable pointed to by "brk" is set to 1.  If
   the read is successful, the actual number of characters read is returned.  If
   no characters were available, then the value 0 is returned.  If an error
   occurs, then the value -1 is returned.


   int32 sim_write_serial (SERHANDLE port, char *buffer, int32 count)
   ------------------------------------------------------------------

   A write is issued to the serial port indicated by "port" to put "count"
   characters from "buffer".  If the write is successful, the actual number of
   characters written is returned.  If an error occurs, then the value -1 is
   returned.


   void sim_close_serial (SERHANDLE port)
   --------------------------------------

   The serial port indicated by "port" is closed.


   int sim_serial_devices (int max, SERIAL_LIST* list)
   ---------------------------------------------------

   enumerates the available host serial ports


   t_stat sim_show_serial (FILE* st, DEVICE *dptr, UNIT* uptr, int32 val, void* desc)
   ---------------------------------

   displays the available host serial ports

*/


#include "sim_defs.h"
#include "sim_serial.h"
#include "sim_tmxr.h"

#include <ctype.h>

#define SER_DEV_NAME_MAX     256                        /* maximum device name size */
#define SER_DEV_DESC_MAX     256                        /* maximum device description size */
#define SER_DEV_CONFIG_MAX    64                        /* maximum device config size */
#define SER_MAX_DEVICE        64                        /* maximum serial devices */

typedef struct serial_list {
    char    name[SER_DEV_NAME_MAX];
    char    desc[SER_DEV_DESC_MAX];
    } SERIAL_LIST;

typedef struct serial_config {                          /* serial port configuration */
    uint32 baudrate;                                    /* baud rate */
    uint32 charsize;                                    /* character size in bits */
    char   parity;                                      /* parity (N/O/E/M/S) */
    uint32 stopbits;                                    /* 0/1/2 stop bits (0 implies 1.5) */
    } SERCONFIG;

static int       sim_serial_os_devices (int max, SERIAL_LIST* list);
static SERHANDLE sim_open_os_serial    (char *name);
static void      sim_close_os_serial   (SERHANDLE port);
static t_stat    sim_config_os_serial  (SERHANDLE port, SERCONFIG config);


static struct open_serial_device {
    SERHANDLE port;
    TMLN *line;
    char name[SER_DEV_NAME_MAX];
    char config[SER_DEV_CONFIG_MAX];
    } *serial_open_devices = NULL;
static int serial_open_device_count = 0;

static struct open_serial_device *_get_open_device (SERHANDLE port)
{
int i;

for (i=0; i<serial_open_device_count; ++i)
    if (serial_open_devices[i].port == port)
        return &serial_open_devices[i];
return NULL;
}

static struct open_serial_device *_get_open_device_byname (const char *name)
{
int i;

for (i=0; i<serial_open_device_count; ++i)
    if (0 == strcmp(name, serial_open_devices[i].name))
        return &serial_open_devices[i];
return NULL;
}

static struct open_serial_device *_serial_add_to_open_list (SERHANDLE port, TMLN *line, const char *name, const char *config)
{
serial_open_devices = (struct open_serial_device *)realloc(serial_open_devices, (++serial_open_device_count)*sizeof(*serial_open_devices));
memset(&serial_open_devices[serial_open_device_count-1], 0, sizeof(serial_open_devices[serial_open_device_count-1]));
serial_open_devices[serial_open_device_count-1].port = port;
serial_open_devices[serial_open_device_count-1].line = line;
strncpy(serial_open_devices[serial_open_device_count-1].name, name, sizeof(serial_open_devices[serial_open_device_count-1].name)-1);
if (config)
    strncpy(serial_open_devices[serial_open_device_count-1].config, config, sizeof(serial_open_devices[serial_open_device_count-1].config));
return &serial_open_devices[serial_open_device_count-1];
}

static void _serial_remove_from_open_list (SERHANDLE port)
{
int i, j;

for (i=0; i<serial_open_device_count; ++i)
    if (serial_open_devices[i].port == port) {
        for (j=i+1; j<serial_open_device_count; ++j)
            serial_open_devices[j-1] = serial_open_devices[j];
        --serial_open_device_count;
        break;
        }
}

/* Generic error message handler.

   This routine should be called for unexpected errors.  Some error returns may
   be expected, e.g., a "file not found" error from an "open" routine.  These
   should return appropriate status codes to the caller, allowing SCP to print
   an error message if desired, rather than printing this generic error message.
*/

static void sim_error_serial (char *routine, int error)
{
sim_printf ("Serial: %s fails with error %d\n", routine, error);
return;
}

/* Used when sorting a list of serial port names */
static int _serial_name_compare (const void *pa, const void *pb)
{
SERIAL_LIST *a = (SERIAL_LIST *)pa;
SERIAL_LIST *b = (SERIAL_LIST *)pb;

return strcmp(a->name, b->name);
}

static int sim_serial_devices (int max, SERIAL_LIST *list)
{
int i, j, ports = sim_serial_os_devices(max, list);

/* Open ports may not show up in the list returned by sim_serial_os_devices 
   so we add the open ports to the list removing duplicates before sorting 
   the resulting list */

for (i=0; i<serial_open_device_count; ++i) {
    for (j=0; j<ports; ++j)
        if (0 == strcmp(serial_open_devices[i].name, list[j].name))
            break;
    if (j<ports)
        continue;
    if (ports >= max)
        break;
    strcpy(list[ports].name, serial_open_devices[i].name);
    strcpy(list[ports].desc, serial_open_devices[i].config);
    ++ports;
    }
if (ports) /* Order the list returned alphabetically by the port name */
    qsort (list, ports, sizeof(list[0]), _serial_name_compare);
return ports;
}

static char* sim_serial_getname (int number, char* name)
{
SERIAL_LIST  list[SER_MAX_DEVICE];
int count = sim_serial_devices(SER_MAX_DEVICE, list);

if (count <= number)
    return NULL;
strcpy(name, list[number].name);
return name;
}

static char* sim_serial_getname_bydesc (char* desc, char* name)
{
SERIAL_LIST  list[SER_MAX_DEVICE];
int count = sim_serial_devices(SER_MAX_DEVICE, list);
int i;
size_t j=strlen(desc);

for (i=0; i<count; i++) {
    int found = 1;
    size_t k = strlen(list[i].desc);

    if (j != k)
        continue;
    for (k=0; k<j; k++)
        if (tolower(list[i].desc[k]) != tolower(desc[k]))
            found = 0;
    if (found == 0)
        continue;

    /* found a case-insensitive description match */
    strcpy(name, list[i].name);
    return name;
    }
/* not found */
return NULL;
}

/* strncasecmp() is not available on all platforms */
static int sim_serial_strncasecmp (char* string1, char* string2, size_t len)
{
size_t i;
unsigned char s1, s2;

for (i=0; i<len; i++) {
    s1 = string1[i];
    s2 = string2[i];
    if (islower (s1))
        s1 = (unsigned char)toupper (s1);
    if (islower (s2))
        s2 = (unsigned char)toupper (s2);
    if (s1 < s2)
        return -1;
    if (s1 > s2)
        return 1;
    if (s1 == 0)
        return 0;
}
return 0;
}

static char* sim_serial_getname_byname (char* name, char* temp)
{
SERIAL_LIST  list[SER_MAX_DEVICE];
int count = sim_serial_devices(SER_MAX_DEVICE, list);
size_t n;
int i, found;

found = 0;
n = strlen(name);
for (i=0; i<count && !found; i++) {
    if ((n == strlen(list[i].name)) &&
        (sim_serial_strncasecmp(name, list[i].name, n) == 0)) {
        found = 1;
        strcpy(temp, list[i].name); /* only case might be different */
        }
    }
return (found ? temp : NULL);
}

char* sim_serial_getdesc_byname (char* name, char* temp)
{
SERIAL_LIST  list[SER_MAX_DEVICE];
int count = sim_serial_devices(SER_MAX_DEVICE, list);
size_t n;
int i, found;

found = 0;
n = strlen(name);
for (i=0; i<count && !found; i++) {
    if ((n == strlen(list[i].name)) &&
        (sim_serial_strncasecmp(name, list[i].name, n) == 0)) {
        found = 1;
        strcpy(temp, list[i].desc);
        }
    }
  return (found ? temp : NULL);
}

t_stat sim_show_serial (FILE* st, DEVICE *dptr, UNIT* uptr, int32 val, char* desc)
{
SERIAL_LIST  list[SER_MAX_DEVICE];
int number = sim_serial_devices(SER_MAX_DEVICE, list);

fprintf(st, "Serial devices:\n");
if (number == -1)
    fprintf(st, "  serial support not available in simulator\n");
else
if (number == 0)
    fprintf(st, "  no serial devices are available\n");
else {
    size_t min, len;
    int i;
    for (i=0, min=0; i<number; i++)
        if ((len = strlen(list[i].name)) > min)
            min = len;
    for (i=0; i<number; i++)
        fprintf(st," ser%d\t%-*s%s%s%s\n", i, (int)min, list[i].name, list[i].desc[0] ? " (" : "", list[i].desc, list[i].desc[0] ? ")" : "");
    }
if (serial_open_device_count) {
    int i;
    char desc[SER_DEV_DESC_MAX], *d;

    fprintf(st,"Open Serial Devices:\n");
    for (i=0; i<serial_open_device_count; i++) {
        d = sim_serial_getdesc_byname(serial_open_devices[i].name, desc);
        fprintf(st, " %s\tLn%02d %s%s%s%s\tConfig: %s\n", serial_open_devices[i].line->mp->dptr->name, (int)(serial_open_devices[i].line->mp->ldsc-serial_open_devices[i].line),
                    serial_open_devices[i].line->destination, d ? " {" : "", d ? d : "", d ? ")" : "", serial_open_devices[i].line->serconfig);
        }
    }
return SCPE_OK;
}

SERHANDLE sim_open_serial (char *name, TMLN *lp, t_stat *stat)
{
char temp1[1024], devname [1024];
char *savname = name;
SERHANDLE port = INVALID_HANDLE;
char *config;
t_stat status;

config = get_glyph_nc (name, devname, ';');             /* separate port name from optional config params */

if ((config == NULL) || (*config == '\0'))
    config = "9600-8N1";

if (stat)
    *stat = SCPE_OK;

/* translate name of type "serX" to real device name */
if ((strlen(devname) <= 5)
    && (tolower(devname[0]) == 's')
    && (tolower(devname[1]) == 'e')
    && (tolower(devname[2]) == 'r')
    && (isdigit(devname[3]))
    && (isdigit(devname[4]) || (devname[4] == '\0'))
   ) {
    int num = atoi(&devname[3]);
    savname = sim_serial_getname(num, temp1);
    if (savname == NULL) {                              /* didn't translate */
        if (stat)
            *stat = SCPE_OPENERR;
        return port;
        }
    }
else {
    /* are they trying to use device description? */
    savname = sim_serial_getname_bydesc(devname, temp1);
    if (savname == NULL) {                              /* didn't translate */
        /* probably is not serX and has no description */
        savname = sim_serial_getname_byname(devname, temp1);
        if (savname == NULL) /* didn't translate */
            savname = devname;
        }
    }

if (_get_open_device_byname (savname)) {
    if (stat)
        *stat = SCPE_OPENERR;
    return INVALID_HANDLE;
    }

port = sim_open_os_serial (savname);

if (port == INVALID_HANDLE) {
    if (stat)
        *stat = SCPE_OPENERR;
    return port;
    }

status = sim_config_serial (port, config);              /* set serial configuration */

if (status != SCPE_OK) {                                /* port configuration error? */
    sim_close_serial (port);                            /* close the port */
    if (stat)
        *stat = status;
    port = INVALID_HANDLE;                              /* report error */
    }

if ((port != INVALID_HANDLE) && (*config) && (lp)) {
    lp->serconfig = (char *)realloc (lp->serconfig, 1 + strlen (config));
    strcpy (lp->serconfig, config);
    }
if (port != INVALID_HANDLE)
    _serial_add_to_open_list (port, lp, savname, config);

return port;
}

void sim_close_serial (SERHANDLE port)
{
sim_close_os_serial (port);
_serial_remove_from_open_list (port);
}

t_stat sim_config_serial  (SERHANDLE port, const char *sconfig)
{
const char *pptr;
const char *sptr, *tptr;
SERCONFIG config = { 0 };
t_bool arg_error = FALSE;
t_stat r;
struct open_serial_device *dev;

if ((sconfig == NULL) || (*sconfig == '\0'))
    sconfig = "9600-8N1";                               /* default settings */
pptr = sconfig;

config.baudrate = (uint32)strtotv (pptr, &sptr, 10);    /* parse baud rate */
arg_error = (pptr == sptr);                             /* check for bad argument */

if (*sptr)                                              /* separator present? */
    sptr++;                                             /* skip it */

config.charsize = (uint32)strtotv (sptr, &tptr, 10);    /* parse character size */
arg_error = arg_error || (sptr == tptr);                /* check for bad argument */

if (*tptr)                                              /* parity character present? */
    config.parity = (char)toupper (*tptr++);            /* save parity character */

config.stopbits = (uint32)strtotv (tptr, &sptr, 10);    /* parse number of stop bits */
arg_error = arg_error || (tptr == sptr);                /* check for bad argument */

if (arg_error)                                          /* bad conversions? */
    return SCPE_ARG;                                    /* report argument error */
if (strcmp (sptr, ".5") == 0)                           /* 1.5 stop bits requested? */
    config.stopbits = 0;                                /* code request */

r = sim_config_os_serial (port, config);
dev = _get_open_device (port);
if (dev) {
    dev->line->serconfig = (char *)realloc (dev->line->serconfig, 1 + strlen (sconfig));
    strcpy (dev->line->serconfig, sconfig);
    }
return r;
}

#if defined (_WIN32)

/* Windows serial implementation */

/* Enumerate the available serial ports.

   The serial port names are extracted from the appropriate place in the 
   windows registry (HKLM\HARDWARE\DEVICEMAP\SERIALCOMM\).  The resulting
   list is sorted alphabetically by device name (COMn).  The device description 
   is set to the OS internal name for the COM device.

*/

static int sim_serial_os_devices (int max, SERIAL_LIST* list)
{
int ports = 0;
HKEY hSERIALCOMM;

memset(list, 0, max*sizeof(*list));
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_QUERY_VALUE, &hSERIALCOMM) == ERROR_SUCCESS) {
    DWORD dwIndex = 0;
    DWORD dwType;
    DWORD dwValueNameSize = sizeof(list[ports].desc);
    DWORD dwDataSize = sizeof(list[ports].name);

    /* Enumerate all the values underneath HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM */
    while (RegEnumValueA(hSERIALCOMM, dwIndex, list[ports].desc, &dwValueNameSize, NULL, &dwType, (BYTE *)list[ports].name, &dwDataSize) == ERROR_SUCCESS) {
        /* String values with non-zero size are the interesting ones */
        if ((dwType == REG_SZ) && (dwDataSize > 0)) {
            if (ports < max)
                ++ports;
            else
                break;
            }
        /* Besure to clear the working entry before trying again */
        memset(list[ports].name, 0, sizeof(list[ports].name));
        memset(list[ports].desc, 0, sizeof(list[ports].desc));
        dwValueNameSize = sizeof(list[ports].desc);
        dwDataSize = sizeof(list[ports].name);
        ++dwIndex;
        }
    RegCloseKey(hSERIALCOMM);
    }
return ports;
}

/* Open a serial port.

   The serial port designated by "name" is opened, and the handle to the port is
   returned.  If an error occurs, INVALID_HANDLE is returned instead.  After
   opening, the port is configured with the default communication parameters
   established by the system, and the timeouts are set for immediate return on a
   read request to enable polling.

   Implementation notes:

    1. We call "GetDefaultCommConfig" to obtain the default communication
       parameters for the specified port.  If the name does not refer to a
       communications port (serial or parallel), the function fails.

    2. There is no way to limit "CreateFile" just to serial ports, so we must
       check after the port is opened.  The "GetCommState" routine will return
       an error if the handle does not refer to a serial port.

    3. Calling "GetDefaultCommConfig" for a serial port returns a structure
       containing a DCB.  This contains the default parameters.  However, some
       of the DCB fields are not set correctly, so we cannot use this directly
       in a call to "SetCommState".  Instead, we must copy the fields of
       interest to a DCB retrieved from a call to "GetCommState".
*/

static SERHANDLE sim_open_os_serial (char *name)
{
SERHANDLE port;
DCB dcb;
COMMCONFIG commdefault;
DWORD error;
DWORD commsize = sizeof (commdefault);
COMMTIMEOUTS cto;

if (!GetDefaultCommConfig (name, &commdefault, &commsize)) {    /* get default comm parameters */
    error = GetLastError ();                                    /* function failed; get error */

    if (error != ERROR_INVALID_PARAMETER)                       /* not a communications port name? */
        sim_error_serial ("GetDefaultCommConfig", (int) error); /* no, so report unexpected error */

    return INVALID_HANDLE;                                      /* indicate bad port name */
    }

port = CreateFile (name, GENERIC_READ | GENERIC_WRITE,  /* open the port */
                   0, NULL, OPEN_EXISTING, 0, 0);

if (port == INVALID_HANDLE_VALUE) {                     /* open failed? */
    error = GetLastError ();                            /* get error code */

    if ((error != ERROR_FILE_NOT_FOUND) &&              /* bad filename? */
        (error != ERROR_ACCESS_DENIED))                 /* already open? */
        sim_error_serial ("CreateFile", (int) error);   /* no, so report unexpected error */

    return INVALID_HANDLE;                              /* indicate bad port name */
    }

if (!GetCommState (port, &dcb)) {                       /* get the current comm parameters */
    error = GetLastError ();                            /* function failed; get error */

    if (error != ERROR_INVALID_PARAMETER)               /* not a serial port name? */
        sim_error_serial ("GetCommState", (int) error); /* no, so report unexpected error */

    CloseHandle (port);                                 /* close the port */
    return INVALID_HANDLE;                              /*   and indicate bad port name */
    }

dcb.BaudRate = commdefault.dcb.BaudRate;                /* copy default parameters of interest */
dcb.Parity   = commdefault.dcb.Parity;
dcb.ByteSize = commdefault.dcb.ByteSize;
dcb.StopBits = commdefault.dcb.StopBits;
dcb.fOutX    = commdefault.dcb.fOutX;
dcb.fInX     = commdefault.dcb.fInX;

dcb.fDtrControl = DTR_CONTROL_DISABLE;                  /* disable DTR initially until poll connects */

if (!SetCommState (port, &dcb)) {                       /* configure the port with default parameters */
    sim_error_serial ("SetCommState",                   /* function failed; report unexpected error */
                      (int) GetLastError ());
    CloseHandle (port);                                 /* close port */
    return INVALID_HANDLE;                              /*   and indicate failure to caller */
    }

cto.ReadIntervalTimeout         = MAXDWORD;             /* set port to return immediately on read */
cto.ReadTotalTimeoutMultiplier  = 0;                    /* i.e., to enable polling */
cto.ReadTotalTimeoutConstant    = 0;
cto.WriteTotalTimeoutMultiplier = 0;
cto.WriteTotalTimeoutConstant   = 0;

if (!SetCommTimeouts (port, &cto)) {                    /* configure port timeouts */
    sim_error_serial ("SetCommTimeouts",                /* function failed; report unexpected error */
                      (int) GetLastError ());
    CloseHandle (port);                                 /* close port */
    return INVALID_HANDLE;                              /*   and indicate failure to caller */
    }

return port;                                            /* return port handle on success */
}


/* Configure a serial port.

   Port parameters are configured as specified in the "config" structure.  If
   "config" contains an invalid configuration value, or if the host system
   rejects the configuration (e.g., by requesting an unsupported combination of
   character size and stop bits), SCPE_ARG is returned to the caller.  If an
   unexpected error occurs, SCPE_IOERR is returned.  If the configuration
   succeeds, SCPE_OK is returned.

   Implementation notes:

    1. We do not enable input parity checking, as the multiplexer library has no
       way of communicating parity errors back to the target simulator.

    2. A zero value for the "stopbits" field of the "config" structure implies
       1.5 stop bits.
*/

static t_stat sim_config_os_serial (SERHANDLE port, SERCONFIG config)
{
static const struct {
    char parity;
    BYTE parity_code;
    } parity_map [] =
        { { 'E', EVENPARITY }, { 'M', MARKPARITY  }, { 'N', NOPARITY },
          { 'O', ODDPARITY  }, { 'S', SPACEPARITY } };

static const int32 parity_count = sizeof (parity_map) / sizeof (parity_map [0]);

DCB dcb;
DWORD error;
int32 i;

if (!GetCommState (port, &dcb)) {                       /* get the current comm parameters */
    sim_error_serial ("GetCommState",                   /* function failed; report unexpected error */
                      (int) GetLastError ());
    return SCPE_IOERR;                                  /* return failure status */
    }

dcb.BaudRate = config.baudrate;                         /* assign baud rate */

if (config.charsize >= 5 && config.charsize <= 8)       /* character size OK? */
    dcb.ByteSize = (BYTE)config.charsize;               /* assign character size */
else
    return SCPE_ARG;                                    /* not a valid size */

for (i = 0; i < parity_count; i++)                      /* assign parity */
    if (config.parity == parity_map [i].parity) {       /* match mapping value? */
        dcb.Parity = parity_map [i].parity_code;        /* assign corresponding code */
        break;
        }

if (i == parity_count)                                  /* parity assigned? */
    return SCPE_ARG;                                    /* not a valid parity specifier */

if (config.stopbits == 1)                               /* assign stop bits */
    dcb.StopBits = ONESTOPBIT;
else if (config.stopbits == 2)
    dcb.StopBits = TWOSTOPBITS;
else if (config.stopbits == 0)                          /* 0 implies 1.5 stop bits */
    dcb.StopBits = ONE5STOPBITS;
else
    return SCPE_ARG;                                    /* not a valid number of stop bits */

if (!SetCommState (port, &dcb)) {                       /* set the configuration */
    error = GetLastError ();                            /* check for error */

    if (error == ERROR_INVALID_PARAMETER)               /* invalid configuration? */
        return SCPE_ARG;                                /* report as argument error */

    sim_error_serial ("SetCommState", (int) error);     /* function failed; report unexpected error */
    return SCPE_IOERR;                                  /* return failure status */
    }

return SCPE_OK;                                         /* return success status */
}


/* Control a serial port.

   The DTR and RTS line of the serial port is set or cleared as indicated in 
   the respective bits_to_set or bits_to_clear parameters.  If the 
   incoming_bits parameter is not NULL, then the modem status bits DCD, RNG, 
   DSR and CTS are returned.

   If unreasonable or nonsense bits_to_set or bits_to_clear bits are 
   specified, then the return status is SCPE_ARG;
   If an error occurs, SCPE_IOERR is returned.
*/

t_stat sim_control_serial (SERHANDLE port, int32 bits_to_set, int32 bits_to_clear, int32 *incoming_bits)
{
if ((bits_to_set & ~(TMXR_MDM_OUTGOING)) ||         /* Assure only settable bits */
    (bits_to_clear & ~(TMXR_MDM_OUTGOING)) ||
    (bits_to_set & bits_to_clear))                  /* and can't set and clear the same bits */
    return SCPE_ARG;
if (bits_to_set&TMXR_MDM_DTR)
    if (!EscapeCommFunction (port, SETDTR)) {
        sim_error_serial ("EscapeCommFunction", (int) GetLastError ());
        return SCPE_IOERR;
        }
if (bits_to_clear&TMXR_MDM_DTR)
    if (!EscapeCommFunction (port, CLRDTR)) {
        sim_error_serial ("EscapeCommFunction", (int) GetLastError ());
        return SCPE_IOERR;
        }
if (bits_to_set&TMXR_MDM_RTS)
    if (!EscapeCommFunction (port, SETRTS)) {
        sim_error_serial ("EscapeCommFunction", (int) GetLastError ());
        return SCPE_IOERR;
        }
if (bits_to_clear&TMXR_MDM_RTS)
    if (!EscapeCommFunction (port, CLRRTS)) {
        sim_error_serial ("EscapeCommFunction", (int) GetLastError ());
        return SCPE_IOERR;
        }
if (incoming_bits) {
    DWORD ModemStat;
    if (GetCommModemStatus (port, &ModemStat)) {
        sim_error_serial ("GetCommModemStatus", (int) GetLastError ());
        return SCPE_IOERR;
        }
    *incoming_bits = ((ModemStat&MS_CTS_ON)  ? TMXR_MDM_CTS : 0) |
                     ((ModemStat&MS_DSR_ON)  ? TMXR_MDM_DSR : 0) |
                     ((ModemStat&MS_RING_ON) ? TMXR_MDM_RNG : 0) |
                     ((ModemStat&MS_RLSD_ON) ? TMXR_MDM_DCD : 0);
    }
return SCPE_OK;
}


/* Read from a serial port.

   The port is checked for available characters.  If any are present, they are
   copied to the passed buffer, and the count of characters is returned.  If no
   characters are available, 0 is returned.  If an error occurs, -1 is returned.
   If a BREAK is detected on the communications line, the corresponding flag in
   the "brk" array is set.

   Implementation notes:

    1. The "ClearCommError" function will set the CE_BREAK flag in the returned
       errors value if a BREAK has occurred.  However, we do not know where in
       the serial stream it happened, as CE_BREAK isn't associated with a
       specific character.  Because the "brk" array does want a flag associated
       with a specific character, we guess at the proper location by setting
       the "brk" entry corresponding to the first NUL in the character stream.
       If no NUL is present, then the "brk" entry associated with the first
       character is set.
*/

int32 sim_read_serial (SERHANDLE port, char *buffer, int32 count, char *brk)
{
DWORD read;
DWORD commerrors;
COMSTAT cs;
char *bptr;

if (!ClearCommError (port, &commerrors, &cs)) {         /* get the comm error flags  */
    sim_error_serial ("ClearCommError",                 /* function failed; report unexpected error */
                      (int) GetLastError ());
    return -1;                                          /* return failure to caller */
    }

if (!ReadFile (port, (LPVOID) buffer,                   /* read any available characters */
               (DWORD) count, &read, NULL)) {
    sim_error_serial ("ReadFile",                       /* function failed; report unexpected error */
                      (int) GetLastError ());
    return -1;                                          /* return failure to caller */
    }

if (commerrors & CE_BREAK) {                            /* was a BREAK detected? */
    bptr = (char *) memchr (buffer, 0, read);           /* search for the first NUL in the buffer */

    if (bptr)                                           /* was one found? */
        brk = brk + (bptr - buffer);                    /* calculate corresponding position */

    *brk = 1;                                           /* set the BREAK flag */
    }

return read;                                            /* return the number of characters read */
}


/* Write to a serial port.

   "Count" characters are written from "buffer" to the serial port.  The actual
   number of characters written to the port is returned.  If an error occurred
   on writing, -1 is returned.
*/

int32 sim_write_serial (SERHANDLE port, char *buffer, int32 count)
{
DWORD written;

if (!WriteFile (port, (LPVOID) buffer,                  /* write the buffer to the serial port */
                (DWORD) count, &written, NULL)) {
    sim_error_serial ("WriteFile",                      /* function failed; report unexpected error */
                      (int) GetLastError ());
    return -1;                                          /* return failure to caller */
    }
else
    return written;                                     /* return number of characters written */
}


/* Close a serial port.

   The serial port is closed.  Errors are ignored.
*/

static void sim_close_os_serial (SERHANDLE port)
{
CloseHandle (port);                                     /* close the port */
return;
}



#elif defined (__unix__) || defined(__APPLE__) || defined(__hpux)

#if defined(__linux) || defined(__linux__)
#include <dirent.h>
#include <libgen.h>
#include <unistd.h>
#include <sys/stat.h>
#endif /* __linux__ */

/* UNIX implementation */

/* Enumerate the available serial ports.

   The serial port names generated by attempting to open /dev/ttyS0 thru
   /dev/ttyS63 and /dev/ttyUSB0 thru /dev/ttyUSB63 and /dev/tty.serial0
   thru /dev/tty.serial63.  Ones we can open and are ttys (as determined 
   by isatty()) are added to the list.  The list is sorted alphabetically 
   by device name.

*/

static int sim_serial_os_devices (int max, SERIAL_LIST* list)
{
int i;
int port;
int ports = 0;

memset(list, 0, max*sizeof(*list));
#if defined(__linux) || defined(__linux__)
if (1) {
    struct dirent **namelist;
    struct stat st;

    i = scandir("/sys/class/tty/", &namelist, NULL, NULL);

    while (i--) {
        if (strcmp(namelist[i]->d_name, ".") &&
            strcmp(namelist[i]->d_name, "..")) {
            char path[1024], devicepath[1024], driverpath[1024];

            sprintf (path, "/sys/class/tty/%s", namelist[i]->d_name);
            sprintf (devicepath, "/sys/class/tty/%s/device", namelist[i]->d_name);
            sprintf (driverpath, "/sys/class/tty/%s/device/driver", namelist[i]->d_name);
            if ((lstat(devicepath, &st) == 0) && S_ISLNK(st.st_mode)) {
                char buffer[1024];

                memset (buffer, 0, sizeof(buffer));
                if (readlink(driverpath, buffer, sizeof(buffer)) > 0) {
                    sprintf (list[ports].name, "/dev/%s", basename (path));
                    port = open (list[ports].name, O_RDWR | O_NOCTTY | O_NONBLOCK);     /* open the port */
                    if (port != -1) {                   /* open OK? */
                        if (isatty (port))              /* is device a TTY? */
                            ++ports;
                        close (port);
                        }
                    }
                }
            }
        free (namelist[i]);
        }
    free (namelist);
    }
#elif defined(__hpux)
for (i=0; (ports < max) && (i < 64); ++i) {
    sprintf (list[ports].name, "/dev/tty%dp%d", i/8, i%8);
    port = open (list[ports].name, O_RDWR | O_NOCTTY | O_NONBLOCK);     /* open the port */
    if (port != -1) {                                   /* open OK? */
        if (isatty (port))                              /* is device a TTY? */
            ++ports;
        close (port);
        }
    }
#else /* Non Linux/HP-UX, just try some well known device names */
for (i=0; (ports < max) && (i < 64); ++i) {
    sprintf (list[ports].name, "/dev/ttyS%d", i);
    port = open (list[ports].name, O_RDWR | O_NOCTTY | O_NONBLOCK);     /* open the port */
    if (port != -1) {                                   /* open OK? */
        if (isatty (port))                              /* is device a TTY? */
            ++ports;
        close (port);
        }
    }
for (i=0; (ports < max) && (i < 64); ++i) {
    sprintf (list[ports].name, "/dev/ttyUSB%d", i);
    port = open (list[ports].name, O_RDWR | O_NOCTTY | O_NONBLOCK);     /* open the port */
    if (port != -1) {                                   /* open OK? */
        if (isatty (port))                              /* is device a TTY? */
            ++ports;
        close (port);
        }
    }
for (i=1; (ports < max) && (i < 64); ++i) {
    sprintf (list[ports].name, "/dev/tty.serial%d", i);
    port = open (list[ports].name, O_RDWR | O_NOCTTY | O_NONBLOCK);     /* open the port */
    if (port != -1) {                                   /* open OK? */
        if (isatty (port))                              /* is device a TTY? */
            ++ports;
        close (port);
        }
    }
#endif
return ports;
}

/* Open a serial port.

   The serial port designated by "name" is opened, and the handle to the port is
   returned.  If an error occurs, INVALID_HANDLE is returned instead.  After
   opening, the port is configured to "raw" mode.

   Implementation notes:

    1. We use a non-blocking open to allow for polling during reads.

    2. There is no way to limit "open" just to serial ports, so we must check
       after the port is opened.  We do this with a combination of "isatty" and
       "tcgetattr".

    3. We configure with PARMRK set and IGNBRK and BRKINT cleared.  This will
       mark a communication line BREAK condition in the input stream with the
       three-character sequence \377 \000 \000.  This is detected during
       reading.
*/

static SERHANDLE sim_open_os_serial (char *name)
{
static const tcflag_t i_clear = IGNBRK |                /* ignore BREAK */
                                BRKINT |                /* signal on BREAK */
                                INPCK  |                /* enable parity checking */
                                ISTRIP |                /* strip character to 7 bits */
                                INLCR  |                /* map NL to CR */
                                IGNCR  |                /* ignore CR */
                                ICRNL  |                /* map CR to NL */
                                IXON   |                /* enable XON/XOFF output control */
                                IXOFF;                  /* enable XON/XOFF input control */

static const tcflag_t i_set   = PARMRK |                /* mark parity errors and line breaks */
                                IGNPAR;                 /* ignore parity errors */

static const tcflag_t o_clear = OPOST;                  /* post-process output */

static const tcflag_t o_set   = 0;

static const tcflag_t c_clear = HUPCL;                  /* hang up line on last close */

static const tcflag_t c_set   = CREAD |                 /* enable receiver */
                                CLOCAL;                 /* ignore modem status lines */

static const tcflag_t l_clear = ISIG    |               /* enable signals */
                                ICANON  |               /* canonical input */
                                ECHO    |               /* echo characters */
                                ECHOE   |               /* echo ERASE as an error correcting backspace */
                                ECHOK   |               /* echo KILL */
                                ECHONL  |               /* echo NL */
                                NOFLSH  |               /* disable flush after interrupt */
                                TOSTOP  |               /* send SIGTTOU for background output */
                                IEXTEN;                 /* enable extended functions */

static const tcflag_t l_set   = 0;


SERHANDLE port;
struct termios tio;

port = open (name, O_RDWR | O_NOCTTY | O_NONBLOCK);     /* open the port */

if (port == -1) {                                       /* open failed? */
    if (errno != ENOENT && errno != EACCES)             /* file not found or can't open? */
        sim_error_serial ("open", errno);               /* no, so report unexpected error */

    return INVALID_HANDLE;                              /* indicate failure to caller */
    }

if (!isatty (port)) {                                   /* is device a TTY? */
    close (port);                                       /* no, so close it */
    return INVALID_HANDLE;                              /*   and return failure to caller */
    }

if (tcgetattr (port, &tio)) {                           /* get the terminal attributes */
    sim_error_serial ("tcgetattr", errno);              /* function failed; report unexpected error */
    close (port);                                       /* close the port */
    return INVALID_HANDLE;                              /*   and return failure to caller */
    }

// which of these methods is best?

#if 1

tio.c_iflag = (tio.c_iflag & ~i_clear) | i_set;           /* configure the serial line for raw mode */
tio.c_oflag = (tio.c_oflag & ~o_clear) | o_set;
tio.c_cflag = (tio.c_cflag & ~c_clear) | c_set;
tio.c_lflag = (tio.c_lflag & ~l_clear) | l_set;
#ifdef VMIN
tio.c_cc[VMIN] = 1;
#endif
#ifdef VTIME
tio.c_cc[VTIME] = 0;
#endif

#elif 0

tio.c_iflag &= ~(IGNBRK | BRKINT | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF);
tio.c_iflag |= PARMRK | IGNPAR;
tio.c_oflag &= ~(OPOST);
tio.c_cflag &= ~(HUPCL);
tio.c_cflag |= CREAD | CLOCAL;
tio.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL | NOFLSH | TOSTOP | IEXTEN);

#elif 0

tio.c_iflag = PARMRK | IGNPAR;
tio.c_oflag = 0;
tio.c_cflag = tio.c_cflag | CLOCAL | CREAD;
tio.c_lflag = 0;

#endif

if (tcsetattr (port, TCSANOW, &tio)) {                  /* set the terminal attributes */
    sim_error_serial ("tcsetattr", errno);              /* function failed; report unexpected error */
    close (port);                                       /* close the port */
    return INVALID_HANDLE;                              /*   and return failure to caller */
    }

return port;                                            /* return port fd for success */
}


/* Configure a serial port.

   Port parameters are configured as specified in the "config" structure.  If
   "config" contains an invalid configuration value, or if the host system
   rejects the configuration (e.g., by requesting an unsupported combination of
   character size and stop bits), SCPE_ARG is returned to the caller.  If an
   unexpected error occurs, SCPE_IOERR is returned.  If the configuration
   succeeds, SCPE_OK is returned.

   Implementation notes:

    1. 1.5 stop bits is not a supported configuration.

*/

static t_stat sim_config_os_serial (SERHANDLE port, SERCONFIG config)
{
struct termios tio;
int32 i;

static const struct {
    uint32  rate;
    speed_t rate_code;
    } baud_map [] =
        { { 50,     B50     }, { 75,     B75     }, { 110,    B110    }, {  134,   B134   },
          { 150,    B150    }, { 200,    B200    }, { 300,    B300    }, {  600,   B600   },
          { 1200,   B1200   }, { 1800,   B1800   }, { 2400,   B2400   }, {  4800,  B4800  },
          { 9600,   B9600   }, { 19200,  B19200  }, { 38400,  B38400  }, {  57600, B57600 },
          { 115200, B115200 } };

static const int32 baud_count = sizeof (baud_map) / sizeof (baud_map [0]);

static const tcflag_t charsize_map [4] = { CS5, CS6, CS7, CS8 };


if (tcgetattr (port, &tio)) {                           /* get the current configuration */
    sim_error_serial ("tcgetattr", errno);              /* function failed; report unexpected error */
    return SCPE_IOERR;                                  /* return failure status */
    }

for (i = 0; i < baud_count; i++)                        /* assign baud rate */
    if (config.baudrate == baud_map [i].rate) {         /* match mapping value? */
        cfsetispeed(&tio, baud_map [i].rate_code);      /* set input rate */
        cfsetospeed(&tio, baud_map [i].rate_code);      /* set output rate */
        break;
        }

if (i == baud_count)                                    /* baud rate assigned? */
    return SCPE_ARG;                                    /* invalid rate specified */

if ((config.charsize >= 5) && (config.charsize <= 8))   /* character size OK? */
    tio.c_cflag = (tio.c_cflag & ~CSIZE) |              /* replace character size code */
                charsize_map [config.charsize - 5];
else
    return SCPE_ARG;                                    /* not a valid size */

switch (config.parity) {                                /* assign parity */
    case 'E':
        tio.c_cflag = (tio.c_cflag & ~PARODD) | PARENB; /* set for even parity */
        break;

    case 'N':
        tio.c_cflag = tio.c_cflag & ~PARENB;            /* set for no parity */
        break;

    case 'O':
        tio.c_cflag = tio.c_cflag | PARODD | PARENB;    /* set for odd parity */
        break;

    default:
        return SCPE_ARG;                                /* not a valid parity specifier */
    }

if (config.stopbits == 1)                               /* one stop bit? */
    tio.c_cflag = tio.c_cflag & ~CSTOPB;                /* clear two-bits flag */
else if (config.stopbits == 2)                          /* two stop bits? */
    tio.c_cflag = tio.c_cflag | CSTOPB;                 /* set two-bits flag */
else                                                    /* some other number? */
    return SCPE_ARG;                                    /* not a valid number of stop bits */

if (tcsetattr (port, TCSAFLUSH, &tio)) {                /* set the new configuration */
    sim_error_serial ("tcsetattr", errno);              /* function failed; report unexpected error */
    return SCPE_IERR;                                   /* return failure status */
    }

return SCPE_OK;                                         /* configuration set successfully */
}


/* Control a serial port.

   The DTR and RTS line of the serial port is set or cleared as indicated in 
   the respective bits_to_set or bits_to_clear parameters.  If the 
   incoming_bits parameter is not NULL, then the modem status bits DCD, RNG, 
   DSR and CTS are returned.

   If unreasonable or nonsense bits_to_set or bits_to_clear bits are 
   specified, then the return status is SCPE_ARG;
   If an error occurs, SCPE_IOERR is returned.
*/

t_stat sim_control_serial (SERHANDLE port, int32 bits_to_set, int32 bits_to_clear, int32 *incoming_bits)
{
int bits;

if ((bits_to_set & ~(TMXR_MDM_OUTGOING)) ||         /* Assure only settable bits */
    (bits_to_clear & ~(TMXR_MDM_OUTGOING)) ||
    (bits_to_set & bits_to_clear))                  /* and can't set and clear the same bits */
    return SCPE_ARG;
if (bits_to_set) {
    bits = ((bits_to_set&TMXR_MDM_DTR) ? TIOCM_DTR : 0) |
           ((bits_to_set&TMXR_MDM_RTS) ? TIOCM_RTS : 0);
    if (ioctl (port, TIOCMBIS, &bits)) {            /* set the desired bits */
        sim_error_serial ("ioctl", errno);          /* report unexpected error */
        return SCPE_IOERR;                          /* return failure status */
        }
    }
if (bits_to_clear) {
    bits = ((bits_to_clear&TMXR_MDM_DTR) ? TIOCM_DTR : 0) |
           ((bits_to_clear&TMXR_MDM_RTS) ? TIOCM_RTS : 0);
    if (ioctl (port, TIOCMBIC, &bits)) {            /* clear the desired bits */
        sim_error_serial ("ioctl", errno);          /* report unexpected error */
        return SCPE_IOERR;                          /* return failure status */
        }
    }
if (incoming_bits) {
    if (ioctl (port, TIOCMGET, &bits)) {            /* get the modem bits */
        sim_error_serial ("ioctl", errno);          /* report unexpected error */
        return SCPE_IOERR;                          /* return failure status */
        }
    *incoming_bits = ((bits&TIOCM_CTS) ? TMXR_MDM_CTS : 0) |
                     ((bits&TIOCM_DSR) ? TMXR_MDM_DSR : 0) |
                     ((bits&TIOCM_RNG) ? TMXR_MDM_RNG : 0) |
                     ((bits&TIOCM_CAR) ? TMXR_MDM_DCD : 0);
    }

return SCPE_OK;
}


/* Read from a serial port.

   The port is checked for available characters.  If any are present, they are
   copied to the passed buffer, and the count of characters is returned.  If no
   characters are available, 0 is returned.  If an error occurs, -1 is returned.
   If a BREAK is detected on the communications line, the corresponding flag in
   the "brk" array is set.

   Implementation notes:

    1. A character with a framing or parity error is indicated in the input
       stream by the three-character sequence \377 \000 \ccc, where "ccc" is the
       bad character.  A communications line BREAK is indicated by the sequence
       \377 \000 \000.  A received \377 character is indicated by the
       two-character sequence \377 \377.  If we find any of these sequences,
       they are replaced by the single intended character by sliding the
       succeeding characters backward by one or two positions.  If a BREAK
       sequence was encountered, the corresponding location in the "brk" array
       is determined, and the flag is set.  Note that there may be multiple
       sequences in the buffer.
*/

int32 sim_read_serial (SERHANDLE port, char *buffer, int32 count, char *brk)
{
int read_count;
char *bptr, *cptr;
int32 remaining;

read_count = read (port, (void *) buffer, (size_t) count);  /* read from the serial port */

if (read_count == -1)                                       /* read error? */
    if (errno == EAGAIN)                                    /* no characters available? */
        return 0;                                           /* return 0 to indicate */
    else                                                    /* some other problem */
        sim_error_serial ("read", errno);                   /* report unexpected error */

else {                                                      /* read succeeded */
    cptr = buffer;                                          /* point at start of buffer */
    remaining = read_count - 1;                             /* stop search one char from end of string */

    while (remaining > 0 &&                                 /* still characters to search? */
           (bptr = memchr (cptr, '\377', remaining))) {     /* search for start of PARMRK sequence */
        remaining = remaining - (bptr - cptr) - 1;          /* calc characters remaining */

        if (*(bptr + 1) == '\377') {                        /* is it a \377 \377 sequence? */
            memmove (bptr + 1, bptr + 2, remaining);        /* slide string backward to leave first \377 */
            remaining = remaining - 1;                      /* drop remaining count */
            read_count = read_count - 1;                    /*   and read count by char eliminated */
            }

        else if (remaining > 0 && *(bptr + 1) == '\0') {    /* is it a \377 \000 \ccc sequence? */
            memmove (bptr, bptr + 2, remaining);            /* slide string backward to leave \ccc */
            remaining = remaining - 2;                      /* drop remaining count */
            read_count = read_count - 2;                    /*   and read count by chars eliminated */

            if (*bptr == '\0')                              /* is it a BREAK sequence? */
                *(brk + (bptr - buffer)) = 1;               /* set corresponding BREAK flag */
            }

        cptr = bptr + 1;                                    /* point at remainder of string */
        }
    }

return (int32) read_count;                                  /* return the number of characters read */
}


/* Write to a serial port.

   "Count" characters are written from "buffer" to the serial port.  The actual
   number of characters written to the port is returned.  If an error occurred
   on writing, -1 is returned.
*/

int32 sim_write_serial (SERHANDLE port, char *buffer, int32 count)
{
int written;

written = write (port, (void *) buffer, (size_t) count);    /* write the buffer to the serial port */

if (written == -1) {
    if (errno == EWOULDBLOCK)
        written = 0;                                        /* not an error, but nothing written */
#if defined(EAGAIN)
    else if (errno == EAGAIN)
        written = 0;                                        /* not an error, but nothing written */
#endif
    else                                                    /* unexpected error? */
        sim_error_serial ("write", errno);                  /* report it */
    }

return (int32) written;                                     /* return number of characters written */
}


/* Close a serial port.

   The serial port is closed.  Errors are ignored.
*/

static void sim_close_os_serial (SERHANDLE port)
{
close (port);                                           /* close the port */
return;
}


#elif defined (VMS)

/* VMS implementation */

#if defined(__VAX)
#define sys$assign SYS$ASSIGN
#define sys$qiow SYS$QIOW
#define sys$dassgn SYS$DASSGN
#define sys$device_scan SYS$DEVICE_SCAN
#define sys$getdviw SYS$GETDVIW
#endif

#include <descrip.h>
#include <ttdef.h>
#include <tt2def.h>
#include <iodef.h>
#include <ssdef.h>
#include <dcdef.h>
#include <dvsdef.h>
#include <dvidef.h>
#include <starlet.h>
#include <unistd.h>

typedef struct {
    unsigned short sense_count;
    unsigned char sense_first_char;
    unsigned char sense_reserved;
    unsigned int stat;
    unsigned int stat2; } SENSE_BUF;

typedef struct {
    unsigned short status;
    unsigned short count;
    unsigned int dev_status; } IOSB;

typedef struct {
    unsigned short buffer_size;
    unsigned short item_code;
    void *buffer_address;
    void *return_length_address;
    } ITEM;

/* Enumerate the available serial ports.

   The serial port names generated by attempting to open /dev/ttyS0 thru
   /dev/ttyS53 and /dev/ttyUSB0 thru /dev/ttyUSB0.  Ones we can open and
   are ttys (as determined by isatty()) are added to the list.  The list 
   is sorted alphabetically by device name.

*/

static int sim_serial_os_devices (int max, SERIAL_LIST* list)
{
$DESCRIPTOR (wild, "*");
char devstr[sizeof(list[0].name)];
$DESCRIPTOR (device, devstr);
int ports;
IOSB iosb;
uint32 status;
uint32 devsts;
#define UCB$M_TEMPLATE 0x2000       /* Device is a template device */
#define UCB$M_ONLINE   0x0010       /* Device is online */
uint32 devtype;
uint32 devdepend;
#define DEV$M_RTM 0x20000000
uint32 devnamlen = 0;
t_bool done = FALSE;
uint32 context[2];
uint32 devclass = DC$_TERM; /* Only interested in terminal devices */
ITEM select_items[] = { {sizeof (devclass), DVS$_DEVCLASS, &devclass, NULL},
                        {                  0,               0,        NULL, NULL}};
ITEM valid_items[] =  { {    sizeof (devsts),        DVI$_STS,     &devsts, NULL},
                        {     sizeof(devstr),     DVI$_DEVNAM,      devstr, &devnamlen},
                        {    sizeof(devtype),    DVI$_DEVTYPE,    &devtype, NULL},
                        {  sizeof(devdepend),  DVI$_DEVDEPEND,  &devdepend, NULL},
                        {                  0,               0,        NULL, NULL}};

memset(context, 0, sizeof(context));
memset(devstr, 0, sizeof(devstr));
memset(list, 0, max*sizeof(*list));
for (ports=0; (ports < max); ++ports) {
    device.dsc$w_length = sizeof (devstr) - 1;
    status = sys$device_scan (&device,
                              &device.dsc$w_length,
                              &wild,
                              select_items,
                              &context);
    switch (status) {
        case SS$_NOSUCHDEV:
        case SS$_NOMOREDEV:
            done = TRUE;
            break;
        default:
            if (0 == (status&1))
                done = TRUE;
            else {
                status = sys$getdviw (0, 0, &device, valid_items, &iosb, NULL, 0, NULL);
                if (status == SS$_NORMAL)
                    status = iosb.status;
                if (status != SS$_NORMAL) {
                    done = TRUE;
                    break;
                    }
                device.dsc$w_length = devnamlen;
                if ((0 == (devsts & UCB$M_TEMPLATE)) &&
                    (0 != (devsts & UCB$M_ONLINE)) &&
                    (0 == (devdepend & DEV$M_RTM))) {
                    devstr[device.dsc$w_length] = '\0';
                    strcpy (list[ports].name, devstr);
                    while (list[ports].name[0] == '_')
                        strcpy (list[ports].name, list[ports].name+1);
                    }
                else
                    --ports;
                }
            break;
        }
    if (done)
        break;
    }
return ports;
}

/* Open a serial port.

   The serial port designated by "name" is opened, and the handle to the port is
   returned.  If an error occurs, INVALID_HANDLE is returned instead.  After
   opening, the port is configured to "raw" mode.

   Implementation notes:

    1. We use a non-blocking open to allow for polling during reads.

    2. There is no way to limit "open" just to serial ports, so we must check
       after the port is opened.  We do this with sys$getdvi.

*/

static SERHANDLE sim_open_os_serial (char *name)
{
uint32 status;
uint32 chan = 0;
IOSB iosb;
$DESCRIPTOR (devnam, name);
uint32 devclass;
ITEM items[] = { {sizeof (devclass), DVI$_DEVCLASS, &devclass, NULL},
                 {                0,             0,      NULL, NULL}};
SENSE_BUF start_mode = { 0 };
SENSE_BUF run_mode = { 0 };

devnam.dsc$w_length = strlen (devnam.dsc$a_pointer);
status = sys$assign (&devnam, &chan, 0, 0);
if (status != SS$_NORMAL) 
    return INVALID_HANDLE;
status = sys$getdviw (0, chan, NULL, items, &iosb, NULL, 0, NULL);
if ((status != SS$_NORMAL)      || 
    (iosb.status != SS$_NORMAL) ||
    (devclass != DC$_TERM)) {
    sys$dassgn (chan);
    return INVALID_HANDLE;
    }
status = sys$qiow (0, chan, IO$_SENSEMODE, &iosb, 0, 0,
    &start_mode, sizeof (start_mode), 0, 0, 0, 0);
if ((status != SS$_NORMAL) || (iosb.status != SS$_NORMAL)) {
    sys$dassgn (chan);
    return INVALID_HANDLE;
    }
run_mode = start_mode;
run_mode.stat = start_mode.stat | TT$M_NOECHO & ~(TT$M_HOSTSYNC | TT$M_TTSYNC | TT$M_HALFDUP);
run_mode.stat2 = start_mode.stat2 | TT2$M_PASTHRU;
status = sys$qiow (0, chan, IO$_SETMODE, &iosb, 0, 0,
    &run_mode, sizeof (run_mode), 0, 0, 0, 0);
if ((status != SS$_NORMAL) || (iosb.status != SS$_NORMAL)) {
    sys$dassgn (chan);
    return INVALID_HANDLE;
    }
return chan;                                            /* return channel for success */
}


/* Configure a serial port.

   Port parameters are configured as specified in the "config" structure.  If
   "config" contains an invalid configuration value, or if the host system
   rejects the configuration (e.g., by requesting an unsupported combination of
   character size and stop bits), SCPE_ARG is returned to the caller.  If an
   unexpected error occurs, SCPE_IOERR is returned.  If the configuration
   succeeds, SCPE_OK is returned.

   Implementation notes:

    1. 1.5 stop bits is not a supported configuration.

*/

static t_stat sim_config_os_serial (SERHANDLE port, SERCONFIG config)
{
int32 i;
SENSE_BUF sense;
uint32 status, speed, parity, charsize, stopbits;
IOSB iosb;
static const struct {
    uint32  rate;
    uint32  rate_code;
    } baud_map [] =
        { { 50,     TT$C_BAUD_50     }, { 75,     TT$C_BAUD_75     }, { 110,    TT$C_BAUD_110    }, {  134,   TT$C_BAUD_134   },
          { 150,    TT$C_BAUD_150    }, { 300,    TT$C_BAUD_300    }, {  600,   TT$C_BAUD_600    }, {  1200,  TT$C_BAUD_1200  },
          { 1800,   TT$C_BAUD_1800   }, { 2000,   TT$C_BAUD_2000   }, { 2400,   TT$C_BAUD_2400   }, {  3600,  TT$C_BAUD_3600  },
          { 4800,   TT$C_BAUD_4800   }, { 7200,   TT$C_BAUD_7200   }, { 9600,   TT$C_BAUD_9600   }, { 19200,  TT$C_BAUD_19200 },
          { 38400,  TT$C_BAUD_38400  }, { 57600,  TT$C_BAUD_57600  }, { 76800,  TT$C_BAUD_76800  }, { 115200, TT$C_BAUD_115200} };

static const int32 baud_count = sizeof (baud_map) / sizeof (baud_map [0]);

status = sys$qiow (0, port, IO$_SENSEMODE, &iosb, 0, 0, &sense, sizeof(sense), 0, NULL, 0, 0);
if (status == SS$_NORMAL)
    status = iosb.status;
if (status != SS$_NORMAL) {
    sim_error_serial ("config-SENSEMODE", status);      /* report unexpected error */
    return SCPE_IOERR;
    }

for (i = 0; i < baud_count; i++)                        /* assign baud rate */
    if (config.baudrate == baud_map [i].rate) {         /* match mapping value? */
        speed = baud_map [i].rate_code << 8 |           /* set input rate */
                baud_map [i].rate_code;                 /* set output rate */
        break;
        }

if (i == baud_count)                                    /* baud rate assigned? */
    return SCPE_ARG;                                    /* invalid rate specified */

if (config.charsize >= 5 && config.charsize <= 8)       /* character size OK? */
    charsize = TT$M_ALTFRAME | config.charsize;         /* set character size */
else
    return SCPE_ARG;                                    /* not a valid size */

switch (config.parity) {                                /* assign parity */
    case 'E':
        parity = TT$M_ALTRPAR | TT$M_PARITY;            /* set for even parity */
        break;

    case 'N':
        parity = TT$M_ALTRPAR;                          /* set for no parity */
        break;

    case 'O':
        parity = TT$M_ALTRPAR | TT$M_PARITY | TT$M_ODD; /* set for odd parity */
        break;

    default:
        return SCPE_ARG;                                /* not a valid parity specifier */
    }


switch (config.stopbits) {
    case 1:                                             /* one stop bit? */
        stopbits = 0;
        break;
    case 2:                                             /* two stop bits? */
        if ((speed & 0xff) <= TT$C_BAUD_150) {          /* Only valid for */
            stopbits = TT$M_TWOSTOP;                    /* speeds 150baud or less */
            break;
            }
    default:
        return SCPE_ARG;                                /* not a valid number of stop bits */
    }

status = sys$qiow (0, port, IO$_SETMODE, &iosb, 0, 0,
    &sense, sizeof (sense), speed, 0, parity | charsize | stopbits, 0);
if (status == SS$_NORMAL)
    status = iosb.status;
if (status != SS$_NORMAL) {
    sim_error_serial ("config-SETMODE", status);        /* report unexpected error */
    return SCPE_IOERR;
    }
return SCPE_OK;                                         /* configuration set successfully */
}


/* Control a serial port.

   The DTR and RTS line of the serial port is set or cleared as indicated in 
   the respective bits_to_set or bits_to_clear parameters.  If the 
   incoming_bits parameter is not NULL, then the modem status bits DCD, RNG, 
   DSR and CTS are returned.

   If unreasonable or nonsense bits_to_set or bits_to_clear bits are 
   specified, then the return status is SCPE_ARG;
   If an error occurs, SCPE_IOERR is returned.
*/

t_stat sim_control_serial (SERHANDLE port, int32 bits_to_set, int32 bits_to_clear, int32 *incoming_bits)
{
uint32 status;
IOSB iosb;
uint32 bits[2] = {0, 0};

if ((bits_to_set & ~(TMXR_MDM_OUTGOING)) ||         /* Assure only settable bits */
    (bits_to_clear & ~(TMXR_MDM_OUTGOING)) ||
    (bits_to_set & bits_to_clear))                  /* and can't set and clear the same bits */
    return SCPE_ARG;
if (bits_to_set)
    bits[0] |= (((bits_to_set&TMXR_MDM_DTR) ? TT$M_DS_DTR : 0) |
                ((bits_to_set&TMXR_MDM_RTS) ? TT$M_DS_RTS : 0)) << 16;
if (bits_to_clear)
    bits[0] |= (((bits_to_clear&TMXR_MDM_DTR) ? TT$M_DS_DTR : 0) |
                ((bits_to_clear&TMXR_MDM_RTS) ? TT$M_DS_RTS : 0)) << 24;
if (bits_to_set || bits_to_clear) {
    status = sys$qiow (0, port, IO$_SETMODE|IO$M_SET_MODEM|IO$M_MAINT, &iosb, 0, 0,
                       bits, 0, 0, 0, 0, 0);
    if (status == SS$_NORMAL)
        status = iosb.status;
    if (status != SS$_NORMAL) {
        sim_error_serial ("control-SETMODE", status);      /* report unexpected error */
        return SCPE_IOERR;
        }
    }
if (incoming_bits) {
    uint32 modem;

    status = sys$qiow (0, port, IO$_SENSEMODE|IO$M_RD_MODEM, &iosb, 0, 0,
                       bits, 0, 0, 0, 0, 0);
    if (status == SS$_NORMAL)
        status = iosb.status;
    if (status != SS$_NORMAL) {
        sim_error_serial ("control-SENSEMODE", status);      /* report unexpected error */
        return SCPE_IOERR;
        }
    modem = bits[0] >> 16;
    *incoming_bits = ((modem&TT$M_DS_CTS)     ? TMXR_MDM_CTS : 0) |
                     ((modem&TT$M_DS_DSR)     ? TMXR_MDM_DSR : 0) |
                     ((modem&TT$M_DS_RING)    ? TMXR_MDM_RNG : 0) |
                     ((modem&TT$M_DS_CARRIER) ? TMXR_MDM_DCD : 0);
    }

return SCPE_OK;
}


/* Read from a serial port.

   The port is checked for available characters.  If any are present, they are
   copied to the passed buffer, and the count of characters is returned.  If no
   characters are available, 0 is returned.  If an error occurs, -1 is returned.
   If a BREAK is detected on the communications line, the corresponding flag in
   the "brk" array is set.

   Implementation notes:

    1. A character with a framing or parity error is indicated in the input
       stream by the three-character sequence \377 \000 \ccc, where "ccc" is the
       bad character.  A communications line BREAK is indicated by the sequence
       \377 \000 \000.  A received \377 character is indicated by the
       two-character sequence \377 \377.  If we find any of these sequences,
       they are replaced by the single intended character by sliding the
       succeeding characters backward by one or two positions.  If a BREAK
       sequence was encountered, the corresponding location in the "brk" array
       is determined, and the flag is set.  Note that there may be multiple
       sequences in the buffer.
*/

int32 sim_read_serial (SERHANDLE port, char *buffer, int32 count, char *brk)
{
int read_count = 0;
uint32 status;
static uint32 term[2] = {0, 0};
unsigned char buf[4];
IOSB iosb;
SENSE_BUF sense;

status = sys$qiow (0, port, IO$_SENSEMODE | IO$M_TYPEAHDCNT, &iosb,
    0, 0, &sense, 8, 0, term, 0, 0);
if (status == SS$_NORMAL)
    status = iosb.status;
if (status != SS$_NORMAL) {
    sim_error_serial ("read", status);                      /* report unexpected error */
    return -1;
    }
if (sense.sense_count == 0)                                 /* no characters available? */
    return 0;                                               /* return 0 to indicate */
status = sys$qiow (0, port, IO$_READLBLK | IO$M_NOECHO | IO$M_NOFILTR | IO$M_TIMED | IO$M_TRMNOECHO, 
                   &iosb, 0, 0, buffer, (count < sense.sense_count) ? count : sense.sense_count, 0, term, 0, 0);
if (status == SS$_NORMAL)
    status = iosb.status;
if (status != SS$_NORMAL) {
    sim_error_serial ("read", status);                      /* report unexpected error */
    return -1;
    }
return (int32)iosb.count;                                   /* return the number of characters read */
}


/* Write to a serial port.

   "Count" characters are written from "buffer" to the serial port.  The actual
   number of characters written to the port is returned.  If an error occurred
   on writing, -1 is returned.
*/

int32 sim_write_serial (SERHANDLE port, char *buffer, int32 count)
{
uint32 status;
static uint32 term[2] = {0, 0};
unsigned char buf[4];
IOSB iosb;
uint32 devsts = 0;
#define UCB$M_BSY   0x100           /* Device I/O busy flag */
ITEM items[] = { {sizeof (devsts), DVI$_STS, &devsts, NULL},
                 {              0,        0,    NULL, NULL}};

status = sys$getdviw (0, port, NULL, items, &iosb, NULL, 0, 0);
if (status == SS$_NORMAL)
    status = iosb.status;
if (status != SS$_NORMAL) {
    sim_error_serial ("write-GETDVI", status);          /* report unexpected error */
    return -1;
    }
if (devsts & UCB$M_BSY)
    return 0;                                           /* Would block */
status = sys$qiow (0, port, IO$_WRITELBLK | IO$M_NOFORMAT,
                   NULL, 0, 0, buffer, count, 0, 0, 0, 0);
if (status != SS$_NORMAL) {
    sim_error_serial ("write", status);                 /* report unexpected error */
    return -1;
    }
return (int32)iosb.count;                               /* return number of characters written */
}


/* Close a serial port.

   The serial port is closed.  Errors are ignored.
*/

static void sim_close_os_serial (SERHANDLE port)
{
sys$dassgn (port);                                      /* close the port */
return;
}


#else

/* Non-implemented stubs */

/* Enumerate the available serial ports. */

static int sim_serial_os_devices (int max, SERIAL_LIST* list)
{
return 0;
}

/* Open a serial port */

static SERHANDLE sim_open_os_serial (char *name)
{
return INVALID_HANDLE;
}


/* Configure a serial port */

static t_stat sim_config_os_serial (SERHANDLE port, SERCONFIG config)
{
return SCPE_IERR;
}


/* Control a serial port */

t_stat sim_control_serial (SERHANDLE port, int32 bits_to_set, int32 bits_to_clear, int32 *incoming_bits)
{
return SCPE_NOFNC;
}


/* Read from a serial port */

int32 sim_read_serial (SERHANDLE port, char *buffer, int32 count, char *brk)
{
return -1;
}


/* Write to a serial port */

int32 sim_write_serial (SERHANDLE port, char *buffer, int32 count)
{
return -1;
}


/* Close a serial port */

static void sim_close_os_serial (SERHANDLE port)
{
return;
}



#endif                                                  /* end else !implemented */
