1 /** \file   rs232-win32-dev.c
2  * \brief   RS232 Device emulation
3  *
4  * \author  Spiro Trikaliotis <spiro.trikaliotis@gmx.de>
5  * \author  groepaz <groepaz@gmx.net>
6  * \author  Greg King <gregdk@users.sf.net>
7  *
8  * The RS232 emulation captures the bytes sent to the RS232 interfaces
9  * available (currently, ACIA 6551, std. user port,
10  * and Daniel Dallmann's fast RS232 with 9600 BPS).
11  *
12  * I/O is done usually to a physical COM port.
13  */
14 
15 /*
16  * This file is part of VICE, the Versatile Commodore Emulator.
17  * See README for copyright notice.
18  *
19  *  This program is free software; you can redistribute it and/or modify
20  *  it under the terms of the GNU General Public License as published by
21  *  the Free Software Foundation; either version 2 of the License, or
22  *  (at your option) any later version.
23  *
24  *  This program is distributed in the hope that it will be useful,
25  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
26  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27  *  GNU General Public License for more details.
28  *
29  *  You should have received a copy of the GNU General Public License
30  *  along with this program; if not, write to the Free Software
31  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
32  *  02111-1307  USA.
33  */
34 
35 #include "vice.h"
36 
37 #include <errno.h>
38 #include <stdint.h>
39 #include <string.h>
40 #include <winsock.h>
41 
42 #ifdef HAVE_IO_H
43 #include <io.h>
44 #endif
45 
46 #include "cmdline.h"
47 #include "coproc.h"
48 #include "log.h"
49 #include "resources.h"
50 #include "rs232.h"
51 #include "rs232dev.h"
52 #include "types.h"
53 #include "util.h"
54 
55 /* #define COPROC_SUPPORT */
56 
57 #define DEBUGRS232
58 
59 #ifdef DEBUGRS232
60 # define DEBUG_LOG_MESSAGE(_xxx) log_message _xxx
61 #else
62 # define DEBUG_LOG_MESSAGE(_xxx)
63 #endif
64 
65 /* ------------------------------------------------------------------------- */
66 
67 /* resource handling */
68 
69 static int devbaud[RS232_NUM_DEVICES];
70 
is_baud_valid(int baud)71 static int is_baud_valid(int baud)
72 {
73     switch (baud) {
74         case     110:
75         case     300:
76         case     600:
77         case    1200:
78         case    2400:
79         case    4800:
80         case    9600:
81         case   19200:
82         case   38400:
83         case   57600:
84         case  115200:
85         case  128000:
86         case  256000:
87 #if 0
88         case  500000:
89         case 1000000:
90 #endif
91                 return 1;
92             break;
93         default:
94             break;
95     }
96     return 0;
97 }
98 
set_devbaud(int val,void * param)99 static int set_devbaud(int val, void *param)
100 {
101     if (is_baud_valid(val)) {
102         devbaud[vice_ptr_to_int(param)] = val;
103     }
104     return 0;
105 }
106 
get_devbaud(int dev)107 static int get_devbaud(int dev)
108 {
109     switch (devbaud[dev]) {
110         case     110: return CBR_110;
111         case     300: return CBR_300;
112         case     600: return CBR_600;
113         case    1200: return CBR_1200;
114         case    2400: return CBR_2400;
115         case    4800: return CBR_4800;
116         case    9600: return CBR_9600;
117         case   19200: return CBR_19200;
118         case   38400: return CBR_38400;
119         case   57600: return CBR_57600;
120         case  115200: return CBR_115200;
121         case  128000: return CBR_128000;
122         case  256000: return CBR_256000;
123 #if 0
124         case  500000: return CBR_500000;
125         case 1000000: return CBR_1000000;
126 #endif
127     }
128 
129     return 0; /* invalid / not set */
130 }
131 
132 /* ------------------------------------------------------------------------- */
133 
134 static const resource_int_t resources_int[] = {
135     { "RsDevice1Baud", 2400, RES_EVENT_NO, NULL,
136       &devbaud[0], set_devbaud, (void *)0 },
137     { "RsDevice2Baud", 38400, RES_EVENT_NO, NULL,
138       &devbaud[1], set_devbaud, (void *)1 },
139     { "RsDevice3Baud", 2400, RES_EVENT_NO, NULL,
140       &devbaud[2], set_devbaud, (void *)2 },
141     { "RsDevice4Baud", 38400, RES_EVENT_NO, NULL,
142       &devbaud[3], set_devbaud, (void *)3 },
143     RESOURCE_INT_LIST_END
144 };
145 
rs232dev_resources_init(void)146 int rs232dev_resources_init(void)
147 {
148     return resources_register_int(resources_int);
149 }
150 
rs232dev_resources_shutdown(void)151 void rs232dev_resources_shutdown(void)
152 {
153 }
154 
155 static const cmdline_option_t cmdline_options[] =
156 {
157     { "-rsdev1baud", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS | CMDLINE_ATTRIB_NEED_BRACKETS,
158       NULL, NULL, "RsDevice1Baud", NULL,
159       "<baudrate>", "Specify baudrate of first RS232 device" },
160     { "-rsdev2baud", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS | CMDLINE_ATTRIB_NEED_BRACKETS,
161       NULL, NULL, "RsDevice2Baud", NULL,
162       "<baudrate>", "Specify baudrate of second RS232 device" },
163     { "-rsdev3baud", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS | CMDLINE_ATTRIB_NEED_BRACKETS,
164       NULL, NULL, "RsDevice3Baud", NULL,
165       "<baudrate>", "Specify baudrate of third RS232 device" },
166     { "-rsdev4baud", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS | CMDLINE_ATTRIB_NEED_BRACKETS,
167       NULL, NULL, "RsDevice4Baud", NULL,
168       "<baudrate>", "Specify baudrate of fourth RS232 device" },
169     CMDLINE_LIST_END
170 };
171 
rs232dev_cmdline_options_init(void)172 int rs232dev_cmdline_options_init(void)
173 {
174     return cmdline_register_options(cmdline_options);
175 }
176 
177 /* ------------------------------------------------------------------------- */
178 
179 /** \brief  descriptor for a rs232 interface/port
180  */
181 typedef struct rs232dev {
182     int inuse;          /**< flag to mark descriptor in-use */
183     int type;           /**< type of connection, T_TTY, T_FILE, T_PROC */
184     HANDLE fd;          /**< Windows file handle for the open RS232 port */
185     HANDLE fd_r;        /**< stdout read pipe from external process */
186     HANDLE fd_w;        /**< stdin write pipe from external process */
187     /* char *file; */
188     DCB restore_dcb;    /**< status of the serial port before using it, for later restoration */
189     int rts;            /**< current status of the serial port's RTS line */
190     int dtr;            /**< current status of the serial port's DTR line */
191 } rs232dev_t;
192 
193 /* type of open connection */
194 #define T_TTY  0
195 #define T_PROC 1
196 #define T_FILE 2
197 
198 static rs232dev_t fds[RS232_NUM_DEVICES];
199 
200 static log_t rs232dev_log = LOG_ERR;
201 
202 /* ------------------------------------------------------------------------- */
203 
204 void rs232dev_close(int fd);
205 
206 /* initializes all RS232 stuff */
rs232dev_init(void)207 void rs232dev_init(void)
208 {
209     rs232dev_log = log_open("RS232dev");
210 }
211 
212 /* reset RS232 stuff */
rs232dev_reset(void)213 void rs232dev_reset(void)
214 {
215     int i;
216 
217     for (i = 0; i < RS232_NUM_DEVICES; i++) {
218         if (fds[i].inuse) {
219             rs232dev_close(i);
220         }
221     }
222 }
223 
224 /* opens a rs232 window, returns handle to give to functions below. */
rs232dev_open(int device)225 int rs232dev_open(int device)
226 {
227     HANDLE serial_port = INVALID_HANDLE_VALUE;
228     int ret = -1;
229     int i;
230 
231     for (i = 0; i < RS232_NUM_DEVICES; i++) {
232         if (!fds[i].inuse) {
233             break;
234         }
235     }
236     if (i >= RS232_NUM_DEVICES) {
237         log_error(rs232dev_log, "rs232dev_open(): No more devices available.");
238         return -1;
239     }
240 
241     DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_open(device %d), use fds[%d].", device, i));
242 
243     memset(&fds[i], 0, sizeof fds[0]);
244 
245 #ifdef COPROC_SUPPORT
246     if (rs232_devfile[device][0] == '|') {
247         log_message(rs232dev_log, "rs232dev_open(): forking '%s'", rs232_devfile[device] + 1);
248         if (fork_coproc(&fds[i].fd_w, &fds[i].fd_r, rs232_devfile[device] + 1) < 0) {
249             log_error(rs232dev_log, "rs232dev_open(): Cannot fork process.");
250             return -1;
251         }
252         fds[i].type = T_PROC;
253         fds[i].inuse = 1;
254         /* fds[i].file = rs232_devfile[device]; */
255         ret = i;
256     } else
257 #endif
258     do {
259         DCB dcb;
260         COMMTIMEOUTS comm_timeouts;
261         char *mode_string = strchr(rs232_devfile[device], ':');
262 
263         if (mode_string != NULL) {
264             *mode_string = 0;
265         }
266 
267         DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_open(): CreateFile(%s).", rs232_devfile[device]));
268         serial_port = CreateFile(rs232_devfile[device], GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
269 
270         if (mode_string != NULL) {
271             *mode_string = ':';
272         }
273 
274 
275         if (serial_port == INVALID_HANDLE_VALUE) {
276             DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_open(): CreateFile('%s') failed: %lu.", rs232_devfile[device], GetLastError()));
277             break;
278         }
279 
280         /* setup clean dcb */
281         memset(&dcb, 0, sizeof dcb);
282         dcb.DCBlength = sizeof dcb;
283 
284         /* save status of the port, so we can restore it when we do not use it anymore */
285         if (!GetCommState(serial_port, &dcb)) {
286             DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_open(): GetCommState() '%s' failed: %lu.", rs232_devfile[device], GetLastError()));
287             break;
288         }
289         fds[i].restore_dcb = dcb;
290 
291         /* set up port with some sane defaults */
292         /* https://docs.microsoft.com/de-de/windows/win32/api/winbase/ns-winbase-dcb */
293         /* https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-dcb */
294         dcb.BaudRate = get_devbaud(device);  /* Setting BaudRate */
295 
296         dcb.ByteSize = 8;             /* Setting ByteSize = 8 */
297         dcb.StopBits = ONESTOPBIT;    /* Setting StopBits = 1 */
298         dcb.Parity   = NOPARITY;      /* Setting Parity = None */
299 
300         dcb.fOutxCtsFlow = FALSE;     /* disable CTS flow control */
301 
302         dcb.fOutxDsrFlow = FALSE;     /* disable DSR flow control */
303         dcb.fDsrSensitivity = FALSE;
304 
305         dcb.fDtrControl = DTR_CONTROL_DISABLE;
306         dcb.fRtsControl = RTS_CONTROL_DISABLE;
307 
308         dcb.fOutX = FALSE;            /* disable output XOFF/XON flow control */
309         dcb.fInX = FALSE;             /* disable input XOFF/XON flow control */
310 
311         /* build more config options from the mode string */
312         if (mode_string != NULL) {
313             ++mode_string;
314 
315             while (*mode_string == ' ') {
316                 ++mode_string;
317             }
318 
319             if (!BuildCommDCB(mode_string, &dcb)) {
320                 DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_open(): BuildCommDCB() for device '%s' failed: %lu.", rs232_devfile[device], GetLastError()));
321                 break;
322             }
323         }
324 
325         DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_open(): SetCommState() baudrate: %lu.", dcb.BaudRate));
326         if (!SetCommState(serial_port, &dcb)) {
327             DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_open(): SetCommState() '%s' failed: %lu.", rs232_devfile[device], GetLastError()));
328             break;
329         }
330 
331         memset(&comm_timeouts, 0, sizeof comm_timeouts);
332 
333         /*
334          * ensure that a read will always terminate and only return
335          * what is already in the buffers
336          */
337         comm_timeouts.ReadIntervalTimeout = UINT32_MAX;
338         comm_timeouts.ReadTotalTimeoutMultiplier = 0;
339         comm_timeouts.ReadTotalTimeoutConstant = 0;
340 
341         /*
342          * Do not use total timeouts for write operations
343          */
344         comm_timeouts.WriteTotalTimeoutConstant = 0;
345         comm_timeouts.WriteTotalTimeoutMultiplier = 0;
346 
347         if (!SetCommTimeouts(serial_port, &comm_timeouts)) {
348             DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_open(): SetCommTimeouts() '%s' failed: %lu.", rs232_devfile[device], GetLastError()));
349             break;
350         }
351 
352         if(!strncasecmp(rs232_devfile[device], "com", 3)) {
353             fds[i].type = T_TTY;
354         } else {
355             fds[i].type = T_FILE;
356         }
357         fds[i].inuse = 1;
358         fds[i].fd = serial_port;
359         /* fds[i].file = rs232_devfile[device]; */
360         ret = i;
361         serial_port = INVALID_HANDLE_VALUE;
362 
363     } while (0);
364 
365     if (serial_port != INVALID_HANDLE_VALUE) {
366         CloseHandle(serial_port);
367     }
368 
369     return ret;
370 }
371 
372 /* closes the rs232 window again */
rs232dev_close(int fd)373 void rs232dev_close(int fd)
374 {
375     DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_close(fd=%d).", fd));
376 
377     if (fd < 0 || fd >= RS232_NUM_DEVICES) {
378         log_error(rs232dev_log, "rs232dev_close(): Attempt to close invalid fd %d.", fd);
379         return;
380     }
381     if (!fds[fd].inuse) {
382         log_error(rs232dev_log, "rs232dev_close(): Attempt to close non-open fd %d.", fd);
383         return;
384     }
385 
386     if (fds[fd].type == T_TTY) {
387         /* restore status of the serial port to what it was before we used it */
388         if (!SetCommState(fds[fd].fd, &fds[fd].restore_dcb)) {
389             DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_close(): SetCommState() '%s' on close failed: %lu.", rs232_devfile[fd], GetLastError()));
390         }
391     }
392 
393     CloseHandle(fds[fd].fd);
394     fds[fd].inuse = 0;
395 }
396 
397 /* sends a byte to the RS232 line */
rs232dev_putc(int fd,uint8_t b)398 int rs232dev_putc(int fd, uint8_t b)
399 {
400     uint32_t number_of_bytes = 1;
401     HANDLE fdw = (fds[fd].type == T_PROC) ? fds[fd].fd_w : fds[fd].fd;
402 
403     /* DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_putc(): Output 0x%X = '%c'.", (unsigned)b, b)); */
404 
405     if (WriteFile(fdw, &b, (DWORD)1, (LPDWORD)&number_of_bytes, NULL) == 0) {
406         return -1;
407     }
408 
409     if (number_of_bytes == 0) {
410         return -1;
411     }
412 
413     return 0;
414 }
415 
416 /* gets a byte from the RS232 line, returns 1 if byte received, byte in *b. */
rs232dev_getc(int fd,uint8_t * b)417 int rs232dev_getc(int fd, uint8_t *b)
418 {
419     uint32_t number_of_bytes = 0;
420     HANDLE fdr = (fds[fd].type == T_PROC) ? fds[fd].fd_r : fds[fd].fd;
421 
422     if (ReadFile(fdr, b, (DWORD)1, (LPDWORD)&number_of_bytes, NULL) == 0) {
423         return -1;
424     }
425 
426     if (number_of_bytes) {
427         /* DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_getc(): Input 0x%X = '%c'.", (unsigned)*b, *b)); */
428         return 1;
429     }
430     return 0;
431 }
432 
433 /* set the status lines of the RS232 device */
rs232dev_set_status(int fd,enum rs232handshake_out status)434 int rs232dev_set_status(int fd, enum rs232handshake_out status)
435 {
436     int new_rts = status & RS232_HSO_RTS;
437     int new_dtr = status & RS232_HSO_DTR;
438 
439     DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_set_status(): RTS:%s DTR:%s",
440         new_rts ? "on" : "off", new_dtr ? "on" : "off"));
441 
442     if ((fd < 0) || (fd >= RS232_NUM_DEVICES)) {
443         log_error(rs232dev_log, "rs232dev_set_status(): Attempted to set status of invalid fd %d.", fd);
444         return -1;
445     }
446 
447     if (fds[fd].type == T_TTY) {
448         /* signal the RS232 device the current status, too */
449         if (new_rts != fds[fd].rts) {
450             fds[fd].rts = new_rts;
451             EscapeCommFunction(fds[fd].fd, new_rts ? SETRTS : CLRRTS);
452         }
453 
454         if (new_dtr != fds[fd].dtr) {
455             fds[fd].dtr = new_dtr;
456             EscapeCommFunction(fds[fd].fd, new_dtr ? SETDTR : CLRDTR);
457         }
458     }
459     return 0;
460 }
461 
462 /* get the status lines of the RS232 device */
rs232dev_get_status(int fd)463 enum rs232handshake_in rs232dev_get_status(int fd)
464 {
465     /* Start with no active flags. */
466     enum rs232handshake_in modem_status = 0;
467 
468     if (fd < 0 || fd >= RS232_NUM_DEVICES) {
469         log_error(rs232dev_log, "rs232dev_get_status(): Attempted to get status of invalid fd %d.", fd);
470         return modem_status;
471     }
472 
473     if (fds[fd].type == T_TTY) {
474         uint32_t modemstat = 0;
475 
476         if (!GetCommModemStatus(fds[fd].fd, (LPDWORD)&modemstat)) {
477             DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_get_status(): Couldn't get modem status for fd %d.", fd));
478             return modem_status;
479         }
480 
481         if ((modemstat & MS_CTS_ON) != 0) {
482             modem_status |= RS232_HSI_CTS;
483         }
484 
485         if ((modemstat & MS_DSR_ON) != 0) {
486             modem_status |= RS232_HSI_DSR;
487         }
488 
489         if ((modemstat & MS_RING_ON) != 0) {
490             modem_status |= RS232_HSI_RI;
491         }
492 
493         if ((modemstat & MS_RLSD_ON) != 0) {
494             modem_status |= RS232_HSI_DCD;
495         }
496 
497         DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_get_status(): got 0x%X.", (unsigned)modem_status));
498         return modem_status;
499     }
500 
501     /* A file always is ready to receive. */
502     return RS232_HSI_DCD | RS232_HSI_DSR | RS232_HSI_CTS;
503 }
504 
505 /* FIXME: only "aciacore.c" calls this. */
506 /* set the bps rate of the physical device */
rs232dev_set_bps(int fd,unsigned int bps)507 void rs232dev_set_bps(int fd, unsigned int bps)
508 {
509     /*! \todo set the physical bps rate */
510     DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_set_bps(): BPS: %u", bps));
511     if ((fd < 0) || (fd >= RS232_NUM_DEVICES)) {
512         log_error(rs232dev_log, "rs232dev_set_bps(): Attempted to set BPS of invalid fd %d.", fd);
513     }
514 }
515