1 /* sim_serial.c: OS-dependent serial port routines
2 
3    Copyright (c) 2008-2020, J. David Bryan
4 
5    Permission is hereby granted, free of charge, to any person obtaining a
6    copy of this software and associated documentation files (the "Software"),
7    to deal in the Software without restriction, including without limitation
8    the rights to use, copy, modify, merge, publish, distribute, sublicense,
9    and/or sell copies of the Software, and to permit persons to whom the
10    Software is furnished to do so, subject to the following conditions:
11 
12    The above copyright notice and this permission notice shall be included in
13    all copies or substantial portions of the Software.
14 
15    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18    THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19    IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 
22    Except as contained in this notice, the name of the author shall not be
23    used in advertising or otherwise to promote the sale, use or other dealings
24    in this Software without prior written authorization from the author.
25 
26    The author gratefully acknowledges the assistance of Holger Veit with the
27    UNIX-specific code and testing.
28 
29    14-Jan-20    JDB     First release version
30    22-Jul-19    JDB     Added VMIN and VTIME settings in 'sim_open_serial"
31                         Added EAGAIN check to "sim_write_serial"
32    07-Oct-08    JDB     Created file
33 
34 
35    This module provides OS-dependent routines to access serial ports on the host
36    machine.  The terminal multiplexer library uses these routines to provide
37    serial connections to simulated terminal interfaces.
38 
39    Currently, the module supports Windows and UNIX.  Use on other systems
40    returns error codes indicating that the functions failed, inhibiting serial
41    port support in SIMH.
42 
43    The following routines are provided:
44 
45      sim_open_serial        open a serial port
46      sim_config_serial      change baud rate and character framing configuration
47      sim_control_serial     set or clear the serial control lines (e.g., DTR)
48      sim_status_serial      get the serial status line values (e.g. DSR)
49      sim_read_serial        read from a serial port
50      sim_write_serial       write to a serial port
51      sim_close_serial       close a serial port
52 
53 
54    The calling sequences are as follows:
55 
56 
57    SERHANDLE sim_open_serial (char *name)
58    --------------------------------------
59 
60    The serial port referenced by the OS-dependent "name" is opened.  If the open
61    is successful, and "name" refers to a serial port on the host system, then a
62    handle to the port is returned.  If not, then the value INVALID_HANDLE is
63    returned.
64 
65 
66    t_stat sim_config_serial (SERHANDLE port, SERCONFIG config)
67    -----------------------------------------------------------
68 
69    The baud rate and framing parameters (character size, parity, and number of
70    stop bits) of the serial port associated with "port" are set.  If any
71    "config" field value is unsupported by the host system, or if the combination
72    of values (e.g., baud rate and number of stop bits) is unsupported, SCPE_ARG
73    is returned.  If the configuration is successful, SCPE_OK is returned.  If
74    the configuration fails, SCPE_IOERR is returned.
75 
76 
77    t_stat sim_control_serial (SERHANDLE port, SERCIRCUIT control)
78    --------------------------------------------------------------
79 
80    The DTR and RTS control lines of the serial port associated with "port" are
81    asserted or denied as directed by the "control" parameter.  If the changes
82    are successful, the function returns SCPE_OK.  SCPE_IOERR is returned if an
83    error occurs.
84 
85 
86    SERCIRCUIT sim_status_serial (SERHANDLE port)
87    ---------------------------------------------
88 
89    The current DSR, CTS, DCD, and RI status line states of the serial port
90    associated with "port" are obtained and returned as the value of the
91    function.  If an error occurs, the "Error_Status" value is returned.
92 
93 
94    int32 sim_read_serial (SERHANDLE port, char *buffer, int32 count, char *brk)
95    ----------------------------------------------------------------------------
96 
97    A non-blocking read is issued for the serial port indicated by "port" to get
98    at most "count" bytes into the string "buffer".  If a serial line break was
99    detected during the read, the variable pointed to by "brk" is set to 1.  If
100    the read is successful, the actual number of characters read is returned.  If
101    no characters were available, then the value 0 is returned.  If an error
102    occurs, then the value -1 is returned.
103 
104 
105    int32 sim_write_serial (SERHANDLE port, char *buffer, int32 count)
106    ------------------------------------------------------------------
107 
108    A write is issued to the serial port indicated by "port" to put "count"
109    characters from "buffer".  If the write is successful, the actual number of
110    characters written is returned.  If an error occurs, then the value -1 is
111    returned.
112 
113 
114    void sim_close_serial (SERHANDLE port)
115    --------------------------------------
116 
117    The serial port indicated by "port" is closed.  Any errors are ignored.
118 */
119 
120 
121 
122 #include <ctype.h>
123 
124 #include "sim_defs.h"
125 #include "sim_serial.h"
126 
127 
128 
129 /* Console output.
130 
131    "cprintf" uses "(f)printf" to to write messages to the console and, if
132    console logging is enabled, to the log output stream.  "..." is the format
133    string and associated values.
134 */
135 
136 #define cprintf(...) \
137           do { \
138               printf (__VA_ARGS__); \
139               if (sim_log) \
140                   fprintf (sim_log, __VA_ARGS__); \
141               } \
142           while (0)
143 
144 
145 
146 /* Windows serial implementation */
147 
148 
149 #if defined (_WIN32)
150 
151 
152 /* Generic error message handler.
153 
154    This routine should be called for unexpected errors.  Some error returns may
155    be expected, e.g., a "file not found" error from an "open" routine.  These
156    should return appropriate status codes to the caller, allowing SCP to print
157    an error message if desired, rather than printing this generic error message.
158 
159 
160    Implementation notes:
161 
162     1. The returned message has a CR LF appended.  This causes problems when the
163        string is output via printf, as the text-mode translation doubles the CR.
164        We avoid this by truncating the message at the last control sequence
165 */
166 
sim_error_serial(const char * routine)167 static void sim_error_serial (const char *routine)
168 {
169 const DWORD error = GetLastError ();            /* get the last error code */
170 LPTSTR      message;
171 DWORD       length;
172 
173 length = FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM          /* get the system's error message */
174                           | FORMAT_MESSAGE_IGNORE_INSERTS   /*   corresponding to the error code */
175                           | FORMAT_MESSAGE_ALLOCATE_BUFFER, /*     into an allocated buffer */
176                         NULL, error, 0,                     /*       using the default language setting */
177                         (LPTSTR) &message, 0, NULL);
178 
179 if (length > 0) {                                       /* if the message was found */
180     while (iscntrl (message [--length]))                /*   then trim off the trailing CR LF */
181         message [length] = '\0';                        /*     that FormatMessage has appended */
182 
183     cprintf ("%s simulator serial I/O error %lu from %s:\n  %s\n",  /* report the error to the console */
184              sim_name, error, routine, message);
185 
186     LocalFree (message);                                /* free the allocated buffer */
187     }
188 
189 else                                                            /* otherwise the message was not found */
190     cprintf ("%s simulator serial I/O error %lu from %s.\n",    /*   so report the error code only */
191              sim_name, error, routine);
192 
193 return;
194 }
195 
196 
197 /* Open a serial port.
198 
199    The serial port designated by the host "name" is opened, and the handle to
200    the port is returned.  If an error occurs, INVALID_HANDLE is returned
201    instead.  After opening, the port is configured with the default
202    communication parameters established by the system, and the timeouts are set
203    for immediate return on a read request to enable polling.
204 
205 
206    Implementation notes:
207 
208     1. The "commsize" value cannot be "const" because the "GetDefaultCommConfig"
209        takes a variable parameter.
210 
211     2. We call "GetDefaultCommConfig" to obtain the default communication
212        parameters for the specified port.  If the name does not refer to a
213        communications port (serial or parallel), the function fails.
214 
215     3. There is no way to limit "CreateFile" just to serial ports, so we must
216        check after the port is opened.  The "GetCommState" routine will return
217        an error if the handle does not refer to a serial port.
218 
219     4. Calling "GetDefaultCommConfig" for a serial port returns a structure
220        containing a DCB.  This contains the default parameters.  However, some
221        of the DCB fields are not set correctly, so we cannot use this directly
222        in a call to "SetCommState".  Instead, we must copy the fields of
223        interest to a DCB retrieved from a call to "GetCommState".
224 */
225 
sim_open_serial(char * name)226 SERHANDLE sim_open_serial (char *name)
227 {
228 SERHANDLE    port;
229 DCB          dcb;
230 COMMCONFIG   commdefault;
231 COMMTIMEOUTS cto;
232 DWORD        error;
233 DWORD        commsize = sizeof commdefault;
234 
235 if (! GetDefaultCommConfig (name, &commdefault, &commsize)) {   /* get the default parameters; if the call failed */
236     error = GetLastError ();                                    /*   then get the error code */
237 
238     if (error != ERROR_INVALID_PARAMETER)                       /* if it's not a bad port name */
239         sim_error_serial ("GetDefaultCommConfig");              /*   then report the unexpected error */
240 
241     return INVALID_HANDLE;                                      /* return failure status */
242     }
243 
244 port = CreateFile (name, GENERIC_READ | GENERIC_WRITE,  /* open the port */
245                    0, NULL, OPEN_EXISTING, 0, 0);
246 
247 if (port == INVALID_HANDLE_VALUE) {                     /* if the open failed */
248     error = GetLastError ();                            /*   then get the error code */
249 
250     if (error != ERROR_FILE_NOT_FOUND                   /* if it's not a bad filename */
251       && error != ERROR_ACCESS_DENIED)                  /*   or it's already open */
252         sim_error_serial ("CreateFile");                /*     then report the unexpected error */
253 
254     return INVALID_HANDLE;                              /* return failure status */
255     }
256 
257 if (! GetCommState (port, &dcb)) {                      /* get the current parameters; if the call failed */
258     error = GetLastError ();                            /*   then get the error code */
259 
260     if (error != ERROR_INVALID_PARAMETER)               /* if it's something other than a bad port name */
261         sim_error_serial ("GetCommState");              /*   then report the unexpected error */
262 
263     CloseHandle (port);                                 /* close the port */
264     return INVALID_HANDLE;                              /*   and return failure status */
265     }
266 
267 dcb.BaudRate = commdefault.dcb.BaudRate;                /* copy the */
268 dcb.Parity   = commdefault.dcb.Parity;                  /*   default parameters */
269 dcb.ByteSize = commdefault.dcb.ByteSize;                /*    of interest */
270 dcb.StopBits = commdefault.dcb.StopBits;                /*      over the */
271 dcb.fOutX    = commdefault.dcb.fOutX;                   /*        current parameters */
272 dcb.fInX     = commdefault.dcb.fInX;
273 
274 dcb.fDtrControl = DTR_CONTROL_DISABLE;                  /* disable DTR and RTS initially */
275 dcb.fRtsControl = RTS_CONTROL_DISABLE;                  /*   until the poll connects */
276 
277 if (! SetCommState (port, &dcb)) {                      /* configure the port with default parameters; if it failed */
278     sim_error_serial ("SetCommState");                  /*   then report the unexpected error */
279 
280     CloseHandle (port);                                 /* close the port */
281     return INVALID_HANDLE;                              /*   and return failure status */
282     }
283 
284 cto.ReadIntervalTimeout         = MAXDWORD;             /* set the port to return immediately on read */
285 cto.ReadTotalTimeoutMultiplier  = 0;                    /*   i.e., to enable polling */
286 cto.ReadTotalTimeoutConstant    = 0;
287 cto.WriteTotalTimeoutMultiplier = 0;
288 cto.WriteTotalTimeoutConstant   = 0;
289 
290 if (! SetCommTimeouts (port, &cto)) {                   /* configure the port timeouts; if the call failed */
291     sim_error_serial ("SetCommTimeouts");               /*   then report the unexpected error */
292 
293     CloseHandle (port);                                 /* close the port */
294     return INVALID_HANDLE;                              /*   and return failure status */
295     }
296 
297 return port;                                            /* return the port handle on success */
298 }
299 
300 
301 /* Configure a serial port.
302 
303    Port parameters are configured as specified in the "config" structure.  If
304    "config" contains an invalid configuration value, or if the host system
305    rejects the configuration (e.g., by requesting an unsupported combination of
306    character size and stop bits), SCPE_ARG is returned to the caller.  If an
307    unexpected error occurs, SCPE_IOERR is returned.  If the configuration
308    succeeds, SCPE_OK is returned.
309 
310 
311    Implementation notes:
312 
313     1. We do not enable input parity checking, as the multiplexer library has no
314        way of communicating parity errors back to the target simulator.
315 
316     2. A zero value for the "stopbits" field of the "config" structure implies
317        1.5 stop bits.
318 */
319 
sim_config_serial(SERHANDLE port,SERCONFIG config)320 t_stat sim_config_serial (SERHANDLE port, SERCONFIG config)
321 {
322 DCB    dcb;
323 DWORD  error;
324 uint32 i;
325 
326 static const struct {
327     char parity;
328     BYTE parity_code;
329     } parity_map [] =
330         { { 'E', EVENPARITY }, { 'M', MARKPARITY  }, { 'N', NOPARITY },
331           { 'O', ODDPARITY  }, { 'S', SPACEPARITY } };
332 
333 static const uint32 parity_count = (uint32) (sizeof parity_map / sizeof parity_map [0]);
334 
335 if (! GetCommState (port, &dcb)) {                      /* get the current comm parameters; if the call failed */
336     sim_error_serial ("GetCommState");                  /*   then report the unexpected error */
337     return SCPE_IOERR;                                  /*     and return failure status */
338     }
339 
340 dcb.BaudRate = config.baudrate;                         /* assign the baud rate */
341 
342 if (config.charsize >= 5 && config.charsize <= 8)       /* if the character size is within range */
343     dcb.ByteSize = (BYTE) config.charsize;              /*   then assign the character size */
344 else                                                    /* otherwise */
345     return SCPE_ARG;                                    /*   report that the value is not a valid size */
346 
347 for (i = 0; i < parity_count; i++)                      /* loop through the parity map */
348     if (config.parity == parity_map [i].parity) {       /* if the requested parity matches a map entry */
349         dcb.Parity = parity_map [i].parity_code;        /*   then assign the corresponding code */
350         break;
351         }
352 
353 if (i == parity_count)                                  /* if the requested parity did not match */
354     return SCPE_ARG;                                    /*   then report that it is not a valid parity specifier */
355 
356 if (config.stopbits == 1)                               /* if one stop bit is requested */
357     dcb.StopBits = ONESTOPBIT;                          /*   then set the configuration value */
358 else if (config.stopbits == 2)                          /* otherwise if two stop bits are requested */
359     dcb.StopBits = TWOSTOPBITS;                         /*   then set the configuration value */
360 else if (config.stopbits == 0)                          /* otherwise if 1.5 stop bits are requested */
361     dcb.StopBits = ONE5STOPBITS;                        /*   then set the configuration value */
362 else                                                    /* otherwise */
363     return SCPE_ARG;                                    /*   report that the value not a valid number of stop bits */
364 
365 if (! SetCommState (port, &dcb)) {                      /* set the configuration; if the call failed */
366     error = GetLastError ();                            /*   then get the error code */
367 
368     if (error == ERROR_INVALID_PARAMETER)               /* if the configuration is invalid */
369         return SCPE_ARG;                                /*   then report an argument error */
370 
371     else {                                              /* otherwise */
372         sim_error_serial ("SetCommState");              /*   report the unexpected error */
373         return SCPE_IOERR;                              /*     and return failure status */
374         }
375     }
376 
377 return SCPE_OK;                                         /* return success status */
378 }
379 
380 
381 /* Control a serial port.
382 
383    The DTR and RTS control lines of the serial port associated with "port" are
384    asserted or denied as directed by the "control" parameter.  If the changes
385    are successful, the function returns SCPE_OK.  SCPE_IOERR is returned if an
386    error occurs.
387 */
388 
sim_control_serial(SERHANDLE port,SERCIRCUIT control)389 t_stat sim_control_serial (SERHANDLE port, SERCIRCUIT control)
390 {
391 DWORD DTR_function, RTS_function;
392 
393 if (control & DTR_Control)                              /* if DTR assertion is requested */
394     DTR_function = SETDTR;                              /*   then set the configuration value */
395 else                                                    /* otherwise */
396     DTR_function = CLRDTR;                              /*   clear the configuration value */
397 
398 if (control & RTS_Control)                              /* if RTS assertion is requested */
399     RTS_function = SETRTS;                              /*   then set the configuration value */
400 else                                                    /* otherwise */
401     RTS_function = CLRRTS;                              /*   clear the configuration value */
402 
403 if (! EscapeCommFunction (port, DTR_function)) {        /* configure the DTR line; if the call failed */
404     sim_error_serial ("EscapeCommFunction DTR");        /*   then report the unexpected error */
405     return SCPE_IOERR;                                  /*     and return I/O error status */
406     }
407 
408 else if (! EscapeCommFunction (port, RTS_function)) {   /* otherwise configure the RTS line; if the call failed */
409     sim_error_serial ("EscapeCommFunction RTS");        /*   then report the unexpected error */
410     return SCPE_IOERR;                                  /*     and return I/O error status */
411     }
412 
413 else                                                    /* otherwise both calls succeeded */
414     return SCPE_OK;                                     /*   so return success status */
415 }
416 
417 
418 /* Get the current status from a serial port.
419 
420    The current DSR, CTS, DCD, and RI status line states of the serial port
421    associated with "port" are obtained and returned as the value of the
422    function.  If an error occurs, the "Error_Status" value is returned.
423 */
424 
sim_status_serial(SERHANDLE port)425 SERCIRCUIT sim_status_serial (SERHANDLE port)
426 {
427 DWORD      state;
428 SERCIRCUIT status = No_Signals;
429 
430 if (! GetCommModemStatus (port, &state)) {              /* get the serial line state; if the call failed */
431     sim_error_serial ("GetCommModemStatus");            /*   then report the unexpected error */
432     return Error_Status;                                /*     and return the error status */
433     }
434 
435 else {                                                  /* otherwise */
436     if (state & MS_DSR_ON)                              /*   if the DSR line is asserted */
437         status |= DSR_Status;                           /*     then include DSR status */
438 
439     if (state & MS_CTS_ON)                              /* if the CTS line is asserted */
440         status |= CTS_Status;                           /*   then include DSR status */
441 
442     if (state & MS_RLSD_ON)                             /* if the DCD line is asserted */
443         status |= DCD_Status;                           /*   then include DCD status */
444 
445     if (state & MS_RING_ON)                             /* if the RI line is asserted */
446         status |= RI_Status;                            /*   then include RI status */
447 
448     return status;                                      /* return the combined status set */
449     }
450 }
451 
452 
453 /* Read from a serial port.
454 
455    The port is checked for available characters.  If any are present, they are
456    copied to the passed buffer, and the count of characters is returned.  If no
457    characters are available, 0 is returned.  If an error occurs, -1 is returned.
458    If a BREAK is detected on the communications line, the corresponding flag in
459    the "brk" array is set.
460 
461 
462    Implementation notes:
463 
464     1. The "ClearCommError" function will set the CE_BREAK flag in the returned
465        errors value if a BREAK has occurred.  However, we do not know where in
466        the serial stream it happened, as CE_BREAK isn't associated with a
467        specific character.  Because the "brk" array does want a flag associated
468        with a specific character, we guess at the proper location by setting
469        the "brk" entry corresponding to the first NUL in the character stream.
470        If no NUL is present, then the "brk" entry associated with the first
471        character is set as our "best guess."
472 */
473 
sim_read_serial(SERHANDLE port,char * buffer,int32 count,char * brk)474 int32 sim_read_serial (SERHANDLE port, char *buffer, int32 count, char *brk)
475 {
476 DWORD   read;
477 DWORD   commerrors;
478 COMSTAT cs;
479 char    *bptr;
480 
481 if (! ClearCommError (port, &commerrors, &cs)) {        /* get the comm error flags; if the call failed  */
482     sim_error_serial ("ClearCommError");                /*   then report the unexpected error */
483     return -1;                                          /*     and return failure status */
484     }
485 
486 if (! ReadFile (port, (LPVOID) buffer,                  /* read any available characters; if the call failed */
487                 (DWORD) count, &read, NULL)) {
488     sim_error_serial ("ReadFile");                      /*   then report the unexpected error */
489     return -1;                                          /*     and return failure status */
490     }
491 
492 if (commerrors & CE_BREAK) {                            /* if a BREAK was detected */
493     bptr = (char *) memchr (buffer, 0, read);           /*   then search for the first NUL in the buffer */
494 
495     if (bptr != NULL)                                   /* if a NUL was found */
496         brk = brk + (bptr - buffer);                    /*   then calculate its position */
497 
498     *brk = 1;                                           /* set the BREAK flag in the caller's array */
499     }
500 
501 return read;                                            /* return the number of characters read */
502 }
503 
504 
505 /* Write to a serial port.
506 
507    "Count" characters are written from "buffer" to the serial port.  The actual
508    number of characters written to the port is returned.  If an error occurred
509    on writing, -1 is returned.
510 */
511 
sim_write_serial(SERHANDLE port,char * buffer,int32 count)512 int32 sim_write_serial (SERHANDLE port, char *buffer, int32 count)
513 {
514 DWORD written;
515 
516 if (! WriteFile (port, (LPVOID) buffer,                 /* write the buffer to the serial port; if the call failed */
517                 (DWORD) count, &written, NULL)) {
518     sim_error_serial ("WriteFile");                     /*   then report the unexpected error */
519     return -1;                                          /*     and return failure status */
520     }
521 
522 else                                                    /* otherwise */
523     return written;                                     /*   return the number of characters written */
524 }
525 
526 
527 /* Close a serial port.
528 
529    The serial port is closed.  Errors are ignored.
530 */
531 
sim_close_serial(SERHANDLE port)532 void sim_close_serial (SERHANDLE port)
533 {
534 CloseHandle (port);                                     /* close the port */
535 return;
536 }
537 
538 
539 
540 /* UNIX implementation */
541 
542 
543 #elif defined (__unix__) || defined (__APPLE__) && defined (__MACH__)
544 
545 
546 /* Generic error message handler.
547 
548    This routine should be called for unexpected errors.  Some error returns may
549    be expected, e.g., a "file not found" error from an "open" routine.  These
550    should return appropriate status codes to the caller, allowing SCP to print
551    an error message if desired, rather than printing this generic error message.
552 */
553 
sim_error_serial(const char * routine)554 static void sim_error_serial (const char *routine)
555 {
556 cprintf ("%s simulator serial I/O error %d from %s:\n  %s.\n", /* report the error to the console */
557          sim_name, errno, routine, strerror (errno));
558 
559 return;
560 }
561 
562 
563 /* Open a serial port.
564 
565    The serial port designated by "name" is opened, and the handle to the port is
566    returned.  If an error occurs, INVALID_HANDLE is returned instead.  After
567    opening, the port is configured to "raw" mode.
568 
569 
570    Implementation notes:
571 
572     1. We use a non-blocking open to allow for polling during reads.
573 
574     2. There is no way to limit "open" just to serial ports, so we must check
575        after the port is opened.  We do this with a combination of "isatty" and
576        "tcgetattr".
577 
578     3. We configure with PARMRK set and IGNBRK and BRKINT cleared.  This will
579        mark a communication line BREAK condition in the input stream with the
580        three-character sequence \377 \000 \000.  This is detected during
581        reading.
582 
583     4. POSIX.1-2001 does not specify whether the setting of O_NONBLOCK takes
584        precedence over MIN or TIME settings.  So we configure the latter to
585        return immediately as well, using Case D as described in the
586        "Non-Canonical Mode Input Processing" section of the Single UNIX
587        Specification version 3, which says: "Case D: MIN=0, TIME=0 The minimum
588        of either the number of bytes requested or the number of bytes currently
589        available shall be returned without waiting for more bytes to be input.
590        If no characters are available, read() shall return a value of zero,
591        having read no data."
592 */
593 
sim_open_serial(char * name)594 SERHANDLE sim_open_serial (char *name)
595 {
596 SERHANDLE port;
597 struct termios tio;
598 
599 static const tcflag_t i_clear =                 /* clear these input modes */
600     IGNBRK |                                    /*   ignore BREAK */
601     BRKINT |                                    /*   signal on BREAK */
602     INPCK  |                                    /*   enable parity checking */
603     ISTRIP |                                    /*   strip character to 7 bits */
604     INLCR  |                                    /*   map NL to CR */
605     IGNCR  |                                    /*   ignore CR */
606     ICRNL  |                                    /*   map CR to NL */
607     IXON   |                                    /*   enable XON/XOFF output control */
608     IXOFF;                                      /*   enable XON/XOFF input control */
609 
610 static const tcflag_t i_set =                   /* set these input modes */
611     PARMRK |                                    /*   mark parity errors and line breaks */
612     IGNPAR;                                     /*   ignore parity errors */
613 
614 static const tcflag_t o_clear =                 /* clear these output modes */
615     OPOST;                                      /*   post-process output */
616 
617 static const tcflag_t o_set = 0;                /* set these output modes (none) */
618 
619 static const tcflag_t c_clear =                 /* clear these control modes */
620     HUPCL;                                      /*   hang up line on last close */
621 
622 static const tcflag_t c_set =                   /* set these control modes */
623     CREAD |                                     /*   enable receiver */
624     CLOCAL;                                     /*   ignore modem status lines */
625 
626 static const tcflag_t l_clear =                 /* clear these local modes */
627     ISIG    |                                   /*   enable signals */
628     ICANON  |                                   /*   canonical input */
629     ECHO    |                                   /*   echo characters */
630     ECHOE   |                                   /*   echo ERASE as an error correcting backspace */
631     ECHOK   |                                   /*   echo KILL */
632     ECHONL  |                                   /*   echo NL */
633     NOFLSH  |                                   /*   disable flush after interrupt */
634     TOSTOP  |                                   /*   send SIGTTOU for background output */
635     IEXTEN;                                     /*   enable extended functions */
636 
637 static const tcflag_t l_set = 0;                /* set these local modes (none) */
638 
639 
640 port = open (name, O_RDWR | O_NOCTTY | O_NONBLOCK);     /* open the port */
641 
642 if (port == -1) {                                       /* if the open failed */
643     if (errno != ENOENT && errno != EACCES)             /*   then if it's not file not found or can't open */
644         sim_error_serial ("open");                      /*     then report the unexpected error */
645 
646     return INVALID_HANDLE;                              /* return failure status */
647     }
648 
649 if (! isatty (port)) {                                  /* if the device is not a TTY */
650     close (port);                                       /*   then close the port */
651     return INVALID_HANDLE;                              /*     and return failure status */
652     }
653 
654 if (tcgetattr (port, &tio)) {                           /* get the terminal attributes; if the call failed */
655     sim_error_serial ("tcgetattr");                     /*   then report the unexpected error */
656 
657     close (port);                                       /* close the port */
658     return INVALID_HANDLE;                              /*   and return failure status */
659     }
660 
661 tio.c_iflag = tio.c_iflag & ~i_clear | i_set;           /* reconfigure the serial line */
662 tio.c_oflag = tio.c_oflag & ~o_clear | o_set;           /*   for non-canonical */
663 tio.c_cflag = tio.c_cflag & ~c_clear | c_set;           /*     input mode */
664 tio.c_lflag = tio.c_lflag & ~l_clear | l_set;
665 
666 #if defined (VMIN) && defined (VTIME)
667 
668 tio.c_cc [VMIN]  = 0;                                   /* read returns zero if there is no data */
669 tio.c_cc [VTIME] = 0;                                   /*   with no timeout */
670 
671 #endif
672 
673 if (tcsetattr (port, TCSANOW, &tio)) {                  /* set the terminal attributes; if the call failed */
674     sim_error_serial ("tcsetattr");                     /*   then report the unexpected error */
675 
676     close (port);                                       /* close the port */
677     return INVALID_HANDLE;                              /*   and return failure status */
678     }
679 
680 return port;                                            /* return the port handle on success */
681 }
682 
683 
684 /* Configure a serial port.
685 
686    Port parameters are configured as specified in the "config" structure.  If
687    "config" contains an invalid configuration value, or if the host system
688    rejects the configuration (e.g., by requesting an unsupported combination of
689    character size and stop bits), SCPE_ARG is returned to the caller.  If an
690    unexpected error occurs, SCPE_IOERR is returned.  If the configuration
691    succeeds, SCPE_OK is returned.
692 
693 
694    Implementation notes:
695 
696     1. 1.5 stop bits is not a supported configuration.
697 */
698 
sim_config_serial(SERHANDLE port,SERCONFIG config)699 t_stat sim_config_serial (SERHANDLE port, SERCONFIG config)
700 {
701 struct termios tio;
702 uint32 i;
703 
704 static const struct {
705     uint32  rate;
706     speed_t rate_code;
707     } baud_map [] =
708         { { 50,     B50     }, { 75,     B75     }, { 110,    B110    }, {  134,   B134   },
709           { 150,    B150    }, { 200,    B200    }, { 300,    B300    }, {  600,   B600   },
710           { 1200,   B1200   }, { 1800,   B1800   }, { 2400,   B2400   }, {  4800,  B4800  },
711           { 9600,   B9600   }, { 19200,  B19200  }, { 38400,  B38400  }, {  57600, B57600 },
712           { 115200, B115200 } };
713 
714 static const uint32 baud_count = (uint32) (sizeof baud_map / sizeof baud_map [0]);
715 
716 static const tcflag_t charsize_map [4] = { CS5, CS6, CS7, CS8 };
717 
718 
719 if (tcgetattr (port, &tio)) {                           /* get the current configuration; if the call failed */
720     sim_error_serial ("tcgetattr");                     /*   then report the unexpected error */
721     return SCPE_IOERR;                                  /*     and return I/O error status */
722     }
723 
724 for (i = 0; i < baud_count; i++)                        /* loop through the baud rate map */
725     if (config.baudrate == baud_map [i].rate) {         /* if the requested rate matches a map entry */
726         cfsetispeed (&tio, baud_map [i].rate_code);     /*   then set the input rate */
727         cfsetospeed (&tio, baud_map [i].rate_code);     /*     and the output rate */
728         break;
729         }
730 
731 if (i == baud_count)                                    /* if the baud rate was not assigned */
732     return SCPE_ARG;                                    /*   then return bad argument status */
733 
734 if (config.charsize >= 5 && config.charsize <= 8)       /* if the character size value is valid */
735     tio.c_cflag = tio.c_cflag & ~CSIZE                  /*   then replace the character size code */
736                     | charsize_map [config.charsize - 5];
737 else                                                    /* otherwise */
738     return SCPE_ARG;                                    /*   return "not a valid size" status */
739 
740 switch (config.parity) {                                /* assign the requested parity */
741     case 'E':
742         tio.c_cflag = tio.c_cflag & ~PARODD | PARENB;   /* set for even parity */
743         break;
744 
745     case 'N':
746         tio.c_cflag = tio.c_cflag & ~PARENB;            /* set for no parity */
747         break;
748 
749     case 'O':
750         tio.c_cflag = tio.c_cflag | PARODD | PARENB;    /* set for odd parity */
751         break;
752 
753     default:
754         return SCPE_ARG;                                /*  return "not a valid parity specifier" status */
755     }
756 
757 if (config.stopbits == 1)                               /* if one stop bit is requested */
758     tio.c_cflag = tio.c_cflag & ~CSTOPB;                /*   then clear the two-stop-bits flag */
759 else if (config.stopbits == 2)                          /* otherwise if two stop bits are requested */
760     tio.c_cflag = tio.c_cflag | CSTOPB;                 /*   then set the two-stop-bits flag */
761 else                                                    /* otherwise */
762     return SCPE_ARG;                                    /*   return "not a valid number of stop bits" status */
763 
764 if (tcsetattr (port, TCSAFLUSH, &tio)) {                /* set the new configuration; if the call failed */
765     sim_error_serial ("tcsetattr");                     /*   then report the unexpected error */
766     return SCPE_IERR;                                   /*     and return failure status */
767     }
768 
769 return SCPE_OK;                                         /* the configuration was set successfully */
770 }
771 
772 
773 /* Control a serial port.
774 
775    The DTR and RTS control lines of the serial port associated with "port" are
776    asserted or denied as directed by the "control" parameter.  If the changes
777    are successful, the function returns SCPE_OK.  SCPE_IOERR is returned if an
778    error occurs.
779 */
780 
sim_control_serial(SERHANDLE port,SERCIRCUIT control)781 t_stat sim_control_serial (SERHANDLE port, SERCIRCUIT control)
782 {
783 int state;
784 
785 if (ioctl (port, TIOCMGET, &state)) {                   /* get the current modem line state; if the call failed */
786     if (errno != EINVAL)                                /*   then if the error is not "control not supported" */
787         sim_error_serial ("ioctl TIOCMGET");            /*     then report the unexpected error */
788 
789     return SCPE_IOERR;                                  /* return failure status */
790     }
791 
792 if (control & DTR_Control)                              /* if DTR assertion is requested */
793     state |= TIOCM_DTR;                                 /*   then set the configuration value */
794 else                                                    /* otherwise */
795     state &= ~TIOCM_DTR;                                /*   clear the configuration value */
796 
797 if (control & RTS_Control)                              /* if RTS assertion is requested */
798     state |= TIOCM_RTS;                                 /*   then set the configuration value */
799 else                                                    /* otherwise */
800     state &= ~TIOCM_RTS;                                /*   clear the configuration value */
801 
802 if (ioctl (port, TIOCMSET, &state)) {                   /* set the new line state; if the call failed */
803     if (errno != EINVAL)                                /*   then if the error is not "control not supported" */
804         sim_error_serial ("ioctl TIOCMSET");            /*     then report the unexpected error */
805 
806     return SCPE_IOERR;                                  /* return failure status */
807     }
808 
809 else                                                    /* otherwise both calls succeeded */
810     return SCPE_OK;                                     /*   so return success status */
811 }
812 
813 
814 /* Get the current status from a serial port.
815 
816    The current DSR, CTS, DCD, and RI status line states of the serial port
817    associated with "port" are obtained and returned as the value of the
818    function.  If an error occurs, the "Error_Status" value is returned.
819 */
820 
sim_status_serial(SERHANDLE port)821 SERCIRCUIT sim_status_serial (SERHANDLE port)
822 {
823 int        state;
824 SERCIRCUIT status = No_Signals;
825 
826 if (ioctl (port, TIOCMGET, &state)) {                   /* get the serial line state; if the call failed */
827     if (errno != EINVAL)                                /*   then if it's not "unsupported call" */
828         sim_error_serial ("ioctl TIOCMGET");            /*     then report the unexpected error */
829 
830     return Error_Status;                                /* return the error status */
831     }
832 
833 else {                                                  /* otherwise */
834     if (state & TIOCM_DSR)                              /*   if the DSR line is asserted */
835         status |= DSR_Status;                           /*     then include DSR status */
836 
837     if (state & TIOCM_CTS)                              /* if the CTS line is asserted */
838         status |= CTS_Status;                           /*   then include DSR status */
839 
840     if (state & TIOCM_CD)                               /* if the DCD line is asserted */
841         status |= DCD_Status;                           /*   then include DCD status */
842 
843     if (state & TIOCM_RI)                               /* if the RI line is asserted */
844         status |= RI_Status;                            /*   then include RI status */
845 
846     return status;                                      /* return the combined status set */
847     }
848 }
849 
850 
851 /* Read from a serial port.
852 
853    The port is checked for available characters.  If any are present, they are
854    copied to the passed buffer, and the count of characters is returned.  If no
855    characters are available, 0 is returned.  If an error occurs, -1 is returned.
856    If a BREAK is detected on the communications line, the corresponding flag in
857    the "brk" array is set.
858 
859 
860    Implementation notes:
861 
862     1. A character with a framing or parity error is indicated in the input
863        stream by the three-character sequence \377 \000 \ccc, where "ccc" is the
864        bad character.  A communications line BREAK is indicated by the sequence
865        \377 \000 \000.  A received \377 character is indicated by the
866        two-character sequence \377 \377.  If we find any of these sequences,
867        they are replaced by the single intended character by sliding the
868        succeeding characters backward by one or two positions.  If a BREAK
869        sequence was encountered, the corresponding location in the "brk" array
870        is determined, and the flag is set.  Note that there may be multiple
871        sequences in the buffer.
872 */
873 
sim_read_serial(SERHANDLE port,char * buffer,int32 count,char * brk)874 int32 sim_read_serial (SERHANDLE port, char *buffer, int32 count, char *brk)
875 {
876 int   read_count;
877 char  *bptr, *cptr;
878 int32 remaining;
879 
880 read_count = read (port, (void *) buffer, (size_t) count);  /* read from the serial port */
881 
882 if (read_count == -1)                                   /* if the call failed */
883     if (errno == EAGAIN)                                /*   then if no characters are available */
884         return 0;                                       /*     then report no characters were returned */
885     else                                                /*   otherwise */
886         sim_error_serial ("read");                      /*     report the unexpected error */
887 
888 else {                                                  /* otherwise the read succeeded */
889     cptr = buffer;                                      /*   so point at the start of the buffer */
890     remaining = read_count - 1;                         /*     and stop the search one character from the end */
891 
892     while (remaining > 0                                /* search for error sequences */
893       && (bptr = memchr (cptr, '\377', remaining))) {   /*   starting with a PARMRK sequence */
894         remaining = remaining - (bptr - cptr) - 1;      /* found one; calculate the count of characters remaining */
895 
896         if (*(bptr + 1) == '\377') {                    /* if this is a \377 \377 sequence */
897             memmove (bptr + 1, bptr + 2, remaining);    /*   then slide the string backward to leave one \377 */
898             remaining = remaining - 1;                  /* drop the remaining count */
899             read_count = read_count - 1;                /*   and the read count by the character eliminated */
900             }
901 
902         else if (remaining > 0 && *(bptr + 1) == '\0') {    /* otherwise if this is a \377 \000 \ccc sequence */
903             memmove (bptr, bptr + 2, remaining);            /*   then slide the string backward to leave \ccc */
904             remaining = remaining - 2;                      /* drop the remaining count */
905             read_count = read_count - 2;                    /*   and the read count by the characters eliminated */
906 
907             if (*bptr == '\0')                          /* if this is a BREAK sequence */
908                 *(brk + (bptr - buffer)) = 1;           /*   then set the corresponding BREAK flag */
909             }
910 
911         cptr = bptr + 1;                                /* point at the remainder of the string */
912         }                                               /*   and loop until the entire string is searched */
913     }
914 
915 return (int32) read_count;                              /* return the number of characters read */
916 }
917 
918 
919 /* Write to a serial port.
920 
921    "Count" characters are written from "buffer" to the serial port.  The actual
922    number of characters written to the port is returned.  If an error occurred
923    on writing, -1 is returned.
924 */
925 
sim_write_serial(SERHANDLE port,char * buffer,int32 count)926 int32 sim_write_serial (SERHANDLE port, char *buffer, int32 count)
927 {
928 int written;
929 
930 written = write (port, (void *) buffer, (size_t) count);    /* write the buffer to the serial port */
931 
932 if (written == -1)                                      /* if an error occurred */
933     if (errno == EAGAIN)                                /*   then if the write should be tried again */
934         return 0;                                       /*     then return 0 bytes written */
935     else                                                /*   otherwise */
936         sim_error_serial ("write");                     /*     report an unexpected error */
937 
938 return (int32) written;                                 /* return number of characters written */
939 }
940 
941 
942 /* Close a serial port.
943 
944    The serial port is closed.  Errors are ignored.
945 */
946 
sim_close_serial(SERHANDLE port)947 void sim_close_serial (SERHANDLE port)
948 {
949 close (port);                                           /* close the port */
950 return;
951 }
952 
953 
954 
955 /* Non-implemented stubs */
956 
957 #else
958 
959 
960 /* Open a serial port */
961 
sim_open_serial(char * name)962 SERHANDLE sim_open_serial (char *name)
963 {
964 return INVALID_HANDLE;
965 }
966 
967 
968 /* Configure a serial port */
969 
sim_config_serial(SERHANDLE port,SERCONFIG config)970 t_stat sim_config_serial (SERHANDLE port, SERCONFIG config)
971 {
972 return SCPE_IERR;
973 }
974 
975 
976 /* Control a serial port */
977 
sim_control_serial(SERHANDLE port,SERCIRCUIT control)978 t_stat sim_control_serial (SERHANDLE port, SERCIRCUIT control)
979 {
980 return SCPE_IERR;
981 }
982 
983 
984 /* Get the current status from a serial port */
985 
sim_status_serial(SERHANDLE port)986 SERCIRCUIT sim_status_serial (SERHANDLE port)
987 {
988 return Error_Status;
989 }
990 
991 
992 /* Read from a serial port */
993 
sim_read_serial(SERHANDLE port,char * buffer,int32 count,char * brk)994 int32 sim_read_serial (SERHANDLE port, char *buffer, int32 count, char *brk)
995 {
996 return -1;
997 }
998 
999 
1000 /* Write to a serial port */
1001 
sim_write_serial(SERHANDLE port,char * buffer,int32 count)1002 int32 sim_write_serial (SERHANDLE port, char *buffer, int32 count)
1003 {
1004 return -1;
1005 }
1006 
1007 
1008 /* Close a serial port */
1009 
sim_close_serial(SERHANDLE port)1010 void sim_close_serial (SERHANDLE port)
1011 {
1012 return;
1013 }
1014 
1015 
1016 #endif                                                  /* end else unimplemented */
1017