1 /*-----------------------------------------------------------------------*\
2  |  bios.c  -- CP/M emulator "front-end" for the Z80 emulator  --  runs  |
3  |  standard CP/M executables with no changes as long as they use CP/M   |
4  |  system calls to do all their work.                                   |
5  |                                                                       |
6  |  Originally by Kevin Kayes, but he refused any responsibility for it, |
7  |  then I later hacked it up, enhanced it, and debugged it.             |
8  |                                                                       |
9  |  Copyright 1986-1988 by Parag Patel.  All Rights Reserved.            |
10  |  Copyright 1994-1995,2000 by CodeGen, Inc.  All Rights Reserved.           |
11 \*-----------------------------------------------------------------------*/
12 
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <ctype.h>
16 #include <string.h>
17 #include <time.h>
18 #include <sys/types.h>
19 #include "cpmdisc.h"
20 #include "defs.h"
21 
22 #ifdef macintosh
23 #include <stat.h>
24 #else
25 #include <sys/stat.h>
26 #endif
27 
28 /* definition of: extern unsigned char	cpm_array[]; */
29 #include "cpm.c"
30 
31 /* The BDOS/CCP had better be built with these values! */
32 #define CCP		0xD400 /* (BIOS - 0x1600) */
33 #define BDOS		0xDC00
34 #define CBIOS		0xEA00
35 
36 #define NENTRY		30	/* number of BIOS entries */
37 #define STACK		0xEF00	/* grows down from here */
38 
39 /* The first NUMHDISCS drives may be specified as hard-drives. */
40 #define NUMHDISCS	2
41 #define NUMDISCS	(MAXDISCS - NUMHDISCS)
42 
43 #if NUMHDISCS > MAXDISCS
44 #	error	Too many hard-discs specified here.
45 #endif
46 
47 /* disk parameter block information */
48 #define DPBSIZE		15
49 #define HDPBLOCK	(CBIOS + NENTRY * 8)
50 #define DPBLOCK		(HDPBLOCK + DPBSIZE)
51 #define DIRBUF		(DPBLOCK + DPBSIZE)
52 #define DPHSIZE		16
53 
54 /* hard disc parameter info */
55 #define HDPBASE		(DIRBUF + SECTORSIZE)
56 
57 #define DPBASE		(HDPBASE + DPHSIZE * NUMHDISCS)
58 #define CVSIZE		0
59 #define ALVSIZE		33
60 #define CVBASE		(DPBASE + DPHSIZE * NUMDISCS)
61 #define ALVBASE		(CVBASE + CVSIZE * NUMDISCS)
62 
63 /* ST-506 allocation vector size */
64 #define HALVBASE	(ALVBASE + ALVSIZE * NUMDISCS)
65 #define HALVSIZE	306
66 
67 /* buffer for where to return time/date info */
68 #define TIMEBUF		(HALVBASE + HALVSIZE * NUMHDISCS)
69 #define TIMEBUFSIZE	5
70 
71 /* just a marker for future expansion */
72 #define END_OF_BIOS	(TIMEBUF + TIMEBUFSIZE)
73 
74 
75 /* ST-506 HD sector info (floppy defs are in cpmdisc.h for makedisc.c) */
76 #define	HDSECTORSPERTRACK	64
77 #define	HDTRACKSPERDISC		610
78 
79 /* offsets into FCB needed for reading/writing Unix files */
80 #define FDOFFSET	12
81 #define BLKOFFSET	16
82 #define SZOFFSET	20
83 
84 /* this batch of macros are not used at the moment */
85 #define CBUFF		CCP+7
86 #define CIBUFF		CCP+8
87 #define DEFAULTFCB	0x005C
88 #define DEFAULTBUF	0x0080
89 #define IOBYTE		0x0003
90 #define DISK		0x0004
91 #define DMA		0x0008
92 #define USER		0x000A
93 #define USERSTART	0x0100
94 
95 
96 /* forward declarations: */
97 static void seldisc(z80info *z80);
98 
99 
100 static void
closeall(z80info * z80)101 closeall(z80info *z80)
102 {
103 	int	i;
104 
105 	for (i = 0; i < MAXDISCS; i++)
106 	{
107 		if (z80->drives[i] != NULL)
108 		{
109 			fclose(z80->drives[i]);
110 			z80->drives[i] = NULL;
111 		}
112 	}
113 }
114 
115 void
warmboot(z80info * z80)116 warmboot(z80info *z80)
117 {
118 	int i;
119 
120 	closeall(z80);
121 
122 	if (silent_exit) {
123 		finish(z80);
124 	}
125 
126 	/* load CCP and BDOS into memory (max 0x1600 in size) */
127 	for (i = 0; i < 0x1600 && i < sizeof cpm_array; i++)
128 		SETMEM(CCP + i, cpm_array[i]);
129 
130 	/* try to load CCP/BDOS from disk, but ignore any errors */
131 	loadfile(z80, "bdos.hex");
132 	loadfile(z80, "ccp.hex");
133 
134 	/* CP/M system reset via "JP 00" - entry into BIOS warm-boot */
135 	SETMEM(0x0000, 0xC3);		/* JP CBIOS+3 */
136 	SETMEM(0x0001, ((CBIOS + 3) & 0xFF));
137 	SETMEM(0x0002, ((CBIOS + 3) >> 8));
138 
139 	/* 0x0003 is the IOBYTE, 0x0004 is the current DISK */
140 	SETMEM(0x0003, 0x00);
141 	SETMEM(0x0004, z80->drive);
142 
143 	/* CP/M syscall via "CALL 05" - entry into BDOS */
144 	SETMEM(0x0005, 0xC3);		/* JP BDOS+6 */
145 	SETMEM(0x0006, ((BDOS+6) & 0xFF));
146 	SETMEM(0x0007, ((BDOS+6) >> 8));
147 
148 	/* fake BIOS entry points */
149 	for (i = 0; i < NENTRY; i++)
150 	{
151 		/* JP <bios-entry> */
152 		SETMEM(CBIOS + 3 * i, 0xC3);
153 		SETMEM(CBIOS + 3 * i + 1, (CBIOS + NENTRY * 3 + i * 5) & 0xFF);
154 		SETMEM(CBIOS + 3 * i + 2, (CBIOS + NENTRY * 3 + i * 5) >> 8);
155 
156 		/* LD A,<bios-call> - start of bios-entry */
157 		SETMEM(CBIOS + NENTRY * 3 + i * 5, 0x3E);
158 		SETMEM(CBIOS + NENTRY * 3 + i * 5 + 1, i);
159 
160 		/* OUT A,0FFH - we use port 0xFF to fake the BIOS call */
161 		SETMEM(CBIOS + NENTRY * 3 + i * 5 + 2, 0xD3);
162 		SETMEM(CBIOS + NENTRY * 3 + i * 5 + 3, 0xFF);
163 
164 		/* RET - end of bios-entry */
165 		SETMEM(CBIOS + NENTRY * 3 + i * 5 + 4, 0xC9);
166 	}
167 
168 	/* disc parameter block - a 5Mb ST-506 hard disc */
169 	SETMEM(HDPBLOCK, HDSECTORSPERTRACK & 0xFF);/* SPT - sectors per track */
170 	SETMEM(HDPBLOCK + 1, HDSECTORSPERTRACK >> 8);
171 	SETMEM(HDPBLOCK + 2, 4);	/* BSH - data block shift factor */
172 	SETMEM(HDPBLOCK + 3, 15);		/* BLM - data block mask */
173 	SETMEM(HDPBLOCK + 4, 0);		/* EXM - extent mask */
174 	SETMEM(HDPBLOCK + 5, 2441 & 0xFF);	/* DSM - total drive capacity */
175 	SETMEM(HDPBLOCK + 6, 2441 >> 8);
176 	SETMEM(HDPBLOCK + 7, 1023 & 0xFF);	/* DRM - total dir entries */
177 	SETMEM(HDPBLOCK + 8, 1023 >> 8);
178 	SETMEM(HDPBLOCK + 9, 0xFF);	/* AL0 - blocks for directory entries */
179 	SETMEM(HDPBLOCK + 10, 0xFF);	/* AL1 */
180 	SETMEM(HDPBLOCK + 11, 0x00);	/* CKS - directory check vector */
181 	SETMEM(HDPBLOCK + 12, 0x00);
182 	SETMEM(HDPBLOCK + 13, RESERVEDTRACKS & 0xFF);/* OFF - reserved tracks */
183 	SETMEM(HDPBLOCK + 14, RESERVEDTRACKS >> 8);
184 
185 	/* disk parameter headers for hard disc */
186 	for (i = 0; i < NUMHDISCS; i++)
187 	{
188 		SETMEM(HDPBASE + DPHSIZE * i, 0x00);	/* XLT */
189 		SETMEM(HDPBASE + DPHSIZE * i + 1, 0x00);
190 		SETMEM(HDPBASE + DPHSIZE * i + 2, 0x00); /* scratch 1 */
191 		SETMEM(HDPBASE + DPHSIZE * i + 3, 0x00);
192 		SETMEM(HDPBASE + DPHSIZE * i + 4, 0x00); /* scratch 2 */
193 		SETMEM(HDPBASE + DPHSIZE * i + 5, 0x00);
194 		SETMEM(HDPBASE + DPHSIZE * i + 6, 0x00); /* scratch 3 */
195 		SETMEM(HDPBASE + DPHSIZE * i + 7, 0x00);
196 		SETMEM(HDPBASE + DPHSIZE * i + 8, DIRBUF & 0xFF); /* DIRBUF */
197 		SETMEM(HDPBASE + DPHSIZE * i + 9, DIRBUF >> 8);
198 		SETMEM(HDPBASE + DPHSIZE * i + 10, HDPBLOCK & 0xFF); /* DPB */
199 		SETMEM(HDPBASE + DPHSIZE * i + 11, HDPBLOCK >> 8);
200 		SETMEM(HDPBASE + DPHSIZE * i + 12, 0x00);
201 		SETMEM(HDPBASE + DPHSIZE * i + 13, 0x00);
202 		SETMEM(HDPBASE + DPHSIZE * i + 14,
203 			(HALVBASE + HALVSIZE * i) & 0xFF);
204 		SETMEM(HDPBASE + DPHSIZE * i + 15,
205 			(HALVBASE + HALVSIZE * i) >> 8);
206 	}
207 
208 	/* disc parameter block - a single-sided single-density 8" 256k disc */
209 	SETMEM(DPBLOCK, SECTORSPERTRACK & 0xFF); /* SPT - sectors per track */
210 	SETMEM(DPBLOCK + 1, SECTORSPERTRACK >> 8);
211 	SETMEM(DPBLOCK + 2, 3);		/* BSH - data block shift factor */
212 	SETMEM(DPBLOCK + 3, 7);			/* BLM - data block mask */
213 	SETMEM(DPBLOCK + 4, 0);			/* EXM - extent mask */
214 	SETMEM(DPBLOCK + 5, 242);	/* DSM - total capacity of drive */
215 	SETMEM(DPBLOCK + 6, 0);
216 	SETMEM(DPBLOCK + 7, 63);	/* DRM - total directory entries */
217 	SETMEM(DPBLOCK + 8, 0);
218 	SETMEM(DPBLOCK + 9, 0xC0);	/* AL0 - blocks for directory entries */
219 	SETMEM(DPBLOCK + 10, 0x00);	/* AL1 */
220 	SETMEM(DPBLOCK + 11, 0x00);	/* CKS - directory check vector */
221 	SETMEM(DPBLOCK + 12, 0x00);
222 	SETMEM(DPBLOCK + 13, RESERVEDTRACKS & 0xFF); /* OFF - reserved tracks */
223 	SETMEM(DPBLOCK + 14, RESERVEDTRACKS >> 8);
224 
225 	/* disc parameter headers */
226 	for (i = 0; i < NUMDISCS; i++)
227 	{
228 		SETMEM(DPBASE + DPHSIZE * i, 0x00);	/* XLT */
229 		SETMEM(DPBASE + DPHSIZE * i + 1, 0x00);
230 		SETMEM(DPBASE + DPHSIZE * i + 2, 0x00); /* scratch 1 */
231 		SETMEM(DPBASE + DPHSIZE * i + 3, 0x00);
232 		SETMEM(DPBASE + DPHSIZE * i + 4, 0x00); /* scratch 2 */
233 		SETMEM(DPBASE + DPHSIZE * i + 5, 0x00);
234 		SETMEM(DPBASE + DPHSIZE * i + 6, 0x00); /* scratch 3 */
235 		SETMEM(DPBASE + DPHSIZE * i + 7, 0x00);
236 		SETMEM(DPBASE + DPHSIZE * i + 8, DIRBUF & 0xFF); /* DIRBUF */
237 		SETMEM(DPBASE + DPHSIZE * i + 9, DIRBUF >> 8);
238 		SETMEM(DPBASE + DPHSIZE * i + 10, DPBLOCK & 0xFF); /* DPB */
239 		SETMEM(DPBASE + DPHSIZE * i + 11, DPBLOCK >> 8);
240 #if (CVSIZE == 0)
241 		SETMEM(DPBASE + DPHSIZE * i + 12, 0x00);
242 		SETMEM(DPBASE + DPHSIZE * i + 13, 0x00);
243 #else
244 		SETMEM(DPBASE + DPHSIZE * i + 12,
245 			(CVBASE + CVSIZE * i) & 0xFF);
246 		SETMEM(DPBASE + DPHSIZE * i + 13,
247 			(CVBASE + CVSIZE * i) >> 8);
248 #endif
249 		SETMEM(DPBASE + DPHSIZE * i + 14,
250 			(ALVBASE + ALVSIZE * i) & 0xFF);
251 		SETMEM(DPBASE + DPHSIZE * i + 15,
252 			(ALVBASE + ALVSIZE * i) >> 8);
253 	}
254 
255 	/* set up the stack for an 8-level RET to do a system reset */
256 	SP = STACK;
257 
258 	for (i = 0; i < 8; i++)
259 	{
260 		/* push reboot entry (CBIOS + 3) onto stack */
261 		--SP;
262 		SETMEM(SP, (CBIOS + 3) >> 8);
263 		--SP;
264 		SETMEM(SP, (CBIOS + 3) & 0xFF);
265 	}
266 
267 	/* set up the default disk (A:) and dma address */
268 	z80->dma = 0x0080;
269 
270 	/* and all our default drive info */
271 	z80->track = 0;
272 	z80->sector = 1;
273 
274 	/* make sure the current file/disk is open */
275 	B = 0;
276 	C = z80->drive;
277 	seldisc(z80);
278 
279 	PC = CCP;
280 }
281 
282 static void
boot(z80info * z80)283 boot(z80info *z80)
284 {
285 	z80->drive = 0;
286 	warmboot(z80);
287 }
288 
289 void
sysreset(z80info * z80)290 sysreset(z80info *z80)
291 {
292 	boot(z80);
293 }
294 
295 static void
consstat(z80info * z80)296 consstat(z80info *z80)
297 {
298 	input(z80, 0x01, 0x01, &A);
299 }
300 
301 static void
consin(z80info * z80)302 consin(z80info *z80)
303 {
304 	input(z80, 0x00, 0x00, &A);
305 
306 /* What is this for? It messing up Ctrl-S...
307 	if (A == CNTL('S'))
308 		input(z80, 0x00, 0x00, &A);
309 */
310 }
311 
312 static void
consout(z80info * z80)313 consout(z80info *z80)
314 {
315 	output(z80, 0x00, 0x00, C & 0x7F);
316 }
317 
318 /* list character in C */
319 static void
list(z80info * z80)320 list(z80info *z80)
321 {
322 	static FILE *fp = NULL;
323 
324 	if (fp == NULL)
325 	{
326 		fp = fopen("list", "w");
327 
328 		if (fp == NULL)
329 			return;
330 	}
331 
332 	/* close up on EOF */
333 	if (C == CNTL('D') || C == '\0')
334 	{
335 		fclose(fp);
336 		fp = NULL;
337 		return;
338 	}
339 
340 	putc(C, fp);
341 }
342 
343 /* punch character in C */
344 static void
punch(z80info * z80)345 punch(z80info *z80)
346 {
347 }
348 
349 /* return reader char in A, ^Z is EOF */
350 static void
reader(z80info * z80)351 reader(z80info *z80)
352 {
353 	A = CNTL('Z');
354 }
355 
356 static void
home(z80info * z80)357 home(z80info *z80)
358 {
359 	z80->track = 0;
360 	z80->sector = 1;
361 }
362 
363 /* Open disk image */
364 
365 static void
realizedisk(z80info * z80)366 realizedisk(z80info *z80)
367 {
368 	int drive = z80->drive;
369 	char drivestr[80];
370 
371 	strcpy(drivestr, drive < NUMHDISCS ? "A-Hdrive" : "A-drive");
372 	drivestr[0] += drive; /* set the 1st letter to the drive name */
373 
374 	if (z80->drives[drive] == NULL)
375 	{
376 		struct stat statbuf;
377 		long secs;
378 		FILE *fp;
379 
380 		fp = fopen(drivestr, "rb+");
381 
382 		if (fp == NULL)
383 			fp = fopen(drivestr, "wb+");
384 
385 		if (fp == NULL)
386 		{
387 			fprintf(stderr, "seldisc(): Cannot open file '%s'!\r\n",
388 					drivestr);
389 			return;
390 		}
391 
392 		if (stat(drivestr, &statbuf) < 0)
393 		{
394 			fprintf(stderr, "seldisc(): Cannot stat file '%s'!\r\n",
395 					drivestr);
396 			fclose(fp);
397 			return;
398 		}
399 
400 		secs = statbuf.st_size / SECTORSIZE;
401 
402 		if (secs == 0)
403 		{
404 			char buf[SECTORSIZE];
405 			memset(buf, 0xE5, SECTORSIZE);
406 
407 			if (fwrite(buf, 1, SECTORSIZE, fp) != SECTORSIZE)
408 			{
409 				fprintf(stderr, "seldisc(): Cannot create file '%s'!\r\n",
410 						drivestr);
411 
412 				fclose(fp);
413 				return;
414 			}
415 
416 			secs = 1;
417 		}
418 
419 		/* printf(stderr,"\r\nOpen %s on drive %d\n", drivestr, drive); */
420 
421 		z80->drives[drive] = fp;
422 		z80->drivelen[drive] = secs * SECTORSIZE;
423 	}
424 }
425 
426 static void
seldisc(z80info * z80)427 seldisc(z80info *z80)
428 {
429 	H = 0;
430 	L = 0;
431 
432 	if (C >= MAXDISCS)
433 	{
434 		fprintf(stderr, "seldisc(): Attempt to open bogus drive %d\r\n",
435 			C);
436 		return;
437 	}
438 
439 	z80->drive = C;
440 
441 	if (z80->drive < NUMHDISCS)
442 	{
443 	    L = (HDPBASE + DPHSIZE * C) & 0xFF;
444 	    H = (HDPBASE + DPHSIZE * C) >> 8;
445 	}
446 	else
447 	{
448 	    L = (DPBASE + DPHSIZE * C) & 0xFF;
449 	    H = (DPBASE + DPHSIZE * C) >> 8;
450 	}
451 
452 	home(z80);
453 }
454 
455 static void
settrack(z80info * z80)456 settrack(z80info *z80)
457 {
458 	int tracks = (z80->drive < NUMHDISCS) ?
459 			HDTRACKSPERDISC : TRACKSPERDISC;
460 
461 	z80->track = (B << 8) + C;
462 
463 	if (z80->track < RESERVEDTRACKS || z80->track >= tracks)
464 		fprintf(stderr, "settrack(): bogus track %d!\r\n",
465 				z80->track);
466 }
467 
468 static void
setsector(z80info * z80)469 setsector(z80info *z80)
470 {
471 	int sectors = (z80->drive < NUMHDISCS) ?
472 			HDSECTORSPERTRACK : SECTORSPERTRACK;
473 
474 	z80->sector = (B << 8) + C;
475 
476 	if (z80->sector < SECTOROFFSET || z80->sector > sectors)
477 		fprintf(stderr, "setsector(): bogus sector %d!\r\n",
478 				z80->sector);
479 }
480 
481 static void
setdma(z80info * z80)482 setdma(z80info *z80)
483 {
484 	z80->dma = (B << 8) + C;
485 }
486 
487 
488 static void
rdsector(z80info * z80)489 rdsector(z80info *z80)
490 {
491 	int n;
492 	int drive = z80->drive;
493 	int sectors = (drive < NUMHDISCS) ? HDSECTORSPERTRACK : SECTORSPERTRACK;
494 	long offset = SECTORSIZE * ((long)z80->sector - SECTOROFFSET +
495 			sectors * ((long)z80->track - TRACKOFFSET));
496 	FILE *fp;
497 	long len;
498 	realizedisk(z80);
499 	fp = z80->drives[drive];
500 	len = z80->drivelen[drive];
501 
502 	if (fp == NULL)
503 	{
504 		fprintf(stderr, "rdsector(): file/drive %d not open!\r\n",
505 			drive);
506 		A = 1;
507 		return;
508 	}
509 
510 	if (len && offset >= len)
511 	{
512 	    memset(&(z80->mem[z80->dma]), 0xE5, SECTORSIZE);
513 	    A = 0;
514 	    return;
515 	}
516 
517 	if (fseek(fp, offset, SEEK_SET) != 0)
518 	{
519 		fprintf(stderr, "rdsector(): fseek failure offset=0x%lX!\r\n",
520 			offset);
521 		A = 1;
522 		return;
523 	}
524 
525 	n = fread(&(z80->mem[z80->dma]), 1, SECTORSIZE, fp);
526 
527 	if (n != SECTORSIZE)
528 	{
529 		fprintf(stderr, "rdsector(): read failure %d!\r\n", n);
530 		A = 1;
531 	}
532 	else
533 		A = 0;
534 }
535 
536 
537 static void
wrsector(z80info * z80)538 wrsector(z80info *z80)
539 {
540 	int drive = z80->drive;
541 	int sectors = (drive < NUMHDISCS) ? HDSECTORSPERTRACK : SECTORSPERTRACK;
542 	long offset = SECTORSIZE * ((long)z80->sector - SECTOROFFSET +
543 			sectors * ((long)z80->track - TRACKOFFSET));
544 	FILE *fp;
545 	long len;
546 	realizedisk(z80);
547 	fp = z80->drives[drive];
548 	len = z80->drivelen[drive];
549 
550 	if (fp == NULL)
551 	{
552 		fprintf(stderr, "wrsector(): file/drive %d not open!\r\n",
553 			drive);
554 		A = 1;
555 		return;
556 	}
557 
558 	if (len && offset > len)
559 	{
560 		char buf[SECTORSIZE];
561 
562 		if (fseek(fp, len, SEEK_SET) != 0)
563 		{
564 			fprintf(stderr, "wrsector(): fseek failure offset=0x%lX!\r\n",
565 				len);
566 			A = 1;
567 			return;
568 		}
569 
570 		memset(buf, 0xE5, SECTORSIZE);
571 
572 		while (offset > len)
573 		{
574 			if (fwrite(buf, 1, SECTORSIZE, fp) != SECTORSIZE)
575 			{
576 				fprintf(stderr, "wrsector(): write failure!\r\n");
577 				A = 1;
578 				return;
579 			}
580 
581 			len += SECTORSIZE;
582 			z80->drivelen[drive] = len;
583 		}
584 	}
585 
586 	if (fseek(fp, offset, SEEK_SET) != 0)
587 	{
588 		fprintf(stderr, "wrsector(): fseek failure offset=0x%lX!\r\n",
589 			offset);
590 		A = 1;
591 		return;
592 	}
593 
594 	if (fwrite(&(z80->mem[z80->dma]), 1, SECTORSIZE, fp) != SECTORSIZE)
595 	{
596 		fprintf(stderr, "wrsector(): write failure!\r\n");
597 		A = 1;
598 	}
599 	else
600 	{
601 		A = 0;
602 
603 		if (offset + SECTORSIZE > len)
604 			z80->drivelen[drive] = offset + SECTORSIZE;
605 	}
606 }
607 
608 static void
secttran(z80info * z80)609 secttran(z80info *z80)
610 {
611 	if (z80->drive < NUMHDISCS)
612 	{
613 		/* simple sector translation for hard disc */
614 		HL = BC + 1;
615 
616 		if (BC >= HDSECTORSPERTRACK)
617 			fprintf(stderr, "secttran(): bogus sector %d!\r\n", BC);
618 	}
619 	else
620 	{
621 		/* we do not need to use DE to find our translation table */
622 		HL = sectorxlat[BC];
623 
624 		if (BC >= SECTORSPERTRACK)
625 			fprintf(stderr, "secttran(): bogus sector %d!\r\n", BC);
626 	}
627 }
628 
629 static void
liststat(z80info * z80)630 liststat(z80info *z80)
631 {
632 	A = 0xFF;
633 }
634 
635 /* These two routines read and write ints at arbitrary aligned addrs.
636  * The values are stored in the z80 in little-endian order regardless
637  * of the byte-order on the host.
638  */
639 static int
addr2int(unsigned char * addr)640 addr2int(unsigned char *addr)
641 {
642 	unsigned char *a = (unsigned char*)addr;
643 	unsigned int t;
644 
645 	t = a[0] | (a[1] << 8) | (a[2] << 16) | (a[3] << 24);
646 	return (int)t;
647 }
648 
649 static void
int2addr(unsigned char * addr,int val)650 int2addr(unsigned char *addr, int val)
651 {
652 	unsigned char *a = (unsigned char*)addr;
653 	unsigned int t = (unsigned int)val;
654 
655 	a[0] = t & 0xFF;
656 	a[1] = (t >> 8) & 0xFF;
657 	a[2] = (t >> 16) & 0xFF;
658 	a[3] = (t >> 24) & 0xFF;
659 }
660 
661 /* Allocate file pointers - index is stored in DE */
662 
663 #define CPM_FILES 4
664 
665 FILE *cpm_file[CPM_FILES];
666 
cpm_file_alloc(FILE * f)667 int cpm_file_alloc(FILE *f)
668 {
669 	int x;
670 	for (x = 0; x != CPM_FILES; ++x)
671 		if (!cpm_file[x]) {
672 			cpm_file[x] = f;
673 			return x;
674 		}
675 	return -1;
676 }
677 
cpm_file_get(int idx)678 FILE *cpm_file_get(int idx)
679 {
680 	if (idx < 0 || idx > CPM_FILES)
681 		return 0;
682 	else
683 		return cpm_file[idx];
684 }
685 
cpm_file_free(int x)686 int cpm_file_free(int x)
687 {
688 	if (x >= 0 && x < CPM_FILES && cpm_file[x]) {
689 		int rtn = fclose(cpm_file[x]);
690 		cpm_file[x] = 0;
691 		return rtn;
692 	} else {
693 		return -1;
694 	}
695 }
696 
697 /* DE points to a CP/M FCB.
698    On return, A contains 0 if all went well, 0xFF otherwise.
699    The algorithm uses the FCB to store info about the UNIX file.
700  */
701 static void
openunix(z80info * z80)702 openunix(z80info *z80)
703 {
704 	char filename[20], *fp;
705 	byte *cp;
706 	int i;
707 	FILE *fd;
708 	int fd_no;
709 
710 	cp = &(z80->mem[DE + 1]);
711 	fp = filename;
712 
713 	for (i = 0; (*cp != ' ') && (i < 8); i++)
714 		*fp++ = tolower(*cp++);
715 
716 	cp = &(z80->mem[DE + 9]);
717 
718 	if (*cp != ' ')
719 	{
720 		*fp++ = '.';
721 
722 		for (i = 0; (*cp != ' ') && (i < 3); i++)
723 			*fp++ = tolower(*cp++);
724 	}
725 
726 	*fp = 0;
727 	A = 0xFF;
728 
729 	/* if file is not readable, try opening it read-only */
730 	if ((fd = fopen(filename, "rb+")) == NULL)
731 		if ((fd = fopen(filename, "rb")) == NULL)
732 			return;
733 
734 	fd_no = cpm_file_alloc(fd);
735 	if (fd_no != -1)
736 		A = 0;
737 
738 	int2addr(&z80->mem[DE + FDOFFSET], fd_no);
739 	int2addr(&z80->mem[DE + BLKOFFSET], 0);
740 	int2addr(&z80->mem[DE + SZOFFSET], 0);
741 }
742 
743 
744 /* DE points to a CP/M FCB.
745    On return, A contains 0 if all went well, 0xFF otherwise.
746    The algorithm uses the FCB to store info about the UNIX file.
747  */
748 static void
createunix(z80info * z80)749 createunix(z80info *z80)
750 {
751 	char filename[20], *fp;
752 	byte *cp;
753 	int i;
754 	FILE *fd;
755 	int fd_no;
756 
757 	cp = &(z80->mem[DE + 1]);
758 	fp = filename;
759 
760 	for (i = 0; (*cp != ' ') && (i < 8); i++)
761 		*fp++ = tolower(*cp++);
762 
763 	cp = &(z80->mem[DE + 9]);
764 
765 	if (*cp != ' ')
766 	{
767 		*fp++ = '.';
768 
769 		for (i = 0; (*cp != ' ') && (i < 3); i++)
770 			*fp++ = tolower(*cp++);
771 	}
772 
773 	*fp = 0;
774 	A = 0xFF;
775 
776 	if ((fd = fopen(filename, "wb+")) == NULL)
777 		return;
778 
779 	fd_no = cpm_file_alloc(fd);
780 	if (fd_no != -1)
781 		A = 0;
782 
783 	int2addr(&z80->mem[DE + FDOFFSET], fd_no);
784 	int2addr(&z80->mem[DE + BLKOFFSET], 0);
785 	int2addr(&z80->mem[DE + SZOFFSET], 0);
786 }
787 
788 
789 /* DE points to a CP/M FCB.
790    On return, A contains 0 if all went well, 0xFF otherwise.
791    The algorithm uses the FCB to store info about the UNIX file.
792  */
793 static void
rdunix(z80info * z80)794 rdunix(z80info *z80)
795 {
796 	byte *cp;
797 	int i, blk, size;
798 	FILE *fd;
799 	int fd_no;
800 
801 	cp = &(z80->mem[z80->dma]);
802 	fd = cpm_file_get((fd_no = addr2int(&z80->mem[DE + FDOFFSET])));
803 	blk = addr2int(&z80->mem[DE + BLKOFFSET]);
804 	size = addr2int(&z80->mem[DE + SZOFFSET]);
805 
806 	A = 0xFF;
807 
808 	if (!fd)
809 		return;
810 
811 	if (fseek(fd, (long)blk << 7, SEEK_SET) != 0)
812 		return;
813 
814 	i = fread(cp, 1, SECTORSIZE, fd);
815 	size = i;
816 
817 	if (i == 0)
818 		return;
819 
820 	for (; i < SECTORSIZE; i++)
821 		cp[i] = CNTL('Z');
822 
823 	A = 0;
824 	blk += 1;
825 
826 	int2addr(&z80->mem[DE + FDOFFSET], fd_no);
827 	int2addr(&z80->mem[DE + BLKOFFSET], blk);
828 	int2addr(&z80->mem[DE + SZOFFSET], size);
829 }
830 
831 
832 /* DE points to a CP/M FCB.
833    On return, A contains 0 if all went well, 0xFF otherwise.
834    The algorithm uses the FCB to store info about the UNIX file.
835  */
836 static void
wrunix(z80info * z80)837 wrunix(z80info *z80)
838 {
839 	byte *cp;
840 	int i, blk, size;
841 	FILE *fd;
842 	int fd_no;
843 
844 	cp = &(z80->mem[z80->dma]);
845 	fd = cpm_file_get((fd_no = addr2int(&z80->mem[DE + FDOFFSET])));
846 	blk = addr2int(&z80->mem[DE + BLKOFFSET]);
847 	size = addr2int(&z80->mem[DE + SZOFFSET]);
848 
849 	A = 0xFF;
850 
851 	if (fseek(fd, (long)blk << 7, SEEK_SET) != 0)
852 		return;
853 
854 	i = fwrite(cp, 1, size = SECTORSIZE, fd);
855 
856 	if (i != SECTORSIZE)
857 		return;
858 
859 	A = 0;
860 	blk += 1;
861 
862 	int2addr(&z80->mem[DE + FDOFFSET], fd_no);
863 	int2addr(&z80->mem[DE + BLKOFFSET], blk);
864 	int2addr(&z80->mem[DE + SZOFFSET], size);
865 }
866 
867 
868 /* DE points to a CP/M FCB.
869    On return, A contains 0 if all went well, 0xFF otherwise.
870  */
871 static void
closeunix(z80info * z80)872 closeunix(z80info *z80)
873 {
874 	int fd_no;
875 
876 	fd_no = addr2int(&z80->mem[DE + FDOFFSET]);
877 	A = 0xFF;
878 
879 	if (cpm_file_free(fd_no))
880 		return;
881 
882 	A = 0;
883 }
884 
885 /* clean up and quit - never returns */
886 void
finish(z80info * z80)887 finish(z80info *z80)
888 {
889 	resetterm();
890 	exit(0);
891 }
892 
893 /*  Get/set the time - although only the get-time part is implemented.
894     If C==0, then get the time, else of C==0xFF, then set the time.
895     HL returns a pointer to our time table:
896 	HL+0:DATE LSB SINCE 1,1,1978
897 	HL+1:DATE MSB
898 	HL+2:HOURS  (BCD)
899 	HL+3:MINUTES (BCD)
900 	HL+4:SECONDS (BCD)
901  */
902 static void
dotime(z80info * z80)903 dotime(z80info *z80)
904 {
905     time_t now;
906     struct tm *t;
907     word days;
908     int y;
909 
910     if (C != 0)		/* do not support setting the time yet */
911 		return;
912 
913     time(&now);
914     t = localtime(&now);
915 
916     /* days since Jan 1, 1978 + one since tm_yday starts at zero */
917     days = (t->tm_year - 78) * 365 + t->tm_yday + 1;
918 
919     /* add in the number of days for the leap years - dumb but accurate */
920     for (y = 78; y < t->tm_year; y++)
921 		if (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0))
922 			days++;
923 
924     HL = TIMEBUF;
925     SETMEM(HL + 0, days & 0xFF);
926     SETMEM(HL + 1, days >> 8);
927     SETMEM(HL + 2, ((t->tm_hour / 10) << 4) + (t->tm_hour % 10));
928     SETMEM(HL + 3, ((t->tm_min / 10) << 4) + (t->tm_min % 10));
929     SETMEM(HL + 4, ((t->tm_sec / 10) << 4) + (t->tm_sec % 10));
930 }
931 
932 void
bios(z80info * z80,int fn)933 bios(z80info *z80, int fn)
934 {
935 	static void (*bioscall[])(z80info *z80) =
936 	{
937 		boot,		/* 0 */
938 		warmboot,	/* 1 */
939 		consstat,	/* 2 */
940 		consin,		/* 3 */
941 		consout,	/* 4 */
942 		list,		/* 5 */
943 		punch,		/* 6 */
944 		reader,		/* 7 */
945 		home,		/* 8 */
946 		seldisc,	/* 9 */
947 		settrack,	/* 10 */
948 		setsector,	/* 11 */
949 		setdma,		/* 12 */
950 		rdsector,	/* 13 */
951 		wrsector,	/* 14 */
952 		liststat,	/* 15 */
953 		secttran,	/* 16 */
954 		openunix,	/* 17 */
955 		createunix,	/* 18 */
956 		rdunix,		/* 19 */
957 		wrunix,		/* 20 */
958 		closeunix,	/* 21 */
959 		finish,		/* 22 */
960 		dotime		/* 23 */
961 	};
962 
963 	if (fn < 0 || fn >= sizeof bioscall / sizeof *bioscall)
964 	{
965 		fprintf(stderr, "Illegal BIOS call %d\r\n", fn);
966 		return;
967 	}
968 
969 	bioscall[fn](z80);
970 	/* let z80 handle return */
971 }
972