| /* 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 (const 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) | |
| { | |
| const SERIAL_LIST *a = (const SERIAL_LIST *)pa; | |
| const SERIAL_LIST *b = (const 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; | |
| const 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); | |
| } | |
| } | |
| for (i=0; (ports < max) && (i < 64); ++i) { | |
| sprintf (list[ports].name, "/dev/tty%02d", 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 < 8); ++i) { | |
| sprintf (list[ports].name, "/dev/ttyU%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 = (char*)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 */ |