1 /*
2 * Z80SIM - a Z80-CPU simulator
3 *
4 * Common I/O devices used by various simulated machines
5 *
6 * Copyright (C) 2014-2019 by Udo Munk
7 *
8 * Emulation of a Tarbell SD 1011D S100 board
9 *
10 * History:
11 * 13-MAR-2014 first fully working version
12 * 15-MAR-2014 some improvements for CP/M 1.3 & 1.4
13 * 17-MAR-2014 close(fd) was missing in write sector lseek error case
14 * AUG-2014 some improvements
15 * 22-JAN-2015 fixed buggy ID field
16 * 11-FEB-2015 implemented write track
17 * 08-MAR-2016 support user path for disk images
18 * 13-MAY-2016 find disk images at -d <path>, ./disks and DISKDIR
19 * 22-JUL-2016 added support for read only disks
20 * 13-JUN-2017 added bootstrap ROM and reset function
21 * 23-APR-2018 cleanup
22 * 01-JUL-2018 check disk images for the correct size
23 * 15-JUL-2018 use logging
24 * 23-SEP-2019 bug fixes and improvements by Mike Douglas
25 * 24-SEP-2019 restore and seek also affect step direction
26 */
27
28 #include <unistd.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <sys/stat.h>
32 #include <fcntl.h>
33 #include "sim.h"
34 #include "simglb.h"
35 #include "log.h"
36
37 /* internal state of the fdc */
38 #define FDC_IDLE 0 /* idle state */
39 #define FDC_READ 1 /* reading sector */
40 #define FDC_WRITE 2 /* writing sector */
41 #define FDC_READADR 3 /* read address */
42 #define FDC_WRTTRK 4 /* write (format) track */
43
44 /* 8" standard disks */
45 #define SEC_SZ 128
46 #define SPT 26
47 #define TRK 77
48
49 static const char *TAG = "Tarbell";
50
51 static BYTE fdc_stat; /* status register */
52 static BYTE fdc_track; /* track register */
53 static BYTE fdc_sec; /* sector register */
54 static int disk; /* current disk # */
55 static int state; /* fdc state */
56 static char fn[MAX_LFN]; /* path/filename for disk image */
57 static int fd; /* fd for disk file i/o */
58 static int dcnt; /* data counter read/write */
59 static BYTE buf[SEC_SZ]; /* buffer for one sector */
60 static int stepdir = -1; /* stepping direction */
61
62 /* these are our disk drives */
63 static char *disks[4] = {
64 "drivea.dsk",
65 "driveb.dsk",
66 "drivec.dsk",
67 "drived.dsk"
68 };
69
70 /* bootstrap ROM */
71 BYTE tarbell_rom[32] = {
72 0xdb, 0xfc, 0xaf, 0x6f, 0x67, 0x3c, 0xd3, 0xfa,
73 0x3e, 0x8c, 0xd3, 0xf8, 0xdb, 0xfc, 0xb7, 0xf2,
74 0x19, 0x00, 0xdb, 0xfb, 0x77, 0x23, 0xc3, 0x0c,
75 0x00, 0xdb, 0xf8, 0xb7, 0xca, 0x7d, 0x00, 0x76 };
76
77 int tarbell_rom_enabled = 0; /* ROM not enabled by default */
78 int tarbell_rom_active = 1; /* ROM is active at power on */
79
80 /*
81 * find and set path for disk images
82 */
dsk_path(void)83 void dsk_path(void) {
84 struct stat sbuf;
85
86 /* if option -d is used disks are there */
87 if (diskdir != NULL) {
88 strcpy(fn, diskd);
89 } else {
90 /* if not first try ./disks */
91 if ((stat("./disks", &sbuf) == 0) && S_ISDIR(sbuf.st_mode)) {
92 strcpy(fn, "./disks");
93 /* nope, then DISKSDIR as set in Makefile */
94 } else {
95 strcpy(fn, DISKSDIR);
96 }
97 }
98 }
99
100 /*
101 * FDC status port
102 */
tarbell_stat_in(void)103 BYTE tarbell_stat_in(void)
104 {
105 return(fdc_stat);
106 }
107
108 /*
109 * FDC command port
110 */
tarbell_cmd_out(BYTE data)111 void tarbell_cmd_out(BYTE data)
112 {
113 if ((data & 0xf0) == 0) { /* restore (seek track 0) */
114 if (fdc_track != 0)
115 stepdir = -1;
116 fdc_track = 0;
117 fdc_stat = 0x04; /* assert track 0 flag */
118
119 } else if ((data & 0xf0) == 0x10) { /* seek */
120 if (fdc_track < TRK)
121 fdc_stat = 0;
122 else
123 fdc_stat = 0x10; /* seek error (assume V set) */
124 if (fdc_track == 0) /* check to set track 0 flag */
125 fdc_stat |= 0x04;
126
127 } else if ((data & 0xe0) == 0x20) { /* step last direction */
128 fdc_track += stepdir;
129 if (fdc_track < TRK)
130 fdc_stat = 0;
131 else
132 fdc_stat = 0x10; /* seek error (assume V set */
133 if (fdc_track == 0) /* check to set track 0 flag */
134 fdc_stat |= 0x04;
135
136 } else if ((data & 0xe0) == 0x40) { /* step in */
137 stepdir = 1;
138 if (data & 0x10)
139 fdc_track++;
140 if (fdc_track < TRK)
141 fdc_stat = 0;
142 else
143 fdc_stat = 0x10; /* seek error (assume V set) */
144 if (fdc_track == 0) /* check to set track 0 flag */
145 fdc_stat |= 0x04;
146
147 } else if ((data & 0xe0) == 0x60) { /* step out */
148 stepdir = -1;
149 if (data & 0x10)
150 fdc_track--;
151 if (fdc_track < TRK)
152 fdc_stat = 0;
153 else
154 fdc_stat = 0x10; /* seek error (assume V set) */
155 if (fdc_track == 0) /* check to set track 0 flag */
156 fdc_stat |= 0x04;
157
158 } else if ((data & 0xf0) == 0x80) { /* read single sector */
159 state = FDC_READ;
160 dcnt = 0;
161 fdc_stat = 0;
162
163 } else if ((data & 0xf0) == 0x90) { /* read multiple sector */
164 LOGW(TAG, "read multiple sectors not implemented");
165 fdc_stat = 0x10; /* record not found */
166
167 } else if ((data & 0xf0) == 0xa0) { /* write single sector */
168 state = FDC_WRITE;
169 dcnt = 0;
170 fdc_stat = 0;
171
172 } else if ((data & 0xf0) == 0xb0) { /* write multiple sector */
173 LOGW(TAG, "write multiple sectors not implemented");
174 fdc_stat = 0x10; /* record not found */
175
176 } else if (data == 0xc4) { /* read address */
177 state = FDC_READADR;
178 dcnt = 0;
179 fdc_stat = 0;
180
181 } else if ((data & 0xf0) == 0xe0) { /* read track */
182 LOGW(TAG, "read track not implemented");
183 fdc_stat = 0x10; /* record not found */
184
185 } else if ((data & 0xf0) == 0xf0) { /* write track */
186 state = FDC_WRTTRK;
187 dcnt = 0;
188 fdc_stat = 0;
189
190 } else if ((data & 0xf0) == 0xd0) { /* force interrupt */
191 state = FDC_IDLE; /* abort any command */
192 fdc_stat = 0;
193
194 } else {
195 LOGW(TAG, "unknown command, %02x", data);
196 fdc_stat = 8; /* CRC error */
197 }
198 }
199
200 /*
201 * FDC track input port
202 */
tarbell_track_in(void)203 BYTE tarbell_track_in(void)
204 {
205 return(fdc_track);
206 }
207
208 /*
209 * FDC track output port
210 */
tarbell_track_out(BYTE data)211 void tarbell_track_out(BYTE data)
212 {
213 fdc_track = data;
214 }
215
216 /*
217 * FDC sector input port
218 */
tarbell_sec_in(void)219 BYTE tarbell_sec_in(void)
220 {
221 return(fdc_sec);
222 }
223
224 /*
225 * FDC sector output port
226 */
tarbell_sec_out(BYTE data)227 void tarbell_sec_out(BYTE data)
228 {
229 fdc_sec = data;
230 }
231
232 /*
233 * FDC data input port
234 */
tarbell_data_in(void)235 BYTE tarbell_data_in(void)
236 {
237 long pos; /* seek position */
238 struct stat s;
239
240 switch (state) {
241 case FDC_READ: /* read data from disk sector */
242
243 /* first byte? */
244 if (dcnt == 0) {
245
246 /* check track/sector */
247 if ((fdc_track >= TRK) || (fdc_sec > SPT)) {
248 state = FDC_IDLE; /* abort command */
249 fdc_stat = 0x10; /* record not found */
250 return((BYTE) 0);
251 }
252
253 /* check disk drive */
254 if ((disk < 0) || (disk > 3)) {
255 state = FDC_IDLE; /* abort command */
256 fdc_stat = 0x80; /* not ready */
257 return((BYTE) 0);
258 }
259
260 /* try to open disk image */
261 dsk_path();
262 strcat(fn, "/");
263 strcat(fn, disks[disk]);
264 if ((fd = open(fn, O_RDONLY)) == -1) {
265 state = FDC_IDLE; /* abort command */
266 fdc_stat = 0x80; /* not ready */
267 return((BYTE) 0);
268 }
269
270 /* check for correct image size */
271 fstat(fd, &s);
272 if (s.st_size != 256256) {
273 state = FDC_IDLE; /* abort command */
274 fdc_stat = 0x80; /* not ready */
275 close(fd);
276 return((BYTE) 0);
277 }
278
279 /* seek to sector */
280 pos = (fdc_track * SPT + fdc_sec - 1) * SEC_SZ;
281 if (lseek(fd, pos, SEEK_SET) == -1L) {
282 state = FDC_IDLE; /* abort command */
283 fdc_stat = 0x10; /* record not found */
284 close(fd);
285 return((BYTE) 0);
286 }
287
288 /* read the sector */
289 if (read(fd, &buf[0], SEC_SZ) != SEC_SZ) {
290 state = FDC_IDLE; /* abort read command */
291 fdc_stat = 0x10; /* record not found */
292 close(fd);
293 return((BYTE) 0);
294 }
295 close(fd);
296 }
297
298 /* last byte? */
299 if (dcnt == SEC_SZ - 1) {
300 state = FDC_IDLE; /* reset DRQ */
301 fdc_stat = 0;
302 }
303
304 /* return byte from buffer and increment counter */
305 return(buf[dcnt++]);
306 break;
307
308 case FDC_READADR: /* read disk address */
309
310 /* first byte? */
311 if (dcnt == 0) {
312 buf[0] = fdc_track; /* build address field */
313 buf[1] = 0;
314 buf[2] = fdc_sec;
315 buf[3] = 0;
316 buf[4] = 0;
317 buf[5] = 0;
318 }
319
320 /* last byte? */
321 if (dcnt == 5) {
322 state = FDC_IDLE; /* reset DRQ */
323 fdc_stat = 0;
324 }
325
326 /* return byte from buffer and increment counter */
327 return(buf[dcnt++]);
328 break;
329
330 default:
331 return((BYTE) 0);
332 break;
333 }
334 }
335
336 /*
337 * FDC data output port
338 */
tarbell_data_out(BYTE data)339 void tarbell_data_out(BYTE data)
340 {
341 long pos; /* seek position */
342 static int wrtstat; /* state while formatting track */
343 static int bcnt; /* byte counter for sector data */
344 static int secs; /* # of sectors written so far */
345 struct stat s;
346
347 switch (state) {
348 case FDC_WRITE: /* write data to disk sector */
349 if (dcnt == 0) {
350
351 /* check track/sector */
352 if ((fdc_track >= TRK) || (fdc_sec > SPT)) {
353 state = FDC_IDLE; /* abort command */
354 fdc_stat = 0x10; /* record not found */
355 return;
356 }
357
358 /* check disk drive */
359 if ((disk < 0) || (disk > 3)) {
360 state = FDC_IDLE; /* abort command */
361 fdc_stat = 0x80; /* not ready */
362 return;
363 }
364
365 /* try to open disk image */
366 dsk_path();
367 strcat(fn, "/");
368 strcat(fn, disks[disk]);
369 if ((fd = open(fn, O_RDWR)) == -1) {
370 if ((fd = open(fn, O_RDONLY)) != -1) {
371 close(fd);
372 fdc_stat = 0x40; /* read only */
373 } else {
374 fdc_stat = 0x80; /* not ready */
375 }
376 state = FDC_IDLE; /* abort command */
377 return;
378 }
379
380 /* check for correct image size */
381 fstat(fd, &s);
382 if (s.st_size != 256256) {
383 state = FDC_IDLE; /* abort command */
384 fdc_stat = 0x80; /* not ready */
385 close(fd);
386 return;
387 }
388
389 /* seek to sector */
390 pos = (fdc_track * SPT + fdc_sec - 1) * SEC_SZ;
391 if (lseek(fd, pos, SEEK_SET) == -1L) {
392 state = FDC_IDLE; /* abort command */
393 fdc_stat = 0x10; /* record not found */
394 close(fd);
395 return;
396 }
397 }
398
399 /* write data bytes into sector buffer */
400 buf[dcnt++] = data;
401
402 /* last byte? */
403 if (dcnt == SEC_SZ) {
404 state = FDC_IDLE; /* reset DRQ */
405 if (write(fd, &buf[0], SEC_SZ) == SEC_SZ)
406 fdc_stat = 0;
407 else
408 fdc_stat = 0x20; /* write fault */
409 close(fd);
410 }
411 break;
412
413 case FDC_WRTTRK: /* write (format) TRACK */
414 if (dcnt == 0) {
415 /* unlink disk image */
416 dsk_path();
417 strcat(fn, "/");
418 strcat(fn, disks[disk]);
419 if (fdc_track == 0)
420 unlink(fn);
421 /* try to create new disk image */
422 if ((fd = open(fn, O_RDWR|O_CREAT, 0644)) == -1) {
423 state = FDC_IDLE; /* abort command */
424 fdc_stat = 0x80; /* not ready */
425 return;
426 }
427 /* seek to track */
428 pos = fdc_track * SPT * SEC_SZ;
429 if (lseek(fd, pos, SEEK_SET) == -1L) {
430 state = FDC_IDLE; /* abort command */
431 fdc_stat = 0x10; /* record not found */
432 close(fd);
433 return;
434 }
435 /* now wait for sector data */
436 wrtstat = 1;
437 secs = 0;
438 }
439 dcnt++;
440 /* wait for sector data address mark */
441 if (wrtstat == 1) {
442 if (data == 0xfb) {
443 wrtstat = 2;
444 bcnt = 0;
445 }
446 return;
447 }
448 /* collect bytes in buffer and write if sector complete */
449 if (wrtstat == 2) {
450 if (data != 0xf7) {
451 buf[bcnt++] = data;
452 return;
453 } else {
454 secs++;
455 if (write(fd, buf, bcnt) == bcnt)
456 fdc_stat = 0;
457 else
458 fdc_stat = 0x20; /* write fault */
459 wrtstat = 1;
460 }
461 }
462 /* all sectors of track written? */
463 if (secs == SPT) {
464 state = FDC_IDLE;
465 close(fd);
466 }
467 break;
468
469 default: /* track # for seek */
470 if (fdc_track != data)
471 stepdir = (data < fdc_track) ? -1 : 1;
472 fdc_track = data;
473 break;
474 }
475 }
476
477 /*
478 * FDC wait input port
479 */
tarbell_wait_in(void)480 BYTE tarbell_wait_in(void)
481 {
482 if (state == FDC_IDLE)
483 return((BYTE) 0); /* don't wait for drive mechanics */
484 else
485 return((BYTE) 0x80); /* but wait on DRQ */
486 }
487
488 /*
489 * FDC extended command output port
490 */
tarbell_ext_out(BYTE data)491 void tarbell_ext_out(BYTE data)
492 {
493 disk = ~data >> 4 & 3; /* get disk # */
494 }
495
496 /*
497 * Reset FDC
498 */
tarbell_reset(void)499 void tarbell_reset(void)
500 {
501 fdc_stat = fdc_track = fdc_sec = disk = state = dcnt = 0;
502 tarbell_rom_active = 1;
503 }
504