1 /*
2 * Z80SIM - a Z80-CPU simulator
3 *
4 * Common I/O devices used by various simulated machines
5 *
6 * Copyright (C) 2014-2018 by Udo Munk
7 *
8 * Emulation of a Cromemco TU-ART S100 board
9 *
10 * History:
11 * DEC-14 first version
12 * JAN-15 better subdue of non printable characters in output
13 * 02-FEB-15 implemented the timers and interrupt flag for TBE
14 * 05-FEB-15 implemented interrupt flag for RDA
15 * 14-FEB-15 improvements, so that the Cromix tty driver works
16 * 10-MAR-15 lpt's implemented for CP/M, CDOS and Cromix
17 * 23-MAR-15 drop only null's
18 * 26-MAR-15 tty's implemented for CDOS and Cromix
19 * 25-APR-18 cleanup
20 * 03-MAY-18 improved accuracy
21 * 15-JUL-18 use logging
22 */
23
24 #include <unistd.h>
25 #include <stdio.h>
26 #include <ctype.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <sys/poll.h>
30 #include "sim.h"
31 #include "simglb.h"
32 #include "log.h"
33 #include "unix_terminal.h"
34 #include "unix_network.h"
35
36 static const char *TAG = "TU-ART";
37
38 /************************/
39 /* Device 0A */
40 /************************/
41
42 int uart0a_int_mask, uart0a_int, uart0a_int_pending, uart0a_rst7;
43 int uart0a_timer1, uart0a_timer2, uart0a_timer3, uart0a_timer4, uart0a_timer5;
44 int uart0a_tbe, uart0a_rda;
45
46 /*
47 * D7 Transmit Buffer Empty
48 * D6 Read Data Available
49 * D5 Interrupt Pending
50 * D4 Start Bit Detected
51 * D3 Full Bit Detected
52 * D2 Serial Rcv
53 * D1 Overrun Error
54 * D0 Frame Error
55 */
cromemco_tuart_0a_status_in(void)56 BYTE cromemco_tuart_0a_status_in(void)
57 {
58 BYTE status = 4;
59
60 if (uart0a_tbe)
61 status |= 128;
62
63 if (uart0a_rda)
64 status |= 64;
65
66 if (uart0a_int_pending)
67 status |= 32;
68
69 return(status);
70 }
71
72 /*
73 * D7 Stop Bits high=1 low=2
74 * D6 9600 A high one of the lower seven bits selects the
75 * D5 4800 corresponding baud rate. If more than one bit is high,
76 * D4 2400 the highest rate selected will result. If none of the
77 * D3 1200 bits are high, the serial transmitter and receiver will
78 * D2 300 be disabled.
79 * D1 150
80 * D0 110
81 */
cromemco_tuart_0a_baud_out(BYTE data)82 void cromemco_tuart_0a_baud_out(BYTE data)
83 {
84 data = data; /* to avoid compiler warning */
85 }
86
cromemco_tuart_0a_data_in(void)87 BYTE cromemco_tuart_0a_data_in(void)
88 {
89 BYTE data;
90 static BYTE last;
91 struct pollfd p[1];
92
93 uart0a_rda = 0;
94
95 again:
96 /* if no input waiting return last */
97 p[0].fd = fileno(stdin);
98 p[0].events = POLLIN;
99 p[0].revents = 0;
100 poll(p, 1, 0);
101 if (!(p[0].revents & POLLIN))
102 return(last);
103
104 if (read(fileno(stdin), &data, 1) == 0) {
105 /* try to reopen tty, input redirection exhausted */
106 freopen("/dev/tty", "r", stdin);
107 set_unix_terminal();
108 goto again;
109 }
110
111 /* process read data */
112 last = data;
113 return(data);
114 }
115
cromemco_tuart_0a_data_out(BYTE data)116 void cromemco_tuart_0a_data_out(BYTE data)
117 {
118 data &= 0x7f;
119 uart0a_tbe = 0;
120 if (data == 0x00)
121 return;
122
123 again:
124 if (write(fileno(stdout), (char *) &data, 1) != 1) {
125 if (errno == EINTR) {
126 goto again;
127 } else {
128 LOGE(TAG, "can't write tu-art 0a data");
129 cpu_error = IOERROR;
130 cpu_state = STOPPED;
131 }
132 }
133 }
134
135 /*
136 * D7 Not Used
137 * D6 Not Used
138 * D5 Test
139 * D4 High Baud
140 * D3 INTA Enable
141 * D2 RST7 Select
142 * D1 Break
143 * D0 Reset
144 */
cromemco_tuart_0a_command_out(BYTE data)145 void cromemco_tuart_0a_command_out(BYTE data)
146 {
147 uart0a_rst7 = (data & 4) ? 1 : 0;
148
149 if (data & 1) {
150 uart0a_rda = 0;
151 uart0a_tbe = 1;
152 uart0a_timer1 = 0;
153 uart0a_timer2 = 0;
154 uart0a_timer3 = 0;
155 uart0a_timer4 = 0;
156 uart0a_timer5 = 0;
157 uart0a_int_pending = 0;
158 }
159 }
160
161 /*
162 * C7 Timer 1
163 * CF Timer 2
164 * D7 !Sens
165 * DF Timer 3
166 * E7 Receiver Data Available
167 * EF Transmitter Buffer Supply
168 * F7 Timer 4
169 * FF Timer 5 or PI7
170 */
cromemco_tuart_0a_interrupt_in(void)171 BYTE cromemco_tuart_0a_interrupt_in(void)
172 {
173 return((BYTE) uart0a_int);
174 }
175
176 /*
177 * D7 Timer 5 or PI7
178 * D6 Timer 4
179 * D5 TBE
180 * D4 RDA
181 * D3 Timer 3
182 * D2 !Sens
183 * D1 Timer 2
184 * D0 Timer 1
185 */
cromemco_tuart_0a_interrupt_out(BYTE data)186 void cromemco_tuart_0a_interrupt_out(BYTE data)
187 {
188 uart0a_int_mask = data;
189 }
190
191 /*
192 * The parallel port is used on the FDC's
193 * for auxiliary disk control/status,
194 * so don't implement something here.
195 */
196
cromemco_tuart_0a_parallel_in(void)197 BYTE cromemco_tuart_0a_parallel_in(void)
198 {
199 return((BYTE) 0);
200 }
201
cromemco_tuart_0a_parallel_out(BYTE data)202 void cromemco_tuart_0a_parallel_out(BYTE data)
203 {
204 data = data; /* to avoid compiler warning */
205 }
206
cromemco_tuart_0a_timer1_out(BYTE data)207 void cromemco_tuart_0a_timer1_out(BYTE data)
208 {
209 uart0a_timer1 = data;
210 }
211
cromemco_tuart_0a_timer2_out(BYTE data)212 void cromemco_tuart_0a_timer2_out(BYTE data)
213 {
214 uart0a_timer2 = data;
215 }
216
cromemco_tuart_0a_timer3_out(BYTE data)217 void cromemco_tuart_0a_timer3_out(BYTE data)
218 {
219 uart0a_timer3 = data;
220 }
221
cromemco_tuart_0a_timer4_out(BYTE data)222 void cromemco_tuart_0a_timer4_out(BYTE data)
223 {
224 uart0a_timer4 = data;
225 }
226
cromemco_tuart_0a_timer5_out(BYTE data)227 void cromemco_tuart_0a_timer5_out(BYTE data)
228 {
229 uart0a_timer5 = data;
230 }
231
232 /************************/
233 /* Device 1A */
234 /************************/
235
236 int uart1a_int_mask;
237 int uart1a_int_mask, uart1a_int, uart1a_int_pending;
238 int uart1a_sense, uart1a_lpt_busy;
239 int uart1a_tbe, uart1a_rda;
240
cromemco_tuart_1a_status_in(void)241 BYTE cromemco_tuart_1a_status_in(void)
242 {
243 BYTE status = (ncons[0].ssc) ? 4 : 0;
244
245 if (uart1a_tbe)
246 status |= 128;
247
248 if (uart1a_rda)
249 status |= 64;
250
251 if (uart1a_int_pending)
252 status |= 32;
253
254 return(status);
255 }
256
cromemco_tuart_1a_baud_out(BYTE data)257 void cromemco_tuart_1a_baud_out(BYTE data)
258 {
259 data = data; /* to avoid compiler warning */
260 }
261
cromemco_tuart_1a_data_in(void)262 BYTE cromemco_tuart_1a_data_in(void)
263 {
264 BYTE data, dummy;
265 static BYTE last;
266 struct pollfd p[1];
267
268 uart1a_rda = 0;
269
270 /* if not connected return last */
271 if (ncons[0].ssc == 0)
272 return(last);
273
274 /* if no input waiting return last */
275 p[0].fd = ncons[0].ssc;
276 p[0].events = POLLIN;
277 p[0].revents = 0;
278 poll(p, 1, 0);
279 if (!(p[0].revents & POLLIN))
280 return(last);
281
282 if (read(ncons[0].ssc, &data, 1) != 1) {
283 if ((errno == EAGAIN) || (errno == EINTR)) {
284 /* EOF, close socket and return last */
285 close(ncons[0].ssc);
286 ncons[0].ssc = 0;
287 return(last);
288 } else {
289 LOGE(TAG, "can't read tu-art 1a data");
290 cpu_error = IOERROR;
291 cpu_state = STOPPED;
292 return(0);
293 }
294 }
295
296 /* process read data */
297 /* telnet client sends \r\n or \r\0, drop second character */
298 if (ncons[0].telnet && (data == '\r'))
299 read(ncons[0].ssc, &dummy, 1);
300 last = data;
301 return(data);
302 }
303
cromemco_tuart_1a_data_out(BYTE data)304 void cromemco_tuart_1a_data_out(BYTE data)
305 {
306 uart1a_tbe = 0;
307 if (ncons[0].ssc == 0)
308 return;
309 data &= 0x7f;
310 if (data == 0x00)
311 return;
312
313 again:
314 if (write(ncons[0].ssc, (char *) &data, 1) != 1) {
315 if (errno == EINTR) {
316 goto again;
317 } else {
318 LOGE(TAG, "can't write tu-art 1a data");
319 cpu_error = IOERROR;
320 cpu_state = STOPPED;
321 }
322 }
323 }
324
cromemco_tuart_1a_command_out(BYTE data)325 void cromemco_tuart_1a_command_out(BYTE data)
326 {
327 if (data & 1) {
328 uart1a_rda = 0;
329 uart1a_tbe = 1;
330 uart1a_int_pending = 0;
331 }
332 }
333
cromemco_tuart_1a_interrupt_in(void)334 BYTE cromemco_tuart_1a_interrupt_in(void)
335 {
336 return((BYTE) uart1a_int);
337 }
338
cromemco_tuart_1a_interrupt_out(BYTE data)339 void cromemco_tuart_1a_interrupt_out(BYTE data)
340 {
341 uart1a_int_mask = data;
342 }
343
cromemco_tuart_1a_parallel_in(void)344 BYTE cromemco_tuart_1a_parallel_in(void)
345 {
346 if (uart1a_lpt_busy == 0)
347 return((BYTE) ~0x20);
348 else
349 return((BYTE) 0xff);
350 }
351
cromemco_tuart_1a_parallel_out(BYTE data)352 void cromemco_tuart_1a_parallel_out(BYTE data)
353 {
354 extern int lpt2;
355
356 if (lpt2 == 0)
357 lpt2 = creat("lpt2.txt", 0664);
358
359 uart1a_sense = 1;
360 uart1a_lpt_busy = 1;
361
362 /* bit 7 is strobe, every byte is send 3 times. First with
363 strobe on, then with strobe off, then with on again.
364 Take over data when strobe not set */
365
366 if (!(data & 0x80) && (data != '\r')) {
367 again:
368 if (write(lpt2, (char *) &data, 1) != 1) {
369 if (errno == EINTR) {
370 goto again;
371 } else {
372 LOGE(TAG, "can't write lpt2.txt");
373 cpu_error = IOERROR;
374 cpu_state = STOPPED;
375 }
376 }
377 }
378 }
379
380 /************************/
381 /* Device 1B */
382 /************************/
383
384 int uart1b_int_mask, uart1b_int, uart1b_int_pending;
385 int uart1b_sense, uart1b_lpt_busy;
386 int uart1b_tbe, uart1b_rda;
387
cromemco_tuart_1b_status_in(void)388 BYTE cromemco_tuart_1b_status_in(void)
389 {
390 BYTE status = (ncons[1].ssc) ? 4 : 0;
391
392 if (uart1b_tbe)
393 status |= 128;
394
395 if (uart1b_rda)
396 status |= 64;
397
398 if (uart1b_int_pending)
399 status |= 32;
400
401 return(status);
402 }
403
cromemco_tuart_1b_baud_out(BYTE data)404 void cromemco_tuart_1b_baud_out(BYTE data)
405 {
406 data = data; /* to avoid compiler warning */
407 }
408
cromemco_tuart_1b_data_in(void)409 BYTE cromemco_tuart_1b_data_in(void)
410 {
411 BYTE data, dummy;
412 static BYTE last;
413 struct pollfd p[1];
414
415 uart1b_rda = 0;
416
417 /* if not connected return last */
418 if (ncons[1].ssc == 0)
419 return(last);
420
421 /* if no input waiting return last */
422 p[0].fd = ncons[1].ssc;
423 p[0].events = POLLIN;
424 p[0].revents = 0;
425 poll(p, 1, 0);
426 if (!(p[0].revents & POLLIN))
427 return(last);
428
429 if (read(ncons[1].ssc, &data, 1) != 1) {
430 if ((errno == EAGAIN) || (errno == EINTR)) {
431 /* EOF, close socket and return last */
432 close(ncons[1].ssc);
433 ncons[1].ssc = 0;
434 return(last);
435 } else {
436 LOGE(TAG, "can't read tu-art 1b data");
437 cpu_error = IOERROR;
438 cpu_state = STOPPED;
439 return(0);
440 }
441 }
442
443 /* process read data */
444 /* telnet client sends \r\n or \r\0, drop second character */
445 if (ncons[1].telnet && (data == '\r'))
446 read(ncons[1].ssc, &dummy, 1);
447 last = data;
448 return(data);
449 }
450
cromemco_tuart_1b_data_out(BYTE data)451 void cromemco_tuart_1b_data_out(BYTE data)
452 {
453 uart1b_tbe = 0;
454 if (ncons[1].ssc == 0)
455 return;
456 data &= 0x7f;
457 if (data == 0x00)
458 return;
459
460 again:
461 if (write(ncons[1].ssc, (char *) &data, 1) != 1) {
462 if (errno == EINTR) {
463 goto again;
464 } else {
465 LOGE(TAG, "can't write tu-art 1b data");
466 cpu_error = IOERROR;
467 cpu_state = STOPPED;
468 }
469 }
470 }
471
cromemco_tuart_1b_command_out(BYTE data)472 void cromemco_tuart_1b_command_out(BYTE data)
473 {
474 if (data & 1) {
475 uart1b_rda = 0;
476 uart1b_tbe = 1;
477 uart1b_int_pending = 0;
478 }
479 }
480
cromemco_tuart_1b_interrupt_in(void)481 BYTE cromemco_tuart_1b_interrupt_in(void)
482 {
483 return((BYTE) uart1b_int);
484 }
485
cromemco_tuart_1b_interrupt_out(BYTE data)486 void cromemco_tuart_1b_interrupt_out(BYTE data)
487 {
488 uart1b_int_mask = data;
489 }
490
cromemco_tuart_1b_parallel_in(void)491 BYTE cromemco_tuart_1b_parallel_in(void)
492 {
493 if (uart1b_lpt_busy == 0)
494 return((BYTE) ~0x20);
495 else
496 return((BYTE) 0xff);
497 }
498
cromemco_tuart_1b_parallel_out(BYTE data)499 void cromemco_tuart_1b_parallel_out(BYTE data)
500 {
501 extern int lpt1;
502
503 if (lpt1 == 0)
504 lpt1 = creat("lpt1.txt", 0664);
505
506 uart1b_sense = 1;
507 uart1b_lpt_busy = 1;
508
509 /* bit 7 is strobe, every byte is send 3 times. First with
510 strobe on, then with strobe off, then with on again.
511 Take over data when strobe not set */
512
513 if (!(data & 0x80) && (data != '\r')) {
514 again:
515 if (write(lpt1, (char *) &data, 1) != 1) {
516 if (errno == EINTR) {
517 goto again;
518 } else {
519 LOGE(TAG, "can't write lpt1.txt");
520 cpu_error = IOERROR;
521 cpu_state = STOPPED;
522 }
523 }
524 }
525 }
526