-
Notifications
You must be signed in to change notification settings - Fork 0
/
io.c
557 lines (458 loc) · 16 KB
/
io.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
// vim:set shiftwidth=2 softtabstop=2 expandtab:
/*
* Z80SIM - a Z80-CPU simulator
*
* Copyright (C) 1987-2008 by Udo Munk
* 2014 fork by Jack Carrozzo <[email protected]>
*
*/
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "config.h"
#include "global.h"
#include "io.h"
// TODO: move this into config.h, make dev struct types similar
// default debugging thresholds
#define DEBUG_PIO 5
#define DEBUG_CTC 5
#define DEBUG_DART 3
#define DEBUG_OTHER 3
static t_iodebug iodebug;
// levels messages can have:
#define D_CONFW 3
#define D_CONFR 3
#define D_RWOPS 3
#define D_ERR 2
#define D_UNIMPL 3
#define D_INFO 4
#define D_ALL 5
#define D_WARN 3
// base addresses for io devices (assumes that A1-0 are
// used in the devices themselves, and A4-2 are demuxed
// to select devices)
#define ADDR_8255 0
#define ADDR_CTC 4
#define ADDR_DART 8
// listen ports for serial sockets
#define DARTA_PORT 8041
#define DARTB_PORT 8042
// TODOs:
//
// - fix the interrupt handling stuff (again), emulate acking properly
// - see if i cant clean up some of the globals and pass
// struct ptrs around.
static pio_state pio; // called pio for var name compliance, but in my application
// it is in fact an 82c55
static ctc_state ctc[4];
static dart_state dart[2]; // 0=chan A, 1=chan B
void init_io(void) { // called at start to init all ports
int i;
iodebug.pio =DEBUG_PIO;
iodebug.ctc =DEBUG_CTC;
iodebug.dart =DEBUG_DART;
iodebug.other =DEBUG_OTHER;
pio.port_a=0xff; // at reset, 8255 ports are in input mode with
pio.port_b=0xff; // light pullups
pio.port_c=0xff;
pio.control=0x9B;
for (i=0;i<4;i++) { // TODO: lookup proper vals
ctc[i].ints_enabled=0;
ctc[i].tc_next=0;
ctc[i].tc=255;
ctc[i].ivector=0;
ctc[i].c_val=255;
ctc[i].prescaler=255;
ctc[i].p_val=255;
}
for (i=0;i<2;i++) {
dart_reset(&dart[i]);
// TODO: add ::1 listener for osx compat
if (0>(dart[i].sock=socket(AF_INET,SOCK_DGRAM,0))) {
perror("cannot create socket\n");
exit(1);
}
bzero((char *)&dart[i].ouraddr,sizeof(dart[i].ouraddr));
dart[i].ouraddr.sin_family=AF_INET;
dart[i].ouraddr.sin_addr.s_addr=htonl(INADDR_ANY);
dart[i].ouraddr.sin_port=htons((0==i)?DARTA_PORT:DARTB_PORT);
dart[i].have_client=0;
dart[i].addrlen=sizeof(dart[i].remaddr);
if (0>(bind(dart[i].sock,(struct sockaddr *)&dart[i].ouraddr,sizeof(dart[i].ouraddr)))) {
perror("socket bind failed");
//exit(1);
} else if (iodebug.dart>4) printf("Dart %c: socket bound successfully.\n",'A'+i);
}
}
void exit_io(void) { // called at exit
if (iodebug.dart>4) printf("DART: closing UDP sockets.\n");
close(dart[0].sock);
close(dart[1].sock);
}
// this is written to emulate CTC funtionality if run once per clock -
// however, it is currently called from the cpu wrapper and thus only
// runs once per instruction, and is as such 4-8x slower than realtime.
// (TODO)
// also, this currently doesnt queue interrupts: only the highest priority is
// sent to the cpu and then cleared. TODO: make this match the hardware (ints
// can be acknowledged separately)
void run_counters(void) {
int i;
for (i=3;i>=0;i--) { // in interrupt priority order (most important last)
if (!ctc[i].ints_enabled) continue; // dont bother with counters that arent
// sending interupts
if (!--ctc[i].p_val) { // prescaler empty
ctc[i].p_val=ctc[i].prescaler; // refill it
if (!--ctc[i].c_val) { // counter empty
ctc[i].c_val=ctc[i].tc; // refill it
int_lsb=ctc[i].ivector+(i<<1); // set the interupt (bits 2-1 are the chan)
int_type=INT_INT;
}
}
}
}
// handles all IN opcodes
BYTE io_in(BYTE adr) {
switch (adr&0xfc) { // zero the last two bits since they arent relevant
case ADDR_8255: return p_8255_in(adr);
case ADDR_CTC: return p_ctc_in(adr);
case ADDR_DART: return p_dart_in(adr);
default: io_trap(adr);
}
return 0;
}
// handles all OUT opcodes
void io_out(BYTE adr, BYTE data) {
switch (adr&0xfc) {
case ADDR_8255: return p_8255_out(adr,data);
case ADDR_CTC: return p_ctc_out(adr,data);
case ADDR_DART: return p_dart_out(adr,data);
default: io_trap(adr);
}
}
// trap unused ports
static BYTE io_trap(BYTE adr) {
if (iodebug.other>=D_ERR) printf("--- No device at port %d! Trapping...\n",adr);
if (i_flag) {
cpu_error = IOTRAP;
cpu_state = STOPPED;
}
return((BYTE) 0);
}
static BYTE p_8255_in(BYTE port) {
port&=0x03;
if (0x03==port) {
if (iodebug.pio>=D_CONFR) printf("--- 8255 control port read (0x%02x)\n",pio.control);
return pio.control;
} else {
BYTE portname='A'+port;
if (iodebug.pio>=D_RWOPS) printf("--- 8255 port %c read ",portname);
switch (port) {
case 0:
if (iodebug.pio>=D_INFO) printf("(%c): 0x%02x\n",(pio.conf_port_a)?'i':'o',pio.port_a);
return pio.port_a;
case 1:
if (iodebug.pio>=D_INFO) printf("(%c): 0x%02x\n",(pio.conf_port_b)?'i':'o',pio.port_b);
return pio.port_b;
case 2:
if (iodebug.pio>=D_INFO) printf("(upper:%c lower:%c): 0x%02x\n",
(pio.conf_port_c_upper)?'i':'o',(pio.conf_port_c_lower)?'i':'o',pio.port_c);
return pio.port_c;
}
}
return 0x00; // can never get here, but removes compiler warning
}
static void p_8255_out(BYTE port,BYTE data) {
port&=0x03;
if (0x03==port) { // control word
if (data&0x80) { // mode set
pio.control=data; // saved in case it's read
// port modes: 1 in, 0 out
pio.conf_port_c_lower =data&0x01; // D0
data>>=1;
pio.conf_port_b =data&0x01; // D1
data>>=2;
pio.conf_port_c_upper =data&0x01; // D3
data>>=1;
pio.conf_port_a =data&0x01; // D4
if (iodebug.pio>=D_CONFW)
printf("--- PIO config set: A:%c B:%c C-upper:%c C-lower:%c (0x%02x)\n",
(pio.conf_port_a)?'i':'o',(pio.conf_port_b)?'i':'o',
(pio.conf_port_c_upper)?'i':'o',(pio.conf_port_c_lower)?'i':'o',pio.control);
} else { // bit set / reset
if (iodebug.pio>=D_ERR) printf("--- PIO bit set/reset attempted, but not emulated!\n");
}
} else { // data
char portname='A'+port;
if (iodebug.pio>=D_RWOPS) printf("--- 8255 port %c written: 0x%02x\n",portname,data);
switch (port) {
case 0: pio.port_a=data; break;
case 1: pio.port_b=data; break;
case 2: pio.port_c=data; break;
// no default required as its handled above
}
}
}
// reads the current value of the down counter on the specified channel
static BYTE p_ctc_in(BYTE port) {
port&=0x03;
if (iodebug.ctc>=D_RWOPS) printf("--- CTC chan %d read: 0x%02x.\n",port,ctc[port].c_val);
return ctc[port].c_val;
}
static void p_ctc_out(BYTE port,BYTE data) {
port&=0x03;
ctc_state *thisctc=&ctc[port];
if (thisctc->tc_next) { // if we indicated the next write would be the tc
thisctc->tc=data;
thisctc->tc_next=0;
if (iodebug.ctc>=D_CONFW) printf("--- CTC chan %d TC set to 0x%02x.\n",port,data);
return;
}
if (data&0x01) { // control word
thisctc->ints_enabled=(data&0x80)?1:0;
thisctc->tc_next=(data&0x04)?1:0;
thisctc->prescaler=(data&0x20)?255:15;
if (iodebug.ctc>=D_CONFW) printf("--- CTC chan %d config word set: 0x%02x. ",port,data);
if (thisctc->ints_enabled) {
if (iodebug.ctc>=D_CONFW) printf("(ints enabled)\n");
} else {
if (iodebug.ctc>=D_CONFW) printf("(ints disabled)\n");
}
} else { // vector word
// there is only one interrupt vector register on the chip, since
// bits 2-1 contain the channel.
//thisctc->ivector=(data&0xf8)|(port<<1);
// set vector for all channels. TODO: this needs to be cleaner.
ctc[0].ivector=data&0xf8;
ctc[0].ivector=data&0xf8;
ctc[0].ivector=data&0xf8;
ctc[0].ivector=data&0xf8;
if (iodebug.ctc>=D_CONFW) printf("--- CTC chan %d ivector set: 0x%02x.\n",port,data);
}
}
/* --- DART / SIO ---
*
* Implemented: async mode, polling
* Unimplemented: all other modes, interrupts (TODO)
*
* Notes:
* - there are a few differences between chan A and B, but they are
* treated identically here.
*/
static BYTE p_dart_in(BYTE port) {
port&=0x03;
char chan='A'+(port&0x01);
char pre[17]; // preamble of our status lines
sprintf(pre,"--- DART chan %c:",chan);
BYTE resp=0x00;
dart_state *thisdart=&dart[port&0x01];
if (port&0x02) { // control
switch (thisdart->reg_ptr) {
case 0: // RR0
// check the socket before we can respond
// TODO: read directly into cb
thisdart->recvlen=recvfrom(thisdart->sock,thisdart->rx_buf,DART_BUFSIZE,MSG_DONTWAIT,
(struct sockaddr *)&(thisdart->remaddr),&(thisdart->addrlen));
if (iodebug.dart>=D_ALL) printf("!-- DART %c sock read, %d bytes returned from %s.\n",
chan,thisdart->recvlen,inet_ntoa(thisdart->remaddr.sin_addr));
if ((thisdart->recvlen)&&(!thisdart->have_client)) thisdart->have_client++;
int i;
if (thisdart->recvlen) {
for (i=0;i<thisdart->recvlen;i++) {
if ((thisdart->cbused)<DART_BUFSIZE) {
thisdart->rx_fifo[thisdart->cbtail++]=thisdart->rx_buf[i];
if (thisdart->cbtail>DART_BUFSIZE) thisdart->cbtail-=DART_BUFSIZE; // handle wrap
thisdart->cbused++;
} else {
thisdart->rx_buf_overrun=1; // TODO: document this behavior
if (iodebug.dart>=D_ALL) printf("!-- DART %c rx buf overrun: %d bytes.\n",
chan,thisdart->recvlen);
}
}
}
// separate but latched like this for flexibility later
thisdart->rx_char_avail=thisdart->cbused;
// D7: break/abort. never happens here. could emulate later if needed.
// D6: Tx underrun
resp|=thisdart->cts_<<5; // CTS D5
// D4: SYNC/Hunt. unused for async.
resp|=thisdart->dcd_<<3; // DCD D3
resp|=0x04; // tx buffer is always empty in our case (D2)
// D1: int pending (no ints emulated)
resp|=(thisdart->rx_char_avail>0); // rx char available D0
break;
case 1: // RR1
// D7: SDLC end of frame (unused in async)
// D6: framing error (wont happen here, but could emulate later if needed)
resp|=(thisdart->rx_buf_overrun<<5); // D5: rx overrun
// D4: parity error (we arent implementing parity)
// D3-1: SDLC residue codes (unused in async)
resp|=thisdart->all_sent; // D0: all sent
break;
default:
if (iodebug.dart>=D_ERR) printf("%s RR%d requested, but nonexistant.\n",pre,thisdart->reg_ptr);
}
thisdart->reg_ptr=0; // back to 0 after any read or write
return resp;
} else { // data
if (thisdart->cbused) {
resp=thisdart->rx_fifo[thisdart->cbhead++];
if (thisdart->cbhead>DART_BUFSIZE) thisdart->cbhead-=DART_BUFSIZE;
thisdart->cbused--;
return resp;
} else {
if (iodebug.dart>=D_ERR) printf("--! DART: read from empty buf!\n");
}
return 0x00;
/*if (thisdart->rx_char_avail) {
thisdart->rx_char_avail--;
return thisdart->rx_buf[0];
} else {
printf("%s read, but no chars available.\n",pre);
return 0x00;
}*/
}
//return((BYTE) getchar());
}
static void p_dart_out(BYTE port,BYTE data) {
port&=0x03;
char chan='A'+(port&0x01);
char pre[17]; // preamble of our status lines
sprintf(pre,"--- DART chan %c:",chan);
dart_state *thisdart=&dart[port&0x01];
// TODO: split this up and make it easier to read
if (port&0x02) { // control word
if (thisdart->reg_ptr) { // write is to a config reg (WR1-7)
switch (thisdart->reg_ptr) {
case 1: // WR1: interrupt config
if (data) { // nonzero interrupt flags set (unimplemented)
if (iodebug.dart>=D_UNIMPL) printf("%s WR1 (ints) written (0x%02x), but unimplemented.\n",
pre,data);
} else {
if (iodebug.dart>=D_CONFW) printf("%s WR1 (ints) set zero.\n",pre);
}
thisdart->interrupt_mode=data;
break;
case 2: // WR2: interrupt vector
if ('A'==chan) {
if (iodebug.dart>=D_ERR)
printf("%s WR2 (int vector) written, but exists only in B. (0x%02x)\n",pre,data);
} else {
if (iodebug.dart>=D_UNIMPL)
printf("%s WR2 (int vector) written, but unimplemented. (0x%02x)\n",pre,data);
}
break;
case 3: // WR3: rx config
if (0xc0!=(data&0xc0)) {
if (iodebug.dart>=D_UNIMPL) printf("%s WR3: RX bits set, but not 8! (0x%02x)\n",pre,data);
} else {
thisdart->rx_bits=8;
if (iodebug.dart>=D_CONFW) printf("%s WR3: RX bits set to 8.\n",pre);
}
if (data&0x3e) {
if (iodebug.dart>=D_UNIMPL)
printf("%s WR3: unimplemented conf requested! (0x%02x)\n",pre,data);
}
break;
case 4: // WR4: prescaler, parity, stop bits
thisdart->parity=data&0x01;
if (thisdart->parity) {
if (iodebug.dart>=D_UNIMPL) printf("%s WR4: parity requested, but unimplemented.\n",pre);
} else {
if (iodebug.dart>=D_CONFW) printf("%s WR4: parity set to none.\n",pre);
}
// D1 is odd/even parity, but not checking since we dont care
thisdart->stopbits=(data&0x0c)>>2;
if (1==thisdart->stopbits) {
if (iodebug.dart>=D_CONFW) printf("%s WR4: 1 stop bit selected.\n",pre);
} else {
if (iodebug.dart>=D_UNIMPL) printf("%s WR4: stop bits val %d set, but unimplemented.\n",
pre,thisdart->stopbits);
}
// these magic numbers just set clk_prescale to the values listed on pg 286
// of the Z80 peripherals doc (UM008101-0601)
thisdart->clk_prescale=(data&0xc0)>>6;
thisdart->clk_prescale=(thisdart->clk_prescale)?(0x10<<(thisdart->clk_prescale-1)):1;
if (iodebug.dart>=D_CONFW)
printf("%s WR4: clock prescale set to x%d.\n",pre,thisdart->clk_prescale);
break;
case 5: // WR5: tx config and status pins
if (data&0x01) {
if (iodebug.dart>=D_UNIMPL) printf("%s WR5: Tx CRC enabled, but unimplemented.\n",pre);
}
// normally in async mode, the RTS pin is only active (low) after all bits
// are sent, but since we aren't emulating at that level, we allow it to be
// set manually here.
thisdart->rts_=(data&0x02)?0:1; // active low
thisdart->tx_enabled=(data&0x08)>>3;
// since we arent emulating at this low a level, support only 8 bits per char
thisdart->tx_bits=8;
if ((data&0x60)==0x60) {
if (iodebug.dart>=D_CONFW) printf("%s WR5: Tx bits/char set to 8.\n",pre);
} else {
if (iodebug.dart>=D_UNIMPL)
printf("%s WR5: Tx bits/char set to something other than 8! Using 8.\n",pre);
}
thisdart->dtr_=(data&0x80)?0:1; // active low
break;
default: // WR6 and WR7 exist in the SIO series only
if (iodebug.dart>=D_UNIMPL)
printf("%s WR%d written (0x%02x), but unimplemented.\n",pre,thisdart->reg_ptr,data);
}
thisdart->reg_ptr=0; // following read or write to any reg, ptr set back to WR0
} else { // this write is to WR0
thisdart->reg_ptr=data&0x07; // last 3 bits set the WR pointer
if (iodebug.dart>=D_INFO) printf("%s reg pointer set to %d.\n",pre,thisdart->reg_ptr);
BYTE cmd=(data&0x38)>>3;
// if cmd is 'channel reset', do it:
if (3==cmd) {
if (iodebug.dart>=D_WARN) printf("%s Channel reset.\n",pre);
dart_reset(thisdart);
} else if (cmd) { // otherwise if it's not the 'Null' cmd, complain:
if (iodebug.dart>=D_UNIMPL) printf("%s Unimplemented CMD bits written to WR0: 0x%02x\n",
pre,data);
}
if (data&0xc0) {
if (iodebug.dart>=D_UNIMPL) printf("%s CRC reset requested, but unimplemented.\n",pre);
}
}
} else { // data word
// TODO: error if configuration is wack
if (iodebug.dart>=D_RWOPS) printf("%s write: 0x%02x.\n",pre,data);
if (thisdart->have_client) {
if (0>sendto(thisdart->sock,&data,1,0,(struct sockaddr *)&(thisdart->remaddr),
thisdart->addrlen))
perror("sendto");
}
}
/*if (!(port&0x03)) { // serial console: ch A data
putchar((int)data);
fflush(stdout);
return;
}*/
}
static void dart_reset(dart_state *dart) {
dart->clk_prescale=0;
dart->rx_bits=0;
dart->tx_bits=0;
dart->rx_enabled=0;
dart->tx_enabled=0;
dart->stopbits=0;
dart->parity=0;
dart->interrupt_mode=0;
dart->reg_ptr=0;
dart->tx_buf_empty=1;
dart->rx_char_avail=0;
dart->all_sent=0;
dart->rx_buf_overrun=0;
dart->cbhead=0;
dart->cbtail=0;
dart->cbused=0;
}