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