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