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