1 /** \file   rs232-win32-dev.c
2  * \brief   RS232 Device emulation
3  *
4  * \author  Spiro Trikaliotis <spiro.trikaliotis@gmx.de>
5  *
6  * The RS232 emulation captures the bytes sent to the RS232 interfaces
7  * available (currently, ACIA 6551, std. C64, and Daniel Dallmann's fast RS232
8  * with 9600 BPS).
9  *
10  * I/O is done to a physical COM port.
11  */
12 
13 /*
14  * This file is part of VICE, the Versatile Commodore Emulator.
15  * See README for copyright notice.
16  *
17  *  This program is free software; you can redistribute it and/or modify
18  *  it under the terms of the GNU General Public License as published by
19  *  the Free Software Foundation; either version 2 of the License, or
20  *  (at your option) any later version.
21  *
22  *  This program is distributed in the hope that it will be useful,
23  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
24  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25  *  GNU General Public License for more details.
26  *
27  *  You should have received a copy of the GNU General Public License
28  *  along with this program; if not, write to the Free Software
29  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
30  *  02111-1307  USA.
31  *
32  */
33 
34 #undef        DEBUG
35 /* #define DEBUG */
36 
37 #define DEBUG_FAKE_INPUT_OUTPUT (5 * 80)
38 #undef DEBUG_FAKE_INPUT_OUTPUT
39 
40 #include "vice.h"
41 
42 #include <errno.h>
43 #include <stdint.h>
44 #include <string.h>
45 #include <winsock.h>
46 
47 #ifdef HAVE_IO_H
48 #include <io.h>
49 #endif
50 
51 #include "log.h"
52 #include "rs232.h"
53 #include "rs232dev.h"
54 #include "types.h"
55 #include "util.h"
56 
57 #ifdef DEBUG
58 # define DEBUG_LOG_MESSAGE(_xxx) log_message _xxx
59 #else
60 # define DEBUG_LOG_MESSAGE(_xxx)
61 #endif
62 
63 /* ------------------------------------------------------------------------- */
64 
rs232dev_resources_init(void)65 int rs232dev_resources_init(void)
66 {
67     return 0;
68 }
69 
rs232dev_resources_shutdown(void)70 void rs232dev_resources_shutdown(void)
71 {
72 }
73 
rs232dev_cmdline_options_init(void)74 int rs232dev_cmdline_options_init(void)
75 {
76     return 0;
77 }
78 
79 /* ------------------------------------------------------------------------- */
80 
81 typedef struct rs232dev {
82     int inuse;
83     HANDLE fd;
84     char *file;
85     DCB restore_dcb;
86     int rts;
87     int dtr;
88 } rs232dev_t;
89 
90 static rs232dev_t fds[RS232_NUM_DEVICES];
91 
92 static log_t rs232dev_log = LOG_ERR;
93 
94 /* ------------------------------------------------------------------------- */
95 
96 void rs232dev_close(int fd);
97 
98 /* initializes all RS232 stuff */
rs232dev_init(void)99 void rs232dev_init(void)
100 {
101     rs232dev_log = log_open("RS232DEV");
102 }
103 
104 /* reset RS232 stuff */
rs232dev_reset(void)105 void rs232dev_reset(void)
106 {
107     int i;
108 
109     for (i = 0; i < RS232_NUM_DEVICES; i++) {
110         if (fds[i].inuse) {
111             rs232dev_close(i);
112         }
113     }
114 }
115 
116 /* opens a rs232 window, returns handle to give to functions below. */
rs232dev_open(int device)117 int rs232dev_open(int device)
118 {
119     HANDLE serial_port;
120     int ret = -1;
121     int i;
122 
123     for (i = 0; i < RS232_NUM_DEVICES; i++) {
124         if (!fds[i].inuse) {
125             break;
126         }
127     }
128     if (i >= RS232_NUM_DEVICES) {
129         log_error(rs232dev_log, "No more devices available.");
130         return -1;
131     }
132 
133     DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev_open(device=%d).", device));
134 
135     do {
136         DCB dcb;
137         COMMTIMEOUTS comm_timeouts;
138         char *mode_string = strchr(rs232_devfile[device], ':');
139 
140         if (mode_string != NULL) {
141             *mode_string = 0;
142         }
143 
144         serial_port = CreateFile(rs232_devfile[device], GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
145 
146         if (mode_string != NULL) {
147             *mode_string = ':';
148         }
149 
150 
151         if (serial_port == INVALID_HANDLE_VALUE) {
152             DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev: CreateFile '%s' failed: %d.\n", rs232_devfile[device], GetLastError()));
153             break;
154         }
155 
156         memset(&dcb, 0, sizeof dcb);
157         dcb.DCBlength = sizeof dcb;
158 
159         if (!GetCommState(serial_port, &dcb)) {
160             DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev: GetCommState '%s' failed: %d.\n", rs232_devfile[device], GetLastError()));
161             break;
162         }
163 
164         fds[i].restore_dcb = dcb;
165 
166         if (mode_string != NULL) {
167             ++mode_string;
168 
169             while (*mode_string == ' ') {
170                 ++mode_string;
171             }
172 
173             if ( ! BuildCommDCB(mode_string, &dcb) ) {
174                 DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev: BuildCommDCB '%s' for device '%s' failed: %d.\n", mode_string + 1, rs232_devfile[device], GetLastError()));
175                 break;
176             }
177         }
178 
179         if (! SetCommState(serial_port, &dcb)) {
180             DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev: SetCommState '%s' failed: %d.\n", rs232_devfile[device], GetLastError()));
181             break;
182         }
183 
184         memset(&comm_timeouts, 0, sizeof comm_timeouts);
185 
186         /*
187          * ensure that a read will always terminate and only return
188          * what is already in the buffers
189          */
190         comm_timeouts.ReadIntervalTimeout = UINT32_MAX;
191         comm_timeouts.ReadTotalTimeoutMultiplier = 0;
192         comm_timeouts.ReadTotalTimeoutConstant = 0;
193 
194         /*
195          * Do not use total timeouts for write operations
196          */
197         comm_timeouts.WriteTotalTimeoutConstant = 0;
198         comm_timeouts.WriteTotalTimeoutMultiplier = 0;
199 
200         if (!SetCommTimeouts(serial_port, &comm_timeouts)) {
201             DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev: SetCommTimeouts '%s' failed: %d.\n", rs232_devfile[device], GetLastError()));
202             break;
203         }
204 
205         fds[i].inuse = 1;
206         fds[i].fd = serial_port;
207         fds[i].file = rs232_devfile[device];
208         ret = i;
209         serial_port = INVALID_HANDLE_VALUE;
210 
211     } while (0);
212 
213     if (serial_port != INVALID_HANDLE_VALUE) {
214         CloseHandle(serial_port);
215     }
216 
217     return ret;
218 }
219 
220 /* closes the rs232 window again */
rs232dev_close(int fd)221 void rs232dev_close(int fd)
222 {
223     DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev: close(fd=%d).", fd));
224 
225     if (fd < 0 || fd >= RS232_NUM_DEVICES) {
226         log_error(rs232dev_log, "Attempt to close invalid fd %d.", fd);
227         return;
228     }
229     if (!fds[fd].inuse) {
230         log_error(rs232dev_log, "Attempt to close non-open fd %d.", fd);
231         return;
232     }
233 
234     if (!SetCommState(fds[fd].fd, &fds[fd].restore_dcb)) {
235         DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev: SetCommState '%s' on close failed: %d.\n", rs232_devfile[fd], GetLastError()));
236     }
237 
238     CloseHandle(fds[fd].fd);
239     fds[fd].inuse = 0;
240 }
241 
242 #if DEBUG_FAKE_INPUT_OUTPUT
243 static char rs232_debug_fake_input = 0;
244 static unsigned rs232_debug_fake_input_available = 0;
245 #endif
246 
247 /* sends a byte to the RS232 line */
rs232dev_putc(int fd,uint8_t b)248 int rs232dev_putc(int fd, uint8_t b)
249 {
250     uint32_t number_of_bytes = 1;
251 
252     DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev: Output %u = `%c'.", (unsigned)b, b));
253 
254 #if DEBUG_FAKE_INPUT_OUTPUT
255 
256     rs232_debug_fake_input_available = DEBUG_FAKE_INPUT_OUTPUT;
257     rs232_debug_fake_input = b;
258 
259 #else
260     if (WriteFile(fds[fd].fd, &b, (DWORD)1, (LPDWORD)&number_of_bytes, NULL) == 0) {
261         return -1;
262     }
263 
264     if (number_of_bytes == 0) {
265         return -1;
266     }
267 #endif
268 
269     return 0;
270 }
271 
272 /* gets a byte from the RS232 line, returns !=0 if byte received, byte in *b. */
rs232dev_getc(int fd,uint8_t * b)273 int rs232dev_getc(int fd, uint8_t *b)
274 {
275     uint32_t number_of_bytes = 1;
276 
277 #if DEBUG_FAKE_INPUT_OUTPUT
278 
279     if (rs232_debug_fake_input_available != 0) {
280         --rs232_debug_fake_input_available;
281         *b = rs232_debug_fake_input;
282     } else {
283         number_of_bytes = 0;
284     }
285 
286 #else
287     if (ReadFile(fds[fd].fd, b, (DWORD)1, (LPDWORD)&number_of_bytes, NULL) == 0) {
288         return -1;
289     }
290 #endif
291 
292     if (number_of_bytes) {
293         DEBUG_LOG_MESSAGE((rs232dev_log, "rs232dev: Input %u = `%c'.", (unsigned)*b, *b));
294         return 1;
295     }
296 
297     return 0;
298 }
299 
300 /* set the status lines of the RS232 device */
rs232dev_set_status(int fd,enum rs232handshake_out status)301 int rs232dev_set_status(int fd, enum rs232handshake_out status)
302 {
303     int new_rts = (status & RS232_HSO_RTS) ? 1 : 0;
304     int new_dtr = (status & RS232_HSO_DTR) ? 1 : 0;
305 
306     /* signal the RS232 device the current status, too */
307 
308     if ( new_rts != fds[fd].rts ) {
309         EscapeCommFunction(fds[fd].fd, new_rts ? SETRTS : CLRRTS);
310         fds[fd].rts = new_rts;
311     }
312 
313     if ( new_dtr != fds[fd].dtr ) {
314         EscapeCommFunction(fds[fd].fd, new_dtr ? SETDTR : CLRDTR);
315         fds[fd].dtr = new_dtr;
316     }
317 
318     return 0;
319 }
320 
321 /* get the status lines of the RS232 device */
rs232dev_get_status(int fd)322 enum rs232handshake_in rs232dev_get_status(int fd)
323 {
324     enum rs232handshake_in modem_status = 0;
325 
326     do {
327         uint32_t modemstat = 0;
328         if (GetCommModemStatus(fds[fd].fd, (LPDWORD)&modemstat) == 0) {
329             DEBUG_LOG_MESSAGE((rs232dev_log, "Could not get modem status for device %d.", device));
330             break;
331         }
332 
333         if (modemstat & MS_CTS_ON) {
334             modem_status |= RS232_HSI_CTS;
335         }
336 
337         if (modemstat & MS_DSR_ON) {
338             modem_status |= RS232_HSI_DSR;
339         }
340     } while (0);
341 
342     return modem_status;
343 }
344 
345 /* set the bps rate of the physical device */
rs232dev_set_bps(int fd,unsigned int bps)346 void rs232dev_set_bps(int fd, unsigned int bps)
347 {
348     /*! \todo set the physical bps rate */
349     DEBUG_LOG_MESSAGE((rs232dev_log, "Setting bps to %u", bps));
350 }
351